diff options
| author | tamaina <tamaina@hotmail.co.jp> | 2023-02-26 11:28:05 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-26 11:28:05 +0900 |
| commit | 18dbcfa0b09715a234a4eca0288e17d5cbf7622c (patch) | |
| tree | efd9e83b3e11919926fd80790ad272d1d9e7bda6 | |
| parent | Simplify search.vue (remove dead code) (#10088) (diff) | |
| download | misskey-18dbcfa0b09715a234a4eca0288e17d5cbf7622c.tar.gz misskey-18dbcfa0b09715a234a4eca0288e17d5cbf7622c.tar.bz2 misskey-18dbcfa0b09715a234a4eca0288e17d5cbf7622c.zip | |
test(server): add validation test of api:notes/create (#10090)
* fix(server): notes/createのバリデーションが効いていない
Fix #10079
Co-Authored-By: mei23 <m@m544.net>
* anyOf内にバリデーションを書いても最初の一つしかチェックされない
* :v:
* wip
* wip
* :v:
* RequiredProp
* Revert "RequiredProp"
This reverts commit 74693900119a590263106fa3adefd008d69ce80c.
* add api:notes/create
* fix lint
* text
* :v:
* improve readability
---------
Co-authored-by: mei23 <m@m544.net>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
19 files changed, 424 insertions, 205 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index c94a34194e..6a0497946d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "search.exclude": { "**/node_modules": true }, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "files.associations": { + "*.test.ts": "typescript" + } }
\ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 668989f122..10d93cd9fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -299,6 +299,27 @@ pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name> - 生成後、ファイルをmigration下に移してください - 作成されたスクリプトは不必要な変更を含むため除去してください +### JSON SchemaのobjectでanyOfを使うとき +JSON Schemaで、objectに対してanyOfを使う場合、anyOfの中でpropertiesを定義しないこと。 +バリデーションが効かないため。(SchemaTypeもそのように作られており、objectのanyOf内のpropertiesは捨てられます) +https://github.com/misskey-dev/misskey/pull/10082 + +テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合: + +``` +export const paramDef = { + type: 'object', + properties: { + hoge: { type: 'string', minLength: 1 }, + fuga: { type: 'string', minLength: 1 }, + }, + anyOf: [ + { required: ['hoge'] }, + { required: ['fuga'] }, + ], +} as const; +``` + ### コネクションには`markRaw`せよ **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs index 5a06889dcd..f9fe4814e6 100644 --- a/packages/backend/.eslintrc.cjs +++ b/packages/backend/.eslintrc.cjs @@ -1,7 +1,7 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], + project: ['./tsconfig.json', './test/tsconfig.json'], }, extends: [ '../shared/.eslintrc.js', diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs index 2f11f6a3e9..8a11ad848c 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.cjs @@ -20,7 +20,7 @@ module.exports = { // collectCoverage: false, // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: ['src/**/*.ts'], + collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts'], // The directory where Jest should output its coverage files coverageDirectory: "coverage", @@ -159,6 +159,7 @@ module.exports = { // The glob patterns Jest uses to detect test files testMatch: [ "<rootDir>/test/unit/**/*.ts", + "<rootDir>/src/**/*.test.ts", //"<rootDir>/test/e2e/**/*.ts" ], diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 7fc4a3e654..6a0802f8a4 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -116,10 +116,10 @@ export type Obj = Record<string, Schema>; // https://github.com/misskey-dev/misskey/issues/8535 // To avoid excessive stack depth error, // deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it). -export type ObjType<s extends Obj, RequiredProps extends keyof s> = +export type ObjType<s extends Obj, RequiredProps extends ReadonlyArray<keyof s>> = UnionToIntersection< { -readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]> } & - { -readonly [R in RequiredProps]-?: SchemaType<s[R]> } & + { -readonly [R in RequiredProps[number]]-?: SchemaType<s[R]> } & { -readonly [P in keyof s]?: SchemaType<s[P]> } >; @@ -136,18 +136,19 @@ type PartialIntersection<T> = Partial<UnionToIntersection<T>>; // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 // To get union, we use `Foo extends any ? Hoge<Foo> : never` type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? SchemaType<X> : never; -type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never; +//type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never; +type UnionObjType<s extends Obj, a extends readonly any[], X extends ReadonlyArray<keyof s> = a[number]> = X extends any ? ObjType<s, X> : never; type ArrayUnion<T> = T extends any ? Array<T> : never; type ObjectSchemaTypeDef<p extends Schema> = p['ref'] extends keyof typeof refs ? Packed<p['ref']> : p['properties'] extends NonNullable<Obj> ? - p['anyOf'] extends ReadonlyArray<Schema> ? - ObjType<p['properties'], NonNullable<p['required']>[number]> & UnionObjectSchemaType<p['anyOf']> & PartialIntersection<UnionObjectSchemaType<p['anyOf']>> - : - ObjType<p['properties'], NonNullable<p['required']>[number]> + p['anyOf'] extends ReadonlyArray<Schema> ? p['anyOf'][number]['required'] extends ReadonlyArray<keyof p['properties']> ? + UnionObjType<p['properties'], NonNullable<p['anyOf'][number]['required']>> & ObjType<p['properties'], NonNullable<p['required']>> + : never + : ObjType<p['properties'], NonNullable<p['required']>> : - p['anyOf'] extends ReadonlyArray<Schema> ? UnionObjectSchemaType<p['anyOf']> & PartialIntersection<UnionObjectSchemaType<p['anyOf']>> : + p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : any diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 85b566aabe..1d27ac2137 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -138,19 +138,13 @@ export const meta = { export const paramDef = { type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + url: { type: 'string' }, + }, anyOf: [ - { - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - }, - required: ['fileId'], - }, - { - properties: { - url: { type: 'string' }, - }, - required: ['url'], - }, + { required: ['fileId'] }, + { required: ['url'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index e0a07a3640..271b33ef4b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -39,19 +39,13 @@ export const meta = { export const paramDef = { type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + url: { type: 'string' }, + }, anyOf: [ - { - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - }, - required: ['fileId'], - }, - { - properties: { - url: { type: 'string' }, - }, - required: ['url'], - }, + { required: ['fileId'] }, + { required: ['url'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/create.test.ts b/packages/backend/src/server/api/endpoints/notes/create.test.ts new file mode 100644 index 0000000000..4e5ec361f9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/create.test.ts @@ -0,0 +1,248 @@ +process.env.NODE_ENV = 'test'; + +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import { describe, test, expect } from '@jest/globals'; +import { getValidator } from '../../../../../test/prelude/get-api-validator.js'; +import { paramDef } from './create.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const VALID = true; +const INVALID = false; + +describe('api:notes/create', () => { + describe('validation', () => { + const v = getValidator(paramDef); + const tooLong = readFile(_dirname + '/../../../../../test/resources/misskey.svg', 'utf-8'); + + test('reject empty', () => { + const valid = v({ }); + expect(valid).toBe(INVALID); + }); + + describe('text', () => { + test('simple post', () => { + expect(v({ text: 'Hello, world!' })) + .toBe(VALID); + }); + + test('null post', () => { + expect(v({ text: null })) + .toBe(INVALID); + }); + + test('0 characters post', () => { + expect(v({ text: '' })) + .toBe(INVALID); + }); + + test('over 3000 characters post', async () => { + expect(v({ text: await tooLong })) + .toBe(INVALID); + }); + }); + + describe('cw', () => { + test('simple cw', () => { + expect(v({ text: 'Hello, world!', cw: 'Hello, world!' })) + .toBe(VALID); + }); + + test('null cw', () => { + expect(v({ text: 'Body', cw: null })) + .toBe(VALID); + }); + + test('0 characters cw', () => { + expect(v({ text: 'Body', cw: '' })) + .toBe(VALID); + }); + + test('reject only cw', () => { + expect(v({ cw: 'Hello, world!' })) + .toBe(INVALID); + }); + + test('over 100 characters cw', async () => { + expect(v({ text: 'Body', cw: await tooLong })) + .toBe(INVALID); + }); + }); + + describe('visibility', () => { + test('public', () => { + expect(v({ text: 'Hello, world!', visibility: 'public' })) + .toBe(VALID); + }); + + test('home', () => { + expect(v({ text: 'Hello, world!', visibility: 'home' })) + .toBe(VALID); + }); + + test('followers', () => { + expect(v({ text: 'Hello, world!', visibility: 'followers' })) + .toBe(VALID); + }); + + test('reject only visibility', () => { + expect(v({ visibility: 'public' })) + .toBe(INVALID); + }); + + test('reject invalid visibility', () => { + expect(v({ text: 'Hello, world!', visibility: 'invalid' })) + .toBe(INVALID); + }); + + test('reject null visibility', () => { + expect(v({ text: 'Hello, world!', visibility: null })) + .toBe(INVALID); + }); + + describe('visibility:specified', () => { + test('specified without visibleUserIds', () => { + expect(v({ text: 'Hello, world!', visibility: 'specified' })) + .toBe(VALID); + }); + + test('specified with empty visibleUserIds', () => { + expect(v({ text: 'Hello, world!', visibility: 'specified', visibleUserIds: [] })) + .toBe(VALID); + }); + + test('reject specified with non unique visibleUserIds', () => { + expect(v({ text: 'Hello, world!', visibility: 'specified', visibleUserIds: ['1', '1', '2'] })) + .toBe(INVALID); + }); + + test('reject specified with null visibleUserIds', () => { + expect(v({ text: 'Hello, world!', visibility: 'specified', visibleUserIds: null })) + .toBe(INVALID); + }); + }); + }); + + describe('fileIds', () => { + test('only fileIds', () => { + expect(v({ fileIds: ['1', '2', '3'] })) + .toBe(VALID); + }); + + test('text and fileIds', () => { + expect(v({ text: 'Hello, world!', fileIds: ['1', '2', '3'] })) + .toBe(VALID); + }); + + test('reject null fileIds', () => { + expect(v({ fileIds: null })) + .toBe(INVALID); + }); + + test('reject text and null fileIds (複合的なanyOfのバリデーションが正しく動作する)', () => { + expect(v({ text: 'Hello, world!', fileIds: null })) + .toBe(INVALID); + }); + + test('reject 0 files', () => { + expect(v({ fileIds: [] })) + .toBe(INVALID); + }); + + test('reject non unique', () => { + expect(v({ fileIds: ['1', '1', '2'] })) + .toBe(INVALID); + }); + + test('reject invalid id', () => { + expect(v({ fileIds: ['あ'] })) + .toBe(INVALID); + }); + + test('reject over 17 files', () => { + const valid = v({ text: 'Hello, world!', fileIds: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18'] }); + expect(valid).toBe(INVALID); + }); + }); + + describe('poll', () => { + test('note with poll', () => { + expect(v({ text: 'Hello, world!', poll: { choices: ['a', 'b', 'c'] } })) + .toBe(VALID); + }); + + test('null poll', () => { + expect(v({ text: 'Hello, world!', poll: null })) + .toBe(VALID); + }); + + test('allow only poll', () => { + expect(v({ poll: { choices: ['a', 'b', 'c'] } })) + .toBe(VALID); + }); + + test('poll with expiresAt', async () => { + expect(v({ poll: { choices: ['a', 'b', 'c'], expiresAt: 1 } })) + .toBe(VALID); + }); + + test('poll with expiredAfter', async () => { + expect(v({ poll: { choices: ['a', 'b', 'c'], expiredAfter: 1 } })) + .toBe(VALID); + }); + + test('reject poll without choices', () => { + expect(v({ poll: { } })) + .toBe(INVALID); + }); + + test('reject poll with empty choices', () => { + expect(v({ poll: { choices: [] } })) + .toBe(INVALID); + }); + + test('reject poll with null choices', () => { + expect(v({ poll: { choices: null } })) + .toBe(INVALID); + }); + + test('reject poll with 1 choice', () => { + expect(v({ poll: { choices: ['a'] } })) + .toBe(INVALID); + }); + + test('reject poll with too long choice', async () => { + expect(v({ poll: { choices: [await tooLong, '2'] } })) + .toBe(INVALID); + }); + + test('reject poll with too many choices', () => { + expect(v({ poll: { choices: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'] } })) + .toBe(INVALID); + }); + + test('reject poll with non unique choices', () => { + expect(v({ poll: { choices: ['a', 'a', 'b', 'c'] } })) + .toBe(INVALID); + }); + + test('reject poll with expiredAfter 0', async () => { + expect(v({ poll: { choices: ['a', 'b', 'c'], expiredAfter: 0 } })) + .toBe(INVALID); + }); + }); + + test('text, fileIds and poll', () => { + expect(v({ text: 'Hello, world!', fileIds: ['1', '2', '3'], poll: { choices: ['a', 'b', 'c'] } })) + .toBe(VALID); + }); + + test('text, invalid fileIds and invalid poll', () => { + expect(v({ text: 'Hello, world!', fileIds: ['あ'], poll: { choices: ['a'] } })) + .toBe(INVALID); + }); + }); +}); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index f4c5a84a4f..2848cd7df1 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -101,74 +101,55 @@ export const paramDef = { noExtractHashtags: { type: 'boolean', default: false }, noExtractEmojis: { type: 'boolean', default: false }, replyId: { type: 'string', format: 'misskey:id', nullable: true }, + renoteId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true }, - }, - anyOf: [ - { - // (re)note with text, files and poll are optional - properties: { - text: { type: 'string', minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, - }, - required: ['text'], + + // anyOf内にバリデーションを書いても最初の一つしかチェックされない + // See https://github.com/misskey-dev/misskey/pull/10082 + text: { + type: 'string', + minLength: 1, + maxLength: MAX_NOTE_TEXT_LENGTH, + nullable: false }, - { - // (re)note with files, text and poll are optional - properties: { - fileIds: { - type: 'array', - uniqueItems: true, - minItems: 1, - maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, - }, - }, - required: ['fileIds'], + fileIds: { + type: 'array', + uniqueItems: true, + minItems: 1, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, }, - { - // (re)note with files, text and poll are optional + mediaIds: { + type: 'array', + uniqueItems: true, + minItems: 1, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, + }, + poll: { + type: 'object', + nullable: true, properties: { - mediaIds: { - deprecated: true, - description: 'Use `fileIds` instead. If both are specified, this property is discarded.', + choices: { type: 'array', uniqueItems: true, - minItems: 1, - maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, - }, - }, - required: ['mediaIds'], - }, - { - // (re)note with poll, text and files are optional - properties: { - poll: { - type: 'object', - nullable: true, - properties: { - choices: { - type: 'array', - uniqueItems: true, - minItems: 2, - maxItems: 10, - items: { type: 'string', minLength: 1, maxLength: 50 }, - }, - multiple: { type: 'boolean' }, - expiresAt: { type: 'integer', nullable: true }, - expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, - }, - required: ['choices'], + minItems: 2, + maxItems: 10, + items: { type: 'string', minLength: 1, maxLength: 50 }, }, + multiple: { type: 'boolean' }, + expiresAt: { type: 'integer', nullable: true }, + expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, }, - required: ['poll'], - }, - { - // pure renote - properties: { - renoteId: { type: 'string', format: 'misskey:id', nullable: true }, - }, - required: ['renoteId'], + required: ['choices'], }, + }, + // (re)note with text, files and poll are optional + anyOf: [ + { required: ['text'] }, + { required: ['fileIds'] }, + { required: ['mediaIds'] }, + { required: ['poll'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index bcd793ac43..da1a4bcc46 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -36,32 +36,25 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - }, - anyOf: [ - { - properties: { - tag: { type: 'string', minLength: 1 }, - }, - required: ['tag'], - }, - { - properties: { - query: { - type: 'array', - description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', - items: { - type: 'array', - items: { - type: 'string', - minLength: 1, - }, - minItems: 1, - }, - minItems: 1, + + tag: { type: 'string', minLength: 1 }, + query: { + type: 'array', + description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', + items: { + type: 'array', + items: { + type: 'string', + minLength: 1, }, + minItems: 1, }, - required: ['query'], + minItems: 1, }, + }, + anyOf: [ + { required: ['tag'] }, + { required: ['query'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 651252afbb..bf2b2a431e 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -29,20 +29,14 @@ export const meta = { export const paramDef = { type: 'object', + properties: { + pageId: { type: 'string', format: 'misskey:id' }, + name: { type: 'string' }, + username: { type: 'string' }, + }, anyOf: [ - { - properties: { - pageId: { type: 'string', format: 'misskey:id' }, - }, - required: ['pageId'], - }, - { - properties: { - name: { type: 'string' }, - username: { type: 'string' }, - }, - required: ['name', 'username'], - }, + { required: ['pageId'] }, + { required: ['name', 'username'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 17ce920011..97f1310c36 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -46,25 +46,18 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + + userId: { type: 'string', format: 'misskey:id' }, + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, }, anyOf: [ - { - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], - }, - { - properties: { - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', - }, - }, - required: ['username', 'host'], - }, + { required: ['userId'] }, + { required: ['username', 'host'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 6dbda0d72f..d406594a2e 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -46,25 +46,18 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + + userId: { type: 'string', format: 'misskey:id' }, + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, }, anyOf: [ - { - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], - }, - { - properties: { - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', - }, - }, - required: ['username', 'host'], - }, + { required: ['userId'] }, + { required: ['username', 'host'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 1cefcf2707..6c340d8fb2 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -31,20 +31,13 @@ export const paramDef = { properties: { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, detail: { type: 'boolean', default: true }, + + username: { type: 'string', nullable: true }, + host: { type: 'string', nullable: true }, }, anyOf: [ - { - properties: { - username: { type: 'string', nullable: true }, - }, - required: ['username'], - }, - { - properties: { - host: { type: 'string', nullable: true }, - }, - required: ['host'], - }, + { required: ['username'] }, + { required: ['host'] }, ], } as const; diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 70258ef009..29f24b045a 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -54,32 +54,22 @@ export const meta = { export const paramDef = { type: 'object', - anyOf: [ - { - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], - }, - { - properties: { - userIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - }, - required: ['userIds'], - }, - { - properties: { - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', - }, - }, - required: ['username'], + properties: { + userId: { type: 'string', format: 'misskey:id' }, + userIds: { type: 'array', uniqueItems: true, items: { + type: 'string', format: 'misskey:id', + } }, + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', }, + }, + anyOf: [ + { required: ['userId'] }, + { required: ['userIds'] }, + { required: ['username'] }, ], } as const; diff --git a/packages/backend/test/prelude/get-api-validator.ts b/packages/backend/test/prelude/get-api-validator.ts new file mode 100644 index 0000000000..1f4a2dbc95 --- /dev/null +++ b/packages/backend/test/prelude/get-api-validator.ts @@ -0,0 +1,11 @@ +import { Schema } from '@/misc/schema'; +import Ajv from 'ajv'; + +export const getValidator = (paramDef: Schema) => { + const ajv = new Ajv({ + useDefaults: true, + }); + ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); + + return ajv.compile(paramDef); +} diff --git a/packages/backend/test/resources/misskey.svg b/packages/backend/test/resources/misskey.svg new file mode 100644 index 0000000000..3fcb2d3ecb --- /dev/null +++ b/packages/backend/test/resources/misskey.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="515px" height="136px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> + <g id="path2" transform="matrix(0.264585,0,0,0.264585,-50.0005,-50.0001)"> + <path d="M256.418,188.976C248.558,188.944 240.758,190.308 233.379,193.013C220.308,197.613 209.533,205.888 201.091,217.802C193.02,229.329 188.977,242.195 188.977,256.409L188.977,508.89C188.977,527.332 195.52,543.29 208.576,556.732C222.032,569.803 237.99,576.331 256.418,576.331C275.259,576.331 291.204,569.803 304.274,556.747C317.73,543.291 324.441,527.332 324.441,508.89L324.441,462.983C324.584,453.04 334.824,455.655 340.01,462.983C349.695,479.767 370.251,494.19 394.193,494.105L394.193,494.119C418.119,494.019 438.005,482.196 448.375,462.983C452.304,458.354 463.377,450.455 464.52,462.983L464.52,508.89C464.52,527.332 471.047,543.29 484.104,556.732C497.574,569.803 513.511,576.331 531.953,576.331C550.78,576.331 566.739,569.803 579.809,556.747C593.265,543.291 599.977,527.332 599.977,508.89L599.977,256.409C599.977,242.195 595.752,229.329 587.309,217.802C579.224,205.874 568.653,197.613 555.597,193.013C547.912,190.314 540.228,188.976 532.543,188.976C511.788,188.976 494.301,197.046 480.073,213.188L411.636,293.281C410.107,294.438 405.006,303.247 394.178,303.247C383.379,303.247 378.868,294.439 377.325,293.296L308.297,213.188C294.47,197.046 277.173,188.976 256.418,188.976ZM682.904,188.983C666.763,188.983 652.926,194.748 641.404,206.271C630.261,217.413 624.691,231.054 624.691,247.196C624.691,263.338 630.261,277.174 641.404,288.697C652.926,299.839 666.763,305.41 682.904,305.41C699.046,305.41 712.88,299.839 724.412,288.697C735.935,277.174 741.693,263.338 741.693,247.196C741.693,231.054 735.935,217.413 724.412,206.271C712.88,194.748 699.046,188.983 682.904,188.983ZM1327.58,193.013C1311.45,193.013 1297.62,198.779 1286.1,210.302C1274.96,221.444 1269.38,235.08 1269.38,251.212L1269.38,519.72C1269.38,535.861 1274.96,549.697 1286.1,561.22C1297.62,572.362 1311.45,577.933 1327.58,577.933C1343.73,577.933 1357.55,572.362 1369.08,561.22C1380.6,549.697 1386.37,535.867 1386.37,519.734C1386.37,508.211 1387.9,502.453 1390.96,502.453C1392.51,502.453 1394.05,503.023 1395.58,504.166L1453.2,560.061C1464.72,571.203 1478.36,576.774 1494.11,576.774C1510.62,576.774 1524.45,571.009 1535.6,559.486C1546.74,547.572 1552.31,533.936 1552.31,518.575C1552.31,502.053 1546.36,488.029 1534.45,476.506C1508.32,450.765 1494.69,437.517 1493.54,436.755C1488.54,431.755 1488.73,427.53 1494.11,424.073L1495.27,423.497L1495.27,422.929L1531.57,399.875C1548.85,388.352 1557.5,372.026 1557.5,350.904C1557.5,339.381 1554.42,328.622 1548.28,318.623C1536.76,301.339 1520.43,292.691 1499.3,292.691C1487.78,292.691 1477.02,295.768 1467.04,301.911C1422.48,331.499 1399.42,346.678 1397.88,347.449C1395.2,349.363 1392.7,349.738 1390.4,348.586C1387.7,347.434 1386.35,344.939 1386.35,341.101L1386.35,251.212C1386.35,235.08 1380.59,221.444 1369.07,210.302C1357.55,198.779 1343.72,193.013 1327.58,193.013ZM1716.37,291.738C1676.42,291.738 1642.24,305.949 1613.81,334.376C1585.76,362.422 1571.74,396.227 1571.74,435.795C1571.74,475.745 1585.76,509.932 1613.81,538.359C1642.24,566.404 1676.42,580.428 1716.37,580.428C1755.94,580.428 1789.94,566.404 1818.37,538.359C1827.2,529.521 1831.62,518.773 1831.62,506.107C1831.62,493.423 1827.2,482.664 1818.37,473.827C1809.53,464.999 1798.77,460.584 1786.11,460.584C1773.42,460.584 1762.66,464.999 1753.83,473.827C1743.46,484.588 1730.97,489.963 1716.37,489.963C1701.4,489.963 1688.53,484.78 1677.77,474.41C1667.39,463.649 1662.2,450.775 1662.2,435.795C1662.2,421.206 1667.59,408.72 1678.35,398.34C1683.73,392.578 1690.26,388.74 1697.93,386.817C1699.87,386.436 1701.4,386.623 1702.55,387.385C1703.32,388.547 1702.93,389.702 1701.39,390.854L1689.87,402.953C1681.03,411.791 1676.61,422.359 1676.61,434.644C1676.61,447.319 1680.45,457.497 1688.13,465.182C1695.81,472.868 1706.57,476.705 1720.41,476.705C1730.01,476.705 1739.61,471.91 1749.21,462.311L1816.06,396.044C1824.9,387.197 1829.32,376.436 1829.32,363.77C1829.32,351.086 1824.9,340.332 1816.06,331.504C1789.17,304.992 1755.94,291.738 1716.37,291.738ZM877.977,292.668C841.947,292.194 813.839,301.679 793.662,321.133C775.996,338.036 767.168,359.358 767.168,385.089C767.549,417.363 780.035,441.565 804.624,457.697C811.918,462.687 820.941,466.72 831.693,469.796C837.083,471.72 846.111,474.02 858.777,476.705C869.919,479.391 882.023,481.886 895.088,484.191C897.774,484.962 898.924,486.312 898.543,488.236C898.543,490.541 897.58,491.691 895.657,491.691C890.667,492.072 886.059,492.266 881.831,492.266C850.328,488.81 829.001,485.927 817.859,483.622C814.793,482.851 811.535,482.463 808.078,482.463C796.165,482.463 785.787,486.884 776.949,495.721C768.502,504.178 764.282,514.551 764.282,526.836C764.282,536.825 767.352,545.854 773.494,553.92C780.027,561.986 788.486,567.169 798.866,569.473C831.13,576.778 860.317,580.428 886.429,580.428C922.16,580.428 950.013,570.825 969.992,551.617C987.277,535.094 995.925,513.775 995.925,487.653C995.925,455.388 983.626,431.187 959.037,415.045C945.972,406.598 927.915,400.45 904.869,396.612C891.042,393.927 879.518,391.427 870.3,389.112L870.3,389.127C867.605,388.356 866.067,386.818 865.686,384.513C865.686,382.59 867.224,381.44 870.3,381.059C873.757,380.678 877.415,380.678 881.262,381.059C913.146,384.135 934.652,386.823 945.794,389.127C948.861,389.889 951.931,390.271 955.007,390.271C967.301,390.271 977.674,386.051 986.121,377.604C994.959,368.767 999.379,358.393 999.379,346.49C999.379,336.109 996.109,326.894 989.576,318.837C983.043,310.761 974.79,305.566 964.81,303.261C938.298,297.5 911.788,294.042 885.285,292.89C882.813,292.77 880.379,292.7 877.977,292.668ZM1128.73,292.668C1092.7,292.194 1064.59,301.679 1044.42,321.133C1026.75,338.036 1017.92,359.358 1017.92,385.089C1018.3,417.363 1030.79,441.565 1055.38,457.697C1062.67,462.687 1071.7,466.72 1082.46,469.796C1087.84,471.72 1096.86,474.02 1109.54,476.705C1120.68,479.391 1132.79,481.886 1145.84,484.191C1148.53,484.962 1149.68,486.312 1149.3,488.236C1149.3,490.541 1148.34,491.691 1146.41,491.691C1141.42,492.072 1136.81,492.266 1132.59,492.266C1101.09,488.81 1079.77,485.927 1068.63,483.622C1065.55,482.851 1062.29,482.463 1058.83,482.463C1046.92,482.463 1036.55,486.884 1027.72,495.721C1019.26,504.178 1015.04,514.551 1015.04,526.836C1015.04,536.825 1018.11,545.854 1024.26,553.92C1030.79,561.986 1039.24,567.169 1049.62,569.473C1081.88,576.778 1111.08,580.428 1137.2,580.428C1172.92,580.428 1200.77,570.825 1220.75,551.617C1238.03,535.094 1246.68,513.775 1246.68,487.653C1246.68,455.388 1234.39,431.187 1209.81,415.045C1196.74,406.598 1178.68,400.45 1155.64,396.612C1141.81,393.927 1130.29,391.427 1121.07,389.112L1121.05,389.127C1118.37,388.356 1116.84,386.818 1116.45,384.513C1116.45,382.59 1117.99,381.44 1121.05,381.059C1124.52,380.678 1128.17,380.678 1132.01,381.059C1163.89,384.135 1185.41,386.823 1196.55,389.127C1199.62,389.889 1202.69,390.271 1205.76,390.271C1218.06,390.271 1228.43,386.051 1236.89,377.604C1245.72,368.767 1250.13,358.393 1250.13,346.49C1250.13,336.109 1246.87,326.894 1240.35,318.837C1233.81,310.761 1225.55,305.566 1215.56,303.261C1189.06,297.5 1162.55,294.042 1136.04,292.89C1133.57,292.77 1131.13,292.7 1128.73,292.668ZM1910.17,296.736C1894.04,296.736 1880.21,302.501 1868.69,314.024C1857.55,325.157 1851.98,338.793 1851.98,354.934L1851.98,435.028C1851.98,473.825 1865.8,507.05 1893.45,534.705C1921.12,562.36 1954.36,576.191 1993.15,576.191C2000.84,576.191 2007.95,575.614 2014.48,574.471C2018.32,573.699 2021,574.469 2022.53,576.774C2023.69,578.307 2023.3,580.42 2021.39,583.115C2016.39,590.029 2005.82,593.486 1989.69,593.486C1983.55,593.486 1975.68,591.949 1966.07,588.873C1956.47,585.797 1948.98,584.259 1943.6,584.259C1920.93,584.259 1904.99,594.638 1895.77,615.388C1892.32,622.302 1890.58,629.598 1890.58,637.283C1890.58,659.948 1900.77,675.892 1921.13,685.11C1941.49,694.709 1964.34,699.505 1989.69,699.505C2033.49,699.505 2068.25,686.639 2093.98,660.898C2120.11,635.166 2133.18,600.784 2133.18,557.758L2133.18,452.308C2133.94,446.157 2134.32,440.399 2134.32,435.028L2134.32,354.934C2134.32,338.802 2128.57,325.166 2117.04,314.024C2105.9,302.501 2092.27,296.736 2076.13,296.736C2059.99,296.736 2046.16,302.501 2034.63,314.024C2023.11,325.157 2017.35,338.793 2017.35,354.934L2017.35,435.028C2017.35,441.551 2015.04,447.309 2010.43,452.308C2005.83,456.918 2000.07,459.225 1993.15,459.225C1986.62,459.225 1980.86,456.918 1975.87,452.308C1971.26,447.309 1968.95,441.551 1968.95,435.028L1968.95,354.934C1968.95,338.802 1963.19,325.166 1951.67,314.024C1940.14,302.501 1926.3,296.736 1910.17,296.736ZM683.473,316.947C667.331,316.947 653.495,322.713 641.972,334.236C630.449,345.768 624.691,359.602 624.691,375.744L624.691,518.118C624.691,534.259 630.449,548.095 641.972,559.618C653.504,570.761 667.341,576.331 683.473,576.331C699.624,576.331 713.27,570.761 724.412,559.618C735.935,548.095 741.693,534.259 741.693,518.118L741.693,375.744C741.693,359.593 735.935,345.759 724.412,334.236C713.261,322.713 699.614,316.947 683.473,316.947Z" style="fill:white;fill-rule:nonzero;"/> + </g> +</svg> diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index da82ddc4a1..8a024a678b 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -33,11 +33,12 @@ "lib": [ "esnext" ], - "types": ["jest"] + "types": ["jest", "node"] }, "compileOnSave": false, "include": [ "./**/*.ts", + "../src/**/*.test.ts", "../src/@types/**/*.ts", ] } diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 6f335a2442..faadbcdfc6 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -26,9 +26,7 @@ "rootDir": "./src", "baseUrl": "./", "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] }, "outDir": "./built", "types": [ @@ -46,4 +44,7 @@ "include": [ "./src/**/*.ts" ], + "exclude": [ + "./src/**/*.test.ts" + ] } |