Merge remote-tracking branch 'misskey/master' into feature/misskey-2024.07

This commit is contained in:
dakkar 2024-08-02 12:25:58 +01:00
commit cfa9b852df
585 changed files with 23423 additions and 9623 deletions

View 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]);
});
});
});

View file

@ -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);

View file

@ -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;

View file

@ -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: {

View 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);
});
});
});
});
});

View 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));
});
});
});