feature: ユーザ作成時にSystemWebhookを発信できるようにする (#14321)
* feature: ユーザ作成時にSystemWebhookを発信できるようにする * fix CHANGELOG.md
This commit is contained in:
parent
0f0660d49e
commit
72bc789746
12 changed files with 237 additions and 62 deletions
|
@ -5,65 +5,24 @@
|
|||
|
||||
import { entities } from 'misskey-js';
|
||||
import { beforeEach, describe, test } from '@jest/globals';
|
||||
import Fastify from 'fastify';
|
||||
import { api, randomString, role, signup, startJobQueue, UserToken } from '../../utils.js';
|
||||
import {
|
||||
api,
|
||||
captureWebhook,
|
||||
randomString,
|
||||
role,
|
||||
signup,
|
||||
startJobQueue,
|
||||
UserToken,
|
||||
WEBHOOK_HOST,
|
||||
} 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<T = SystemWebhookPayload>(postAction: () => Promise<void>): Promise<T> {
|
||||
const fastify = Fastify();
|
||||
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const result = await new Promise<string>(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<entities.AdminSystemWebhookCreateRequest>, credential?: UserToken): Promise<entities.AdminSystemWebhookCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/system-webhook/create',
|
||||
|
|
130
packages/backend/test/e2e/synalio/user-create.ts
Normal file
130
packages/backend/test/e2e/synalio/user-create.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { entities } from 'misskey-js';
|
||||
import { beforeEach, describe, test } from '@jest/globals';
|
||||
import {
|
||||
api,
|
||||
captureWebhook,
|
||||
randomString,
|
||||
role,
|
||||
signup,
|
||||
startJobQueue,
|
||||
UserToken,
|
||||
WEBHOOK_HOST,
|
||||
} from '../../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
|
||||
describe('[シナリオ] ユーザ作成', () => {
|
||||
let queue: INestApplicationContext;
|
||||
let admin: entities.SignupResponse;
|
||||
|
||||
async function createSystemWebhook(args?: Partial<entities.AdminSystemWebhookCreateRequest>, credential?: UserToken): Promise<entities.AdminSystemWebhookCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/system-webhook/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['userCreated'],
|
||||
url: WEBHOOK_HOST,
|
||||
secret: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
queue = await startJobQueue();
|
||||
admin = await signup({ username: 'admin' });
|
||||
|
||||
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('ユーザが作成された -> userCreatedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['userCreated'],
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
});
|
||||
|
||||
// webhookの送出後にいろいろやってるのでちょっと待つ必要がある
|
||||
await setTimeout(2000);
|
||||
|
||||
console.log(alice);
|
||||
console.log(JSON.stringify(webhookBody, null, 2));
|
||||
|
||||
expect(webhookBody.hookId).toBe(webhook.id);
|
||||
expect(webhookBody.type).toBe('userCreated');
|
||||
|
||||
const body = webhookBody.body as entities.UserLite;
|
||||
expect(alice.id).toBe(body.id);
|
||||
expect(alice.name).toBe(body.name);
|
||||
expect(alice.username).toBe(body.username);
|
||||
expect(alice.host).toBe(body.host);
|
||||
expect(alice.avatarUrl).toBe(body.avatarUrl);
|
||||
expect(alice.avatarBlurhash).toBe(body.avatarBlurhash);
|
||||
expect(alice.avatarDecorations).toEqual(body.avatarDecorations);
|
||||
expect(alice.isBot).toBe(body.isBot);
|
||||
expect(alice.isCat).toBe(body.isCat);
|
||||
expect(alice.instance).toEqual(body.instance);
|
||||
expect(alice.emojis).toEqual(body.emojis);
|
||||
expect(alice.onlineStatus).toBe(body.onlineStatus);
|
||||
expect(alice.badgeRoles).toEqual(body.badgeRoles);
|
||||
});
|
||||
|
||||
test('ユーザ作成 -> userCreatedが未許可の場合は送出されない', async () => {
|
||||
await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
expect(alice.id).not.toBeNull();
|
||||
});
|
||||
|
||||
test('ユーザ作成 -> Webhookが無効の場合は送出されない', async () => {
|
||||
await createSystemWebhook({
|
||||
on: ['userCreated'],
|
||||
isActive: false,
|
||||
});
|
||||
|
||||
let alice: any = null;
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
expect(alice.id).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,13 +12,14 @@ import WebSocket, { ClientOptions } from 'ws';
|
|||
import fetch, { File, RequestInit, type Headers } from 'node-fetch';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||
import { type Response } from 'node-fetch';
|
||||
import Fastify from 'fastify';
|
||||
import { entities } from '../src/postgres.js';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
import { type Response } from 'node-fetch';
|
||||
import { ApiError } from "@/server/api/error.js";
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
||||
|
||||
|
@ -27,11 +28,23 @@ export interface UserToken {
|
|||
bearer?: boolean;
|
||||
}
|
||||
|
||||
export type SystemWebhookPayload = {
|
||||
server: string;
|
||||
hookId: string;
|
||||
eventId: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
body: any;
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
export const port = config.port;
|
||||
export const origin = config.url;
|
||||
export const host = new URL(config.url).host;
|
||||
|
||||
export const WEBHOOK_HOST = 'http://localhost:15080';
|
||||
export const WEBHOOK_PORT = 15080;
|
||||
|
||||
export const cookie = (me: UserToken): string => {
|
||||
return `token=${me.token};`;
|
||||
};
|
||||
|
@ -645,3 +658,37 @@ export async function sendEnvResetRequest() {
|
|||
export function castAsError(obj: Record<string, unknown>): { error: ApiError } {
|
||||
return obj as { error: ApiError };
|
||||
}
|
||||
|
||||
export async function captureWebhook<T = SystemWebhookPayload>(postAction: () => Promise<void>, port = WEBHOOK_PORT): Promise<T> {
|
||||
const fastify = Fastify();
|
||||
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const result = await new Promise<string>(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 });
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue