/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { entities } from 'cherrypick-js'; import { beforeEach, describe, test } from '@jest/globals'; import Fastify from 'fastify'; import { api, randomString, role, signup, startJobQueue, UserToken } from '../../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; const WEBHOOK_HOST = 'http://localhost:15080'; const WEBHOOK_PORT = 15080; process.env.NODE_ENV = 'test'; describe('[シナリオ] ユーザ通報', () => { let queue: INestApplicationContext; let admin: entities.SignupResponse; let alice: entities.SignupResponse; let bob: entities.SignupResponse; type SystemWebhookPayload = { server: string; hookId: string; eventId: string; createdAt: string; type: string; body: any; } // ------------------------------------------------------------------------------------------- async function captureWebhook(postAction: () => Promise): Promise { const fastify = Fastify(); let timeoutHandle: NodeJS.Timeout | null = null; const result = await new Promise(async (resolve, reject) => { fastify.all('/', async (req, res) => { timeoutHandle && clearTimeout(timeoutHandle); const body = JSON.stringify(req.body); res.status(200).send('ok'); await fastify.close(); resolve(body); }); await fastify.listen({ port: WEBHOOK_PORT }); timeoutHandle = setTimeout(async () => { await fastify.close(); reject(new Error('timeout')); }, 3000); try { await postAction(); } catch (e) { await fastify.close(); reject(e); } }); await fastify.close(); return JSON.parse(result) as T; } async function createSystemWebhook(args?: Partial, credential?: UserToken): Promise { const res = await api( 'admin/system-webhook/create', { isActive: true, name: randomString(), on: ['abuseReport'], url: WEBHOOK_HOST, secret: randomString(), ...args, }, credential ?? admin, ); return res.body; } async function createAbuseReportNotificationRecipient(args?: Partial, credential?: UserToken): Promise { const res = await api( 'admin/abuse-report/notification-recipient/create', { isActive: true, name: randomString(), method: 'webhook', ...args, }, credential ?? admin, ); return res.body; } async function createAbuseReport(args?: Partial, credential?: UserToken): Promise { const res = await api( 'users/report-abuse', { userId: alice.id, comment: randomString(), ...args, }, credential ?? admin, ); return res.body; } async function resolveAbuseReport(args?: Partial, credential?: UserToken): Promise { const res = await api( 'admin/resolve-abuse-user-report', { reportId: admin.id, ...args, }, credential ?? admin, ); return res.body; } // ------------------------------------------------------------------------------------------- beforeAll(async () => { queue = await startJobQueue(); admin = await signup({ username: 'admin' }); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); await role(admin, { isAdministrator: true }); }, 1000 * 60 * 2); afterAll(async () => { await queue.close(); }); // ------------------------------------------------------------------------------------------- describe('SystemWebhook', () => { beforeEach(async () => { const webhooks = await api('admin/system-webhook/list', {}, admin); for (const webhook of webhooks.body) { await api('admin/system-webhook/delete', { id: webhook.id }, admin); } }); test('通報を受けた -> abuseReportが送出される', async () => { const webhook = await createSystemWebhook({ on: ['abuseReport'], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }); console.log(JSON.stringify(webhookBody, null, 2)); expect(webhookBody.hookId).toBe(webhook.id); expect(webhookBody.type).toBe('abuseReport'); expect(webhookBody.body.targetUserId).toBe(alice.id); expect(webhookBody.body.reporterId).toBe(bob.id); expect(webhookBody.body.comment).toBe(abuse.comment); }); test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが送出される', async () => { const webhook = await createSystemWebhook({ on: ['abuseReport', 'abuseReportResolved'], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }); console.log(JSON.stringify(webhookBody1, null, 2)); expect(webhookBody1.hookId).toBe(webhook.id); expect(webhookBody1.type).toBe('abuseReport'); expect(webhookBody1.body.targetUserId).toBe(alice.id); expect(webhookBody1.body.reporterId).toBe(bob.id); expect(webhookBody1.body.assigneeId).toBeNull(); expect(webhookBody1.body.resolved).toBe(false); expect(webhookBody1.body.comment).toBe(abuse.comment); // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: webhookBody1.body.id, forward: false, }, admin); }); console.log(JSON.stringify(webhookBody2, null, 2)); expect(webhookBody2.hookId).toBe(webhook.id); expect(webhookBody2.type).toBe('abuseReportResolved'); expect(webhookBody2.body.targetUserId).toBe(alice.id); expect(webhookBody2.body.reporterId).toBe(bob.id); expect(webhookBody2.body.assigneeId).toBe(admin.id); expect(webhookBody2.body.resolved).toBe(true); expect(webhookBody2.body.comment).toBe(abuse.comment); }); test('通報を受けた -> abuseReportが未許可の場合は送出されない', async () => { const webhook = await createSystemWebhook({ on: [], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }).catch(e => e.message); expect(webhookBody).toBe('timeout'); }); test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが送出される', async () => { const webhook = await createSystemWebhook({ on: ['abuseReportResolved'], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }).catch(e => e.message); expect(webhookBody1).toBe('timeout'); const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id; // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, forward: false, }, admin); }); console.log(JSON.stringify(webhookBody2, null, 2)); expect(webhookBody2.hookId).toBe(webhook.id); expect(webhookBody2.type).toBe('abuseReportResolved'); expect(webhookBody2.body.targetUserId).toBe(alice.id); expect(webhookBody2.body.reporterId).toBe(bob.id); expect(webhookBody2.body.assigneeId).toBe(admin.id); expect(webhookBody2.body.resolved).toBe(true); expect(webhookBody2.body.comment).toBe(abuse.comment); }); test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => { const webhook = await createSystemWebhook({ on: ['abuseReport'], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }); console.log(JSON.stringify(webhookBody1, null, 2)); expect(webhookBody1.hookId).toBe(webhook.id); expect(webhookBody1.type).toBe('abuseReport'); expect(webhookBody1.body.targetUserId).toBe(alice.id); expect(webhookBody1.body.reporterId).toBe(bob.id); expect(webhookBody1.body.assigneeId).toBeNull(); expect(webhookBody1.body.resolved).toBe(false); expect(webhookBody1.body.comment).toBe(abuse.comment); // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: webhookBody1.body.id, forward: false, }, admin); }).catch(e => e.message); expect(webhookBody2).toBe('timeout'); }); test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => { const webhook = await createSystemWebhook({ on: [], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }).catch(e => e.message); expect(webhookBody1).toBe('timeout'); const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id; // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, forward: false, }, admin); }).catch(e => e.message); expect(webhookBody2).toBe('timeout'); }); test('通報を受けた -> Webhookが無効の場合は送出されない', async () => { const webhook = await createSystemWebhook({ on: ['abuseReport', 'abuseReportResolved'], isActive: false, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }).catch(e => e.message); expect(webhookBody1).toBe('timeout'); const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id; // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, forward: false, }, admin); }).catch(e => e.message); expect(webhookBody2).toBe('timeout'); }); test('通報を受けた -> 通知設定が無効の場合は送出されない', async () => { const webhook = await createSystemWebhook({ on: ['abuseReport', 'abuseReportResolved'], isActive: true, }); await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id, isActive: false }); // 通報(bob -> alice) const abuse = { userId: alice.id, comment: randomString(), }; const webhookBody1 = await captureWebhook(async () => { await createAbuseReport(abuse, bob); }).catch(e => e.message); expect(webhookBody1).toBe('timeout'); const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id; // 解決 const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, forward: false, }, admin); }).catch(e => e.message); expect(webhookBody2).toBe('timeout'); }); }); });