Merge remote-tracking branch 'misskey/master' into feature/misskey-2024.07
This commit is contained in:
commit
cfa9b852df
585 changed files with 23423 additions and 9623 deletions
|
@ -1,11 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: ['../.eslintrc.cjs'],
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
};
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
redistest:
|
||||
image: redis:7
|
|
@ -206,7 +206,7 @@ describe('2要素認証', () => {
|
|||
username,
|
||||
}, alice);
|
||||
assert.strictEqual(usersShowResponse.status, 200);
|
||||
assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true);
|
||||
assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
|
||||
|
||||
const signinResponse = await api('signin', {
|
||||
...signinParam(),
|
||||
|
@ -248,7 +248,7 @@ describe('2要素認証', () => {
|
|||
keyName,
|
||||
credentialId,
|
||||
creationOptions: registerKeyResponse.body,
|
||||
}) as any, alice);
|
||||
} as any) as any, alice);
|
||||
assert.strictEqual(keyDoneResponse.status, 200);
|
||||
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
|
||||
assert.strictEqual(keyDoneResponse.body.name, keyName);
|
||||
|
@ -257,22 +257,22 @@ describe('2要素認証', () => {
|
|||
username,
|
||||
});
|
||||
assert.strictEqual(usersShowResponse.status, 200);
|
||||
assert.strictEqual(usersShowResponse.body.securityKeys, true);
|
||||
assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, true);
|
||||
|
||||
const signinResponse = await api('signin', {
|
||||
...signinParam(),
|
||||
});
|
||||
assert.strictEqual(signinResponse.status, 200);
|
||||
assert.strictEqual(signinResponse.body.i, undefined);
|
||||
assert.notEqual(signinResponse.body.challenge, undefined);
|
||||
assert.notEqual(signinResponse.body.allowCredentials, undefined);
|
||||
assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url'));
|
||||
assert.notEqual((signinResponse.body as unknown as { challenge: unknown | undefined }).challenge, undefined);
|
||||
assert.notEqual((signinResponse.body as unknown as { allowCredentials: unknown | undefined }).allowCredentials, undefined);
|
||||
assert.strictEqual((signinResponse.body as unknown as { allowCredentials: {id: string}[] }).allowCredentials[0].id, credentialId.toString('base64url'));
|
||||
|
||||
const signinResponse2 = await api('signin', signinWithSecurityKeyParam({
|
||||
keyName,
|
||||
credentialId,
|
||||
requestOptions: signinResponse.body,
|
||||
}));
|
||||
} as any));
|
||||
assert.strictEqual(signinResponse2.status, 200);
|
||||
assert.notEqual(signinResponse2.body.i, undefined);
|
||||
|
||||
|
@ -307,7 +307,7 @@ describe('2要素認証', () => {
|
|||
keyName,
|
||||
credentialId,
|
||||
creationOptions: registerKeyResponse.body,
|
||||
}) as any, alice);
|
||||
} as any) as any, alice);
|
||||
assert.strictEqual(keyDoneResponse.status, 200);
|
||||
|
||||
const passwordLessResponse = await api('i/2fa/password-less', {
|
||||
|
@ -319,7 +319,7 @@ describe('2要素認証', () => {
|
|||
username,
|
||||
});
|
||||
assert.strictEqual(usersShowResponse.status, 200);
|
||||
assert.strictEqual(usersShowResponse.body.usePasswordLessLogin, true);
|
||||
assert.strictEqual((usersShowResponse.body as unknown as { usePasswordLessLogin: boolean }).usePasswordLessLogin, true);
|
||||
|
||||
const signinResponse = await api('signin', {
|
||||
...signinParam(),
|
||||
|
@ -333,7 +333,7 @@ describe('2要素認証', () => {
|
|||
keyName,
|
||||
credentialId,
|
||||
requestOptions: signinResponse.body,
|
||||
}),
|
||||
} as any),
|
||||
password: '',
|
||||
});
|
||||
assert.strictEqual(signinResponse2.status, 200);
|
||||
|
@ -370,7 +370,7 @@ describe('2要素認証', () => {
|
|||
keyName,
|
||||
credentialId,
|
||||
creationOptions: registerKeyResponse.body,
|
||||
}) as any, alice);
|
||||
} as any) as any, alice);
|
||||
assert.strictEqual(keyDoneResponse.status, 200);
|
||||
|
||||
const renamedKey = 'other-key';
|
||||
|
@ -383,6 +383,7 @@ describe('2要素認証', () => {
|
|||
const iResponse = await api('i', {
|
||||
}, alice);
|
||||
assert.strictEqual(iResponse.status, 200);
|
||||
assert.ok(iResponse.body.securityKeysList);
|
||||
const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url'));
|
||||
assert.strictEqual(securityKeys.length, 1);
|
||||
assert.strictEqual(securityKeys[0].name, renamedKey);
|
||||
|
@ -419,13 +420,14 @@ describe('2要素認証', () => {
|
|||
keyName,
|
||||
credentialId,
|
||||
creationOptions: registerKeyResponse.body,
|
||||
}) as any, alice);
|
||||
} as any) as any, alice);
|
||||
assert.strictEqual(keyDoneResponse.status, 200);
|
||||
|
||||
// テストの実行順によっては複数残ってるので全部消す
|
||||
const iResponse = await api('i', {
|
||||
}, alice);
|
||||
assert.strictEqual(iResponse.status, 200);
|
||||
assert.ok(iResponse.body.securityKeysList);
|
||||
for (const key of iResponse.body.securityKeysList) {
|
||||
const removeKeyResponse = await api('i/2fa/remove-key', {
|
||||
token: otpToken(registerResponse.body.secret),
|
||||
|
@ -439,7 +441,7 @@ describe('2要素認証', () => {
|
|||
username,
|
||||
});
|
||||
assert.strictEqual(usersShowResponse.status, 200);
|
||||
assert.strictEqual(usersShowResponse.body.securityKeys, false);
|
||||
assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, false);
|
||||
|
||||
const signinResponse = await api('signin', {
|
||||
...signinParam(),
|
||||
|
@ -470,7 +472,7 @@ describe('2要素認証', () => {
|
|||
username,
|
||||
});
|
||||
assert.strictEqual(usersShowResponse.status, 200);
|
||||
assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true);
|
||||
assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
|
||||
|
||||
const unregisterResponse = await api('i/2fa/unregister', {
|
||||
token: otpToken(registerResponse.body.secret),
|
||||
|
|
|
@ -163,8 +163,7 @@ describe('アンテナ', () => {
|
|||
});
|
||||
|
||||
test('が上限いっぱいまで作成できること', async () => {
|
||||
// antennaLimit + 1まで作れるのがキモ
|
||||
const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit + 1)].map(() => successfulApiCall({
|
||||
const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit)].map(() => successfulApiCall({
|
||||
endpoint: 'antennas/create',
|
||||
parameters: { ...defaultParam },
|
||||
user: alice,
|
||||
|
|
|
@ -410,21 +410,21 @@ describe('API visibility', () => {
|
|||
test('[HTL] public-post が 自分が見れる', async () => {
|
||||
const res = await api('notes/timeline', { limit: 100 }, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === pub.id);
|
||||
const notes = res.body.filter(n => n.id === pub.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
});
|
||||
|
||||
test('[HTL] public-post が 非フォロワーから見れない', async () => {
|
||||
const res = await api('notes/timeline', { limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === pub.id);
|
||||
const notes = res.body.filter(n => n.id === pub.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
});
|
||||
|
||||
test('[HTL] followers-post が フォロワーから見れる', async () => {
|
||||
const res = await api('notes/timeline', { limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === fol.id);
|
||||
const notes = res.body.filter(n => n.id === fol.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
});
|
||||
//#endregion
|
||||
|
@ -433,21 +433,21 @@ describe('API visibility', () => {
|
|||
test('[replies] followers-reply が フォロワーから見れる', async () => {
|
||||
const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
||||
const notes = res.body.filter(n => n.id === folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
});
|
||||
|
||||
test('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async () => {
|
||||
const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
||||
const notes = res.body.filter(n => n.id === folR.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
});
|
||||
|
||||
test('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => {
|
||||
const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
||||
const notes = res.body.filter(n => n.id === folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
});
|
||||
//#endregion
|
||||
|
@ -456,14 +456,14 @@ describe('API visibility', () => {
|
|||
test('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => {
|
||||
const res = await api('notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
||||
const notes = res.body.filter(n => n.id === folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
});
|
||||
|
||||
test('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async () => {
|
||||
const res = await api('notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id === folM.id);
|
||||
const notes = res.body.filter(n => n.id === folM.id);
|
||||
assert.strictEqual(notes[0].text, '@target x');
|
||||
});
|
||||
//#endregion
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, post, signup } from '../utils.js';
|
||||
import { api, castAsError, post, signup } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Block', () => {
|
||||
|
@ -33,7 +33,7 @@ describe('Block', () => {
|
|||
const res = await api('following/create', { userId: alice.id }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0');
|
||||
});
|
||||
|
||||
test('ブロックされているユーザーにリアクションできない', async () => {
|
||||
|
@ -42,7 +42,8 @@ describe('Block', () => {
|
|||
const res = await api('notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec');
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec');
|
||||
});
|
||||
|
||||
test('ブロックされているユーザーに返信できない', async () => {
|
||||
|
@ -51,7 +52,8 @@ describe('Block', () => {
|
|||
const res = await api('notes/create', { replyId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
});
|
||||
|
||||
test('ブロックされているユーザーのノートをRenoteできない', async () => {
|
||||
|
@ -60,7 +62,7 @@ describe('Block', () => {
|
|||
const res = await api('notes/create', { renoteId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
});
|
||||
|
||||
// TODO: ユーザーリストに入れられないテスト
|
||||
|
|
|
@ -79,14 +79,14 @@ describe('クリップ', () => {
|
|||
};
|
||||
|
||||
const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial<ApiRequest<'clips/delete'>> = {}): Promise<void> => {
|
||||
return await successfulApiCall({
|
||||
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> => {
|
||||
|
@ -153,8 +153,7 @@ describe('クリップ', () => {
|
|||
});
|
||||
|
||||
test('の作成はポリシーで定められた数以上はできない。', async () => {
|
||||
// ポリシー + 1まで作れるという所がミソ
|
||||
const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
|
||||
const clipLimit = DEFAULT_POLICIES.clipLimit;
|
||||
for (let i = 0; i < clipLimit; i++) {
|
||||
await create();
|
||||
}
|
||||
|
@ -327,7 +326,7 @@ describe('クリップ', () => {
|
|||
});
|
||||
|
||||
test('の一覧(clips/list)が取得できる(上限いっぱい)', async () => {
|
||||
const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
|
||||
const clipLimit = DEFAULT_POLICIES.clipLimit;
|
||||
const clips = await createMany({}, clipLimit);
|
||||
const res = await list({
|
||||
parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる
|
||||
|
@ -455,25 +454,25 @@ describe('クリップ', () => {
|
|||
let aliceClip: Misskey.entities.Clip;
|
||||
|
||||
const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial<ApiRequest<'clips/favorite'>> = {}): Promise<void> => {
|
||||
return successfulApiCall({
|
||||
await 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({
|
||||
await 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[]> => {
|
||||
|
@ -705,7 +704,7 @@ describe('クリップ', () => {
|
|||
|
||||
// TODO: 17000msくらいかかる...
|
||||
test('をポリシーで定められた上限いっぱい(200)を超えて追加はできない。', async () => {
|
||||
const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1;
|
||||
const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit;
|
||||
const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, {
|
||||
text: `test ${i}`,
|
||||
}) as unknown)) as Misskey.entities.Note[];
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('Drive', () => {
|
|||
|
||||
const marker = Math.random().toString();
|
||||
|
||||
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg';
|
||||
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg';
|
||||
|
||||
const catcher = makeStreamCatcher(
|
||||
alice,
|
||||
|
@ -41,14 +41,14 @@ describe('Drive', () => {
|
|||
const file = await catcher;
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
assert.strictEqual(file.name, 'Lenna.jpg');
|
||||
assert.strictEqual(file.name, '192.jpg');
|
||||
assert.strictEqual(file.type, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('ローカルからアップロードできる', async () => {
|
||||
// APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする
|
||||
|
||||
const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' });
|
||||
const res = await uploadFile(alice, { path: '192.jpg', name: 'テスト画像' });
|
||||
|
||||
assert.strictEqual(res.body?.name, 'テスト画像.jpg');
|
||||
assert.strictEqual(res.body.type, 'image/jpeg');
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as assert from 'assert';
|
|||
// https://github.com/node-fetch/node-fetch/pull/1664
|
||||
import { Blob } from 'node-fetch';
|
||||
import { MiUser } from '@/models/_.js';
|
||||
import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import { api, castAsError, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Endpoints', () => {
|
||||
|
@ -117,12 +117,21 @@ describe('Endpoints', () => {
|
|||
assert.strictEqual(res.body.birthday, myBirthday);
|
||||
});
|
||||
|
||||
test('名前を空白にできる', async () => {
|
||||
test('名前を空白のみにした場合nullになる', async () => {
|
||||
const res = await api('i/update', {
|
||||
name: ' ',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.name, ' ');
|
||||
assert.strictEqual(res.body.name, null);
|
||||
});
|
||||
|
||||
test('名前の前後に空白(ホワイトスペース)を入れてもトリムされる', async () => {
|
||||
const res = await api('i/update', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space
|
||||
name: ' あ い う \u0009\u000b\u000c\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.name, 'あ い う');
|
||||
});
|
||||
|
||||
test('誕生日の設定を削除できる', async () => {
|
||||
|
@ -155,7 +164,7 @@ describe('Endpoints', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.id, alice.id);
|
||||
assert.strictEqual((res.body as unknown as { id: string }).id, alice.id);
|
||||
});
|
||||
|
||||
test('ユーザーが存在しなかったら怒る', async () => {
|
||||
|
@ -266,6 +275,68 @@ describe('Endpoints', () => {
|
|||
assert.strictEqual(res.status, 400);
|
||||
});
|
||||
|
||||
test('リノートにリアクションできない', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const bobRenote = await post(bob, { renoteId: bobNote.id });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobRenote.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'CANNOT_REACT_TO_RENOTE');
|
||||
});
|
||||
|
||||
test('引用にリアクションできる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const bobRenote = await post(bob, { text: 'hi again', renoteId: bobNote.id });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobRenote.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
});
|
||||
|
||||
test('空文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobNote.id,
|
||||
reaction: '',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const reaction = await api('notes/reactions', {
|
||||
noteId: bobNote.id,
|
||||
});
|
||||
|
||||
assert.strictEqual(reaction.body.length, 1);
|
||||
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||
});
|
||||
|
||||
test('絵文字ではない文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobNote.id,
|
||||
reaction: 'Hello!',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const reaction = await api('notes/reactions', {
|
||||
noteId: bobNote.id,
|
||||
});
|
||||
|
||||
assert.strictEqual(reaction.body.length, 1);
|
||||
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||
});
|
||||
|
||||
test('空のパラメータで怒られる', async () => {
|
||||
// @ts-expect-error param must not be empty
|
||||
const res = await api('notes/reactions/create', {}, alice);
|
||||
|
@ -523,7 +594,7 @@ describe('Endpoints', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body!.name, 'Lenna.jpg');
|
||||
assert.strictEqual(res.body!.name, '192.jpg');
|
||||
});
|
||||
|
||||
test('ファイルに名前を付けられる', async () => {
|
||||
|
@ -993,7 +1064,7 @@ describe('Endpoints', () => {
|
|||
userId: bob.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res1.status, 204);
|
||||
assert.strictEqual(res2.body?.memo, memo);
|
||||
assert.strictEqual((res2.body as unknown as { memo: string })?.memo, memo);
|
||||
});
|
||||
|
||||
test('自分に関するメモを更新できる', async () => {
|
||||
|
@ -1008,7 +1079,7 @@ describe('Endpoints', () => {
|
|||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res1.status, 204);
|
||||
assert.strictEqual(res2.body?.memo, memo);
|
||||
assert.strictEqual((res2.body as unknown as { memo: string })?.memo, memo);
|
||||
});
|
||||
|
||||
test('メモを削除できる', async () => {
|
||||
|
@ -1029,7 +1100,7 @@ describe('Endpoints', () => {
|
|||
}, alice);
|
||||
|
||||
// memoには常に文字列かnullが入っている(5cac151)
|
||||
assert.strictEqual(res.body.memo, null);
|
||||
assert.strictEqual((res.body as unknown as { memo: string | null }).memo, null);
|
||||
});
|
||||
|
||||
test('メモは個人ごとに独立して保存される', async () => {
|
||||
|
@ -1056,8 +1127,8 @@ describe('Endpoints', () => {
|
|||
}, carol),
|
||||
]);
|
||||
|
||||
assert.strictEqual(resAlice.body.memo, memoAliceToBob);
|
||||
assert.strictEqual(resCarol.body.memo, memoCarolToBob);
|
||||
assert.strictEqual((resAlice.body as unknown as { memo: string }).memo, memoAliceToBob);
|
||||
assert.strictEqual((resCarol.body as unknown as { memo: string }).memo, memoCarolToBob);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,14 +61,14 @@ describe('export-clips', () => {
|
|||
});
|
||||
|
||||
test('basic export', async () => {
|
||||
let res = await api('clips/create', {
|
||||
const res1 = await api('clips/create', {
|
||||
name: 'foo',
|
||||
description: 'bar',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res1.status, 200);
|
||||
|
||||
res = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
const res2 = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res2.status, 204);
|
||||
|
||||
const exported = await pollFirstDriveFile();
|
||||
assert.strictEqual(exported[0].name, 'foo');
|
||||
|
@ -77,7 +77,7 @@ describe('export-clips', () => {
|
|||
});
|
||||
|
||||
test('export with notes', async () => {
|
||||
let res = await api('clips/create', {
|
||||
const res = await api('clips/create', {
|
||||
name: 'foo',
|
||||
description: 'bar',
|
||||
}, alice);
|
||||
|
@ -96,15 +96,15 @@ describe('export-clips', () => {
|
|||
});
|
||||
|
||||
for (const note of [note1, note2]) {
|
||||
res = await api('clips/add-note', {
|
||||
const res2 = await api('clips/add-note', {
|
||||
clipId: clip.id,
|
||||
noteId: note.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
assert.strictEqual(res2.status, 204);
|
||||
}
|
||||
|
||||
res = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
const res3 = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res3.status, 204);
|
||||
|
||||
const exported = await pollFirstDriveFile();
|
||||
assert.strictEqual(exported[0].name, 'foo');
|
||||
|
@ -116,19 +116,19 @@ describe('export-clips', () => {
|
|||
});
|
||||
|
||||
test('multiple clips', async () => {
|
||||
let res = await api('clips/create', {
|
||||
const res1 = await api('clips/create', {
|
||||
name: 'kawaii',
|
||||
description: 'kawaii',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const clip1 = res.body;
|
||||
assert.strictEqual(res1.status, 200);
|
||||
const clip1 = res1.body;
|
||||
|
||||
res = await api('clips/create', {
|
||||
const res2 = await api('clips/create', {
|
||||
name: 'yuri',
|
||||
description: 'yuri',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const clip2 = res.body;
|
||||
assert.strictEqual(res2.status, 200);
|
||||
const clip2 = res2.body;
|
||||
|
||||
const note1 = await post(alice, {
|
||||
text: 'baz1',
|
||||
|
@ -138,20 +138,26 @@ describe('export-clips', () => {
|
|||
text: 'baz2',
|
||||
});
|
||||
|
||||
res = await api('clips/add-note', {
|
||||
clipId: clip1.id,
|
||||
noteId: note1.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
{
|
||||
const res = await api('clips/add-note', {
|
||||
clipId: clip1.id,
|
||||
noteId: note1.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
}
|
||||
|
||||
res = await api('clips/add-note', {
|
||||
clipId: clip2.id,
|
||||
noteId: note2.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
{
|
||||
const res = await api('clips/add-note', {
|
||||
clipId: clip2.id,
|
||||
noteId: note2.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
}
|
||||
|
||||
res = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
{
|
||||
const res = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
}
|
||||
|
||||
const exported = await pollFirstDriveFile();
|
||||
assert.strictEqual(exported[0].name, 'kawaii');
|
||||
|
@ -163,7 +169,7 @@ describe('export-clips', () => {
|
|||
});
|
||||
|
||||
test('Clipping other user\'s note', async () => {
|
||||
let res = await api('clips/create', {
|
||||
const res = await api('clips/create', {
|
||||
name: 'kawaii',
|
||||
description: 'kawaii',
|
||||
}, alice);
|
||||
|
@ -175,14 +181,14 @@ describe('export-clips', () => {
|
|||
visibility: 'followers',
|
||||
});
|
||||
|
||||
res = await api('clips/add-note', {
|
||||
const res2 = await api('clips/add-note', {
|
||||
clipId: clip.id,
|
||||
noteId: note.id,
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
assert.strictEqual(res2.status, 204);
|
||||
|
||||
res = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res.status, 204);
|
||||
const res3 = await api('i/export-clips', {}, alice);
|
||||
assert.strictEqual(res3.status, 204);
|
||||
|
||||
const exported = await pollFirstDriveFile();
|
||||
assert.strictEqual(exported[0].name, 'kawaii');
|
||||
|
|
|
@ -153,6 +153,23 @@ describe('Webリソース', () => {
|
|||
path: path('nonexisting'),
|
||||
status: 404,
|
||||
}));
|
||||
|
||||
describe(' has entry such ', () => {
|
||||
beforeEach(() => {
|
||||
post(alice, { text: "**a**" })
|
||||
});
|
||||
|
||||
test('MFMを含まない。', async () => {
|
||||
const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text());
|
||||
const _body: unknown = content.body;
|
||||
// JSONフィードのときは改めて文字列化する
|
||||
const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string;
|
||||
|
||||
if (body.includes("**a**")) {
|
||||
throw new Error("MFM shouldn't be included");
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||
|
|
|
@ -7,19 +7,20 @@ import { INestApplicationContext } from '@nestjs/common';
|
|||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import * as assert from 'assert';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { MiUser, UsersRepository } from '@/models/_.js';
|
||||
import { MiRepository, MiUser, UsersRepository, miRepository } from '@/models/_.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { jobQueue } from '@/boot/common.js';
|
||||
import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
|
||||
import { api, castAsError, initTestDb, signup, successfulApiCall, uploadFile } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Account Move', () => {
|
||||
let jq: INestApplicationContext;
|
||||
let url: URL;
|
||||
|
||||
let root: any;
|
||||
let root: misskey.entities.SignupResponse;
|
||||
let alice: misskey.entities.SignupResponse;
|
||||
let bob: misskey.entities.SignupResponse;
|
||||
let carol: misskey.entities.SignupResponse;
|
||||
|
@ -42,7 +43,7 @@ describe('Account Move', () => {
|
|||
dave = await signup({ username: 'dave' });
|
||||
eve = await signup({ username: 'eve' });
|
||||
frank = await signup({ username: 'frank' });
|
||||
Users = connection.getRepository(MiUser);
|
||||
Users = connection.getRepository(MiUser).extend(miRepository as MiRepository<MiUser>);
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -92,8 +93,8 @@ describe('Account Move', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
});
|
||||
|
||||
test('Unable to add duplicated aliases to alsoKnownAs', async () => {
|
||||
|
@ -102,8 +103,8 @@ describe('Account Move', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'INVALID_PARAM');
|
||||
assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'INVALID_PARAM');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
|
||||
});
|
||||
|
||||
test('Unable to add itself', async () => {
|
||||
|
@ -112,8 +113,8 @@ describe('Account Move', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'FORBIDDEN_TO_SET_YOURSELF');
|
||||
assert.strictEqual(res.body.error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'FORBIDDEN_TO_SET_YOURSELF');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4');
|
||||
});
|
||||
|
||||
test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
|
||||
|
@ -122,16 +123,16 @@ describe('Account Move', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(res1.status, 400);
|
||||
assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
assert.strictEqual(castAsError(res1.body).error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(castAsError(res1.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
|
||||
const res2 = await api('i/update', {
|
||||
alsoKnownAs: ['@alice', 'nonexist'],
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(res2.status, 400);
|
||||
assert.strictEqual(res2.body.error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(res2.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
assert.strictEqual(castAsError(res2.body).error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(castAsError(res2.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
});
|
||||
|
||||
test('Able to add two existing local account to alsoKnownAs', async () => {
|
||||
|
@ -240,8 +241,8 @@ describe('Account Move', () => {
|
|||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NOT_ROOT_FORBIDDEN');
|
||||
assert.strictEqual(res.body.error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NOT_ROOT_FORBIDDEN');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24');
|
||||
});
|
||||
|
||||
test('Unable to move to a nonexisting local account', async () => {
|
||||
|
@ -250,8 +251,8 @@ describe('Account Move', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_USER');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||
});
|
||||
|
||||
test('Unable to move if alsoKnownAs is invalid', async () => {
|
||||
|
@ -260,8 +261,8 @@ describe('Account Move', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||
assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||
});
|
||||
|
||||
test('Relationships have been properly migrated', async () => {
|
||||
|
@ -271,43 +272,51 @@ describe('Account Move', () => {
|
|||
|
||||
assert.strictEqual(move.status, 200);
|
||||
|
||||
await sleep(1000 * 3); // wait for jobs to finish
|
||||
await setTimeout(1000 * 3); // wait for jobs to finish
|
||||
|
||||
// Unfollow delayed?
|
||||
const aliceFollowings = await api('users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(aliceFollowings.status, 200);
|
||||
assert.ok(aliceFollowings);
|
||||
assert.strictEqual(aliceFollowings.body.length, 3);
|
||||
|
||||
const carolFollowings = await api('users/following', {
|
||||
userId: carol.id,
|
||||
}, carol);
|
||||
assert.strictEqual(carolFollowings.status, 200);
|
||||
assert.ok(carolFollowings);
|
||||
assert.strictEqual(carolFollowings.body.length, 2);
|
||||
assert.strictEqual(carolFollowings.body[0].followeeId, bob.id);
|
||||
assert.strictEqual(carolFollowings.body[1].followeeId, alice.id);
|
||||
|
||||
const blockings = await api('blocking/list', {}, dave);
|
||||
assert.strictEqual(blockings.status, 200);
|
||||
assert.ok(blockings);
|
||||
assert.strictEqual(blockings.body.length, 2);
|
||||
assert.strictEqual(blockings.body[0].blockeeId, bob.id);
|
||||
assert.strictEqual(blockings.body[1].blockeeId, alice.id);
|
||||
|
||||
const mutings = await api('mute/list', {}, dave);
|
||||
assert.strictEqual(mutings.status, 200);
|
||||
assert.ok(mutings);
|
||||
assert.strictEqual(mutings.body.length, 2);
|
||||
assert.strictEqual(mutings.body[0].muteeId, bob.id);
|
||||
assert.strictEqual(mutings.body[1].muteeId, alice.id);
|
||||
|
||||
const rootLists = await api('users/lists/list', {}, root);
|
||||
assert.strictEqual(rootLists.status, 200);
|
||||
assert.ok(rootLists);
|
||||
assert.ok(rootLists.body[0].userIds);
|
||||
assert.strictEqual(rootLists.body[0].userIds.length, 2);
|
||||
assert.ok(rootLists.body[0].userIds.find((id: string) => id === bob.id));
|
||||
assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.id));
|
||||
|
||||
const eveLists = await api('users/lists/list', {}, eve);
|
||||
assert.strictEqual(eveLists.status, 200);
|
||||
assert.ok(eveLists);
|
||||
assert.ok(eveLists.body[0].userIds);
|
||||
assert.strictEqual(eveLists.body[0].userIds.length, 1);
|
||||
assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id));
|
||||
});
|
||||
|
@ -330,7 +339,7 @@ describe('Account Move', () => {
|
|||
});
|
||||
|
||||
test('Unfollowed after 10 sec (24 hours in production).', async () => {
|
||||
await sleep(1000 * 8);
|
||||
await setTimeout(1000 * 8);
|
||||
|
||||
const following = await api('users/following', {
|
||||
userId: alice.id,
|
||||
|
@ -346,8 +355,8 @@ describe('Account Move', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||
assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||
});
|
||||
|
||||
test('Follow and follower counts are properly adjusted', async () => {
|
||||
|
@ -418,8 +427,9 @@ describe('Account Move', () => {
|
|||
] as const)('Prohibit access after moving: %s', async (endpoint) => {
|
||||
const res = await api(endpoint, {}, alice);
|
||||
assert.strictEqual(res.status, 403);
|
||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
});
|
||||
|
||||
test('Prohibit access after moving: /antennas/update', async () => {
|
||||
|
@ -437,16 +447,19 @@ describe('Account Move', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 403);
|
||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
});
|
||||
|
||||
test('Prohibit access after moving: /drive/files/create', async () => {
|
||||
// FIXME: 一旦逃げておく
|
||||
const res = await uploadFile(alice);
|
||||
|
||||
assert.strictEqual(res.status, 403);
|
||||
assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
});
|
||||
|
||||
test('Prohibit updating alsoKnownAs after moving', async () => {
|
||||
|
@ -455,8 +468,8 @@ describe('Account Move', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 403);
|
||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,8 +47,8 @@ describe('Mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||
});
|
||||
|
||||
test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => {
|
||||
|
@ -92,9 +92,9 @@ describe('Mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||
});
|
||||
|
||||
test('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async () => {
|
||||
|
@ -108,9 +108,9 @@ describe('Mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -124,8 +124,8 @@ describe('Mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||
|
@ -138,8 +138,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||
|
@ -152,8 +152,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
||||
|
@ -166,8 +166,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
||||
|
@ -180,8 +180,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
||||
|
@ -193,8 +193,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
|
||||
await api('following/delete', { userId: alice.id }, bob);
|
||||
await api('following/delete', { userId: alice.id }, carol);
|
||||
|
@ -210,8 +210,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
|
||||
await api('following/delete', { userId: alice.id }, bob);
|
||||
await api('following/delete', { userId: alice.id }, carol);
|
||||
|
@ -228,8 +228,8 @@ describe('Mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||
const aliceNote = await post(alice, { text: 'hi' });
|
||||
|
@ -241,8 +241,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||
|
@ -255,8 +255,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
||||
|
@ -269,8 +269,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
||||
|
@ -283,8 +283,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
|
||||
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
||||
|
@ -296,8 +296,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
|
||||
await api('following/delete', { userId: alice.id }, bob);
|
||||
await api('following/delete', { userId: alice.id }, carol);
|
||||
|
@ -313,8 +313,8 @@ describe('Mute', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Repository } from "typeorm";
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||
import { api, castAsError, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Note', () => {
|
||||
let Notes: any;
|
||||
let Notes: Repository<MiNote>;
|
||||
|
||||
let root: misskey.entities.SignupResponse;
|
||||
let alice: misskey.entities.SignupResponse;
|
||||
|
@ -41,7 +43,7 @@ describe('Note', () => {
|
|||
});
|
||||
|
||||
test('ファイルを添付できる', async () => {
|
||||
const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
|
||||
const res = await api('notes/create', {
|
||||
fileIds: [file.id],
|
||||
|
@ -53,7 +55,7 @@ describe('Note', () => {
|
|||
}, 1000 * 10);
|
||||
|
||||
test('他人のファイルで怒られる', async () => {
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
|
||||
const res = await api('notes/create', {
|
||||
text: 'test',
|
||||
|
@ -61,8 +63,8 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
}, 1000 * 10);
|
||||
|
||||
test('存在しないファイルで怒られる', async () => {
|
||||
|
@ -72,8 +74,8 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
});
|
||||
|
||||
test('不正なファイルIDで怒られる', async () => {
|
||||
|
@ -81,8 +83,8 @@ describe('Note', () => {
|
|||
fileIds: ['kyoppie'],
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||
});
|
||||
|
||||
test('返信できる', async () => {
|
||||
|
@ -101,6 +103,7 @@ describe('Note', () => {
|
|||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||
assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
|
||||
assert.ok(res.body.createdNote.reply);
|
||||
assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
|
||||
});
|
||||
|
||||
|
@ -118,6 +121,7 @@ describe('Note', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||
assert.ok(res.body.createdNote.renote);
|
||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||
});
|
||||
|
||||
|
@ -137,6 +141,7 @@ describe('Note', () => {
|
|||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||
assert.ok(res.body.createdNote.renote);
|
||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||
});
|
||||
|
||||
|
@ -218,7 +223,7 @@ describe('Note', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(bobReply.status, 400);
|
||||
assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE');
|
||||
assert.strictEqual(castAsError(bobReply.body).error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE');
|
||||
});
|
||||
|
||||
test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => {
|
||||
|
@ -256,7 +261,7 @@ describe('Note', () => {
|
|||
}, bob);
|
||||
|
||||
assert.strictEqual(bobReply.status, 400);
|
||||
assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY');
|
||||
assert.strictEqual(castAsError(bobReply.body).error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY');
|
||||
});
|
||||
|
||||
test('文字数ぎりぎりで怒られない', async () => {
|
||||
|
@ -333,6 +338,7 @@ describe('Note', () => {
|
|||
assert.strictEqual(res.body.createdNote.text, post.text);
|
||||
|
||||
const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
|
||||
assert.ok(noteDoc);
|
||||
assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
|
||||
});
|
||||
|
||||
|
@ -345,6 +351,7 @@ describe('Note', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.ok(res.body.createdNote.files);
|
||||
assert.strictEqual(res.body.createdNote.files.length, 1);
|
||||
assert.strictEqual(res.body.createdNote.files[0].id, file.body!.id);
|
||||
});
|
||||
|
@ -363,8 +370,9 @@ describe('Note', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
const myNote = res.body.find((note: { id: string; files: { id: string }[] }) => note.id === createdNote.body.createdNote.id);
|
||||
assert.notEqual(myNote, null);
|
||||
const myNote = res.body.find(note => note.id === createdNote.body.createdNote.id);
|
||||
assert.ok(myNote);
|
||||
assert.ok(myNote.files);
|
||||
assert.strictEqual(myNote.files.length, 1);
|
||||
assert.strictEqual(myNote.files[0].id, file.body!.id);
|
||||
});
|
||||
|
@ -389,7 +397,9 @@ describe('Note', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
||||
assert.notEqual(myNote, null);
|
||||
assert.ok(myNote);
|
||||
assert.ok(myNote.renote);
|
||||
assert.ok(myNote.renote.files);
|
||||
assert.strictEqual(myNote.renote.files.length, 1);
|
||||
assert.strictEqual(myNote.renote.files[0].id, file.body!.id);
|
||||
});
|
||||
|
@ -415,7 +425,9 @@ describe('Note', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
const myNote = res.body.find((note: { id: string }) => note.id === reply.body.createdNote.id);
|
||||
assert.notEqual(myNote, null);
|
||||
assert.ok(myNote);
|
||||
assert.ok(myNote.reply);
|
||||
assert.ok(myNote.reply.files);
|
||||
assert.strictEqual(myNote.reply.files.length, 1);
|
||||
assert.strictEqual(myNote.reply.files[0].id, file.body!.id);
|
||||
});
|
||||
|
@ -446,7 +458,10 @@ describe('Note', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
||||
assert.notEqual(myNote, null);
|
||||
assert.ok(myNote);
|
||||
assert.ok(myNote.renote);
|
||||
assert.ok(myNote.renote.reply);
|
||||
assert.ok(myNote.renote.reply.files);
|
||||
assert.strictEqual(myNote.renote.reply.files.length, 1);
|
||||
assert.strictEqual(myNote.renote.reply.files[0].id, file.body!.id);
|
||||
});
|
||||
|
@ -474,7 +489,7 @@ describe('Note', () => {
|
|||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -498,7 +513,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(liftnsfw.status, 400);
|
||||
assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE');
|
||||
assert.strictEqual(castAsError(liftnsfw.body).error.code, 'RESTRICTED_BY_ROLE');
|
||||
|
||||
const oldaddnsfw = await api('drive/files/update', {
|
||||
fileId: file.body!.id,
|
||||
|
@ -710,7 +725,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(note1.status, 400);
|
||||
assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
assert.strictEqual(castAsError(note1.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
});
|
||||
|
||||
test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
|
||||
|
@ -727,7 +742,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(note2.status, 400);
|
||||
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
assert.strictEqual(castAsError(note2.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
});
|
||||
|
||||
test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
|
||||
|
@ -744,7 +759,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(note2.status, 400);
|
||||
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
assert.strictEqual(castAsError(note2.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||
});
|
||||
|
||||
test('禁止ワードを含んでるリモートノートもエラーになる', async () => {
|
||||
|
@ -786,7 +801,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 0,
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -807,7 +822,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(note.status, 400);
|
||||
assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||
assert.strictEqual(castAsError(note.body).error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||
|
||||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
|
@ -840,7 +855,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 0,
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -863,7 +878,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(note.status, 400);
|
||||
assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||
assert.strictEqual(castAsError(note.body).error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||
|
||||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
|
@ -896,7 +911,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 1,
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -951,6 +966,7 @@ describe('Note', () => {
|
|||
|
||||
assert.strictEqual(deleteOneRes.status, 204);
|
||||
let mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||
assert.ok(mainNote);
|
||||
assert.strictEqual(mainNote.repliesCount, 1);
|
||||
|
||||
const deleteTwoRes = await api('notes/delete', {
|
||||
|
@ -959,6 +975,7 @@ describe('Note', () => {
|
|||
|
||||
assert.strictEqual(deleteTwoRes.status, 204);
|
||||
mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||
assert.ok(mainNote);
|
||||
assert.strictEqual(mainNote.repliesCount, 0);
|
||||
});
|
||||
});
|
||||
|
@ -980,7 +997,7 @@ describe('Note', () => {
|
|||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'UNAVAILABLE');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -992,7 +1009,7 @@ describe('Note', () => {
|
|||
const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_NOTE');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_NOTE');
|
||||
});
|
||||
|
||||
test('不可視なノートは翻訳できない', async () => {
|
||||
|
@ -1000,7 +1017,7 @@ describe('Note', () => {
|
|||
const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob);
|
||||
|
||||
assert.strictEqual(bobTranslateAttempt.status, 400);
|
||||
assert.strictEqual(bobTranslateAttempt.body.error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
|
||||
assert.strictEqual(castAsError(bobTranslateAttempt.body).error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
|
||||
});
|
||||
|
||||
test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
|
||||
|
@ -1016,7 +1033,7 @@ describe('Note', () => {
|
|||
|
||||
// NOTE: デフォルトでは登録されていないので落ちる
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
||||
assert.strictEqual(castAsError(res.body).error.code, 'UNAVAILABLE');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, post, signup, sleep, waitFire } from '../utils.js';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { api, post, signup, waitFire } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Renote Mute', () => {
|
||||
|
@ -35,15 +36,15 @@ describe('Renote Mute', () => {
|
|||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolRenote.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolRenote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||
});
|
||||
|
||||
test('タイムラインにリノートミュートしているユーザーの引用が含まれる', async () => {
|
||||
|
@ -52,15 +53,15 @@ describe('Renote Mute', () => {
|
|||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolRenote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolRenote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||
});
|
||||
|
||||
// #12956
|
||||
|
@ -69,14 +70,14 @@ describe('Renote Mute', () => {
|
|||
const bobRenote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobRenote.id), true);
|
||||
});
|
||||
|
||||
test('ストリームにリノートミュートしているユーザーのリノートが流れない', async () => {
|
||||
|
|
33
packages/backend/test/e2e/reversi-game.ts
Normal file
33
packages/backend/test/e2e/reversi-game.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ReversiMatchResponse } from 'misskey-js/entities.js';
|
||||
import { api, signup } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('ReversiGame', () => {
|
||||
let alice: misskey.entities.SignupResponse;
|
||||
let bob: misskey.entities.SignupResponse;
|
||||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
test('matches when alice invites bob and bob accepts', async () => {
|
||||
const response1 = await api('reversi/match', { userId: bob.id }, alice);
|
||||
assert.strictEqual(response1.status, 204);
|
||||
assert.strictEqual(response1.body, null);
|
||||
const response2 = await api('reversi/match', { userId: alice.id }, bob);
|
||||
assert.strictEqual(response2.status, 200);
|
||||
assert.notStrictEqual(response2.body, null);
|
||||
const body = response2.body as ReversiMatchResponse;
|
||||
assert.strictEqual(body.user1.id, alice.id);
|
||||
assert.strictEqual(body.user2.id, bob.id);
|
||||
});
|
||||
});
|
|
@ -34,6 +34,7 @@ describe('Streaming', () => {
|
|||
let kyoko: misskey.entities.SignupResponse;
|
||||
let chitose: misskey.entities.SignupResponse;
|
||||
let kanako: misskey.entities.SignupResponse;
|
||||
let erin: misskey.entities.SignupResponse;
|
||||
|
||||
// Remote users
|
||||
let akari: misskey.entities.SignupResponse;
|
||||
|
@ -53,6 +54,7 @@ describe('Streaming', () => {
|
|||
kyoko = await signup({ username: 'kyoko' });
|
||||
chitose = await signup({ username: 'chitose' });
|
||||
kanako = await signup({ username: 'kanako' });
|
||||
erin = await signup({ username: 'erin' }); // erin: A generic fifth participant
|
||||
|
||||
akari = await signup({ username: 'akari', host: 'example.com' });
|
||||
chinatsu = await signup({ username: 'chinatsu', host: 'example.com' });
|
||||
|
@ -71,6 +73,12 @@ describe('Streaming', () => {
|
|||
// Follow: kyoko => chitose
|
||||
await api('following/create', { userId: chitose.id }, kyoko);
|
||||
|
||||
// Follow: erin <=> ayano each other.
|
||||
// erin => ayano: withReplies: true
|
||||
await api('following/create', { userId: ayano.id, withReplies: true }, erin);
|
||||
// ayano => erin: withReplies: false
|
||||
await api('following/create', { userId: erin.id, withReplies: false }, ayano);
|
||||
|
||||
// Mute: chitose => kanako
|
||||
await api('mute/create', { userId: kanako.id }, chitose);
|
||||
|
||||
|
@ -297,6 +305,28 @@ describe('Streaming', () => {
|
|||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
test('withReplies: true のとき自分のfollowers投稿に対するリプライが流れる', async () => {
|
||||
const erinNote = await post(erin, { text: 'hi', visibility: 'followers' });
|
||||
const fired = await waitFire(
|
||||
erin, 'homeTimeline', // erin:home
|
||||
() => api('notes/create', { text: 'hello', replyId: erinNote.id }, ayano), // ayano reply to erin's followers post
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id, // wait ayano
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
test('withReplies: false でも自分の投稿に対するリプライが流れる', async () => {
|
||||
const ayanoNote = await post(ayano, { text: 'hi', visibility: 'followers' });
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'hello', replyId: ayanoNote.id }, erin), // erin reply to ayano's followers post
|
||||
msg => msg.type === 'note' && msg.body.userId === erin.id, // wait erin
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
}); // Home
|
||||
|
||||
describe('Local Timeline', () => {
|
||||
|
@ -475,6 +505,38 @@ describe('Streaming', () => {
|
|||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
test('withReplies: true のとき自分のfollowers投稿に対するリプライが流れる', async () => {
|
||||
const erinNote = await post(erin, { text: 'hi', visibility: 'followers' });
|
||||
const fired = await waitFire(
|
||||
erin, 'homeTimeline', // erin:home
|
||||
() => api('notes/create', { text: 'hello', replyId: erinNote.id }, ayano), // ayano reply to erin's followers post
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id, // wait ayano
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
test('withReplies: false でも自分の投稿に対するリプライが流れる', async () => {
|
||||
const ayanoNote = await post(ayano, { text: 'hi', visibility: 'followers' });
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'hello', replyId: ayanoNote.id }, erin), // erin reply to ayano's followers post
|
||||
msg => msg.type === 'note' && msg.body.userId === erin.id, // wait erin
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
test('withReplies: true のフォローしていない人のfollowersノートに対するリプライが流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
erin, 'homeTimeline', // erin:home
|
||||
() => api('notes/create', { text: 'hello', replyId: chitose.id }, ayano), // ayano reply to chitose's post
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id, // wait ayano
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global Timeline', () => {
|
||||
|
|
360
packages/backend/test/e2e/synalio/abuse-report.ts
Normal file
360
packages/backend/test/e2e/synalio/abuse-report.ts
Normal file
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { entities } from 'misskey-js';
|
||||
import { beforeEach, describe, test } from '@jest/globals';
|
||||
import {
|
||||
api,
|
||||
captureWebhook,
|
||||
randomString,
|
||||
role,
|
||||
signup,
|
||||
startJobQueue,
|
||||
UserToken,
|
||||
WEBHOOK_HOST,
|
||||
} from '../../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
|
||||
describe('[シナリオ] ユーザ通報', () => {
|
||||
let queue: INestApplicationContext;
|
||||
let admin: entities.SignupResponse;
|
||||
let alice: entities.SignupResponse;
|
||||
let bob: entities.SignupResponse;
|
||||
|
||||
async function createSystemWebhook(args?: Partial<entities.AdminSystemWebhookCreateRequest>, credential?: UserToken): Promise<entities.AdminSystemWebhookCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/system-webhook/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: WEBHOOK_HOST,
|
||||
secret: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function createAbuseReportNotificationRecipient(args?: Partial<entities.AdminAbuseReportNotificationRecipientCreateRequest>, credential?: UserToken): Promise<entities.AdminAbuseReportNotificationRecipientCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/abuse-report/notification-recipient/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
method: 'webhook',
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function createAbuseReport(args?: Partial<entities.UsersReportAbuseRequest>, credential?: UserToken): Promise<entities.EmptyResponse> {
|
||||
const res = await api(
|
||||
'users/report-abuse',
|
||||
{
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function resolveAbuseReport(args?: Partial<entities.AdminResolveAbuseUserReportRequest>, credential?: UserToken): Promise<entities.EmptyResponse> {
|
||||
const res = await api(
|
||||
'admin/resolve-abuse-user-report',
|
||||
{
|
||||
reportId: admin.id,
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
queue = await startJobQueue();
|
||||
admin = await signup({ username: 'admin' });
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
||||
await role(admin, { isAdministrator: true });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
await queue.close();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
describe('SystemWebhook', () => {
|
||||
beforeEach(async () => {
|
||||
const webhooks = await api('admin/system-webhook/list', {}, admin);
|
||||
for (const webhook of webhooks.body) {
|
||||
await api('admin/system-webhook/delete', { id: webhook.id }, admin);
|
||||
}
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody, null, 2));
|
||||
|
||||
expect(webhookBody.hookId).toBe(webhook.id);
|
||||
expect(webhookBody.type).toBe('abuseReport');
|
||||
expect(webhookBody.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody1, null, 2));
|
||||
expect(webhookBody1.hookId).toBe(webhook.id);
|
||||
expect(webhookBody1.type).toBe('abuseReport');
|
||||
expect(webhookBody1.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody1.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody1.body.assigneeId).toBeNull();
|
||||
expect(webhookBody1.body.resolved).toBe(false);
|
||||
expect(webhookBody1.body.comment).toBe(abuse.comment);
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: webhookBody1.body.id,
|
||||
forward: false,
|
||||
}, admin);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody2, null, 2));
|
||||
expect(webhookBody2.hookId).toBe(webhook.id);
|
||||
expect(webhookBody2.type).toBe('abuseReportResolved');
|
||||
expect(webhookBody2.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody2.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody2.body.assigneeId).toBe(admin.id);
|
||||
expect(webhookBody2.body.resolved).toBe(true);
|
||||
expect(webhookBody2.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody2, null, 2));
|
||||
expect(webhookBody2.hookId).toBe(webhook.id);
|
||||
expect(webhookBody2.type).toBe('abuseReportResolved');
|
||||
expect(webhookBody2.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody2.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody2.body.assigneeId).toBe(admin.id);
|
||||
expect(webhookBody2.body.resolved).toBe(true);
|
||||
expect(webhookBody2.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody1, null, 2));
|
||||
expect(webhookBody1.hookId).toBe(webhook.id);
|
||||
expect(webhookBody1.type).toBe('abuseReport');
|
||||
expect(webhookBody1.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody1.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody1.body.assigneeId).toBeNull();
|
||||
expect(webhookBody1.body.resolved).toBe(false);
|
||||
expect(webhookBody1.body.comment).toBe(abuse.comment);
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: webhookBody1.body.id,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> Webhookが無効の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: false,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> 通知設定が無効の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id, isActive: false });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
});
|
||||
});
|
130
packages/backend/test/e2e/synalio/user-create.ts
Normal file
130
packages/backend/test/e2e/synalio/user-create.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { entities } from 'misskey-js';
|
||||
import { beforeEach, describe, test } from '@jest/globals';
|
||||
import {
|
||||
api,
|
||||
captureWebhook,
|
||||
randomString,
|
||||
role,
|
||||
signup,
|
||||
startJobQueue,
|
||||
UserToken,
|
||||
WEBHOOK_HOST,
|
||||
} from '../../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
|
||||
describe('[シナリオ] ユーザ作成', () => {
|
||||
let queue: INestApplicationContext;
|
||||
let admin: entities.SignupResponse;
|
||||
|
||||
async function createSystemWebhook(args?: Partial<entities.AdminSystemWebhookCreateRequest>, credential?: UserToken): Promise<entities.AdminSystemWebhookCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/system-webhook/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['userCreated'],
|
||||
url: WEBHOOK_HOST,
|
||||
secret: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
queue = await startJobQueue();
|
||||
admin = await signup({ username: 'admin' });
|
||||
|
||||
await role(admin, { isAdministrator: true });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
await queue.close();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
describe('SystemWebhook', () => {
|
||||
beforeEach(async () => {
|
||||
const webhooks = await api('admin/system-webhook/list', {}, admin);
|
||||
for (const webhook of webhooks.body) {
|
||||
await api('admin/system-webhook/delete', { id: webhook.id }, admin);
|
||||
}
|
||||
});
|
||||
|
||||
test('ユーザが作成された -> userCreatedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['userCreated'],
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
});
|
||||
|
||||
// webhookの送出後にいろいろやってるのでちょっと待つ必要がある
|
||||
await setTimeout(2000);
|
||||
|
||||
console.log(alice);
|
||||
console.log(JSON.stringify(webhookBody, null, 2));
|
||||
|
||||
expect(webhookBody.hookId).toBe(webhook.id);
|
||||
expect(webhookBody.type).toBe('userCreated');
|
||||
|
||||
const body = webhookBody.body as entities.UserLite;
|
||||
expect(alice.id).toBe(body.id);
|
||||
expect(alice.name).toBe(body.name);
|
||||
expect(alice.username).toBe(body.username);
|
||||
expect(alice.host).toBe(body.host);
|
||||
expect(alice.avatarUrl).toBe(body.avatarUrl);
|
||||
expect(alice.avatarBlurhash).toBe(body.avatarBlurhash);
|
||||
expect(alice.avatarDecorations).toEqual(body.avatarDecorations);
|
||||
expect(alice.isBot).toBe(body.isBot);
|
||||
expect(alice.isCat).toBe(body.isCat);
|
||||
expect(alice.instance).toEqual(body.instance);
|
||||
expect(alice.emojis).toEqual(body.emojis);
|
||||
expect(alice.onlineStatus).toBe(body.onlineStatus);
|
||||
expect(alice.badgeRoles).toEqual(body.badgeRoles);
|
||||
});
|
||||
|
||||
test('ユーザ作成 -> userCreatedが未許可の場合は送出されない', async () => {
|
||||
await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
expect(alice.id).not.toBeNull();
|
||||
});
|
||||
|
||||
test('ユーザ作成 -> Webhookが無効の場合は送出されない', async () => {
|
||||
await createSystemWebhook({
|
||||
on: ['userCreated'],
|
||||
isActive: false,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
expect(alice.id).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -33,9 +33,9 @@ describe('Note thread mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some(note => note.id === carolReplyWithoutMention.id), false);
|
||||
});
|
||||
|
||||
test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => {
|
||||
|
@ -93,8 +93,8 @@ describe('Note thread mute', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReplyWithoutMention.id), false);
|
||||
|
||||
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,8 +17,8 @@ describe('users/notes', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.png');
|
||||
jpgNote = await post(alice, {
|
||||
fileIds: [jpg.id],
|
||||
});
|
||||
|
|
|
@ -234,7 +234,7 @@ describe('ユーザー', () => {
|
|||
rolePublic = await role(root, { isPublic: true, name: 'Public Role' });
|
||||
await api('admin/roles/assign', { userId: userRolePublic.id, roleId: rolePublic.id }, root);
|
||||
userRoleBadge = await signup({ username: 'userRoleBadge' });
|
||||
roleBadge = await role(root, { asBadge: true, name: 'Badge Role' });
|
||||
roleBadge = await role(root, { asBadge: true, name: 'Badge Role', isPublic: true });
|
||||
await api('admin/roles/assign', { userId: userRoleBadge.id, roleId: roleBadge.id }, root);
|
||||
userSilenced = await signup({ username: 'userSilenced' });
|
||||
await post(userSilenced, { text: 'test' });
|
||||
|
@ -684,7 +684,16 @@ describe('ユーザー', () => {
|
|||
iconUrl: roleBadge.iconUrl,
|
||||
displayOrder: roleBadge.displayOrder,
|
||||
}]);
|
||||
assert.deepStrictEqual(response.roles, []); // バッヂだからといってrolesが取れるとは限らない
|
||||
assert.deepStrictEqual(response.roles, [{
|
||||
id: roleBadge.id,
|
||||
name: roleBadge.name,
|
||||
color: roleBadge.color,
|
||||
iconUrl: roleBadge.iconUrl,
|
||||
description: roleBadge.description,
|
||||
isModerator: roleBadge.isModerator,
|
||||
isAdministrator: roleBadge.isAdministrator,
|
||||
displayOrder: roleBadge.displayOrder,
|
||||
}]);
|
||||
});
|
||||
test('をID指定のリスト形式で取得することができる(空)', async () => {
|
||||
const parameters = { userIds: [] };
|
||||
|
|
22
packages/backend/test/eslint.config.js
Normal file
22
packages/backend/test/eslint.config.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import globals from 'globals';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { just, nothing } from '../../src/misc/prelude/maybe.js';
|
||||
|
||||
describe('just', () => {
|
||||
test('has a value', () => {
|
||||
assert.deepStrictEqual(just(3).isJust(), true);
|
||||
});
|
||||
|
||||
test('has the inverse called get', () => {
|
||||
assert.deepStrictEqual(just(3).get(), 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nothing', () => {
|
||||
test('has no value', () => {
|
||||
assert.deepStrictEqual(nothing().isJust(), false);
|
||||
});
|
||||
});
|
BIN
packages/backend/test/resources/192.jpg
Normal file
BIN
packages/backend/test/resources/192.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
BIN
packages/backend/test/resources/192.png
Normal file
BIN
packages/backend/test/resources/192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 463 KiB |
343
packages/backend/test/unit/AbuseReportNotificationService.ts
Normal file
343
packages/backend/test/unit/AbuseReportNotificationService.ts
Normal file
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||
import {
|
||||
AbuseReportNotificationRecipientRepository,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
MiSystemWebhook,
|
||||
MiUser,
|
||||
SystemWebhooksRepository,
|
||||
UserProfilesRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { randomString } from '../utils.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
describe('AbuseReportNotificationService', () => {
|
||||
let app: TestingModule;
|
||||
let service: AbuseReportNotificationService;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
let usersRepository: UsersRepository;
|
||||
let userProfilesRepository: UserProfilesRepository;
|
||||
let systemWebhooksRepository: SystemWebhooksRepository;
|
||||
let abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository;
|
||||
let idService: IdService;
|
||||
let roleService: jest.Mocked<RoleService>;
|
||||
let emailService: jest.Mocked<EmailService>;
|
||||
let webhookService: jest.Mocked<SystemWebhookService>;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
let root: MiUser;
|
||||
let alice: MiUser;
|
||||
let bob: MiUser;
|
||||
let systemWebhook1: MiSystemWebhook;
|
||||
let systemWebhook2: MiSystemWebhook;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
async function createUser(data: Partial<MiUser> = {}) {
|
||||
const user = await usersRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
...data,
|
||||
})
|
||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
await userProfilesRepository.insert({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async function createWebhook(data: Partial<MiSystemWebhook> = {}) {
|
||||
return systemWebhooksRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
...data,
|
||||
})
|
||||
.then(x => systemWebhooksRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
async function createRecipient(data: Partial<MiAbuseReportNotificationRecipient> = {}) {
|
||||
return abuseReportNotificationRecipientRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
...data,
|
||||
})
|
||||
.then(x => abuseReportNotificationRecipientRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await Test
|
||||
.createTestingModule({
|
||||
imports: [
|
||||
GlobalModule,
|
||||
],
|
||||
providers: [
|
||||
AbuseReportNotificationService,
|
||||
IdService,
|
||||
{
|
||||
provide: RoleService, useFactory: () => ({ getModeratorIds: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: MetaService, useFactory: () => ({ fetch: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: ModerationLogService, useFactory: () => ({ log: () => Promise.resolve() }),
|
||||
},
|
||||
{
|
||||
provide: GlobalEventService, useFactory: () => ({ publishAdminStream: jest.fn() }),
|
||||
},
|
||||
],
|
||||
})
|
||||
.compile();
|
||||
|
||||
usersRepository = app.get(DI.usersRepository);
|
||||
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||
systemWebhooksRepository = app.get(DI.systemWebhooksRepository);
|
||||
abuseReportNotificationRecipientRepository = app.get(DI.abuseReportNotificationRecipientRepository);
|
||||
|
||||
service = app.get(AbuseReportNotificationService);
|
||||
idService = app.get(IdService);
|
||||
roleService = app.get(RoleService) as jest.Mocked<RoleService>;
|
||||
emailService = app.get<EmailService>(EmailService) as jest.Mocked<EmailService>;
|
||||
webhookService = app.get<SystemWebhookService>(SystemWebhookService) as jest.Mocked<SystemWebhookService>;
|
||||
|
||||
app.enableShutdownHooks();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
||||
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
||||
bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
|
||||
systemWebhook1 = await createWebhook();
|
||||
systemWebhook2 = await createWebhook();
|
||||
|
||||
roleService.getModeratorIds.mockResolvedValue([root.id, alice.id, bob.id]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
emailService.sendEmail.mockClear();
|
||||
webhookService.enqueueSystemWebhook.mockClear();
|
||||
|
||||
await usersRepository.delete({});
|
||||
await userProfilesRepository.delete({});
|
||||
await systemWebhooksRepository.delete({});
|
||||
await abuseReportNotificationRecipientRepository.delete({});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
describe('createRecipient', () => {
|
||||
test('作成成功1', async () => {
|
||||
const params = {
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
method: 'email' as RecipientMethod,
|
||||
userId: alice.id,
|
||||
systemWebhookId: null,
|
||||
};
|
||||
|
||||
const recipient1 = await service.createRecipient(params, root);
|
||||
expect(recipient1).toMatchObject(params);
|
||||
});
|
||||
|
||||
test('作成成功2', async () => {
|
||||
const params = {
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
method: 'webhook' as RecipientMethod,
|
||||
userId: null,
|
||||
systemWebhookId: systemWebhook1.id,
|
||||
};
|
||||
|
||||
const recipient1 = await service.createRecipient(params, root);
|
||||
expect(recipient1).toMatchObject(params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRecipient', () => {
|
||||
test('更新成功1', async () => {
|
||||
const recipient1 = await createRecipient({
|
||||
method: 'email',
|
||||
userId: alice.id,
|
||||
});
|
||||
|
||||
const params = {
|
||||
id: recipient1.id,
|
||||
isActive: false,
|
||||
name: randomString(),
|
||||
method: 'email' as RecipientMethod,
|
||||
userId: bob.id,
|
||||
systemWebhookId: null,
|
||||
};
|
||||
|
||||
const recipient2 = await service.updateRecipient(params, root);
|
||||
expect(recipient2).toMatchObject(params);
|
||||
});
|
||||
|
||||
test('更新成功2', async () => {
|
||||
const recipient1 = await createRecipient({
|
||||
method: 'webhook',
|
||||
systemWebhookId: systemWebhook1.id,
|
||||
});
|
||||
|
||||
const params = {
|
||||
id: recipient1.id,
|
||||
isActive: false,
|
||||
name: randomString(),
|
||||
method: 'webhook' as RecipientMethod,
|
||||
userId: null,
|
||||
systemWebhookId: systemWebhook2.id,
|
||||
};
|
||||
|
||||
const recipient2 = await service.updateRecipient(params, root);
|
||||
expect(recipient2).toMatchObject(params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRecipient', () => {
|
||||
test('削除成功1', async () => {
|
||||
const recipient1 = await createRecipient({
|
||||
method: 'email',
|
||||
userId: alice.id,
|
||||
});
|
||||
|
||||
await service.deleteRecipient(recipient1.id, root);
|
||||
|
||||
await expect(abuseReportNotificationRecipientRepository.findOneBy({ id: recipient1.id })).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchRecipients', () => {
|
||||
async function create() {
|
||||
const recipient1 = await createRecipient({
|
||||
method: 'email',
|
||||
userId: alice.id,
|
||||
});
|
||||
const recipient2 = await createRecipient({
|
||||
method: 'email',
|
||||
userId: bob.id,
|
||||
});
|
||||
|
||||
const recipient3 = await createRecipient({
|
||||
method: 'webhook',
|
||||
systemWebhookId: systemWebhook1.id,
|
||||
});
|
||||
const recipient4 = await createRecipient({
|
||||
method: 'webhook',
|
||||
systemWebhookId: systemWebhook2.id,
|
||||
});
|
||||
|
||||
return [recipient1, recipient2, recipient3, recipient4];
|
||||
}
|
||||
|
||||
test('フィルタなし', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({});
|
||||
expect(recipients).toEqual([recipient1, recipient2, recipient3, recipient4]);
|
||||
});
|
||||
|
||||
test('フィルタなし(非モデレータは除外される)', async () => {
|
||||
roleService.getModeratorIds.mockClear();
|
||||
roleService.getModeratorIds.mockResolvedValue([root.id, bob.id]);
|
||||
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({});
|
||||
// aliceはモデレータではないので除外される
|
||||
expect(recipients).toEqual([recipient2, recipient3, recipient4]);
|
||||
});
|
||||
|
||||
test('フィルタなし(非モデレータでも除外されないオプション設定)', async () => {
|
||||
roleService.getModeratorIds.mockClear();
|
||||
roleService.getModeratorIds.mockResolvedValue([root.id, bob.id]);
|
||||
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({}, { removeUnauthorized: false });
|
||||
expect(recipients).toEqual([recipient1, recipient2, recipient3, recipient4]);
|
||||
});
|
||||
|
||||
test('emailのみ', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ method: ['email'] });
|
||||
expect(recipients).toEqual([recipient1, recipient2]);
|
||||
});
|
||||
|
||||
test('webhookのみ', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ method: ['webhook'] });
|
||||
expect(recipients).toEqual([recipient3, recipient4]);
|
||||
});
|
||||
|
||||
test('すべて', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ method: ['email', 'webhook'] });
|
||||
expect(recipients).toEqual([recipient1, recipient2, recipient3, recipient4]);
|
||||
});
|
||||
|
||||
test('ID指定', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ ids: [recipient1.id, recipient3.id] });
|
||||
expect(recipients).toEqual([recipient1, recipient3]);
|
||||
});
|
||||
|
||||
test('ID指定(method=emailではないIDが混ざりこまない)', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ ids: [recipient1.id, recipient3.id], method: ['email'] });
|
||||
expect(recipients).toEqual([recipient1]);
|
||||
});
|
||||
|
||||
test('ID指定(method=webhookではないIDが混ざりこまない)', async () => {
|
||||
const [recipient1, recipient2, recipient3, recipient4] = await create();
|
||||
|
||||
const recipients = await service.fetchRecipients({ ids: [recipient1.id, recipient3.id], method: ['webhook'] });
|
||||
expect(recipients).toEqual([recipient3]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -23,10 +23,10 @@ describe('ApMfmService', () => {
|
|||
|
||||
describe('getNoteHtml', () => {
|
||||
test('Do not provide _misskey_content for simple text', () => {
|
||||
const note: MiNote = {
|
||||
const note = {
|
||||
text: 'テキスト #タグ @mention 🍊 :emoji: https://example.com',
|
||||
mentionedRemoteUsers: '[]',
|
||||
} as any;
|
||||
};
|
||||
|
||||
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
||||
|
||||
|
@ -35,10 +35,10 @@ describe('ApMfmService', () => {
|
|||
});
|
||||
|
||||
test('Provide _misskey_content for MFM', () => {
|
||||
const note: MiNote = {
|
||||
const note = {
|
||||
text: '$[tada foo]',
|
||||
mentionedRemoteUsers: '[]',
|
||||
} as any;
|
||||
};
|
||||
|
||||
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ModuleMocker } from 'jest-mock';
|
|||
import { Test } from '@nestjs/testing';
|
||||
import { afterAll, beforeAll, describe, test } from '@jest/globals';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { FileInfoService } from '@/core/FileInfoService.js';
|
||||
import { FileInfo, FileInfoService } from '@/core/FileInfoService.js';
|
||||
//import { DI } from '@/di-symbols.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
|
@ -27,6 +27,15 @@ const moduleMocker = new ModuleMocker(global);
|
|||
describe('FileInfoService', () => {
|
||||
let app: TestingModule;
|
||||
let fileInfoService: FileInfoService;
|
||||
const strip = (fileInfo: FileInfo): Omit<Partial<FileInfo>, 'warnings' | 'blurhash' | 'sensitive' | 'porn'> => {
|
||||
const fi: Partial<FileInfo> = fileInfo;
|
||||
delete fi.warnings;
|
||||
delete fi.sensitive;
|
||||
delete fi.blurhash;
|
||||
delete fi.porn;
|
||||
|
||||
return fi;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await Test.createTestingModule({
|
||||
|
@ -58,11 +67,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('Empty file', async () => {
|
||||
const path = `${resources}/emptyfile`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 0,
|
||||
md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
|
@ -78,32 +83,24 @@ describe('FileInfoService', () => {
|
|||
|
||||
describe('IMAGE', () => {
|
||||
test('Generic JPEG', async () => {
|
||||
const path = `${resources}/Lenna.jpg`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const path = `${resources}/192.jpg`;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 25360,
|
||||
md5: '091b3f259662aa31e2ffef4519951168',
|
||||
size: 5131,
|
||||
md5: '8c9ed0677dd2b8f9f7472c3af247e5e3',
|
||||
type: {
|
||||
mime: 'image/jpeg',
|
||||
ext: 'jpg',
|
||||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
width: 192,
|
||||
height: 192,
|
||||
orientation: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('Generic APNG', async () => {
|
||||
const path = `${resources}/anime.png`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 1868,
|
||||
md5: '08189c607bea3b952704676bb3c979e0',
|
||||
|
@ -119,11 +116,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('Generic AGIF', async () => {
|
||||
const path = `${resources}/anime.gif`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 2248,
|
||||
md5: '32c47a11555675d9267aee1a86571e7e',
|
||||
|
@ -139,11 +132,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('PNG with alpha', async () => {
|
||||
const path = `${resources}/with-alpha.png`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 3772,
|
||||
md5: 'f73535c3e1e27508885b69b10cf6e991',
|
||||
|
@ -159,11 +148,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('Generic SVG', async () => {
|
||||
const path = `${resources}/image.svg`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 505,
|
||||
md5: 'b6f52b4b021e7b92cdd04509c7267965',
|
||||
|
@ -180,11 +165,7 @@ describe('FileInfoService', () => {
|
|||
test('SVG with XML definition', async () => {
|
||||
// https://github.com/misskey-dev/misskey/issues/4413
|
||||
const path = `${resources}/with-xml-def.svg`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 544,
|
||||
md5: '4b7a346cde9ccbeb267e812567e33397',
|
||||
|
@ -200,11 +181,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('Dimension limit', async () => {
|
||||
const path = `${resources}/25000x25000.png`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 75933,
|
||||
md5: '268c5dde99e17cf8fe09f1ab3f97df56',
|
||||
|
@ -220,11 +197,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('Rotate JPEG', async () => {
|
||||
const path = `${resources}/rotate.jpg`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 12624,
|
||||
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
||||
|
@ -242,11 +215,7 @@ describe('FileInfoService', () => {
|
|||
describe('AUDIO', () => {
|
||||
test('MP3', async () => {
|
||||
const path = `${resources}/kick_gaba7.mp3`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
@ -262,11 +231,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('WAV', async () => {
|
||||
const path = `${resources}/kick_gaba7.wav`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
@ -282,11 +247,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('AAC', async () => {
|
||||
const path = `${resources}/kick_gaba7.aac`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
@ -302,11 +263,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('FLAC', async () => {
|
||||
const path = `${resources}/kick_gaba7.flac`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
@ -322,11 +279,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('MPEG-4 AUDIO (M4A)', async () => {
|
||||
const path = `${resources}/kick_gaba7.m4a`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
@ -342,11 +295,7 @@ describe('FileInfoService', () => {
|
|||
|
||||
test('WEBM AUDIO', async () => {
|
||||
const path = `${resources}/kick_gaba7.webm`;
|
||||
const info = await fileInfoService.getFileInfo(path) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
const info = strip(await fileInfoService.getFileInfo(path));
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
|
|
|
@ -3,17 +3,23 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { jest } from '@jest/globals';
|
||||
import { ModuleMocker } from 'jest-mock';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as lolex from '@sinonjs/fake-timers';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
|
||||
import {
|
||||
MiRole,
|
||||
MiRoleAssignment,
|
||||
MiUser,
|
||||
RoleAssignmentsRepository,
|
||||
RolesRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { genAidx } from '@/misc/id/aidx.js';
|
||||
|
@ -23,7 +29,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { sleep } from '../utils.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
||||
|
@ -39,27 +45,27 @@ describe('RoleService', () => {
|
|||
let notificationService: jest.Mocked<NotificationService>;
|
||||
let clock: lolex.InstalledClock;
|
||||
|
||||
function createUser(data: Partial<MiUser> = {}) {
|
||||
async function createUser(data: Partial<MiUser> = {}) {
|
||||
const un = secureRndstr(16);
|
||||
return usersRepository.insert({
|
||||
const x = await usersRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
username: un,
|
||||
usernameLower: un,
|
||||
...data,
|
||||
})
|
||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
return await usersRepository.findOneByOrFail(x.identifiers[0]);
|
||||
}
|
||||
|
||||
function createRole(data: Partial<MiRole> = {}) {
|
||||
return rolesRepository.insert({
|
||||
async function createRole(data: Partial<MiRole> = {}) {
|
||||
const x = await rolesRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
updatedAt: new Date(),
|
||||
lastUsedAt: new Date(),
|
||||
name: '',
|
||||
description: '',
|
||||
...data,
|
||||
})
|
||||
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
return await rolesRepository.findOneByOrFail(x.identifiers[0]);
|
||||
}
|
||||
|
||||
function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial<MiRole> = {}) {
|
||||
|
@ -71,6 +77,20 @@ describe('RoleService', () => {
|
|||
});
|
||||
}
|
||||
|
||||
async function assignRole(args: Partial<MiRoleAssignment>) {
|
||||
const id = genAidx(Date.now());
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setDate(expiresAt.getDate() + 1);
|
||||
|
||||
await roleAssignmentsRepository.insert({
|
||||
id,
|
||||
expiresAt,
|
||||
...args,
|
||||
});
|
||||
|
||||
return await roleAssignmentsRepository.findOneByOrFail({ id });
|
||||
}
|
||||
|
||||
function aidx() {
|
||||
return genAidx(Date.now());
|
||||
}
|
||||
|
@ -258,13 +278,103 @@ describe('RoleService', () => {
|
|||
|
||||
// ストリーミング経由で反映されるまでちょっと待つ
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const resultAfter25hAgain = await roleService.getUserPolicies(user.id);
|
||||
expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getModeratorIds', () => {
|
||||
test('includeAdmins = false, excludeExpire = false', async () => {
|
||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
|
||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
|
||||
]);
|
||||
|
||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||
const role2 = await createRole({ name: 'moderator', isModerator: true });
|
||||
const role3 = await createRole({ name: 'normal' });
|
||||
|
||||
await Promise.all([
|
||||
assignRole({ userId: adminUser1.id, roleId: role1.id }),
|
||||
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: modeUser1.id, roleId: role2.id }),
|
||||
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: normalUser1.id, roleId: role3.id }),
|
||||
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
]);
|
||||
|
||||
const result = await roleService.getModeratorIds(false, false);
|
||||
expect(result).toEqual([modeUser1.id, modeUser2.id]);
|
||||
});
|
||||
|
||||
test('includeAdmins = false, excludeExpire = true', async () => {
|
||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
|
||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
|
||||
]);
|
||||
|
||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||
const role2 = await createRole({ name: 'moderator', isModerator: true });
|
||||
const role3 = await createRole({ name: 'normal' });
|
||||
|
||||
await Promise.all([
|
||||
assignRole({ userId: adminUser1.id, roleId: role1.id }),
|
||||
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: modeUser1.id, roleId: role2.id }),
|
||||
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: normalUser1.id, roleId: role3.id }),
|
||||
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
]);
|
||||
|
||||
const result = await roleService.getModeratorIds(false, true);
|
||||
expect(result).toEqual([modeUser1.id]);
|
||||
});
|
||||
|
||||
test('includeAdmins = true, excludeExpire = false', async () => {
|
||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
|
||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
|
||||
]);
|
||||
|
||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||
const role2 = await createRole({ name: 'moderator', isModerator: true });
|
||||
const role3 = await createRole({ name: 'normal' });
|
||||
|
||||
await Promise.all([
|
||||
assignRole({ userId: adminUser1.id, roleId: role1.id }),
|
||||
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: modeUser1.id, roleId: role2.id }),
|
||||
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: normalUser1.id, roleId: role3.id }),
|
||||
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
]);
|
||||
|
||||
const result = await roleService.getModeratorIds(true, false);
|
||||
expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]);
|
||||
});
|
||||
|
||||
test('includeAdmins = true, excludeExpire = true', async () => {
|
||||
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
|
||||
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
|
||||
]);
|
||||
|
||||
const role1 = await createRole({ name: 'admin', isAdministrator: true });
|
||||
const role2 = await createRole({ name: 'moderator', isModerator: true });
|
||||
const role3 = await createRole({ name: 'normal' });
|
||||
|
||||
await Promise.all([
|
||||
assignRole({ userId: adminUser1.id, roleId: role1.id }),
|
||||
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: modeUser1.id, roleId: role2.id }),
|
||||
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
assignRole({ userId: normalUser1.id, roleId: role3.id }),
|
||||
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
|
||||
]);
|
||||
|
||||
const result = await roleService.getModeratorIds(true, true);
|
||||
expect(result).toEqual([adminUser1.id, modeUser1.id]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('conditional role', () => {
|
||||
test('~かつ~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
|
@ -697,7 +807,7 @@ describe('RoleService', () => {
|
|||
await roleService.assign(user.id, role.id);
|
||||
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const assignments = await roleAssignmentsRepository.find({
|
||||
where: {
|
||||
|
@ -725,7 +835,7 @@ describe('RoleService', () => {
|
|||
await roleService.assign(user.id, role.id);
|
||||
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const assignments = await roleAssignmentsRepository.find({
|
||||
where: {
|
||||
|
|
516
packages/backend/test/unit/SystemWebhookService.ts
Normal file
516
packages/backend/test/unit/SystemWebhookService.ts
Normal file
|
@ -0,0 +1,516 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MiUser } from '@/models/User.js';
|
||||
import { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import { SystemWebhooksRepository, UsersRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { randomString } from '../utils.js';
|
||||
|
||||
describe('SystemWebhookService', () => {
|
||||
let app: TestingModule;
|
||||
let service: SystemWebhookService;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
let usersRepository: UsersRepository;
|
||||
let systemWebhooksRepository: SystemWebhooksRepository;
|
||||
let idService: IdService;
|
||||
let queueService: jest.Mocked<QueueService>;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
let root: MiUser;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
async function createUser(data: Partial<MiUser> = {}) {
|
||||
return await usersRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
...data,
|
||||
})
|
||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
async function createWebhook(data: Partial<MiSystemWebhook> = {}) {
|
||||
return systemWebhooksRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
...data,
|
||||
})
|
||||
.then(x => systemWebhooksRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
async function beforeAllImpl() {
|
||||
app = await Test
|
||||
.createTestingModule({
|
||||
imports: [
|
||||
GlobalModule,
|
||||
],
|
||||
providers: [
|
||||
SystemWebhookService,
|
||||
IdService,
|
||||
LoggerService,
|
||||
GlobalEventService,
|
||||
{
|
||||
provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: ModerationLogService, useFactory: () => ({ log: () => Promise.resolve() }),
|
||||
},
|
||||
],
|
||||
})
|
||||
.compile();
|
||||
|
||||
usersRepository = app.get(DI.usersRepository);
|
||||
systemWebhooksRepository = app.get(DI.systemWebhooksRepository);
|
||||
|
||||
service = app.get(SystemWebhookService);
|
||||
idService = app.get(IdService);
|
||||
queueService = app.get(QueueService) as jest.Mocked<QueueService>;
|
||||
|
||||
app.enableShutdownHooks();
|
||||
}
|
||||
|
||||
async function afterAllImpl() {
|
||||
await app.close();
|
||||
}
|
||||
|
||||
async function beforeEachImpl() {
|
||||
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
||||
}
|
||||
|
||||
async function afterEachImpl() {
|
||||
await usersRepository.delete({});
|
||||
await systemWebhooksRepository.delete({});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
describe('アプリを毎回作り直す必要のないグループ', () => {
|
||||
beforeAll(beforeAllImpl);
|
||||
afterAll(afterAllImpl);
|
||||
beforeEach(beforeEachImpl);
|
||||
afterEach(afterEachImpl);
|
||||
|
||||
describe('fetchSystemWebhooks', () => {
|
||||
test('フィルタなし', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook1, webhook2, webhook3, webhook4]);
|
||||
});
|
||||
|
||||
test('activeのみ', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks({ isActive: true });
|
||||
expect(fetchedWebhooks).toEqual([webhook1, webhook3]);
|
||||
});
|
||||
|
||||
test('特定のイベントのみ', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks({ on: ['abuseReport'] });
|
||||
expect(fetchedWebhooks).toEqual([webhook1, webhook2]);
|
||||
});
|
||||
|
||||
test('activeな特定のイベントのみ', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks({ on: ['abuseReport'], isActive: true });
|
||||
expect(fetchedWebhooks).toEqual([webhook1]);
|
||||
});
|
||||
|
||||
test('ID指定', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks({ ids: [webhook1.id, webhook4.id] });
|
||||
expect(fetchedWebhooks).toEqual([webhook1, webhook4]);
|
||||
});
|
||||
|
||||
test('ID指定(他条件とANDになるか見たい)', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
const webhook3 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
const webhook4 = await createWebhook({
|
||||
isActive: false,
|
||||
on: [],
|
||||
});
|
||||
|
||||
const fetchedWebhooks = await service.fetchSystemWebhooks({ ids: [webhook1.id, webhook4.id], isActive: false });
|
||||
expect(fetchedWebhooks).toEqual([webhook4]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSystemWebhook', () => {
|
||||
test('作成成功 ', async () => {
|
||||
const params = {
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'] as SystemWebhookEventType[],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
};
|
||||
|
||||
const webhook = await service.createSystemWebhook(params, root);
|
||||
expect(webhook).toMatchObject(params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSystemWebhook', () => {
|
||||
test('更新成功', async () => {
|
||||
const webhook = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
|
||||
const params = {
|
||||
id: webhook.id,
|
||||
isActive: false,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'] as SystemWebhookEventType[],
|
||||
url: randomString(),
|
||||
secret: randomString(),
|
||||
};
|
||||
|
||||
const updatedWebhook = await service.updateSystemWebhook(params, root);
|
||||
expect(updatedWebhook).toMatchObject(params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteSystemWebhook', () => {
|
||||
test('削除成功', async () => {
|
||||
const webhook = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
|
||||
await service.deleteSystemWebhook(webhook.id, root);
|
||||
|
||||
await expect(systemWebhooksRepository.findOneBy({ id: webhook.id })).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('アプリを毎回作り直す必要があるグループ', () => {
|
||||
beforeEach(async () => {
|
||||
await beforeAllImpl();
|
||||
await beforeEachImpl();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await afterEachImpl();
|
||||
await afterAllImpl();
|
||||
});
|
||||
|
||||
describe('enqueueSystemWebhook', () => {
|
||||
test('キューに追加成功', async () => {
|
||||
const webhook = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
|
||||
|
||||
expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('非アクティブなWebhookはキューに追加されない', async () => {
|
||||
const webhook = await createWebhook({
|
||||
isActive: false,
|
||||
on: ['abuseReport'],
|
||||
});
|
||||
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
|
||||
|
||||
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('未許可のイベント種別が渡された場合はWebhookはキューに追加されない', async () => {
|
||||
const webhook1 = await createWebhook({
|
||||
isActive: true,
|
||||
on: [],
|
||||
});
|
||||
const webhook2 = await createWebhook({
|
||||
isActive: true,
|
||||
on: ['abuseReportResolved'],
|
||||
});
|
||||
await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' });
|
||||
await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' });
|
||||
|
||||
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchActiveSystemWebhooks', () => {
|
||||
describe('systemWebhookCreated', () => {
|
||||
test('ActiveなWebhookが追加された時、キャッシュに追加されている', async () => {
|
||||
const webhook = await service.createSystemWebhook(
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook]);
|
||||
});
|
||||
|
||||
test('NotActiveなWebhookが追加された時、キャッシュに追加されていない', async () => {
|
||||
const webhook = await service.createSystemWebhook(
|
||||
{
|
||||
isActive: false,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('systemWebhookUpdated', () => {
|
||||
test('ActiveなWebhookが編集された時、キャッシュに反映されている', async () => {
|
||||
const id = idService.gen();
|
||||
await createWebhook({ id });
|
||||
// キャッシュ作成
|
||||
const webhook1 = await service.fetchActiveSystemWebhooks();
|
||||
// 読み込まれていることをチェック
|
||||
expect(webhook1.length).toEqual(1);
|
||||
expect(webhook1[0].id).toEqual(id);
|
||||
|
||||
const webhook2 = await service.updateSystemWebhook(
|
||||
{
|
||||
id,
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook2]);
|
||||
});
|
||||
|
||||
test('NotActiveなWebhookが編集された時、キャッシュに追加されない', async () => {
|
||||
const id = idService.gen();
|
||||
await createWebhook({ id, isActive: false });
|
||||
// キャッシュ作成
|
||||
const webhook1 = await service.fetchActiveSystemWebhooks();
|
||||
// 読み込まれていないことをチェック
|
||||
expect(webhook1.length).toEqual(0);
|
||||
|
||||
const webhook2 = await service.updateSystemWebhook(
|
||||
{
|
||||
id,
|
||||
isActive: false,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: 'https://example.com',
|
||||
secret: randomString(),
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('NotActiveなWebhookがActiveにされた時、キャッシュに追加されている', async () => {
|
||||
const id = idService.gen();
|
||||
const baseWebhook = await createWebhook({ id, isActive: false });
|
||||
// キャッシュ作成
|
||||
const webhook1 = await service.fetchActiveSystemWebhooks();
|
||||
// 読み込まれていないことをチェック
|
||||
expect(webhook1.length).toEqual(0);
|
||||
|
||||
const webhook2 = await service.updateSystemWebhook(
|
||||
{
|
||||
...baseWebhook,
|
||||
isActive: true,
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook2]);
|
||||
});
|
||||
|
||||
test('ActiveなWebhookがNotActiveにされた時、キャッシュから削除されている', async () => {
|
||||
const id = idService.gen();
|
||||
const baseWebhook = await createWebhook({ id, isActive: true });
|
||||
// キャッシュ作成
|
||||
const webhook1 = await service.fetchActiveSystemWebhooks();
|
||||
// 読み込まれていることをチェック
|
||||
expect(webhook1.length).toEqual(1);
|
||||
expect(webhook1[0].id).toEqual(id);
|
||||
|
||||
const webhook2 = await service.updateSystemWebhook(
|
||||
{
|
||||
...baseWebhook,
|
||||
isActive: false,
|
||||
},
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('systemWebhookDeleted', () => {
|
||||
test('キャッシュから削除されている', async () => {
|
||||
const id = idService.gen();
|
||||
const baseWebhook = await createWebhook({ id, isActive: true });
|
||||
// キャッシュ作成
|
||||
const webhook1 = await service.fetchActiveSystemWebhooks();
|
||||
// 読み込まれていることをチェック
|
||||
expect(webhook1.length).toEqual(1);
|
||||
expect(webhook1[0].id).toEqual(id);
|
||||
|
||||
const webhook2 = await service.deleteSystemWebhook(
|
||||
id,
|
||||
root,
|
||||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
265
packages/backend/test/unit/UserSearchService.ts
Normal file
265
packages/backend/test/unit/UserSearchService.ts
Normal file
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { describe, jest, test } from '@jest/globals';
|
||||
import { In } from 'typeorm';
|
||||
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||
import { FollowingsRepository, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
describe('UserSearchService', () => {
|
||||
let app: TestingModule;
|
||||
let service: UserSearchService;
|
||||
|
||||
let usersRepository: UsersRepository;
|
||||
let followingsRepository: FollowingsRepository;
|
||||
let idService: IdService;
|
||||
let userProfilesRepository: UserProfilesRepository;
|
||||
|
||||
let root: MiUser;
|
||||
let alice: MiUser;
|
||||
let alyce: MiUser;
|
||||
let alycia: MiUser;
|
||||
let alysha: MiUser;
|
||||
let alyson: MiUser;
|
||||
let alyssa: MiUser;
|
||||
let bob: MiUser;
|
||||
let bobbi: MiUser;
|
||||
let bobbie: MiUser;
|
||||
let bobby: MiUser;
|
||||
|
||||
async function createUser(data: Partial<MiUser> = {}) {
|
||||
const user = await usersRepository
|
||||
.insert({
|
||||
id: idService.gen(),
|
||||
...data,
|
||||
})
|
||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
await userProfilesRepository.insert({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async function createFollowings(follower: MiUser, followees: MiUser[]) {
|
||||
for (const followee of followees) {
|
||||
await followingsRepository.insert({
|
||||
id: idService.gen(),
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setActive(users: MiUser[]) {
|
||||
for (const user of users) {
|
||||
await usersRepository.update(user.id, {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setInactive(users: MiUser[]) {
|
||||
for (const user of users) {
|
||||
await usersRepository.update(user.id, {
|
||||
updatedAt: new Date(0),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function setSuspended(users: MiUser[]) {
|
||||
for (const user of users) {
|
||||
await usersRepository.update(user.id, {
|
||||
isSuspended: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await Test
|
||||
.createTestingModule({
|
||||
imports: [
|
||||
GlobalModule,
|
||||
],
|
||||
providers: [
|
||||
UserSearchService,
|
||||
{
|
||||
provide: UserEntityService, useFactory: jest.fn(() => ({
|
||||
// とりあえずIDが返れば確認が出来るので
|
||||
packMany: (value: any) => value,
|
||||
})),
|
||||
},
|
||||
IdService,
|
||||
],
|
||||
})
|
||||
.compile();
|
||||
|
||||
await app.init();
|
||||
|
||||
usersRepository = app.get(DI.usersRepository);
|
||||
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||
followingsRepository = app.get(DI.followingsRepository);
|
||||
|
||||
service = app.get(UserSearchService);
|
||||
idService = app.get(IdService);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
||||
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
||||
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
||||
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
||||
alysha = await createUser({ username: 'Alysha', usernameLower: 'alysha' });
|
||||
alyson = await createUser({ username: 'Alyson', usernameLower: 'alyson', host: 'example.com' });
|
||||
alyssa = await createUser({ username: 'Alyssa', usernameLower: 'alyssa', host: 'example.com' });
|
||||
bob = await createUser({ username: 'Bob', usernameLower: 'bob' });
|
||||
bobbi = await createUser({ username: 'Bobbi', usernameLower: 'bobbi' });
|
||||
bobbie = await createUser({ username: 'Bobbie', usernameLower: 'bobbie', host: 'example.com' });
|
||||
bobby = await createUser({ username: 'Bobby', usernameLower: 'bobby', host: 'example.com' });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await usersRepository.delete({});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
test('フォロー中のアクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setActive([alice, alyce, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setInactive([alycia, alysha, alyson]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
// alycia, alysha, alysonは非アクティブなので後ろに行く
|
||||
expect(result).toEqual([alice, alyce, alyssa, alycia, alysha, alyson].map(x => x.id));
|
||||
});
|
||||
|
||||
test('フォロー中の非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await createFollowings(root, [alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
// alice, alyceはフォローしていないので後ろに行く
|
||||
expect(result).toEqual([alycia, alysha, alyson, alyssa, alice, alyce].map(x => x.id));
|
||||
});
|
||||
|
||||
test('フォローしていないアクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setInactive([alice, alyce, alycia]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
// alice, alyce, alyciaは非アクティブなので後ろに行く
|
||||
expect(result).toEqual([alysha, alyson, alyssa, alice, alyce, alycia].map(x => x.id));
|
||||
});
|
||||
|
||||
test('フォローしていない非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
expect(result).toEqual([alice, alyce, alycia, alysha, alyson, alyssa].map(x => x.id));
|
||||
});
|
||||
|
||||
test('フォロー(アクティブ)、フォロー(非アクティブ)、非フォロー(アクティブ)、非フォロー(非アクティブ)混在時の優先順位度確認', async () => {
|
||||
await createFollowings(root, [alyson, alyssa, bob, bobbi, bobbie]);
|
||||
await setActive([root, alyssa, bob, bobbi, alyce, alycia]);
|
||||
await setInactive([alyson, alice, alysha, bobbie, bobby]);
|
||||
|
||||
const result = await service.search(
|
||||
{ },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
// 見る用
|
||||
// const users = await usersRepository.findBy({ id: In(result) }).then(it => new Map(it.map(x => [x.id, x])));
|
||||
// console.log(result.map(x => users.get(x as any)).map(it => it?.username));
|
||||
|
||||
// フォローしててアクティブなので先頭: alyssa, bob, bobbi
|
||||
// フォローしてて非アクティブなので次: alyson, bobbie
|
||||
// フォローしてないけどアクティブなので次: alyce, alycia, root(アルファベット順的にここになる)
|
||||
// フォローしてないし非アクティブなので最後: alice, alysha, bobby
|
||||
expect(result).toEqual([alyssa, bob, bobbi, alyson, bobbie, alyce, alycia, root, alice, alysha, bobby].map(x => x.id));
|
||||
});
|
||||
|
||||
test('[非ログイン] アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setInactive([alice, alyce, alycia]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
);
|
||||
|
||||
// alice, alyce, alyciaは非アクティブなので後ろに行く
|
||||
expect(result).toEqual([alysha, alyson, alyssa, alice, alyce, alycia].map(x => x.id));
|
||||
});
|
||||
|
||||
test('[非ログイン] 非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
);
|
||||
|
||||
expect(result).toEqual([alice, alyce, alycia, alysha, alyson, alyssa].map(x => x.id));
|
||||
});
|
||||
|
||||
test('フォロー中のアクティブユーザのうち、"al"から始まり"example.com"にいる人が全員ヒットする', async () => {
|
||||
await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al', host: 'exam' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
expect(result).toEqual([alyson, alyssa].map(x => x.id));
|
||||
});
|
||||
|
||||
test('サスペンド済みユーザは出ない', async () => {
|
||||
await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||
await setSuspended([alice, alyce, alycia]);
|
||||
|
||||
const result = await service.search(
|
||||
{ username: 'al' },
|
||||
{ limit: 100 },
|
||||
root,
|
||||
);
|
||||
|
||||
expect(result).toEqual([alysha, alyson, alyssa].map(x => x.id));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,11 +12,14 @@ import WebSocket, { ClientOptions } from 'ws';
|
|||
import fetch, { File, RequestInit, type Headers } from 'node-fetch';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||
import { type Response } from 'node-fetch';
|
||||
import Fastify from 'fastify';
|
||||
import { entities } from '../src/postgres.js';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
||||
|
||||
|
@ -25,11 +28,23 @@ export interface UserToken {
|
|||
bearer?: boolean;
|
||||
}
|
||||
|
||||
export type SystemWebhookPayload = {
|
||||
server: string;
|
||||
hookId: string;
|
||||
eventId: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
body: any;
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
export const port = config.port;
|
||||
export const origin = config.url;
|
||||
export const host = new URL(config.url).host;
|
||||
|
||||
export const WEBHOOK_HOST = 'http://localhost:15080';
|
||||
export const WEBHOOK_PORT = 15080;
|
||||
|
||||
export const cookie = (me: UserToken): string => {
|
||||
return `token=${me.token};`;
|
||||
};
|
||||
|
@ -47,27 +62,28 @@ export const successfulApiCall = async <E extends keyof misskey.Endpoints, P ext
|
|||
const res = await api(endpoint, parameters, user);
|
||||
const status = assertion.status ?? (res.body == null ? 204 : 200);
|
||||
assert.strictEqual(res.status, status, inspect(res.body, { depth: 5, colors: true }));
|
||||
return res.body;
|
||||
|
||||
return res.body as misskey.api.SwitchCaseResponseType<E, P>;
|
||||
};
|
||||
|
||||
export const failedApiCall = async <T, E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
||||
export const failedApiCall = async <E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
||||
status: number,
|
||||
code: string,
|
||||
id: string
|
||||
}): Promise<T> => {
|
||||
}): Promise<void> => {
|
||||
const { endpoint, parameters, user } = request;
|
||||
const { status, code, id } = assertion;
|
||||
const res = await api(endpoint, parameters, user);
|
||||
assert.strictEqual(res.status, status, inspect(res.body));
|
||||
assert.strictEqual(res.body.error.code, code, inspect(res.body));
|
||||
assert.strictEqual(res.body.error.id, id, inspect(res.body));
|
||||
return res.body;
|
||||
assert.ok(res.body);
|
||||
assert.strictEqual(castAsError(res.body as any).error.code, code, inspect(res.body));
|
||||
assert.strictEqual(castAsError(res.body as any).error.id, id, inspect(res.body));
|
||||
};
|
||||
|
||||
export const api = async <E extends keyof misskey.Endpoints>(path: E, params: misskey.Endpoints[E]['req'], me?: UserToken): Promise<{
|
||||
export const api = async <E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(path: E, params: P, me?: UserToken): Promise<{
|
||||
status: number,
|
||||
headers: Headers,
|
||||
body: any
|
||||
body: misskey.api.SwitchCaseResponseType<E, P>
|
||||
}> => {
|
||||
const bodyAuth: Record<string, string> = {};
|
||||
const headers: Record<string, string> = {
|
||||
|
@ -88,13 +104,14 @@ export const api = async <E extends keyof misskey.Endpoints>(path: E, params: mi
|
|||
});
|
||||
|
||||
const body = res.headers.get('content-type') === 'application/json; charset=utf-8'
|
||||
? await res.json()
|
||||
? await res.json() as misskey.api.SwitchCaseResponseType<E, P>
|
||||
: null;
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
headers: res.headers,
|
||||
body,
|
||||
// FIXME: removing this non-null assertion: requires better typing around empty response.
|
||||
body: body!,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -140,7 +157,8 @@ export const post = async (user: UserToken, params: misskey.Endpoints['notes/cre
|
|||
|
||||
const res = await api('notes/create', q, user);
|
||||
|
||||
return res.body ? res.body.createdNote : null;
|
||||
// FIXME: the return type should reflect this fact.
|
||||
return (res.body ? res.body.createdNote : null)!;
|
||||
};
|
||||
|
||||
export const createAppToken = async (user: UserToken, permissions: (typeof misskey.permissions)[number][]) => {
|
||||
|
@ -296,7 +314,7 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
|
|||
body: misskey.entities.DriveFile | null
|
||||
}> => {
|
||||
const absPath = path == null
|
||||
? new URL('resources/Lenna.jpg', import.meta.url)
|
||||
? new URL('resources/192.jpg', import.meta.url)
|
||||
: isAbsolute(path.toString())
|
||||
? new URL(path)
|
||||
: new URL(path, new URL('resources/', import.meta.url));
|
||||
|
@ -454,7 +472,7 @@ export type SimpleGetResponse = {
|
|||
type: string | null,
|
||||
location: string | null
|
||||
};
|
||||
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise<SimpleGetResponse> => {
|
||||
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined, bodyExtractor: (res: Response) => Promise<string | null> = _ => Promise.resolve(null)): Promise<SimpleGetResponse> => {
|
||||
const res = await relativeFetch(path, {
|
||||
headers: {
|
||||
Accept: accept,
|
||||
|
@ -482,7 +500,7 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
|
|||
const body =
|
||||
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
|
||||
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
|
||||
null;
|
||||
await bodyExtractor(res);
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
|
@ -604,14 +622,6 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
|||
return db;
|
||||
}
|
||||
|
||||
export function sleep(msec: number) {
|
||||
return new Promise<void>(res => {
|
||||
setTimeout(() => {
|
||||
res();
|
||||
}, msec);
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendEnvUpdateRequest(params: { key: string, value?: string }) {
|
||||
const res = await fetch(
|
||||
`http://localhost:${port + 1000}/env`,
|
||||
|
@ -642,3 +652,43 @@ export async function sendEnvResetRequest() {
|
|||
throw new Error('server env update failed.');
|
||||
}
|
||||
}
|
||||
|
||||
// 与えられた値を強制的にエラーとみなす。この関数は型安全性を破壊するため、異常系のアサーション以外で用いられるべきではない。
|
||||
// FIXME(misskey-js): misskey-jsがエラー情報を公開するようになったらこの関数を廃止する
|
||||
export function castAsError(obj: Record<string, unknown>): { error: ApiError } {
|
||||
return obj as { error: ApiError };
|
||||
}
|
||||
|
||||
export async function captureWebhook<T = SystemWebhookPayload>(postAction: () => Promise<void>, port = WEBHOOK_PORT): Promise<T> {
|
||||
const fastify = Fastify();
|
||||
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const result = await new Promise<string>(async (resolve, reject) => {
|
||||
fastify.all('/', async (req, res) => {
|
||||
timeoutHandle && clearTimeout(timeoutHandle);
|
||||
|
||||
const body = JSON.stringify(req.body);
|
||||
res.status(200).send('ok');
|
||||
await fastify.close();
|
||||
resolve(body);
|
||||
});
|
||||
|
||||
await fastify.listen({ port });
|
||||
|
||||
timeoutHandle = setTimeout(async () => {
|
||||
await fastify.close();
|
||||
reject(new Error('timeout'));
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
await postAction();
|
||||
} catch (e) {
|
||||
await fastify.close();
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
await fastify.close();
|
||||
|
||||
return JSON.parse(result) as T;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue