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

157 lines
4.5 KiB
TypeScript

import { readFileSync } from 'fs';
import { join } from 'path';
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import * as request from 'supertest';
import { JwtAuthGuard } from '../../../../src/admin/auth/jwt-auth.guard';
import { QqbotPluginPlatformController } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.controller';
import { QqbotPluginPlatformModule } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.module';
import { QqbotPluginPlatformService } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.service';
import {
QQBOT_PLUGIN_PLATFORM_ENTITIES,
QqbotPlugin,
QqbotPluginAccountBinding,
QqbotPluginAsset,
QqbotPluginConfig,
QqbotPluginEventHandler,
QqbotPluginInstallation,
QqbotPluginOperation,
QqbotPluginRuntimeEvent,
QqbotPluginVersion,
} from '../../../../src/modules/qqbot/plugin-platform/persistence';
import {
collectControllerRoutes,
routeKey,
} from '../../../helpers/controller-route.helper';
const createRepositoryMock = () => ({
find: jest.fn(async () => []),
save: jest.fn(async (value) => value),
update: jest.fn(async () => ({ affected: 1 })),
});
const createManifest = () => ({
assets: [],
configSchema: {
type: 'object',
},
entry: 'src/index.ts',
events: [],
minApiSdkVersion: '1.0.0',
name: 'Demo Plugin',
operations: [
{
handlerName: 'echo',
key: 'demo-plugin.echo',
name: 'Echo',
permissions: ['qqbot.send'],
timeoutMs: 3000,
},
],
permissions: ['qqbot.send'],
pluginKey: 'demo-plugin',
runtime: {
maxConcurrency: 1,
memoryMb: 128,
timeoutMs: 5000,
workerType: 'node-worker',
},
version: '0.1.0',
});
describe('QQBot plugin platform API contract', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [QqbotPluginPlatformController],
providers: [
QqbotPluginPlatformService,
...[
QqbotPlugin,
QqbotPluginVersion,
QqbotPluginInstallation,
QqbotPluginOperation,
QqbotPluginEventHandler,
QqbotPluginAccountBinding,
QqbotPluginConfig,
QqbotPluginAsset,
QqbotPluginRuntimeEvent,
].map((entity) => ({
provide: getRepositoryToken(entity),
useFactory: createRepositoryMock,
})),
],
})
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true })
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterEach(async () => {
await app.close();
});
it('registers plugin-platform as a first-class AppModule import', () => {
const source = readFileSync(
join(__dirname, '../../../../src/app.module.ts'),
'utf8',
);
expect(source).toContain('QqbotPluginPlatformModule');
});
it('exposes plugin-platform management routes', () => {
expect(routeKey).toBeDefined();
expect(
collectControllerRoutes([QqbotPluginPlatformController]).map(routeKey),
).toEqual(
expect.arrayContaining([
'GET /qqbot/plugin-platform/installations',
'POST /qqbot/plugin-platform/upload',
'POST /qqbot/plugin-platform/validate',
'POST /qqbot/plugin-platform/install',
'POST /qqbot/plugin-platform/install-local',
'POST /qqbot/plugin-platform/enable',
'POST /qqbot/plugin-platform/disable',
'POST /qqbot/plugin-platform/upgrade',
'POST /qqbot/plugin-platform/uninstall',
'POST /qqbot/plugin-platform/config',
'GET /qqbot/plugin-platform/runtime-events',
'GET /qqbot/plugin-platform/account-bindings',
]),
);
});
it('validates manifests through the real HTTP wrapper', async () => {
const response = await request(app.getHttpServer())
.post('/qqbot/plugin-platform/validate')
.send({
manifest: createManifest(),
})
.expect(200);
expect(response.body).toMatchObject({
code: 200,
data: {
manifest: {
pluginKey: 'demo-plugin',
runtime: {
timeoutMs: 5000,
},
},
valid: true,
},
});
});
it('keeps TypeORM entity registration aligned with the persistence contract', () => {
expect(QqbotPluginPlatformModule).toBeDefined();
expect(QQBOT_PLUGIN_PLATFORM_ENTITIES).toHaveLength(9);
});
});