summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints
diff options
context:
space:
mode:
authortamaina <tamaina@hotmail.co.jp>2023-02-26 11:28:05 +0900
committerGitHub <noreply@github.com>2023-02-26 11:28:05 +0900
commit18dbcfa0b09715a234a4eca0288e17d5cbf7622c (patch)
treeefd9e83b3e11919926fd80790ad272d1d9e7bda6 /packages/backend/src/server/api/endpoints
parentSimplify search.vue (remove dead code) (#10088) (diff)
downloadsharkey-18dbcfa0b09715a234a4eca0288e17d5cbf7622c.tar.gz
sharkey-18dbcfa0b09715a234a4eca0288e17d5cbf7622c.tar.bz2
sharkey-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>
Diffstat (limited to 'packages/backend/src/server/api/endpoints')
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/show-file.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/show.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.test.ts248
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts99
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search-by-tag.ts39
-rw-r--r--packages/backend/src/server/api/endpoints/pages/show.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/users/followers.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/users/following.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts17
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts40
10 files changed, 363 insertions, 190 deletions
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;