1
0
forked from mirror/misskey
misskey/packages/backend/test/e2e/clips.ts
zyoshoka 38837bd388
test(backend): refactor tests (#13499)
* test(backend): refactor tests

* fix: failed test
2024-03-03 20:15:35 +09:00

931 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js';
import type * as Misskey from 'misskey-js';
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
describe('クリップ', () => {
let alice: Misskey.entities.SignupResponse;
let bob: Misskey.entities.SignupResponse;
let aliceNote: Misskey.entities.Note;
let aliceHomeNote: Misskey.entities.Note;
let aliceFollowersNote: Misskey.entities.Note;
let aliceSpecifiedNote: Misskey.entities.Note;
let bobNote: Misskey.entities.Note;
let bobHomeNote: Misskey.entities.Note;
let bobFollowersNote: Misskey.entities.Note;
let bobSpecifiedNote: Misskey.entities.Note;
const compareBy = <T extends { id: string }, >(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
return selector(a).localeCompare(selector(b));
};
const defaultCreate = (): Pick<Misskey.entities.ClipsCreateRequest, 'name'> => ({
name: 'test',
});
const create = async (parameters: Partial<Misskey.entities.ClipsCreateRequest> = {}, request: Partial<ApiRequest<'clips/create'>> = {}): Promise<Misskey.entities.Clip> => {
const clip = await successfulApiCall({
endpoint: 'clips/create',
parameters: {
...defaultCreate(),
...parameters,
},
user: alice,
...request,
});
// 入力が結果として入っていること
assert.deepStrictEqual(clip, {
...clip,
...defaultCreate(),
...parameters,
});
return clip;
};
const createMany = async (parameters: Partial<Misskey.entities.ClipsCreateRequest>, count = 10, user = alice): Promise<Misskey.entities.Clip[]> => {
return await Promise.all([...Array(count)].map((_, i) => create({
name: `test${i}`,
...parameters,
}, { user })));
};
const update = async (parameters: Optional<Misskey.entities.ClipsUpdateRequest, 'name'>, request: Partial<ApiRequest<'clips/update'>> = {}): Promise<Misskey.entities.Clip> => {
const clip = await successfulApiCall({
endpoint: 'clips/update',
parameters: {
name: 'updated',
...parameters,
},
user: alice,
...request,
});
// 入力が結果として入っていること。clipIdはidになるので消しておく
delete (parameters as { clipId?: string }).clipId;
assert.deepStrictEqual(clip, {
...clip,
...parameters,
});
return clip;
};
const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial<ApiRequest<'clips/delete'>> = {}): Promise<void> => {
return await successfulApiCall({
endpoint: 'clips/delete',
parameters,
user: alice,
...request,
}, {
status: 204,
}) as any as void;
};
const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial<ApiRequest<'clips/show'>> = {}): Promise<Misskey.entities.Clip> => {
return await successfulApiCall({
endpoint: 'clips/show',
parameters,
user: alice,
...request,
});
};
const list = async (request: Partial<ApiRequest<'clips/list'>>): Promise<Misskey.entities.Clip[]> => {
return successfulApiCall({
endpoint: 'clips/list',
parameters: {},
user: alice,
...request,
});
};
const usersClips = async (parameters: Misskey.entities.UsersClipsRequest, request: Partial<ApiRequest<'users/clips'>> = {}): Promise<Misskey.entities.Clip[]> => {
return await successfulApiCall({
endpoint: 'users/clips',
parameters,
user: alice,
...request,
});
};
beforeAll(async () => {
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
aliceNote = await post(alice, { text: 'test' });
aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' });
aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' });
aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' });
bobNote = await post(bob, { text: 'test' });
bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' });
bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' });
bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' });
}, 1000 * 60 * 2);
afterEach(async () => {
// テスト間で影響し合わないように毎回全部消す。
for (const user of [alice, bob]) {
const list = await api('clips/list', { limit: 11 }, user);
for (const clip of list.body) {
await api('clips/delete', { clipId: clip.id }, user);
}
}
});
test('の作成ができる', async () => {
const res = await create();
// ISO 8601で日付が返ってくること
assert.strictEqual(res.createdAt, new Date(res.createdAt).toISOString());
assert.strictEqual(res.lastClippedAt, null);
assert.strictEqual(res.name, 'test');
assert.strictEqual(res.description, null);
assert.strictEqual(res.isPublic, false);
assert.strictEqual(res.favoritedCount, 0);
assert.strictEqual(res.isFavorited, false);
});
test('の作成はポリシーで定められた数以上はできない。', async () => {
// ポリシー + 1まで作れるという所がミソ
const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
for (let i = 0; i < clipLimit; i++) {
await create();
}
await failedApiCall({
endpoint: 'clips/create',
parameters: defaultCreate(),
user: alice,
}, {
status: 400,
code: 'TOO_MANY_CLIPS',
id: '920f7c2d-6208-4b76-8082-e632020f5883',
});
});
const createClipAllowedPattern = [
{ label: 'nameが最大長', parameters: { name: 'x'.repeat(100) } },
{ label: 'private', parameters: { isPublic: false } },
{ label: 'public', parameters: { isPublic: true } },
{ label: 'descriptionがnull', parameters: { description: null } },
{ label: 'descriptionが最大長', parameters: { description: 'a'.repeat(2048) } },
];
test.each(createClipAllowedPattern)('の作成は$labelでもできる', async ({ parameters }) => await create(parameters));
const createClipDenyPattern = [
{ label: 'nameがnull', parameters: { name: null } },
{ label: 'nameが最大長+1', parameters: { name: 'x'.repeat(101) } },
{ label: 'isPublicがboolじゃない', parameters: { isPublic: 'true' } },
{ label: 'descriptionがゼロ長', parameters: { description: '' } },
{ label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } },
];
test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({
endpoint: 'clips/create',
// @ts-expect-error invalid params
parameters: {
...defaultCreate(),
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}));
test('の更新ができる', async () => {
const res = await update({
clipId: (await create()).id,
name: 'updated',
description: 'new description',
isPublic: true,
});
// ISO 8601で日付が返ってくること
assert.strictEqual(res.createdAt, new Date(res.createdAt).toISOString());
assert.strictEqual(res.lastClippedAt, null);
assert.strictEqual(res.name, 'updated');
assert.strictEqual(res.description, 'new description');
assert.strictEqual(res.isPublic, true);
assert.strictEqual(res.favoritedCount, 0);
assert.strictEqual(res.isFavorited, false);
});
test.each(createClipAllowedPattern)('の更新は$labelでもできる', async ({ parameters }) => await update({
clipId: (await create()).id,
name: 'updated',
...parameters,
}));
test.each([
{ label: 'clipIdがnull', parameters: { clipId: null } },
{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
code: 'NO_SUCH_CLIP',
id: 'b4d92d70-b216-46fa-9a3f-a8c811699257',
} },
{ label: '他人のクリップ', user: () => bob, assertion: {
code: 'NO_SUCH_CLIP',
id: 'b4d92d70-b216-46fa-9a3f-a8c811699257',
} },
...createClipDenyPattern as any,
])('の更新は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
endpoint: 'clips/update',
parameters: {
clipId: (await create({}, { user: (user ?? (() => alice))() })).id,
name: 'updated',
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assertion,
}));
test('の削除ができる', async () => {
await deleteClip({
clipId: (await create()).id,
});
assert.deepStrictEqual(await list({}), []);
});
test.each([
{ label: 'clipIdがnull', parameters: { clipId: null } },
{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
code: 'NO_SUCH_CLIP',
id: '70ca08ba-6865-4630-b6fb-8494759aa754',
} },
{ label: '他人のクリップ', user: () => bob, assertion: {
code: 'NO_SUCH_CLIP',
id: '70ca08ba-6865-4630-b6fb-8494759aa754',
} },
])('の削除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
endpoint: 'clips/delete',
parameters: {
// @ts-expect-error clipId must not be null
clipId: (await create({}, { user: (user ?? (() => alice))() })).id,
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assertion,
}));
test('のID指定取得ができる', async () => {
const clip = await create();
const res = await show({ clipId: clip.id });
assert.deepStrictEqual(res, clip);
});
test('のID指定取得は他人のPrivateなクリップは取得できない', async () => {
const clip = await create({ isPublic: false }, { user: bob } );
failedApiCall({
endpoint: 'clips/show',
parameters: { clipId: clip.id },
user: alice,
}, {
status: 400,
code: 'NO_SUCH_CLIP',
id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20',
});
});
test.each([
{ label: 'clipId未指定', parameters: { clipId: undefined } },
{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assetion: {
code: 'NO_SUCH_CLIP',
id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20',
} },
])('ID指定取得は$labelならできない', async ({ parameters, assetion }) => failedApiCall({
endpoint: 'clips/show',
// @ts-expect-error clipId must not be undefined
parameters: {
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assetion,
}));
test('(clips/list)()', async () => {
const res = await list({});
assert.deepStrictEqual(res, []);
});
test('(clips/list)()', async () => {
const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
const clips = await createMany({}, clipLimit);
const res = await list({
parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる
});
// 返ってくる配列には順序保障がないのでidでソートして厳密比較
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)),
clips.sort(compareBy(s => s.id)),
);
});
test('()', async () => {
const res = await usersClips({
userId: alice.id,
});
assert.deepStrictEqual(res, []);
});
test.each([
{ label: '' },
{ label: '', user: () => bob },
])('$label取得できる', async () => {
const clips = await createMany({ isPublic: true });
const res = await usersClips({
userId: alice.id,
});
// 返ってくる配列には順序保障がないのでidでソートして厳密比較
assert.deepStrictEqual(
res.sort(compareBy<Misskey.entities.Clip>(s => s.id)),
clips.sort(compareBy(s => s.id)));
// 認証状態で見たときだけisFavoritedが入っている
for (const clip of res) {
assert.strictEqual(clip.isFavorited, false);
}
});
test.each([
{ label: '', user: () => undefined },
{ label: '', parameters: { userId: 'xxxxxxx' } },
])('$labelでも取得できる', async ({ parameters, user }) => {
const clips = await createMany({ isPublic: true });
const res = await usersClips({
userId: alice.id,
limit: clips.length,
...parameters,
}, {
user: (user ?? (() => alice))(),
});
// 未認証で見たときはisFavoritedは入らない
for (const clip of res) {
assert.strictEqual('isFavorited' in clip, false);
}
});
test('Privateなクリップを含まない()', async () => {
await create({ isPublic: false });
const aliceClip = await create({ isPublic: true });
const res = await usersClips({
userId: alice.id,
limit: 2,
});
assert.deepStrictEqual(res, [aliceClip]);
});
test('ID指定で範囲選択ができる', async () => {
const clips = await createMany({ isPublic: true }, 7);
clips.sort(compareBy(s => s.id));
const res = await usersClips({
userId: alice.id,
sinceId: clips[1].id,
untilId: clips[5].id,
limit: 4,
});
// Promise.allで返ってくる配列には順序保障がないのでidでソートして厳密比較
assert.deepStrictEqual(
res.sort(compareBy<Misskey.entities.Clip>(s => s.id)),
[clips[2], clips[3], clips[4]], // sinceIdとuntilId自体は結果に含まれない
clips[1].id + ' ... ' + clips[3].id + ' with ' + clips.map(s => s.id) + ' vs. ' + res.map(s => s.id));
});
test.each([
{ label: 'userId未指定', parameters: { userId: undefined } },
{ label: 'limitゼロ', parameters: { limit: 0 } },
{ label: 'limit最大+1', parameters: { limit: 101 } },
])('$labelだと取得できない', async ({ parameters }) => failedApiCall({
endpoint: 'users/clips',
parameters: {
// @ts-expect-error userId must not be undefined
userId: alice.id,
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}));
test.each([
{ label: '', endpoint: 'clips/create' as const },
{ label: '', endpoint: 'clips/update' as const },
{ label: '', endpoint: 'clips/delete' as const },
{ label: '', endpoint: 'clips/list' as const },
{ label: '', endpoint: 'clips/favorite' as const },
{ label: '', endpoint: 'clips/unfavorite' as const },
{ label: '', endpoint: 'clips/my-favorites' as const },
{ label: '', endpoint: 'clips/add-note' as const },
{ label: '', endpoint: 'clips/remove-note' as const },
])('$labelは未認証ではできない', async ({ endpoint }) => await failedApiCall({
endpoint: endpoint,
parameters: {},
user: undefined,
}, {
status: 401,
code: 'CREDENTIAL_REQUIRED',
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
}));
describe('', () => {
let aliceClip: Misskey.entities.Clip;
const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial<ApiRequest<'clips/favorite'>> = {}): Promise<void> => {
return successfulApiCall({
endpoint: 'clips/favorite',
parameters,
user: alice,
...request,
}, {
status: 204,
}) as any as void;
};
const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial<ApiRequest<'clips/unfavorite'>> = {}): Promise<void> => {
return successfulApiCall({
endpoint: 'clips/unfavorite',
parameters,
user: alice,
...request,
}, {
status: 204,
}) as any as void;
};
const myFavorites = async (request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
return successfulApiCall({
endpoint: 'clips/my-favorites',
parameters: {},
user: alice,
...request,
});
};
beforeEach(async () => {
aliceClip = await create();
});
test('', async () => {
await favorite({ clipId: aliceClip.id });
const clip = await show({ clipId: aliceClip.id });
assert.strictEqual(clip.favoritedCount, 1);
assert.strictEqual(clip.isFavorited, true);
});
test('Publicな他人のクリップに設定できる', async () => {
const publicClip = await create({ isPublic: true });
await favorite({ clipId: publicClip.id }, { user: bob });
const clip = await show({ clipId: publicClip.id }, { user: bob });
assert.strictEqual(clip.favoritedCount, 1);
assert.strictEqual(clip.isFavorited, true);
// isFavoritedは見る人によって切り替わる。
const clip2 = await show({ clipId: publicClip.id });
assert.strictEqual(clip2.favoritedCount, 1);
assert.strictEqual(clip2.isFavorited, false);
});
test('1', async () => {
const publicClip = await create({ isPublic: true });
await favorite({ clipId: publicClip.id }, { user: bob });
await favorite({ clipId: publicClip.id });
const clip = await show({ clipId: publicClip.id }, { user: bob });
assert.strictEqual(clip.favoritedCount, 2);
assert.strictEqual(clip.isFavorited, true);
const clip2 = await show({ clipId: publicClip.id });
assert.strictEqual(clip2.favoritedCount, 2);
assert.strictEqual(clip2.isFavorited, true);
});
test('11', async () => {
const clips = [
aliceClip,
...await createMany({}, 10, alice),
...await createMany({ isPublic: true }, 10, bob),
];
for (const clip of clips) {
await favorite({ clipId: clip.id });
}
// pagenationはない。全部一気にとれる。
const favorited = await myFavorites();
assert.strictEqual(favorited.length, clips.length);
for (const clip of favorited) {
assert.strictEqual(clip.favoritedCount, 1);
assert.strictEqual(clip.isFavorited, true);
}
});
test('', async () => {
await favorite({ clipId: aliceClip.id });
await failedApiCall({
endpoint: 'clips/favorite',
parameters: {
clipId: aliceClip.id,
},
user: alice,
}, {
status: 400,
code: 'ALREADY_FAVORITED',
id: '92658936-c625-4273-8326-2d790129256e',
});
});
test.each([
{ label: 'clipIdがnull', parameters: { clipId: null } },
{ label: '', parameters: { clipId: 'xxxxxx' }, assertion: {
code: 'NO_SUCH_CLIP',
id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5',
} },
{ label: '', user: () => bob, assertion: {
code: 'NO_SUCH_CLIP',
id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5',
} },
])('$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
endpoint: 'clips/favorite',
parameters: {
// @ts-expect-error clipId must not be null
clipId: (await create({}, { user: (user ?? (() => alice))() })).id,
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assertion,
}));
test('', async () => {
await favorite({ clipId: aliceClip.id });
await unfavorite({ clipId: aliceClip.id });
const clip = await show({ clipId: aliceClip.id });
assert.strictEqual(clip.favoritedCount, 0);
assert.strictEqual(clip.isFavorited, false);
assert.deepStrictEqual(await myFavorites(), []);
});
test.each([
{ label: 'clipIdがnull', parameters: { clipId: null } },
{ label: '', parameters: { clipId: 'xxxxxx' }, assertion: {
code: 'NO_SUCH_CLIP',
id: '2603966e-b865-426c-94a7-af4a01241dc1',
} },
{ label: '', user: () => bob, assertion: {
code: 'NOT_FAVORITED',
id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187',
} },
{ label: '', assertion: {
code: 'NOT_FAVORITED',
id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187',
} },
])('$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
endpoint: 'clips/unfavorite',
parameters: {
// @ts-expect-error clipId must not be null
clipId: (await create({}, { user: (user ?? (() => alice))() })).id,
...parameters,
},
user: alice,
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assertion,
}));
test('', async () => {
await favorite({ clipId: aliceClip.id });
const favorited = await myFavorites();
assert.deepStrictEqual(favorited, [await show({ clipId: aliceClip.id })]);
});
test('', async () => {
await favorite({ clipId: aliceClip.id });
const favorited = await myFavorites({ user: bob });
assert.deepStrictEqual(favorited, []);
});
});
describe('', () => {
let aliceClip: Misskey.entities.Clip;
const sampleNotes = (): Misskey.entities.Note[] => [
aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote,
bobNote, bobHomeNote, bobFollowersNote, bobSpecifiedNote,
];
const addNote = async (parameters: Misskey.entities.ClipsAddNoteRequest, request: Partial<ApiRequest<'clips/add-note'>> = {}): Promise<void> => {
return successfulApiCall({
endpoint: 'clips/add-note',
parameters,
user: alice,
...request,
}, {
status: 204,
}) as any as void;
};
const removeNote = async (parameters: Misskey.entities.ClipsRemoveNoteRequest, request: Partial<ApiRequest<'clips/remove-note'>> = {}): Promise<void> => {
return successfulApiCall({
endpoint: 'clips/remove-note',
parameters,
user: alice,
...request,
}, {
status: 204,
}) as any as void;
};
const notes = async (parameters: Misskey.entities.ClipsNotesRequest, request: Partial<ApiRequest<'clips/notes'>> = {}): Promise<Misskey.entities.Note[]> => {
return successfulApiCall({
endpoint: 'clips/notes',
parameters,
user: alice,
...request,
});
};
beforeEach(async () => {
aliceClip = await create();
});
test('', async () => {
await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
const res = await show({ clipId: aliceClip.id });
assert.strictEqual(res.lastClippedAt, res.lastClippedAt ? new Date(res.lastClippedAt).toISOString() : null);
assert.deepStrictEqual((await notes({ clipId: aliceClip.id })).map(x => x.id), [aliceNote.id]);
// 他人の非公開ノートも突っ込める
await addNote({ clipId: aliceClip.id, noteId: bobHomeNote.id });
await addNote({ clipId: aliceClip.id, noteId: bobFollowersNote.id });
await addNote({ clipId: aliceClip.id, noteId: bobSpecifiedNote.id });
});
test('', async () => {
await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
await failedApiCall({
endpoint: 'clips/add-note',
parameters: {
clipId: aliceClip.id,
noteId: aliceNote.id,
},
user: alice,
}, {
status: 400,
code: 'ALREADY_CLIPPED',
id: '734806c4-542c-463a-9311-15c512803965',
});
});
// TODO: 17000msくらいかかる...
test('(200)', async () => {
const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1;
const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, {
text: `test ${i}`,
}) as unknown)) as Misskey.entities.Note[];
await Promise.all(noteList.map(s => addNote({ clipId: aliceClip.id, noteId: s.id })));
await failedApiCall({
endpoint: 'clips/add-note',
parameters: {
clipId: aliceClip.id,
noteId: aliceNote.id,
},
user: alice,
}, {
status: 400,
code: 'TOO_MANY_CLIP_NOTES',
id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118',
});
});
test('', async () => await failedApiCall({
endpoint: 'clips/add-note',
parameters: {
clipId: aliceClip.id,
noteId: aliceNote.id,
},
user: bob,
}, {
status: 400,
code: 'NO_SUCH_CLIP',
id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
}));
test.each([
{ label: 'clipId未指定', parameters: { clipId: undefined } },
{ label: 'noteId未指定', parameters: { noteId: undefined } },
{ label: '', parameters: { clipId: 'xxxxxx' }, assetion: {
code: 'NO_SUCH_CLIP',
id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
} },
{ label: '', parameters: { noteId: 'xxxxxx' }, assetion: {
code: 'NO_SUCH_NOTE',
id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b',
} },
{ label: '', user: () => bob, assetion: {
code: 'NO_SUCH_CLIP',
id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
} },
])('$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({
endpoint: 'clips/add-note',
parameters: {
// @ts-expect-error clipId must not be undefined
clipId: aliceClip.id,
// @ts-expect-error noteId must not be undefined
noteId: aliceNote.id,
...parameters,
},
user: (user ?? (() => alice))(),
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assetion,
}));
test('', async () => {
await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
await removeNote({ clipId: aliceClip.id, noteId: aliceNote.id });
assert.deepStrictEqual(await notes({ clipId: aliceClip.id }), []);
});
test.each([
{ label: 'clipId未指定', parameters: { clipId: undefined } },
{ label: 'noteId未指定', parameters: { noteId: undefined } },
{ label: '', parameters: { clipId: 'xxxxxx' }, assetion: {
code: 'NO_SUCH_CLIP',
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる
} },
{ label: '', parameters: { noteId: 'xxxxxx' }, assetion: {
code: 'NO_SUCH_NOTE',
id: 'aff017de-190e-434b-893e-33a9ff5049d8', // add-noteと異なる
} },
{ label: '', user: () => bob, assetion: {
code: 'NO_SUCH_CLIP',
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる
} },
])('$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({
endpoint: 'clips/remove-note',
parameters: {
// @ts-expect-error clipId must not be undefined
clipId: aliceClip.id,
// @ts-expect-error noteId must not be undefined
noteId: aliceNote.id,
...parameters,
},
user: (user ?? (() => alice))(),
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assetion,
}));
test('', async () => {
const noteList = sampleNotes();
for (const note of noteList) {
await addNote({ clipId: aliceClip.id, noteId: note.id });
}
const res = await notes({ clipId: aliceClip.id });
// 自分のノートは非公開でも入れられるし、見える
// 他人の非公開ノートは入れられるけど、除外される
const expects = [
aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote,
bobNote, bobHomeNote,
];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test('IDとlimitで取得できる', async () => {
const noteList = sampleNotes();
noteList.sort(compareBy(s => s.id));
for (const note of noteList) {
await addNote({ clipId: aliceClip.id, noteId: note.id });
}
const res = await notes({
clipId: aliceClip.id,
sinceId: noteList[2].id,
limit: 3,
});
// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
const expects = [noteList[3], noteList[4], noteList[5]];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test('ID範囲指定で取得できる', async () => {
const noteList = sampleNotes();
noteList.sort(compareBy(s => s.id));
for (const note of noteList) {
await addNote({ clipId: aliceClip.id, noteId: note.id });
}
const res = await notes({
clipId: aliceClip.id,
sinceId: noteList[1].id,
untilId: noteList[4].id,
});
// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
const expects = [noteList[2], noteList[3]];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test.todo('Remoteのートもクリップできる');
test('Publicなクリップからも取得できる', async () => {
const bobClip = await create({ isPublic: true }, { user: bob } );
await addNote({ clipId: bobClip.id, noteId: aliceNote.id }, { user: bob });
const res = await notes({ clipId: bobClip.id });
assert.deepStrictEqual(res.map(x => x.id), [aliceNote.id]);
});
test('Publicなクリップなら認証なしでも取得できる(hideされて返ってくる)', async () => {
const publicClip = await create({ isPublic: true });
await addNote({ clipId: publicClip.id, noteId: aliceNote.id });
await addNote({ clipId: publicClip.id, noteId: aliceHomeNote.id });
await addNote({ clipId: publicClip.id, noteId: aliceFollowersNote.id });
await addNote({ clipId: publicClip.id, noteId: aliceSpecifiedNote.id });
const res = await notes({ clipId: publicClip.id }, { user: undefined });
const expects = [
aliceNote, aliceHomeNote,
// 認証なしだと非公開ートは結果には含むけどhideされる。
hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote),
];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test.todo('etc.');
test.each([
{ label: 'clipId未指定', parameters: { clipId: undefined } },
{ label: 'limitゼロ', parameters: { limit: 0 } },
{ label: 'limit最大+1', parameters: { limit: 101 } },
{ label: '', parameters: { clipId: 'xxxxxx' }, assertion: {
code: 'NO_SUCH_CLIP',
id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
} },
{ label: 'Privateなクリップから', user: () => bob, assertion: {
code: 'NO_SUCH_CLIP',
id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
} },
{ label: 'Privateなクリップから', user: () => undefined, assertion: {
code: 'NO_SUCH_CLIP',
id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
} },
])('$labelだと取得できない', async ({ parameters, user, assertion }) => failedApiCall({
endpoint: 'clips/notes',
parameters: {
// @ts-expect-error clipId must not be undefined
clipId: aliceClip.id,
...parameters,
},
user: (user ?? (() => alice))(),
}, {
status: 400,
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
...assertion,
}));
});
});