kt-template-online-api/test/modules/qqbot/plugin-platform/plugin-task-api-contract.spec.ts

149 lines
4.6 KiB
TypeScript

import { QqbotPluginPlatformTaskController } from '../../../../src/modules/qqbot/plugin-platform/contract/plugin-platform-task.controller';
import { ToolsService } from '../../../../src/common';
import { QqbotPluginTaskService } from '../../../../src/modules/qqbot/plugin-platform/application/task';
import {
collectControllerRoutes,
routeKey,
} from '../../../helpers/controller-route.helper';
/**
* 创建 QQBot 插件平台对象或配置。
*/
const createRepositoryMock = () => ({
findAndCount: jest.fn(async () => [[], 0]),
findOne: jest.fn(async () => null),
save: jest.fn(async (value) => value),
update: jest.fn(async () => ({ affected: 1 })),
});
describe('QQBot plugin task API contract', () => {
it('exposes task management routes under plugin-platform ownership', () => {
expect(
collectControllerRoutes([QqbotPluginPlatformTaskController]).map(
routeKey,
),
).toEqual(
expect.arrayContaining([
'GET /qqbot/plugin-platform/tasks/page',
'GET /qqbot/plugin-platform/tasks/:id',
'POST /qqbot/plugin-platform/tasks/:id/enable',
'POST /qqbot/plugin-platform/tasks/:id/disable',
'POST /qqbot/plugin-platform/tasks/:id/cron',
'POST /qqbot/plugin-platform/tasks/:id/run',
'GET /qqbot/plugin-platform/tasks/:id/runs',
]),
);
});
it('applies task page filters and safe pagination defaults', async () => {
const taskRepository = createRepositoryMock();
const runRepository = createRepositoryMock();
const pluginRepository = createRepositoryMock();
pluginRepository.findOne.mockResolvedValue({ id: 'plugin-1' });
const service = new QqbotPluginTaskService(
taskRepository as any,
runRepository as any,
pluginRepository as any,
new ToolsService(),
);
const page = await service.pageTasks({
enabled: 'true',
pageNo: 'bad',
pageSize: 'also-bad',
pluginKey: 'bangdream',
status: 'scheduled',
taskKey: 'bangdream.bestdori.sync-main-data',
});
expect(pluginRepository.findOne).toHaveBeenCalledWith({
where: { pluginKey: 'bangdream' },
});
expect(taskRepository.findAndCount).toHaveBeenCalledWith({
order: { createTime: 'DESC' },
skip: 0,
take: 10,
where: {
enabled: true,
pluginId: 'plugin-1',
runtimeStatus: 'scheduled',
taskKey: 'bangdream.bestdori.sync-main-data',
},
});
expect(page).toMatchObject({ pageNo: 1, pageSize: 10, total: 0 });
});
it('applies task run status, trigger, and time-range filters', async () => {
const taskRepository = createRepositoryMock();
const runRepository = createRepositoryMock();
const pluginRepository = createRepositoryMock();
const service = new QqbotPluginTaskService(
taskRepository as any,
runRepository as any,
pluginRepository as any,
new ToolsService(),
);
await service.pageTaskRuns('task-1', {
endTime: '2026-06-17 23:59:59',
pageNo: 2,
pageSize: 20,
startTime: '2026-06-17 00:00:00',
status: 'success',
triggerType: 'schedule',
});
expect(runRepository.findAndCount).toHaveBeenCalledWith({
order: { createTime: 'DESC' },
skip: 20,
take: 20,
where: {
createTime: expect.any(Object),
status: 'success',
taskId: 'task-1',
triggerType: 'schedule',
},
});
});
it('clears stale next run time when disabling one task', async () => {
const taskRepository = createRepositoryMock();
const runRepository = createRepositoryMock();
const pluginRepository = createRepositoryMock();
const scheduler = {
removeTaskScheduler: jest.fn(async () => undefined),
};
const nextRunAt = new Date('2026-06-17T06:00:00.000Z');
taskRepository.findOne.mockResolvedValue({
cronExpression: '0 */6 * * *',
enabled: true,
id: 'task-1',
nextRunAt,
runtimeStatus: 'scheduled',
});
const service = new QqbotPluginTaskService(
taskRepository as any,
runRepository as any,
pluginRepository as any,
new ToolsService(),
scheduler as any,
);
const task = await service.disableTask('task-1');
expect(taskRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
enabled: false,
nextRunAt: null,
runtimeStatus: 'disabled',
}),
);
expect(scheduler.removeTaskScheduler).toHaveBeenCalledWith('task-1');
expect(task).toMatchObject({
enabled: false,
nextRunAt: null,
runtimeStatus: 'disabled',
});
});
});