Merge branch 'develop' into ed25519
This commit is contained in:
commit
d200da8690
278 changed files with 8215 additions and 3937 deletions
|
|
@ -44,6 +44,7 @@ describe('アンテナ', () => {
|
|||
users: [''],
|
||||
withFile: false,
|
||||
withReplies: false,
|
||||
excludeBots: false,
|
||||
};
|
||||
|
||||
let root: User;
|
||||
|
|
@ -156,6 +157,7 @@ describe('アンテナ', () => {
|
|||
users: [''],
|
||||
withFile: false,
|
||||
withReplies: false,
|
||||
excludeBots: false,
|
||||
localOnly: false,
|
||||
};
|
||||
assert.deepStrictEqual(response, expected);
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ 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, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||
import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Note', () => {
|
||||
let Notes: any;
|
||||
|
||||
let root: misskey.entities.SignupResponse;
|
||||
let alice: misskey.entities.SignupResponse;
|
||||
let bob: misskey.entities.SignupResponse;
|
||||
let tom: misskey.entities.SignupResponse;
|
||||
|
|
@ -21,6 +22,7 @@ describe('Note', () => {
|
|||
beforeAll(async () => {
|
||||
const connection = await initTestDb(true);
|
||||
Notes = connection.getRepository(MiNote);
|
||||
root = await signup({ username: 'root' });
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
tom = await signup({ username: 'tom', host: 'example.com' });
|
||||
|
|
@ -473,14 +475,14 @@ describe('Note', () => {
|
|||
value: true,
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
||||
const assign = await api('admin/roles/assign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(assign.status, 204);
|
||||
assert.strictEqual(file.body!.isSensitive, false);
|
||||
|
|
@ -508,11 +510,11 @@ describe('Note', () => {
|
|||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
});
|
||||
}, root);
|
||||
|
||||
await api('admin/roles/delete', {
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -644,7 +646,7 @@ describe('Note', () => {
|
|||
sensitiveWords: [
|
||||
'test',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(sensitive.status, 204);
|
||||
|
||||
|
|
@ -663,7 +665,7 @@ describe('Note', () => {
|
|||
sensitiveWords: [
|
||||
'/Test/i',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(sensitive.status, 204);
|
||||
|
||||
|
|
@ -680,7 +682,7 @@ describe('Note', () => {
|
|||
sensitiveWords: [
|
||||
'Test hoge',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(sensitive.status, 204);
|
||||
|
||||
|
|
@ -697,7 +699,7 @@ describe('Note', () => {
|
|||
prohibitedWords: [
|
||||
'test',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(prohibited.status, 204);
|
||||
|
||||
|
|
@ -716,7 +718,7 @@ describe('Note', () => {
|
|||
prohibitedWords: [
|
||||
'/Test/i',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(prohibited.status, 204);
|
||||
|
||||
|
|
@ -733,7 +735,7 @@ describe('Note', () => {
|
|||
prohibitedWords: [
|
||||
'Test hoge',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(prohibited.status, 204);
|
||||
|
||||
|
|
@ -750,7 +752,7 @@ describe('Note', () => {
|
|||
prohibitedWords: [
|
||||
'test',
|
||||
],
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(prohibited.status, 204);
|
||||
|
||||
|
|
@ -785,7 +787,7 @@ describe('Note', () => {
|
|||
value: 0,
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
||||
|
|
@ -794,7 +796,7 @@ describe('Note', () => {
|
|||
const assign = await api('admin/roles/assign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(assign.status, 204);
|
||||
|
||||
|
|
@ -810,11 +812,11 @@ describe('Note', () => {
|
|||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
});
|
||||
}, root);
|
||||
|
||||
await api('admin/roles/delete', {
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
});
|
||||
|
||||
test('ダイレクト投稿もエラーになる', async () => {
|
||||
|
|
@ -839,7 +841,7 @@ describe('Note', () => {
|
|||
value: 0,
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
||||
|
|
@ -848,7 +850,7 @@ describe('Note', () => {
|
|||
const assign = await api('admin/roles/assign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(assign.status, 204);
|
||||
|
||||
|
|
@ -866,11 +868,11 @@ describe('Note', () => {
|
|||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
});
|
||||
}, root);
|
||||
|
||||
await api('admin/roles/delete', {
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
});
|
||||
|
||||
test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => {
|
||||
|
|
@ -895,7 +897,7 @@ describe('Note', () => {
|
|||
value: 1,
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
||||
|
|
@ -904,7 +906,7 @@ describe('Note', () => {
|
|||
const assign = await api('admin/roles/assign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
|
||||
assert.strictEqual(assign.status, 204);
|
||||
|
||||
|
|
@ -921,11 +923,11 @@ describe('Note', () => {
|
|||
await api('admin/roles/unassign', {
|
||||
userId: alice.id,
|
||||
roleId: res.body.id,
|
||||
});
|
||||
}, root);
|
||||
|
||||
await api('admin/roles/delete', {
|
||||
roleId: res.body.id,
|
||||
}, alice);
|
||||
}, root);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -960,4 +962,61 @@ describe('Note', () => {
|
|||
assert.strictEqual(mainNote.repliesCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('notes/translate', () => {
|
||||
describe('翻訳機能の利用が許可されていない場合', () => {
|
||||
let cannotTranslateRole: misskey.entities.Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
cannotTranslateRole = await role(root, {}, { canUseTranslator: false });
|
||||
await api('admin/roles/assign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
|
||||
});
|
||||
|
||||
test('翻訳機能の利用が許可されていない場合翻訳できない', async () => {
|
||||
const aliceNote = await post(alice, { text: 'Hello' });
|
||||
const res = await api('notes/translate', {
|
||||
noteId: aliceNote.id,
|
||||
targetLang: 'ja',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await api('admin/roles/unassign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
|
||||
});
|
||||
});
|
||||
|
||||
test('存在しないノートは翻訳できない', async () => {
|
||||
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');
|
||||
});
|
||||
|
||||
test('不可視なノートは翻訳できない', async () => {
|
||||
const aliceNote = await post(alice, { visibility: 'followers', text: 'Hello' });
|
||||
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');
|
||||
});
|
||||
|
||||
test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
|
||||
const aliceNote = await post(alice, { text: null, poll: { choices: ['kinoko', 'takenoko'] } });
|
||||
const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
});
|
||||
|
||||
test('サーバーに DeepL 認証キーが登録されていない場合翻訳できない', async () => {
|
||||
const aliceNote = await post(alice, { text: 'Hello' });
|
||||
const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
|
||||
|
||||
// NOTE: デフォルトでは登録されていないので落ちる
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,6 +63,22 @@ describe('Renote Mute', () => {
|
|||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||
});
|
||||
|
||||
// #12956
|
||||
test('タイムラインにリノートミュートしているユーザーの通常ノートのリノートが含まれる', async () => {
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobRenote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(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);
|
||||
});
|
||||
|
||||
test('ストリームにリノートミュートしているユーザーのリノートが流れない', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
|
|
@ -86,4 +102,17 @@ describe('Renote Mute', () => {
|
|||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
// #12956
|
||||
test('ストリームにリノートミュートしているユーザーの通常ノートのリノートが流れてくる', async () => {
|
||||
const carolbNote = await post(carol, { text: 'hi' });
|
||||
|
||||
const fired = await waitFire(
|
||||
alice, 'localTimeline',
|
||||
() => api('notes/create', { renoteId: carolbNote.id }, bob),
|
||||
msg => msg.type === 'note' && msg.body.userId === bob.id,
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ describe('Streaming', () => {
|
|||
takumiNote = await post(takumi, { text: 'piyo' });
|
||||
|
||||
// Follow: ayano => kyoko
|
||||
await api('following/create', { userId: kyoko.id }, ayano);
|
||||
await api('following/create', { userId: kyoko.id, withReplies: false }, ayano);
|
||||
|
||||
// Follow: ayano => akari
|
||||
await follow(ayano, akari);
|
||||
|
|
@ -158,19 +158,17 @@ describe('Streaming', () => {
|
|||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
/* なんか失敗する
|
||||
test('フォローしているユーザーの visibility: followers な投稿への返信が流れる', async () => {
|
||||
const note = await api('notes/create', { text: 'foo', visibility: 'followers' }, kyoko);
|
||||
const note = await post(kyoko, { text: 'foo', visibility: 'followers' });
|
||||
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.body.id }, kyoko), // kyoko posts
|
||||
() => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.id }, kyoko), // kyoko posts
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id && msg.body.reply.text === 'foo',
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
*/
|
||||
|
||||
test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信が流れない', async () => {
|
||||
const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' });
|
||||
|
|
@ -511,6 +509,16 @@ describe('Streaming', () => {
|
|||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
test('withReplies = falseでフォローしてる人によるリプライが流れてくる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'globalTimeline', // ayano:Global
|
||||
() => api('notes/create', { text: 'foo', replyId: kanakoNote.id }, kyoko), // kyoko posts
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UserList Timeline', () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { initTestDb, sendEnvResetRequest } from './utils.js';
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
|
|||
BIN
packages/backend/test/resources/kick_gaba7.m4a
Normal file
BIN
packages/backend/test/resources/kick_gaba7.m4a
Normal file
Binary file not shown.
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { GlobalModule } from '@/GlobalModule.js';
|
|||
import { FileInfoService } from '@/core/FileInfoService.js';
|
||||
//import { DI } from '@/di-symbols.js';
|
||||
import { AiService } from '@/core/AiService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ describe('FileInfoService', () => {
|
|||
],
|
||||
providers: [
|
||||
AiService,
|
||||
LoggerService,
|
||||
FileInfoService,
|
||||
],
|
||||
})
|
||||
|
|
@ -323,8 +325,26 @@ describe('FileInfoService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* video/webmとして検出されてしまう
|
||||
test('MPEG-4 AUDIO (M4A)', async () => {
|
||||
const path = `${resources}/kick_gaba7.m4a`;
|
||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
delete info.width;
|
||||
delete info.height;
|
||||
delete info.orientation;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 9817,
|
||||
md5: '74c9279a4abe98789565f1dc1a541a42',
|
||||
type: {
|
||||
mime: 'audio/mp4',
|
||||
ext: 'm4a',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('WEBM AUDIO', async () => {
|
||||
const path = `${resources}/kick_gaba7.webm`;
|
||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
||||
|
|
@ -337,13 +357,12 @@ describe('FileInfoService', () => {
|
|||
delete info.orientation;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 8879,
|
||||
md5: '3350083dec312419cfdc06c16413aca7',
|
||||
md5: '53bc1adcb6acbbda67ff9bd484896438',
|
||||
type: {
|
||||
mime: 'audio/webm',
|
||||
ext: 'webm',
|
||||
},
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ describe('MfmService', () => {
|
|||
const output = '<p>foo <i>bar</i></p>';
|
||||
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
|
||||
});
|
||||
|
||||
test('escape', () => {
|
||||
const input = '```\n<p>Hello, world!</p>\n```';
|
||||
const output = '<p><pre><code><p>Hello, world!</p></code></pre></p>';
|
||||
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromHtml', () => {
|
||||
|
|
|
|||
144
packages/backend/test/unit/NoteCreateService.ts
Normal file
144
packages/backend/test/unit/NoteCreateService.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
import { IPoll } from '@/models/Poll.js';
|
||||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
|
||||
describe('NoteCreateService', () => {
|
||||
let noteCreateService: NoteCreateService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const app = await Test.createTestingModule({
|
||||
imports: [GlobalModule, CoreModule],
|
||||
}).compile();
|
||||
noteCreateService = app.get<NoteCreateService>(NoteCreateService);
|
||||
});
|
||||
|
||||
describe('is-renote', () => {
|
||||
const base: MiNote = {
|
||||
id: 'some-note-id',
|
||||
replyId: null,
|
||||
reply: null,
|
||||
renoteId: null,
|
||||
renote: null,
|
||||
threadId: null,
|
||||
text: null,
|
||||
name: null,
|
||||
cw: null,
|
||||
userId: 'some-user-id',
|
||||
user: null,
|
||||
localOnly: false,
|
||||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 0,
|
||||
clippedCount: 0,
|
||||
reactions: {},
|
||||
visibility: 'public',
|
||||
uri: null,
|
||||
url: null,
|
||||
fileIds: [],
|
||||
attachedFileTypes: [],
|
||||
visibleUserIds: [],
|
||||
mentions: [],
|
||||
mentionedRemoteUsers: '',
|
||||
reactionAndUserPairCache: [],
|
||||
emojis: [],
|
||||
tags: [],
|
||||
hasPoll: false,
|
||||
channelId: null,
|
||||
channel: null,
|
||||
userHost: null,
|
||||
replyUserId: null,
|
||||
replyUserHost: null,
|
||||
renoteUserId: null,
|
||||
renoteUserHost: null,
|
||||
};
|
||||
|
||||
const poll: IPoll = {
|
||||
choices: ['kinoko', 'takenoko'],
|
||||
multiple: false,
|
||||
expiresAt: null,
|
||||
};
|
||||
|
||||
const file: MiDriveFile = {
|
||||
id: 'some-file-id',
|
||||
userId: null,
|
||||
user: null,
|
||||
userHost: null,
|
||||
md5: '',
|
||||
name: '',
|
||||
type: '',
|
||||
size: 0,
|
||||
comment: null,
|
||||
blurhash: null,
|
||||
properties: {},
|
||||
storedInternal: false,
|
||||
url: '',
|
||||
thumbnailUrl: null,
|
||||
webpublicUrl: null,
|
||||
webpublicType: null,
|
||||
accessKey: null,
|
||||
thumbnailAccessKey: null,
|
||||
webpublicAccessKey: null,
|
||||
uri: null,
|
||||
src: null,
|
||||
folderId: null,
|
||||
folder: null,
|
||||
isSensitive: false,
|
||||
maybeSensitive: false,
|
||||
maybePorn: false,
|
||||
isLink: false,
|
||||
requestHeaders: null,
|
||||
requestIp: null,
|
||||
};
|
||||
|
||||
test('note without renote should not be Renote', () => {
|
||||
const note = { renote: null };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renote should be Renote and not be Quote', () => {
|
||||
const note = { renote: base };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renote and text should be Quote', () => {
|
||||
const note = { renote: base, text: 'some-text' };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and cw should be Quote', () => {
|
||||
const note = { renote: base, cw: 'some-cw' };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and reply should be Quote', () => {
|
||||
const note = { renote: base, reply: { ...base, id: 'another-note-id' } };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and poll should be Quote', () => {
|
||||
const note = { renote: base, poll };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and non-empty files should be Quote', () => {
|
||||
const note = { renote: base, files: [file] };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
|
|
@ -20,6 +22,7 @@ import { IdService } from '@/core/IdService.js';
|
|||
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 type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
|
@ -52,12 +55,26 @@ describe('RoleService', () => {
|
|||
id: genAidx(Date.now()),
|
||||
updatedAt: new Date(),
|
||||
lastUsedAt: new Date(),
|
||||
name: '',
|
||||
description: '',
|
||||
...data,
|
||||
})
|
||||
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial<MiRole> = {}) {
|
||||
return createRole({
|
||||
name: `[conditional] ${condFormula.type}`,
|
||||
target: 'conditional',
|
||||
condFormula: condFormula,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
function aidx() {
|
||||
return genAidx(Date.now());
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
clock = lolex.install({
|
||||
now: new Date(),
|
||||
|
|
@ -73,6 +90,7 @@ describe('RoleService', () => {
|
|||
CacheService,
|
||||
IdService,
|
||||
GlobalEventService,
|
||||
UserEntityService,
|
||||
{
|
||||
provide: NotificationService,
|
||||
useFactory: () => ({
|
||||
|
|
@ -209,79 +227,6 @@ describe('RoleService', () => {
|
|||
expect(result.driveCapacityMb).toBe(100);
|
||||
});
|
||||
|
||||
test('conditional role', async () => {
|
||||
const user1 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
});
|
||||
const user2 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
followersCount: 10,
|
||||
});
|
||||
await createRole({
|
||||
name: 'a',
|
||||
policies: {
|
||||
canManageCustomEmojis: {
|
||||
useDefault: false,
|
||||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
id: '232a4221-9816-49a6-a967-ae0fac52ec5e',
|
||||
type: 'and',
|
||||
values: [{
|
||||
id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530',
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 10,
|
||||
}, {
|
||||
id: '1bd67839-b126-4f92-bad0-4e285dab453b',
|
||||
type: 'createdMoreThan',
|
||||
sec: 60 * 60 * 24 * 7,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
metaService.fetch.mockResolvedValue({
|
||||
policies: {
|
||||
canManageCustomEmojis: false,
|
||||
},
|
||||
} as any);
|
||||
|
||||
const user1Policies = await roleService.getUserPolicies(user1.id);
|
||||
const user2Policies = await roleService.getUserPolicies(user2.id);
|
||||
expect(user1Policies.canManageCustomEmojis).toBe(false);
|
||||
expect(user2Policies.canManageCustomEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createRole({
|
||||
name: 'conditional role',
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
// idはバックエンドのロジックに必要ない?
|
||||
id: 'bdc612bd-9d54-4675-ae83-0499c82ea670',
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
},
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('expired role', async () => {
|
||||
const user = await createUser();
|
||||
const role = await createRole({
|
||||
|
|
@ -320,6 +265,427 @@ describe('RoleService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('conditional role', () => {
|
||||
test('~かつ~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'and',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~または~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'or',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~ではない', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'not',
|
||||
value: role1.condFormula,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ローカルユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocal',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('リモートユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isRemote',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('サスペンド済みユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isSuspended: false }),
|
||||
createUser({ isSuspended: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('鍵アカウントユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isLocked: false }),
|
||||
createUser({ isLocked: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocked',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('botユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isBot: false }),
|
||||
createUser({ isBot: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('猫である', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isCat: false }),
|
||||
createUser({ isCat: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('「ユーザを見つけやすくする」が有効なアカウント', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isExplorable: false }),
|
||||
createUser({ isExplorable: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isExplorable',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経過した', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdLessThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経っていない', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdMoreThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesLessThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesMoreThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign', () => {
|
||||
test('公開ロールの場合は通知される', async () => {
|
||||
const user = await createUser();
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ import { ApImageService } from '@/core/activitypub/models/ApImageService.js';
|
|||
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
|
||||
import { CONTEXT } from '@/core/activitypub/misc/contexts.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type { IActor, IApDocument, ICollection, IPost } from '@/core/activitypub/type.js';
|
||||
import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js';
|
||||
import { MiMeta, MiNote } from '@/models/_.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
|
|
@ -88,6 +90,7 @@ describe('ActivityPub', () => {
|
|||
let noteService: ApNoteService;
|
||||
let personService: ApPersonService;
|
||||
let rendererService: ApRendererService;
|
||||
let jsonLdService: JsonLdService;
|
||||
let resolver: MockResolver;
|
||||
|
||||
const metaInitial = {
|
||||
|
|
@ -128,6 +131,7 @@ describe('ActivityPub', () => {
|
|||
personService = app.get<ApPersonService>(ApPersonService);
|
||||
rendererService = app.get<ApRendererService>(ApRendererService);
|
||||
imageService = app.get<ApImageService>(ApImageService);
|
||||
jsonLdService = app.get<JsonLdService>(JsonLdService);
|
||||
resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
|
||||
|
||||
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
|
||||
|
|
@ -295,7 +299,7 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
imageObject,
|
||||
);
|
||||
assert.ok(!driveFile.isLink);
|
||||
assert.ok(driveFile && !driveFile.isLink);
|
||||
|
||||
const sensitiveImageObject: IApDocument = {
|
||||
type: 'Document',
|
||||
|
|
@ -308,7 +312,7 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
sensitiveImageObject,
|
||||
);
|
||||
assert.ok(!sensitiveDriveFile.isLink);
|
||||
assert.ok(sensitiveDriveFile && !sensitiveDriveFile.isLink);
|
||||
});
|
||||
|
||||
test('cacheRemoteFiles=false disables caching', async () => {
|
||||
|
|
@ -324,7 +328,7 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
imageObject,
|
||||
);
|
||||
assert.ok(driveFile.isLink);
|
||||
assert.ok(driveFile && driveFile.isLink);
|
||||
|
||||
const sensitiveImageObject: IApDocument = {
|
||||
type: 'Document',
|
||||
|
|
@ -337,7 +341,7 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
sensitiveImageObject,
|
||||
);
|
||||
assert.ok(sensitiveDriveFile.isLink);
|
||||
assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
|
||||
});
|
||||
|
||||
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
|
||||
|
|
@ -353,7 +357,7 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
imageObject,
|
||||
);
|
||||
assert.ok(!driveFile.isLink);
|
||||
assert.ok(driveFile && !driveFile.isLink);
|
||||
|
||||
const sensitiveImageObject: IApDocument = {
|
||||
type: 'Document',
|
||||
|
|
@ -366,7 +370,57 @@ describe('ActivityPub', () => {
|
|||
await createRandomRemoteUser(resolver, personService),
|
||||
sensitiveImageObject,
|
||||
);
|
||||
assert.ok(sensitiveDriveFile.isLink);
|
||||
assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
|
||||
});
|
||||
|
||||
test('Link is not an attachment files', async () => {
|
||||
const linkObject: IObject = {
|
||||
type: 'Link',
|
||||
href: 'https://example.com/',
|
||||
};
|
||||
const driveFile = await imageService.createImage(
|
||||
await createRandomRemoteUser(resolver, personService),
|
||||
linkObject,
|
||||
);
|
||||
assert.strictEqual(driveFile, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSON-LD', () =>{
|
||||
test('Compaction', async () => {
|
||||
const jsonLd = jsonLdService.use();
|
||||
|
||||
const object = {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{
|
||||
_misskey_quote: 'https://misskey-hub.net/ns#_misskey_quote',
|
||||
unknown: 'https://example.org/ns#unknown',
|
||||
undefined: null,
|
||||
},
|
||||
],
|
||||
id: 'https://example.com/notes/42',
|
||||
type: 'Note',
|
||||
attributedTo: 'https://example.com/users/1',
|
||||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
content: 'test test foo',
|
||||
_misskey_quote: 'https://example.com/notes/1',
|
||||
unknown: 'test test bar',
|
||||
undefined: 'test test baz',
|
||||
};
|
||||
const compacted = await jsonLd.compact(object);
|
||||
|
||||
assert.deepStrictEqual(compacted, {
|
||||
'@context': CONTEXT,
|
||||
id: 'https://example.com/notes/42',
|
||||
type: 'Note',
|
||||
attributedTo: 'https://example.com/users/1',
|
||||
to: 'as:Public',
|
||||
content: 'test test foo',
|
||||
_misskey_quote: 'https://example.com/notes/1',
|
||||
'https://example.org/ns#unknown': 'test test bar',
|
||||
// undefined: 'test test baz',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
528
packages/backend/test/unit/entities/UserEntityService.ts
Normal file
528
packages/backend/test/unit/entities/UserEntityService.ts
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { genAidx } from '@/misc/id/aidx.js';
|
||||
import {
|
||||
BlockingsRepository,
|
||||
FollowingsRepository, FollowRequestsRepository,
|
||||
MiUserProfile, MutingsRepository, RenoteMutingsRepository,
|
||||
UserMemoRepository,
|
||||
UserProfilesRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
||||
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
||||
import { ApImageService } from '@/core/activitypub/models/ApImageService.js';
|
||||
import { ApMfmService } from '@/core/activitypub/ApMfmService.js';
|
||||
import { MfmService } from '@/core/MfmService.js';
|
||||
import { HashtagService } from '@/core/HashtagService.js';
|
||||
import UsersChart from '@/core/chart/charts/users.js';
|
||||
import { ChartLoggerService } from '@/core/chart/ChartLoggerService.js';
|
||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { ReactionService } from '@/core/ReactionService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
describe('UserEntityService', () => {
|
||||
describe('pack/packMany', () => {
|
||||
let app: TestingModule;
|
||||
let service: UserEntityService;
|
||||
let usersRepository: UsersRepository;
|
||||
let userProfileRepository: UserProfilesRepository;
|
||||
let userMemosRepository: UserMemoRepository;
|
||||
let followingRepository: FollowingsRepository;
|
||||
let followingRequestRepository: FollowRequestsRepository;
|
||||
let blockingRepository: BlockingsRepository;
|
||||
let mutingRepository: MutingsRepository;
|
||||
let renoteMutingsRepository: RenoteMutingsRepository;
|
||||
|
||||
async function createUser(userData: Partial<MiUser> = {}, profileData: Partial<MiUserProfile> = {}) {
|
||||
const un = secureRndstr(16);
|
||||
const user = await usersRepository
|
||||
.insert({
|
||||
...userData,
|
||||
id: genAidx(Date.now()),
|
||||
username: un,
|
||||
usernameLower: un,
|
||||
})
|
||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
await userProfileRepository.insert({
|
||||
...profileData,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async function memo(writer: MiUser, target: MiUser, memo: string) {
|
||||
await userMemosRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
userId: writer.id,
|
||||
targetUserId: target.id,
|
||||
memo,
|
||||
});
|
||||
}
|
||||
|
||||
async function follow(follower: MiUser, followee: MiUser) {
|
||||
await followingRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function requestFollow(requester: MiUser, requestee: MiUser) {
|
||||
await followingRequestRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
followerId: requester.id,
|
||||
followeeId: requestee.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function block(blocker: MiUser, blockee: MiUser) {
|
||||
await blockingRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
blockerId: blocker.id,
|
||||
blockeeId: blockee.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function mute(mutant: MiUser, mutee: MiUser) {
|
||||
await mutingRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
muterId: mutant.id,
|
||||
muteeId: mutee.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function muteRenote(mutant: MiUser, mutee: MiUser) {
|
||||
await renoteMutingsRepository.insert({
|
||||
id: genAidx(Date.now()),
|
||||
muterId: mutant.id,
|
||||
muteeId: mutee.id,
|
||||
});
|
||||
}
|
||||
|
||||
function randomIntRange(weight = 10) {
|
||||
return [...Array(Math.floor(Math.random() * weight))].map((it, idx) => idx);
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const services = [
|
||||
UserEntityService,
|
||||
ApPersonService,
|
||||
NoteEntityService,
|
||||
PageEntityService,
|
||||
CustomEmojiService,
|
||||
AnnouncementService,
|
||||
RoleService,
|
||||
FederatedInstanceService,
|
||||
IdService,
|
||||
AvatarDecorationService,
|
||||
UtilityService,
|
||||
EmojiEntityService,
|
||||
ModerationLogService,
|
||||
GlobalEventService,
|
||||
DriveFileEntityService,
|
||||
MetaService,
|
||||
FetchInstanceMetadataService,
|
||||
CacheService,
|
||||
ApResolverService,
|
||||
ApNoteService,
|
||||
ApImageService,
|
||||
ApMfmService,
|
||||
MfmService,
|
||||
HashtagService,
|
||||
UsersChart,
|
||||
ChartLoggerService,
|
||||
InstanceChart,
|
||||
ApLoggerService,
|
||||
AccountMoveService,
|
||||
ReactionService,
|
||||
NotificationService,
|
||||
];
|
||||
|
||||
app = await Test.createTestingModule({
|
||||
imports: [GlobalModule, CoreModule],
|
||||
providers: [
|
||||
...services,
|
||||
...services.map(x => ({ provide: x.name, useExisting: x })),
|
||||
],
|
||||
}).compile();
|
||||
await app.init();
|
||||
app.enableShutdownHooks();
|
||||
|
||||
service = app.get<UserEntityService>(UserEntityService);
|
||||
usersRepository = app.get<UsersRepository>(DI.usersRepository);
|
||||
userProfileRepository = app.get<UserProfilesRepository>(DI.userProfilesRepository);
|
||||
userMemosRepository = app.get<UserMemoRepository>(DI.userMemosRepository);
|
||||
followingRepository = app.get<FollowingsRepository>(DI.followingsRepository);
|
||||
followingRequestRepository = app.get<FollowRequestsRepository>(DI.followRequestsRepository);
|
||||
blockingRepository = app.get<BlockingsRepository>(DI.blockingsRepository);
|
||||
mutingRepository = app.get<MutingsRepository>(DI.mutingsRepository);
|
||||
renoteMutingsRepository = app.get<RenoteMutingsRepository>(DI.renoteMutingsRepository);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
test('UserLite', async() => {
|
||||
const me = await createUser();
|
||||
const who = await createUser();
|
||||
|
||||
await memo(me, who, 'memo');
|
||||
|
||||
const actual = await service.pack(who, me, { schema: 'UserLite' }) as any;
|
||||
// no detail
|
||||
expect(actual.memo).toBeUndefined();
|
||||
// no detail and me
|
||||
expect(actual.birthday).toBeUndefined();
|
||||
// no detail and me
|
||||
expect(actual.achievements).toBeUndefined();
|
||||
});
|
||||
|
||||
test('UserDetailedNotMe', async() => {
|
||||
const me = await createUser();
|
||||
const who = await createUser({}, { birthday: '2000-01-01' });
|
||||
|
||||
await memo(me, who, 'memo');
|
||||
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailedNotMe' }) as any;
|
||||
// is detail
|
||||
expect(actual.memo).toBe('memo');
|
||||
// is detail
|
||||
expect(actual.birthday).toBe('2000-01-01');
|
||||
// no detail and me
|
||||
expect(actual.achievements).toBeUndefined();
|
||||
});
|
||||
|
||||
test('MeDetailed', async() => {
|
||||
const achievements = [{ name: 'achievement', unlockedAt: new Date().getTime() }];
|
||||
const me = await createUser({}, {
|
||||
birthday: '2000-01-01',
|
||||
achievements: achievements,
|
||||
});
|
||||
await memo(me, me, 'memo');
|
||||
|
||||
const actual = await service.pack(me, me, { schema: 'MeDetailed' }) as any;
|
||||
// is detail
|
||||
expect(actual.memo).toBe('memo');
|
||||
// is detail
|
||||
expect(actual.birthday).toBe('2000-01-01');
|
||||
// is detail and me
|
||||
expect(actual.achievements).toEqual(achievements);
|
||||
});
|
||||
|
||||
describe('packManyによるpreloadがある時、preloadが無い時とpackの結果が同じになるか見たい', () => {
|
||||
test('no-preload', async() => {
|
||||
const me = await createUser();
|
||||
// meがフォローしてる人たち
|
||||
const followeeMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of followeeMe) {
|
||||
await follow(me, who);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(true);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meをフォローしてる人たち
|
||||
const followerMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of followerMe) {
|
||||
await follow(who, me);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(true);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meがフォローリクエストを送った人たち
|
||||
const requestsFromYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of requestsFromYou) {
|
||||
await requestFollow(me, who);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(true);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meにフォローリクエストを送った人たち
|
||||
const requestsToYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of requestsToYou) {
|
||||
await requestFollow(who, me);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(true);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meがブロックしてる人たち
|
||||
const blockingYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of blockingYou) {
|
||||
await block(me, who);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(true);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meをブロックしてる人たち
|
||||
const blockingMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of blockingMe) {
|
||||
await block(who, me);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(true);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meがミュートしてる人たち
|
||||
const muters = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of muters) {
|
||||
await mute(me, who);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(true);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
|
||||
// meがリノートミュートしてる人たち
|
||||
const renoteMuters = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of renoteMuters) {
|
||||
await muteRenote(me, who);
|
||||
const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any;
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('preload', async() => {
|
||||
const me = await createUser();
|
||||
|
||||
{
|
||||
// meがフォローしてる人たち
|
||||
const followeeMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of followeeMe) {
|
||||
await follow(me, who);
|
||||
}
|
||||
const actualList = await service.packMany(followeeMe, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(true);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meをフォローしてる人たち
|
||||
const followerMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of followerMe) {
|
||||
await follow(who, me);
|
||||
}
|
||||
const actualList = await service.packMany(followerMe, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(true);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meがフォローリクエストを送った人たち
|
||||
const requestsFromYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of requestsFromYou) {
|
||||
await requestFollow(me, who);
|
||||
}
|
||||
const actualList = await service.packMany(requestsFromYou, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(true);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meにフォローリクエストを送った人たち
|
||||
const requestsToYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of requestsToYou) {
|
||||
await requestFollow(who, me);
|
||||
}
|
||||
const actualList = await service.packMany(requestsToYou, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(true);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meがブロックしてる人たち
|
||||
const blockingYou = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of blockingYou) {
|
||||
await block(me, who);
|
||||
}
|
||||
const actualList = await service.packMany(blockingYou, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(true);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meをブロックしてる人たち
|
||||
const blockingMe = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of blockingMe) {
|
||||
await block(who, me);
|
||||
}
|
||||
const actualList = await service.packMany(blockingMe, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(true);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meがミュートしてる人たち
|
||||
const muters = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of muters) {
|
||||
await mute(me, who);
|
||||
}
|
||||
const actualList = await service.packMany(muters, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(true);
|
||||
expect(actual.isRenoteMuted).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// meがリノートミュートしてる人たち
|
||||
const renoteMuters = await Promise.all(randomIntRange().map(() => createUser()));
|
||||
for (const who of renoteMuters) {
|
||||
await muteRenote(me, who);
|
||||
}
|
||||
const actualList = await service.packMany(renoteMuters, me, { schema: 'UserDetailed' }) as any;
|
||||
for (const actual of actualList) {
|
||||
expect(actual.isFollowing).toBe(false);
|
||||
expect(actual.isFollowed).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestFromYou).toBe(false);
|
||||
expect(actual.hasPendingFollowRequestToYou).toBe(false);
|
||||
expect(actual.isBlocking).toBe(false);
|
||||
expect(actual.isBlocked).toBe(false);
|
||||
expect(actual.isMuted).toBe(false);
|
||||
expect(actual.isRenoteMuted).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
88
packages/backend/test/unit/misc/is-renote.ts
Normal file
88
packages/backend/test/unit/misc/is-renote.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
|
||||
const base: MiNote = {
|
||||
id: 'some-note-id',
|
||||
replyId: null,
|
||||
reply: null,
|
||||
renoteId: null,
|
||||
renote: null,
|
||||
threadId: null,
|
||||
text: null,
|
||||
name: null,
|
||||
cw: null,
|
||||
userId: 'some-user-id',
|
||||
user: null,
|
||||
localOnly: false,
|
||||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 0,
|
||||
clippedCount: 0,
|
||||
reactions: {},
|
||||
visibility: 'public',
|
||||
uri: null,
|
||||
url: null,
|
||||
fileIds: [],
|
||||
attachedFileTypes: [],
|
||||
visibleUserIds: [],
|
||||
mentions: [],
|
||||
mentionedRemoteUsers: '',
|
||||
reactionAndUserPairCache: [],
|
||||
emojis: [],
|
||||
tags: [],
|
||||
hasPoll: false,
|
||||
channelId: null,
|
||||
channel: null,
|
||||
userHost: null,
|
||||
replyUserId: null,
|
||||
replyUserHost: null,
|
||||
renoteUserId: null,
|
||||
renoteUserHost: null,
|
||||
};
|
||||
|
||||
describe('misc:is-renote', () => {
|
||||
test('note without renoteId should not be Renote', () => {
|
||||
expect(isRenote(base)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renoteId should be Renote and not be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renoteId and text should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', text: 'some-text' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and cw should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', cw: 'some-cw' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and replyId should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', replyId: 'some-reply-id' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and poll should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', hasPoll: true };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and non-empty fileIds should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', fileIds: ['some-file-id'] };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
|
||||
class Mock {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue