summaryrefslogtreecommitdiff
path: root/packages/backend/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/test/unit')
-rw-r--r--packages/backend/test/unit/AbuseReportNotificationService.ts50
-rw-r--r--packages/backend/test/unit/CaptchaService.ts622
-rw-r--r--packages/backend/test/unit/CustomEmojiService.ts817
-rw-r--r--packages/backend/test/unit/MfmService.ts18
-rw-r--r--packages/backend/test/unit/SystemWebhookService.ts49
-rw-r--r--packages/backend/test/unit/UserWebhookService.ts91
-rw-r--r--packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts12
7 files changed, 1646 insertions, 13 deletions
diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts
index 235af29f0d..1326003c5e 100644
--- a/packages/backend/test/unit/AbuseReportNotificationService.ts
+++ b/packages/backend/test/unit/AbuseReportNotificationService.ts
@@ -3,13 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { jest } from '@jest/globals';
+import { describe, jest } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { randomString } from '../utils.js';
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
import {
AbuseReportNotificationRecipientRepository,
MiAbuseReportNotificationRecipient,
+ MiAbuseUserReport,
MiSystemWebhook,
MiUser,
SystemWebhooksRepository,
@@ -112,7 +113,10 @@ describe('AbuseReportNotificationService', () => {
provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }),
},
{
- provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }),
+ provide: UserEntityService, useFactory: () => ({
+ pack: (v: any) => Promise.resolve(v),
+ packMany: (v: any) => Promise.resolve(v),
+ }),
},
{
provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
@@ -344,4 +348,46 @@ describe('AbuseReportNotificationService', () => {
expect(recipients).toEqual([recipient3]);
});
});
+
+ describe('notifySystemWebhook', () => {
+ test('非アクティブな通報通知はWebhook送信から除外される', async () => {
+ const recipient1 = await createRecipient({
+ method: 'webhook',
+ systemWebhookId: systemWebhook1.id,
+ isActive: true,
+ });
+ const recipient2 = await createRecipient({
+ method: 'webhook',
+ systemWebhookId: systemWebhook2.id,
+ isActive: false,
+ });
+
+ const reports: MiAbuseUserReport[] = [
+ {
+ id: idService.gen(),
+ targetUserId: alice.id,
+ targetUser: alice,
+ reporterId: bob.id,
+ reporter: bob,
+ assigneeId: null,
+ assignee: null,
+ resolved: false,
+ forwarded: false,
+ comment: 'test',
+ moderationNote: '',
+ resolvedAs: null,
+ targetUserHost: null,
+ reporterHost: null,
+ },
+ ];
+
+ await service.notifySystemWebhook(reports, 'abuseReport');
+
+ // 実際に除外されるかはSystemWebhookService側で確認する.
+ // ここでは非アクティブな通報通知を除外設定できているかを確認する
+ expect(webhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+ expect(webhookService.enqueueSystemWebhook.mock.calls[0][0]).toBe('abuseReport');
+ expect(webhookService.enqueueSystemWebhook.mock.calls[0][2]).toEqual({ excludes: [systemWebhook2.id] });
+ });
+ });
});
diff --git a/packages/backend/test/unit/CaptchaService.ts b/packages/backend/test/unit/CaptchaService.ts
new file mode 100644
index 0000000000..51b70b05a1
--- /dev/null
+++ b/packages/backend/test/unit/CaptchaService.ts
@@ -0,0 +1,622 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterAll, beforeAll, beforeEach, describe, expect, jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { Response } from 'node-fetch';
+import {
+ CaptchaError,
+ CaptchaErrorCode,
+ captchaErrorCodes,
+ CaptchaSaveResult,
+ CaptchaService,
+} from '@/core/CaptchaService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { HttpRequestService } from '@/core/HttpRequestService.js';
+import { MetaService } from '@/core/MetaService.js';
+import { MiMeta } from '@/models/Meta.js';
+import { LoggerService } from '@/core/LoggerService.js';
+
+describe('CaptchaService', () => {
+ let app: TestingModule;
+ let service: CaptchaService;
+ let httpRequestService: jest.Mocked<HttpRequestService>;
+ let metaService: jest.Mocked<MetaService>;
+
+ beforeAll(async () => {
+ app = await Test.createTestingModule({
+ imports: [
+ GlobalModule,
+ ],
+ providers: [
+ CaptchaService,
+ LoggerService,
+ {
+ provide: HttpRequestService, useFactory: () => ({ send: jest.fn() }),
+ },
+ {
+ provide: MetaService, useFactory: () => ({
+ fetch: jest.fn(),
+ update: jest.fn(),
+ }),
+ },
+ ],
+ }).compile();
+
+ app.enableShutdownHooks();
+
+ service = app.get(CaptchaService);
+ httpRequestService = app.get(HttpRequestService) as jest.Mocked<HttpRequestService>;
+ metaService = app.get(MetaService) as jest.Mocked<MetaService>;
+ });
+
+ beforeEach(() => {
+ httpRequestService.send.mockClear();
+ metaService.update.mockClear();
+ metaService.fetch.mockClear();
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+
+ function successMock(result: object) {
+ httpRequestService.send.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => (result),
+ } as Response);
+ }
+
+ function failureHttpMock() {
+ httpRequestService.send.mockResolvedValue({
+ ok: false,
+ status: 400,
+ } as Response);
+ }
+
+ function failureVerificationMock(result: object) {
+ httpRequestService.send.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => (result),
+ } as Response);
+ }
+
+ async function testCaptchaError(code: CaptchaErrorCode, test: () => Promise<void>) {
+ try {
+ await test();
+ expect(false).toBe(true);
+ } catch (e) {
+ expect(e instanceof CaptchaError).toBe(true);
+
+ const _e = e as CaptchaError;
+ expect(_e.code).toBe(code);
+ }
+ }
+
+ describe('verifyRecaptcha', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyRecaptcha('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyRecaptcha('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyRecaptcha('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyRecaptcha('secret', 'response'));
+ });
+ });
+
+ describe('verifyHcaptcha', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyHcaptcha('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyHcaptcha('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyHcaptcha('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyHcaptcha('secret', 'response'));
+ });
+ });
+
+ describe('verifyMcaptcha', () => {
+ const host = 'https://localhost';
+
+ test('success', async () => {
+ successMock({ valid: true });
+ await service.verifyMcaptcha('secret', 'sitekey', host, 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyMcaptcha('secret', 'sitekey', host, null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ valid: false });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+ });
+ });
+
+ describe('verifyTurnstile', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyTurnstile('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTurnstile('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyTurnstile('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTurnstile('secret', 'response'));
+ });
+ });
+
+ describe('verifyTestcaptcha', () => {
+ test('success', async () => {
+ await service.verifyTestcaptcha('testcaptcha-passed');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTestcaptcha(null));
+ });
+
+ test('verificationFailed', async () => {
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTestcaptcha('testcaptcha-failed'));
+ });
+ });
+
+ describe('get', () => {
+ function setupMeta(meta: Partial<MiMeta>) {
+ metaService.fetch.mockResolvedValue(meta as MiMeta);
+ }
+
+ test('values', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ hcaptchaSiteKey: 'hcaptcha-sitekey',
+ hcaptchaSecretKey: 'hcaptcha-secret',
+ mcaptchaSitekey: 'mcaptcha-sitekey',
+ mcaptchaSecretKey: 'mcaptcha-secret',
+ mcaptchaInstanceUrl: 'https://localhost',
+ recaptchaSiteKey: 'recaptcha-sitekey',
+ recaptchaSecretKey: 'recaptcha-secret',
+ turnstileSiteKey: 'turnstile-sitekey',
+ turnstileSecretKey: 'turnstile-secret',
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('none');
+ expect(result.hcaptcha.siteKey).toBe('hcaptcha-sitekey');
+ expect(result.hcaptcha.secretKey).toBe('hcaptcha-secret');
+ expect(result.mcaptcha.siteKey).toBe('mcaptcha-sitekey');
+ expect(result.mcaptcha.secretKey).toBe('mcaptcha-secret');
+ expect(result.mcaptcha.instanceUrl).toBe('https://localhost');
+ expect(result.recaptcha.siteKey).toBe('recaptcha-sitekey');
+ expect(result.recaptcha.secretKey).toBe('recaptcha-secret');
+ expect(result.turnstile.siteKey).toBe('turnstile-sitekey');
+ expect(result.turnstile.secretKey).toBe('turnstile-secret');
+ });
+
+ describe('provider', () => {
+ test('none', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('none');
+ });
+
+ test('hcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: true,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('hcaptcha');
+ });
+
+ test('mcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: true,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('mcaptcha');
+ });
+
+ test('recaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: true,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('recaptcha');
+ });
+
+ test('turnstile', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: true,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('turnstile');
+ });
+
+ test('testcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: true,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('testcaptcha');
+ });
+ });
+ });
+
+ describe('save', () => {
+ const host = 'https://localhost';
+
+ describe('[success] 検証に成功した時だけ保存できる+他のプロバイダの設定値を誤って更新しない', () => {
+ beforeEach(() => {
+ successMock({ success: true, valid: true });
+ });
+
+ async function assertSuccess(promise: Promise<CaptchaSaveResult>, expectMeta: Partial<MiMeta>) {
+ await expect(promise)
+ .resolves
+ .toStrictEqual({ success: true });
+ const partialParams = metaService.update.mock.calls[0][0];
+ expect(partialParams).toStrictEqual(expectMeta);
+ }
+
+ test('none', async () => {
+ await assertSuccess(
+ service.save('none'),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ },
+ );
+ });
+
+ test('hcaptcha', async () => {
+ await assertSuccess(
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: true,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ hcaptchaSiteKey: 'hcaptcha-sitekey',
+ hcaptchaSecretKey: 'hcaptcha-secret',
+ },
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertSuccess(
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: true,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ mcaptchaSitekey: 'mcaptcha-sitekey',
+ mcaptchaSecretKey: 'mcaptcha-secret',
+ mcaptchaInstanceUrl: host,
+ },
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertSuccess(
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: true,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ recaptchaSiteKey: 'recaptcha-sitekey',
+ recaptchaSecretKey: 'recaptcha-secret',
+ },
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertSuccess(
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: true,
+ enableTestcaptcha: false,
+ turnstileSiteKey: 'turnstile-sitekey',
+ turnstileSecretKey: 'turnstile-secret',
+ },
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertSuccess(
+ service.save('testcaptcha', {
+ sitekey: 'testcaptcha-sitekey',
+ secret: 'testcaptcha-secret',
+ captchaResult: 'testcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: true,
+ },
+ );
+ });
+ });
+
+ describe('[failure] 検証に失敗した場合は保存できない+設定値の更新そのものが発生しない', () => {
+ async function assertFailure(code: CaptchaErrorCode, promise: Promise<CaptchaSaveResult>) {
+ const res = await promise;
+ expect(res.success).toBe(false);
+ if (!res.success) {
+ expect(res.error.code).toBe(code);
+ }
+ expect(metaService.update).not.toBeCalled();
+ }
+
+ describe('invalidParameters', () => {
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('testcaptcha', {
+ captchaResult: null,
+ }),
+ );
+ });
+ });
+
+ describe('requestFailed', () => {
+ beforeEach(() => {
+ failureHttpMock();
+ });
+
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hcaptcha-passed',
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ );
+ });
+
+ // testchapchaはrequestFailedがない
+ });
+
+ describe('verificationFailed', () => {
+ beforeEach(() => {
+ failureVerificationMock({ success: false, valid: false, 'error-codes': ['code01', 'code02'] });
+ });
+
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hccaptcha-passed',
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('testcaptcha', {
+ captchaResult: 'testcaptcha-failed',
+ }),
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/packages/backend/test/unit/CustomEmojiService.ts b/packages/backend/test/unit/CustomEmojiService.ts
new file mode 100644
index 0000000000..10b687c6a0
--- /dev/null
+++ b/packages/backend/test/unit/CustomEmojiService.ts
@@ -0,0 +1,817 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterEach, beforeAll, describe, test } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { EmojisRepository } from '@/models/_.js';
+import { MiEmoji } from '@/models/Emoji.js';
+
+describe('CustomEmojiService', () => {
+ let app: TestingModule;
+ let service: CustomEmojiService;
+
+ let emojisRepository: EmojisRepository;
+ let idService: IdService;
+
+ beforeAll(async () => {
+ app = await Test
+ .createTestingModule({
+ imports: [
+ GlobalModule,
+ ],
+ providers: [
+ CustomEmojiService,
+ UtilityService,
+ IdService,
+ EmojiEntityService,
+ ModerationLogService,
+ GlobalEventService,
+ ],
+ })
+ .compile();
+ app.enableShutdownHooks();
+
+ service = app.get<CustomEmojiService>(CustomEmojiService);
+ emojisRepository = app.get<EmojisRepository>(DI.emojisRepository);
+ idService = app.get<IdService>(IdService);
+ });
+
+ describe('fetchEmojis', () => {
+ async function insert(data: Partial<MiEmoji>[]) {
+ for (const d of data) {
+ const id = idService.gen();
+ await emojisRepository.insert({
+ id: id,
+ updatedAt: new Date(),
+ ...d,
+ });
+ }
+ }
+
+ function call(params: Parameters<CustomEmojiService['fetchEmojis']>['0']) {
+ return service.fetchEmojis(
+ params,
+ {
+ // テスト向けに
+ sortKeys: ['+id'],
+ },
+ );
+ }
+
+ function defaultData(suffix: string, override?: Partial<MiEmoji>): Partial<MiEmoji> {
+ return {
+ name: `emoji${suffix}`,
+ host: null,
+ category: 'default',
+ originalUrl: `https://example.com/emoji${suffix}.png`,
+ publicUrl: `https://example.com/emoji${suffix}.png`,
+ type: 'image/png',
+ aliases: [`emoji${suffix}`],
+ license: 'CC0',
+ isSensitive: false,
+ localOnly: false,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: [],
+ ...override,
+ };
+ }
+
+ afterEach(async () => {
+ await emojisRepository.delete({});
+ });
+
+ describe('単独', () => {
+ test('updatedAtFrom', async () => {
+ await insert([
+ defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+ defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+ defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+ ]);
+
+ const actual = await call({
+ query: {
+ updatedAtFrom: '2021-01-02T00:00:00.000Z',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('updatedAtTo', async () => {
+ await insert([
+ defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+ defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+ defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+ ]);
+
+ const actual = await call({
+ query: {
+ updatedAtTo: '2021-01-02T00:00:00.000Z',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ describe('name', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'emoji001',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'emoji001 emoji002',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ defaultData('003', { name: 'em003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'oji',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('host', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: 'example.com',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: '1.example.com 2.example.com',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji003');
+ expect(actual.emojis[1].name).toBe('emoji004');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: 'example',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: '%',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('uri', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'uri002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'uri001 uri003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'ri',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('publicUrl', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'publicUrl002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'publicUrl001 publicUrl003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'Url',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('type', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'type002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'type001 type003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'pe',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('aliases', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'alias002',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002', 'alias004'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ defaultData('004', { aliases: ['alias004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'alias001 alias004',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ expect(actual.emojis[2].name).toBe('emoji004');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002', 'alias004'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ defaultData('004', { aliases: ['alias004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'ias',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('category', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'category002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'category001 category003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'egory',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('license', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'license002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'license001 license003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'cense',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('isSensitive', () => {
+ test('true', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ isSensitive: true,
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('false', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ isSensitive: false,
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('null', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {},
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+ });
+
+ describe('localOnly', () => {
+ test('true', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ localOnly: true,
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('false', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ localOnly: false,
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('null', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {},
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+ });
+
+ describe('roleId', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+ defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002'] }),
+ defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ roleIds: ['role002'],
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+ defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002', 'role003'] }),
+ defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+ defaultData('004', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ roleIds: ['role001', 'role003'],
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ expect(actual.emojis[2].name).toBe('emoji003');
+ });
+ });
+ });
+ });
+});
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index 8d5683329f..73d9c7b60c 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -108,6 +108,24 @@ describe('MfmService', () => {
assert.deepStrictEqual(mfmService.fromHtml('<p>a <a></a> d</p>'), 'a d');
});
+ test('ruby', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'), 'a $[ruby Misskey ミスキー] b');
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'), 'a $[ruby Misskey ミスキー]$[ruby Misskey ミスキー] b');
+ });
+
+ test('ruby with spaces', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Miss key<rp>(</rp><rt>ミスキー</rt><rp>)</rp> b</ruby> c</p>'), 'a Miss key(ミスキー) b c');
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp> b</ruby> c</p>'), 'a Misskey(ミス キー) b c');
+ assert.deepStrictEqual(
+ mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'),
+ 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b'
+ );
+ });
+
+ test('ruby with other inline tags', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby><strong>Misskey</strong><rp>(</rp><rt>ミスキー</rt><rp>)</rp> b</ruby> c</p>'), 'a **Misskey**(ミスキー) b c');
+ });
+
test('mention', () => {
assert.deepStrictEqual(mfmService.fromHtml('<p>a <a href="https://example.com/@user" class="u-url mention">@user</a> d</p>'), 'a @user@example.com d');
});
diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts
index 5401dd74d8..fee4acb305 100644
--- a/packages/backend/test/unit/SystemWebhookService.ts
+++ b/packages/backend/test/unit/SystemWebhookService.ts
@@ -314,9 +314,10 @@ describe('SystemWebhookService', () => {
isActive: true,
on: ['abuseReport'],
});
- await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
- expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook);
});
test('非アクティブなWebhookはキューに追加されない', async () => {
@@ -324,7 +325,7 @@ describe('SystemWebhookService', () => {
isActive: false,
on: ['abuseReport'],
});
- await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
});
@@ -338,11 +339,49 @@ describe('SystemWebhookService', () => {
isActive: true,
on: ['abuseReportResolved'],
});
- await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any);
- await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
});
+
+ test('混在した時、有効かつ許可されたイベント種別のみ', async () => {
+ const webhook1 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+ const webhook2 = await createWebhook({
+ isActive: true,
+ on: ['abuseReportResolved'],
+ });
+ const webhook3 = await createWebhook({
+ isActive: false,
+ on: ['abuseReport'],
+ });
+ const webhook4 = await createWebhook({
+ isActive: false,
+ on: ['abuseReportResolved'],
+ });
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
+
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1);
+ });
+
+ test('除外指定した場合は送信されない', async () => {
+ const webhook1 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+ const webhook2 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any, { excludes: [webhook2.id] });
+
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1);
+ });
});
describe('fetchActiveSystemWebhooks', () => {
diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts
index 0e88835a02..db8f96df28 100644
--- a/packages/backend/test/unit/UserWebhookService.ts
+++ b/packages/backend/test/unit/UserWebhookService.ts
@@ -1,4 +1,3 @@
-
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
@@ -71,7 +70,7 @@ describe('UserWebhookService', () => {
LoggerService,
GlobalEventService,
{
- provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }),
+ provide: QueueService, useFactory: () => ({ userWebhookDeliver: jest.fn() }),
},
],
})
@@ -242,4 +241,92 @@ describe('UserWebhookService', () => {
});
});
});
+
+ describe('アプリを毎回作り直す必要があるグループ', () => {
+ beforeEach(async () => {
+ await beforeAllImpl();
+ await beforeEachImpl();
+ });
+
+ afterEach(async () => {
+ await afterEachImpl();
+ await afterAllImpl();
+ });
+
+ describe('enqueueUserWebhook', () => {
+ test('キューに追加成功', async () => {
+ const webhook = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook);
+ });
+
+ test('非アクティブなWebhookはキューに追加されない', async () => {
+ const webhook = await createWebhook({
+ active: false,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('未許可のイベント種別が渡された場合はWebhookはキューに追加されない', async () => {
+ const webhook1 = await createWebhook({
+ active: true,
+ on: [],
+ });
+ const webhook2 = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook1.userId, 'renote', { foo: 'bar' } as any);
+ await service.enqueueUserWebhook(webhook2.userId, 'renote', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('ユーザIDが異なるWebhookはキューに追加されない', async () => {
+ const webhook = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(idService.gen(), 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('混在した時、有効かつ許可されたイベント種別のみ', async () => {
+ const userId = root.id;
+ const webhook1 = await createWebhook({
+ userId,
+ active: true,
+ on: ['note'],
+ });
+ const webhook2 = await createWebhook({
+ userId,
+ active: true,
+ on: ['renote'],
+ });
+ const webhook3 = await createWebhook({
+ userId,
+ active: false,
+ on: ['note'],
+ });
+ const webhook4 = await createWebhook({
+ userId,
+ active: false,
+ on: ['renote'],
+ });
+ await service.enqueueUserWebhook(userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook1);
+ });
+ });
+ });
});
diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
index 1506283a3c..d96e6b916a 100644
--- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -18,6 +18,7 @@ import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
import { EmailService } from '@/core/EmailService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
+import { SystemWebhookEventType } from '@/models/SystemWebhook.js';
const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
@@ -334,9 +335,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]);
await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 });
- expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2);
+ // typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する.
+ // ここでは呼び出されているか、typeが正しいかのみを確認する
+ expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+ expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsWarning');
});
});
@@ -372,8 +374,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]);
await service.notifyChangeToInvitationOnly();
+ // typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する.
+ // ここでは呼び出されているか、typeが正しいかのみを確認する
expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2);
+ expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsInvitationOnlyChanged');
});
});
});