/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; import { randomString } from '../utils.js'; import { MiUser } from '@/models/User.js'; import { MiWebhook, UsersRepository, WebhooksRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { GlobalModule } from '@/GlobalModule.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 { UserWebhookService } from '@/core/UserWebhookService.js'; describe('UserWebhookService', () => { let app: TestingModule; let service: UserWebhookService; // -------------------------------------------------------------------------------------- let usersRepository: UsersRepository; let userWebhooksRepository: WebhooksRepository; let idService: IdService; let queueService: jest.Mocked; // -------------------------------------------------------------------------------------- let root: MiUser; // -------------------------------------------------------------------------------------- async function createUser(data: Partial = {}) { return await usersRepository .insert({ id: idService.gen(), ...data, }) .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); } async function createWebhook(data: Partial = {}) { return userWebhooksRepository .insert({ id: idService.gen(), name: randomString(), on: ['mention'], url: 'https://example.com', secret: randomString(), userId: root.id, ...data, }) .then(x => userWebhooksRepository.findOneByOrFail(x.identifiers[0])); } // -------------------------------------------------------------------------------------- async function beforeAllImpl() { app = await Test .createTestingModule({ imports: [ GlobalModule, ], providers: [ UserWebhookService, IdService, LoggerService, GlobalEventService, { provide: QueueService, useFactory: () => ({ userWebhookDeliver: jest.fn() }), }, ], }) .compile(); usersRepository = app.get(DI.usersRepository); userWebhooksRepository = app.get(DI.webhooksRepository); service = app.get(UserWebhookService); idService = app.get(IdService); queueService = app.get(QueueService) as jest.Mocked; 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 userWebhooksRepository.delete({}); } // -------------------------------------------------------------------------------------- describe('アプリを毎回作り直す必要のないグループ', () => { beforeAll(beforeAllImpl); afterAll(afterAllImpl); beforeEach(beforeEachImpl); afterEach(afterEachImpl); describe('fetchSystemWebhooks', () => { test('フィルタなし', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks(); expect(fetchedWebhooks).toEqual([webhook1, webhook2, webhook3, webhook4]); }); test('activeのみ', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks({ isActive: true }); expect(fetchedWebhooks).toEqual([webhook1, webhook3]); }); test('特定のイベントのみ', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'] }); expect(fetchedWebhooks).toEqual([webhook1, webhook2]); }); test('activeな特定のイベントのみ', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'], isActive: true }); expect(fetchedWebhooks).toEqual([webhook1]); }); test('ID指定', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id] }); expect(fetchedWebhooks).toEqual([webhook1, webhook4]); }); test('ID指定(他条件とANDになるか見たい)', async () => { const webhook1 = await createWebhook({ active: true, on: ['mention'], }); const webhook2 = await createWebhook({ active: false, on: ['mention'], }); const webhook3 = await createWebhook({ active: true, on: ['reply'], }); const webhook4 = await createWebhook({ active: false, on: [], }); const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id], isActive: false }); expect(fetchedWebhooks).toEqual([webhook4]); }); }); }); 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); }); }); }); });