import { HttpException, HttpStatus, INestApplication } from '@nestjs/common';
import { APP_FILTER, APP_INTERCEPTOR, Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { Readable } from 'stream';
import { AppController } from '../src/app.controller';
import { AppService } from '../src/app.service';
import { AdminAuthService } from '../src/modules/admin/identity/auth/admin-auth.service';
import { JwtAuthGuard } from '../src/modules/admin/identity/auth/jwt-auth.guard';
import { ComponentController } from '../src/modules/admin/platform-config/component/component.controller';
import { ComponentService } from '../src/modules/admin/platform-config/component/component.service';
import { DictController } from '../src/modules/admin/platform-config/dict/dict.controller';
import { DictService } from '../src/modules/admin/platform-config/dict/dict.service';
import { SystemLogController } from '../src/modules/admin/platform-config/system-log/system-log.controller';
import { SystemLogService } from '../src/modules/admin/platform-config/system-log/system-log.service';
import {
ApiExceptionFilter,
IS_PUBLIC_KEY,
SaveBodyInterceptor,
ToolsService,
} from '../src/common';
import { BlogArticleService } from '../src/modules/blog/application/blog-article.service';
import { BlogTermService } from '../src/modules/blog/application/blog-term.service';
import { BlogThemeConfigService } from '../src/modules/blog/application/blog-theme-config.service';
import { BlogArticleController } from '../src/modules/blog/contract/blog-article.controller';
import { BlogTermController } from '../src/modules/blog/contract/blog-term.controller';
import { BlogThemeConfigController } from '../src/modules/blog/contract/blog-theme-config.controller';
import { MinioClientService } from '../src/modules/asset/application/asset-minio.service';
import { MinioClientController } from '../src/modules/asset/contract/asset-minio.controller';
import { WordpressService } from '../src/modules/wordpress/application/wordpress.service';
import { WordpressArticleController } from '../src/modules/wordpress/contract/wordpress-article.controller';
import { WordpressAuthController } from '../src/modules/wordpress/contract/wordpress-auth.controller';
import { WordpressCategoryController } from '../src/modules/wordpress/contract/wordpress-category.controller';
import { WordpressTagController } from '../src/modules/wordpress/contract/wordpress-tag.controller';
import { WordpressThemeController } from '../src/modules/wordpress/contract/wordpress-theme.controller';
import { PinoLogger } from 'nestjs-pino';
import {
collectControllerRoutes,
routeKey,
} from './helpers/controller-route.helper';
import type { RouteTestCase } from './test.types';
const component = {
id: '2041739550026043392',
name: '基础折线图',
type: 1,
componentType: 1,
typeMsg: '图表',
componentTypeMsg: '折线图',
image: '',
template: '{}',
createTime: '2026-05-13 10:30:00',
updateTime: '2026-05-13 10:30:00',
is_deleted: false,
};
const dictOptions = [
{
label: '图表',
value: 1,
},
];
const chartOptions = [
{
label: '折线图',
value: 1,
},
];
const dictItem = {
childrenCode: 'CHART',
dictCode: 'COMPONENT_TYPE',
id: '2041700000000300001',
label: '图表',
sort: 1,
status: 1,
value: '1',
};
const dictTreeItem = {
...dictItem,
children: [
{
childrenCode: null,
dictCode: 'CHART',
id: '2041700000000300002',
label: '折线图',
sort: 1,
status: 1,
treeKey: '2041700000000300001/2041700000000300002',
value: '1',
},
],
treeKey: '2041700000000300001',
};
const dictGroupItem = {
dictCode: 'COMPONENT_TYPE',
id: 'dict-code:COMPONENT_TYPE',
itemCount: 2,
label: 'COMPONENT_TYPE',
value: 'COMPONENT_TYPE',
};
const systemLogItem = {
context: 'ApiExceptionFilter',
durationMs: 12,
hostname: 'kt-template-online-api',
id: '1760000000000000000-0-0',
level: 'error',
message: 'Loki smoke log',
method: 'GET',
path: '/system/logs',
raw: '{"level":50,"msg":"Loki smoke log"}',
requestId: 'request-id',
statusCode: 500,
timestamp: '2026-06-04 08:00:00',
timestampNs: '1760000000000000000',
};
const uploadResult = {
bucketName: 'kt-template-online',
objectName: 'uploads/demo.txt',
etag: 'etag',
size: 4,
mimeType: 'text/plain',
url: 'http://127.0.0.1:9000/kt-template-online/uploads/demo.txt',
};
const objectStat = {
name: 'uploads/demo.txt',
size: 4,
etag: 'etag',
lastModified: '2026-05-13 10:30:00',
};
const wordpressAuthContext = {
authorization: 'Bearer wordpress-client-token',
};
const wordpressUser = {
id: 1,
name: 'WordPress Admin',
slug: 'wordpress-admin',
};
const wordpressLoginResult = {
auth: {
nonce: 'wordpress-rest-nonce',
type: 'cookie',
},
cookie: 'wordpress_logged_in_demo=1',
user: wordpressUser,
};
const wordpressArticle = {
id: 1,
title: {
rendered: 'WordPress 文章',
},
status: 'draft',
};
const blogArticle = {
authorName: 'KwiTsukasa',
categories: ['技术'],
categoriesResolved: [{ name: '技术', slug: 'tech' }],
contentHtml: '
本地文章
',
contentMarkdown: '# 本地文章',
id: '2041800000000000001',
slug: 'local-article',
status: 'draft',
tags: ['Milkdown'],
tagsResolved: [{ name: 'Milkdown', slug: 'milkdown' }],
title: '本地文章',
};
const wordpressTerm = {
id: 1,
name: 'WordPress 分类',
slug: 'wordpress-category',
};
const blogTerm = {
count: 1,
description: '本地分类描述',
id: '2041800000000000100',
name: '技术',
slug: 'tech',
};
const wordpressThemeConfig = {
bodyClass: ['home', 'blog', 'wp-theme-argon'],
darkmodeAutoSwitch: 'alwayson',
enableCustomThemeColor: true,
htmlClass: [
'triple-column',
'immersion-color',
'toolbar-blur',
'article-header-style-default',
],
site: {
description: '',
home: 'https://blog.kwitsukasa.top',
title: 'KwiTsukasa的小站',
url: 'https://blog.kwitsukasa.top',
},
themeCardRadius: 4,
themeColor: '#c3a1ed',
themeColorRgb: '195,161,237',
themeVersion: '1.3.5',
};
const componentServiceMock = {
all: jest.fn(),
page: jest.fn(),
save: jest.fn(),
remove: jest.fn(),
update: jest.fn(),
find: jest.fn(),
};
const authServiceMock = {
currentUser: jest.fn(),
};
const unauthorizedException = () =>
new HttpException(
{
msg: 'Unauthorized Exception',
err: 'Unauthorized Exception',
},
HttpStatus.UNAUTHORIZED,
);
const dictServiceMock = {
codes: jest.fn(),
getDictCodeOptions: jest.fn(),
getDictByKey: jest.fn(),
getComponentDictByType: jest.fn(),
groups: jest.fn(),
page: jest.fn(),
remove: jest.fn(),
save: jest.fn(),
toggle: jest.fn(),
tree: jest.fn(),
update: jest.fn(),
};
const systemLogServiceMock = {
levels: jest.fn(),
page: jest.fn(),
status: jest.fn(),
summary: jest.fn(),
};
const pinoLoggerMock = {
error: jest.fn(),
setContext: jest.fn(),
warn: jest.fn(),
};
const minioServiceMock = {
checkConnection: jest.fn(),
ensureBucket: jest.fn(),
uploadObject: jest.fn(),
listObjects: jest.fn(),
getPresignedUrl: jest.fn(),
getObject: jest.fn(),
removeObject: jest.fn(),
};
const blogArticleServiceMock = {
page: jest.fn(),
detail: jest.fn(),
save: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
categoryOptions: jest.fn(),
tagOptions: jest.fn(),
publicList: jest.fn(),
publicDetail: jest.fn(),
importFromWordpress: jest.fn(),
};
const blogTermServiceMock = {
detail: jest.fn(),
options: jest.fn(),
page: jest.fn(),
remove: jest.fn(),
save: jest.fn(),
update: jest.fn(),
};
const blogThemeConfigServiceMock = {
importFromWordpress: jest.fn(),
publicConfig: jest.fn(),
save: jest.fn(),
};
const wordpressServiceMock = {
getAuthContext: jest.fn(),
loginWithConfiguredAdmin: jest.fn(),
setAuthCookie: jest.fn(),
clearAuthCookie: jest.fn(),
checkAuth: jest.fn(),
articleList: jest.fn(),
articleDetail: jest.fn(),
articleSave: jest.fn(),
articleUpdate: jest.fn(),
articleRemove: jest.fn(),
publicArticleList: jest.fn(),
publicArticleDetail: jest.fn(),
tagList: jest.fn(),
tagDetail: jest.fn(),
tagSave: jest.fn(),
tagUpdate: jest.fn(),
tagRemove: jest.fn(),
categoryList: jest.fn(),
categoryDetail: jest.fn(),
categorySave: jest.fn(),
categoryUpdate: jest.fn(),
categoryRemove: jest.fn(),
themeConfig: jest.fn(),
};
const controllerClasses = [
AppController,
ComponentController,
DictController,
SystemLogController,
BlogArticleController,
BlogTermController,
BlogThemeConfigController,
MinioClientController,
WordpressAuthController,
WordpressArticleController,
WordpressTagController,
WordpressCategoryController,
WordpressThemeController,
];
const controllerRoutes = collectControllerRoutes(controllerClasses);
const routeTestCases: Record = {
'GET /': async (server) => {
await request(server).get('/').expect(301).expect('Location', '/api#/');
},
'GET /component/allList': async (server) => {
componentServiceMock.all.mockResolvedValue([component]);
const response = await request(server)
.get('/component/allList')
.expect(200);
expect(componentServiceMock.all).toHaveBeenCalledWith();
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [component],
});
},
'GET /component/list': async (server) => {
componentServiceMock.page.mockResolvedValue({
list: [component],
total: 1,
});
const response = await request(server)
.get('/component/list')
.query({
pageNo: 1,
pageSize: 10,
name: '折线',
})
.expect(200);
expect(componentServiceMock.page).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '10',
name: '折线',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
list: [component],
total: 1,
},
});
},
'POST /component/save': async (server) => {
componentServiceMock.save.mockResolvedValue({
id: component.id,
});
const response = await request(server)
.post('/component/save')
.send({
id: '2041739550026043999',
name: component.name,
type: component.type,
componentType: component.componentType,
image: '',
template: '{}',
})
.expect(200);
expect(componentServiceMock.save).toHaveBeenCalledWith({
name: component.name,
type: component.type,
componentType: component.componentType,
image: '',
template: '{}',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: component.id,
});
},
'POST /component/remove': async (server) => {
componentServiceMock.remove.mockResolvedValue(true);
const response = await request(server)
.post('/component/remove')
.query({ id: component.id })
.expect(200);
expect(componentServiceMock.remove).toHaveBeenCalledWith(component.id);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: true,
});
},
'POST /component/update': async (server) => {
componentServiceMock.update.mockResolvedValue(true);
const response = await request(server)
.post('/component/update')
.send({
id: component.id,
name: component.name,
})
.expect(200);
expect(componentServiceMock.update).toHaveBeenCalledWith({
id: component.id,
name: component.name,
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: true,
});
},
'GET /component/detail': async (server) => {
componentServiceMock.find.mockResolvedValue(component);
const response = await request(server)
.get('/component/detail')
.query({ id: component.id })
.expect(200);
expect(componentServiceMock.find).toHaveBeenCalledWith(component.id);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: component,
});
},
'GET /blog/article/list': async (server) => {
blogArticleServiceMock.page.mockResolvedValue({
list: [blogArticle],
total: 1,
});
const response = await request(server)
.get('/blog/article/list')
.query({ pageNo: 1, pageSize: 10, search: '本地' })
.expect(200);
expect(blogArticleServiceMock.page).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '10',
search: '本地',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [blogArticle],
total: 1,
},
});
},
'GET /blog/article/detail': async (server) => {
blogArticleServiceMock.detail.mockResolvedValue(blogArticle);
const response = await request(server)
.get('/blog/article/detail')
.query({ id: blogArticle.id })
.expect(200);
expect(blogArticleServiceMock.detail).toHaveBeenCalledWith(blogArticle.id);
expect(response.body).toMatchObject({
code: 200,
data: blogArticle,
});
},
'GET /blog/article/public/list': async (server) => {
blogArticleServiceMock.publicList.mockResolvedValue({
list: [blogArticle],
total: 1,
});
const response = await request(server)
.get('/blog/article/public/list')
.query({ pageNo: 1, pageSize: 10 })
.expect(200);
expect(blogArticleServiceMock.publicList).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '10',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [blogArticle],
total: 1,
},
});
},
'GET /blog/article/public/detail': async (server) => {
blogArticleServiceMock.publicDetail.mockResolvedValue(blogArticle);
const response = await request(server)
.get('/blog/article/public/detail')
.query({ slug: blogArticle.slug })
.expect(200);
expect(blogArticleServiceMock.publicDetail).toHaveBeenCalledWith({
id: undefined,
slug: blogArticle.slug,
});
expect(response.body).toMatchObject({
code: 200,
data: blogArticle,
});
},
'POST /blog/article/save': async (server) => {
blogArticleServiceMock.save.mockResolvedValue(blogArticle);
const response = await request(server)
.post('/blog/article/save')
.send({
content: '# 本地文章',
contentFormat: 'markdown',
title: '本地文章',
})
.expect(200);
expect(blogArticleServiceMock.save).toHaveBeenCalledWith({
content: '# 本地文章',
contentFormat: 'markdown',
title: '本地文章',
});
expect(response.body).toMatchObject({
code: 200,
data: blogArticle,
});
},
'POST /blog/article/update': async (server) => {
blogArticleServiceMock.update.mockResolvedValue(blogArticle);
const response = await request(server)
.post('/blog/article/update')
.send({
content: '',
contentFormat: 'html',
id: blogArticle.id,
title: '本地文章',
})
.expect(200);
expect(blogArticleServiceMock.update).toHaveBeenCalledWith({
content: '',
contentFormat: 'html',
id: blogArticle.id,
title: '本地文章',
});
expect(response.body).toMatchObject({
code: 200,
data: blogArticle,
});
},
'POST /blog/article/remove': async (server) => {
blogArticleServiceMock.remove.mockResolvedValue(true);
const response = await request(server)
.post('/blog/article/remove')
.query({ id: blogArticle.id })
.expect(200);
expect(blogArticleServiceMock.remove).toHaveBeenCalledWith(blogArticle.id);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /blog/article/category-options': async (server) => {
blogArticleServiceMock.categoryOptions.mockResolvedValue({
list: [{ count: 1, id: 'tech', name: '技术', slug: 'tech' }],
total: 1,
});
const response = await request(server)
.get('/blog/article/category-options')
.query({ pageNo: 1, pageSize: 20, search: '技' })
.expect(200);
expect(blogArticleServiceMock.categoryOptions).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '20',
search: '技',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [{ count: 1, id: 'tech', name: '技术', slug: 'tech' }],
total: 1,
},
});
},
'GET /blog/article/tag-options': async (server) => {
blogArticleServiceMock.tagOptions.mockResolvedValue({
list: [{ count: 1, id: 'milkdown', name: 'Milkdown', slug: 'milkdown' }],
total: 1,
});
const response = await request(server)
.get('/blog/article/tag-options')
.query({ pageNo: 1, pageSize: 20, search: 'milk' })
.expect(200);
expect(blogArticleServiceMock.tagOptions).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '20',
search: 'milk',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [
{ count: 1, id: 'milkdown', name: 'Milkdown', slug: 'milkdown' },
],
total: 1,
},
});
},
'POST /blog/article/import-wordpress': async (server) => {
blogArticleServiceMock.importFromWordpress.mockResolvedValue({
created: 1,
items: [
{
action: 'created',
id: blogArticle.id,
slug: blogArticle.slug,
title: blogArticle.title,
},
],
skipped: 0,
total: 1,
updated: 0,
});
const response = await request(server)
.post('/blog/article/import-wordpress')
.send({
overwrite: false,
pageNo: 1,
pageSize: 10,
})
.expect(200);
expect(blogArticleServiceMock.importFromWordpress).toHaveBeenCalledWith({
overwrite: false,
pageNo: 1,
pageSize: 10,
});
expect(response.body).toMatchObject({
code: 200,
data: {
created: 1,
skipped: 0,
total: 1,
updated: 0,
},
});
},
'GET /blog/category/list': async (server) => {
blogTermServiceMock.page.mockResolvedValue({
list: [blogTerm],
total: 1,
});
const response = await request(server)
.get('/blog/category/list')
.query({ pageNo: 1, pageSize: 10, search: '技' })
.expect(200);
expect(blogTermServiceMock.page).toHaveBeenCalledWith('category', {
pageNo: '1',
pageSize: '10',
search: '技',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [blogTerm],
total: 1,
},
});
},
'GET /blog/category/detail': async (server) => {
blogTermServiceMock.detail.mockResolvedValue(blogTerm);
const response = await request(server)
.get('/blog/category/detail')
.query({ id: blogTerm.id })
.expect(200);
expect(blogTermServiceMock.detail).toHaveBeenCalledWith(
'category',
blogTerm.id,
);
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/category/save': async (server) => {
blogTermServiceMock.save.mockResolvedValue(blogTerm);
const response = await request(server)
.post('/blog/category/save')
.send({
description: blogTerm.description,
name: blogTerm.name,
slug: blogTerm.slug,
})
.expect(200);
expect(blogTermServiceMock.save).toHaveBeenCalledWith('category', {
description: blogTerm.description,
name: blogTerm.name,
slug: blogTerm.slug,
});
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/category/update': async (server) => {
blogTermServiceMock.update.mockResolvedValue(blogTerm);
const response = await request(server)
.post('/blog/category/update')
.send({
id: blogTerm.id,
name: blogTerm.name,
})
.expect(200);
expect(blogTermServiceMock.update).toHaveBeenCalledWith('category', {
id: blogTerm.id,
name: blogTerm.name,
});
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/category/remove': async (server) => {
blogTermServiceMock.remove.mockResolvedValue(true);
const response = await request(server)
.post('/blog/category/remove')
.query({ id: blogTerm.id })
.expect(200);
expect(blogTermServiceMock.remove).toHaveBeenCalledWith(
'category',
blogTerm.id,
);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /blog/tag/list': async (server) => {
blogTermServiceMock.page.mockResolvedValue({
list: [blogTerm],
total: 1,
});
const response = await request(server)
.get('/blog/tag/list')
.query({ pageNo: 1, pageSize: 10, search: '技' })
.expect(200);
expect(blogTermServiceMock.page).toHaveBeenCalledWith('tag', {
pageNo: '1',
pageSize: '10',
search: '技',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [blogTerm],
total: 1,
},
});
},
'GET /blog/tag/detail': async (server) => {
blogTermServiceMock.detail.mockResolvedValue(blogTerm);
const response = await request(server)
.get('/blog/tag/detail')
.query({ id: blogTerm.id })
.expect(200);
expect(blogTermServiceMock.detail).toHaveBeenCalledWith('tag', blogTerm.id);
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/tag/save': async (server) => {
blogTermServiceMock.save.mockResolvedValue(blogTerm);
const response = await request(server)
.post('/blog/tag/save')
.send({
name: blogTerm.name,
slug: blogTerm.slug,
})
.expect(200);
expect(blogTermServiceMock.save).toHaveBeenCalledWith('tag', {
name: blogTerm.name,
slug: blogTerm.slug,
});
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/tag/update': async (server) => {
blogTermServiceMock.update.mockResolvedValue(blogTerm);
const response = await request(server)
.post('/blog/tag/update')
.send({
id: blogTerm.id,
name: blogTerm.name,
})
.expect(200);
expect(blogTermServiceMock.update).toHaveBeenCalledWith('tag', {
id: blogTerm.id,
name: blogTerm.name,
});
expect(response.body).toMatchObject({
code: 200,
data: blogTerm,
});
},
'POST /blog/tag/remove': async (server) => {
blogTermServiceMock.remove.mockResolvedValue(true);
const response = await request(server)
.post('/blog/tag/remove')
.query({ id: blogTerm.id })
.expect(200);
expect(blogTermServiceMock.remove).toHaveBeenCalledWith('tag', blogTerm.id);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /blog/term/options': async (server) => {
blogTermServiceMock.options.mockResolvedValue({
list: [blogTerm],
total: 1,
});
const response = await request(server)
.get('/blog/term/options')
.query({ kind: 'category', pageNo: 1, pageSize: 10 })
.expect(200);
expect(blogTermServiceMock.options).toHaveBeenCalledWith('category', {
kind: 'category',
pageNo: '1',
pageSize: '10',
});
expect(response.body).toMatchObject({
code: 200,
data: {
list: [blogTerm],
total: 1,
},
});
},
'GET /blog/theme/config': async (server) => {
blogThemeConfigServiceMock.publicConfig.mockResolvedValue(
wordpressThemeConfig,
);
const response = await request(server)
.get('/blog/theme/config')
.expect(200);
expect(blogThemeConfigServiceMock.publicConfig).toHaveBeenCalledWith();
expect(response.body).toMatchObject({
code: 200,
data: wordpressThemeConfig,
});
},
'POST /blog/theme/save': async (server) => {
blogThemeConfigServiceMock.save.mockResolvedValue(wordpressThemeConfig);
const response = await request(server)
.post('/blog/theme/save')
.send({
config: wordpressThemeConfig,
source: 'local-admin',
})
.expect(200);
expect(blogThemeConfigServiceMock.save).toHaveBeenCalledWith({
config: wordpressThemeConfig,
source: 'local-admin',
});
expect(response.body).toMatchObject({
code: 200,
data: wordpressThemeConfig,
});
},
'POST /blog/theme/import-wordpress': async (server) => {
blogThemeConfigServiceMock.importFromWordpress.mockResolvedValue(
wordpressThemeConfig,
);
const response = await request(server)
.post('/blog/theme/import-wordpress')
.expect(200);
expect(
blogThemeConfigServiceMock.importFromWordpress,
).toHaveBeenCalledWith();
expect(response.body).toMatchObject({
code: 200,
data: wordpressThemeConfig,
});
},
'GET /dict/getDictByKey': async (server) => {
dictServiceMock.getDictByKey.mockResolvedValue(dictOptions);
const response = await request(server)
.get('/dict/getDictByKey')
.query({ dictKey: 'COMPONENT_TYPE' })
.expect(200);
expect(dictServiceMock.getDictByKey).toHaveBeenCalledWith('COMPONENT_TYPE');
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: dictOptions,
});
},
'GET /dict/getComponentDictByType': async (server) => {
dictServiceMock.getComponentDictByType.mockResolvedValue(chartOptions);
const response = await request(server)
.get('/dict/getComponentDictByType')
.query({ type: 1 })
.expect(200);
expect(dictServiceMock.getComponentDictByType).toHaveBeenCalledWith(1);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: chartOptions,
});
},
'GET /dict/list': async (server) => {
dictServiceMock.page.mockResolvedValue({
items: [dictItem],
total: 1,
});
const response = await request(server)
.get('/dict/list')
.query({ dictCode: 'COMPONENT_TYPE', pageNo: 1, pageSize: 10 })
.expect(200);
expect(dictServiceMock.page).toHaveBeenCalledWith({
dictCode: 'COMPONENT_TYPE',
pageNo: '1',
pageSize: '10',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
items: [dictItem],
total: 1,
},
});
},
'GET /dict/tree': async (server) => {
dictServiceMock.tree.mockResolvedValue([dictTreeItem]);
const response = await request(server)
.get('/dict/tree')
.query({ dictCode: 'COMPONENT_TYPE' })
.expect(200);
expect(dictServiceMock.tree).toHaveBeenCalledWith({
dictCode: 'COMPONENT_TYPE',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [dictTreeItem],
});
},
'GET /dict/groups': async (server) => {
dictServiceMock.groups.mockResolvedValue({
items: [dictGroupItem],
total: 1,
});
const response = await request(server)
.get('/dict/groups')
.query({ keyword: 'COMPONENT', pageNo: 1, pageSize: 10 })
.expect(200);
expect(dictServiceMock.groups).toHaveBeenCalledWith({
keyword: 'COMPONENT',
pageNo: '1',
pageSize: '10',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
items: [dictGroupItem],
total: 1,
},
});
},
'GET /dict/codes': async (server) => {
dictServiceMock.getDictCodeOptions.mockResolvedValue([
{
label: 'COMPONENT_TYPE',
value: 'COMPONENT_TYPE',
},
]);
const response = await request(server).get('/dict/codes').expect(200);
expect(dictServiceMock.getDictCodeOptions).toHaveBeenCalledWith();
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [
{
label: 'COMPONENT_TYPE',
value: 'COMPONENT_TYPE',
},
],
});
},
'POST /dict/save': async (server) => {
dictServiceMock.save.mockResolvedValue(dictItem.id);
const response = await request(server)
.post('/dict/save')
.send({
childrenCode: 'CHART',
dictCode: 'COMPONENT_TYPE',
label: '图表',
sort: 1,
status: 1,
value: '1',
})
.expect(200);
expect(dictServiceMock.save).toHaveBeenCalledWith({
childrenCode: 'CHART',
dictCode: 'COMPONENT_TYPE',
label: '图表',
sort: 1,
status: 1,
value: '1',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: dictItem.id,
});
},
'POST /dict/update': async (server) => {
dictServiceMock.update.mockResolvedValue(null);
const response = await request(server)
.post('/dict/update')
.send({
id: dictItem.id,
label: '图表',
})
.expect(200);
expect(dictServiceMock.update).toHaveBeenCalledWith({
id: dictItem.id,
label: '图表',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: null,
});
},
'DELETE /dict/:id': async (server) => {
dictServiceMock.remove.mockResolvedValue(null);
const response = await request(server)
.delete(`/dict/${dictItem.id}`)
.expect(200);
expect(dictServiceMock.remove).toHaveBeenCalledWith(dictItem.id);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: null,
});
},
'POST /dict/toggle': async (server) => {
dictServiceMock.toggle.mockResolvedValue(null);
const response = await request(server)
.post('/dict/toggle')
.query({ id: dictItem.id, status: 0 })
.expect(200);
expect(dictServiceMock.toggle).toHaveBeenCalledWith(dictItem.id, 0);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: null,
});
},
'GET /system/logs': async (server) => {
systemLogServiceMock.page.mockResolvedValue({
items: [systemLogItem],
total: 1,
});
const response = await request(server)
.get('/system/logs')
.query({ level: 'error', pageNo: 1, pageSize: 20 })
.expect(200);
expect(systemLogServiceMock.page).toHaveBeenCalledWith({
level: 'error',
pageNo: '1',
pageSize: '20',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
items: [systemLogItem],
total: 1,
},
});
},
'GET /system/logs/levels': async (server) => {
systemLogServiceMock.levels.mockReturnValue([
{ label: 'error', value: 'error' },
]);
const response = await request(server)
.get('/system/logs/levels')
.expect(200);
expect(systemLogServiceMock.levels).toHaveBeenCalledWith();
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [{ label: 'error', value: 'error' }],
});
},
'GET /system/logs/status': async (server) => {
systemLogServiceMock.status.mockReturnValue({
app: 'kt-template-online-api',
configured: true,
env: 'test',
host: 'http://loki:3100',
selector: '{app="kt-template-online-api",env="test"}',
});
const response = await request(server)
.get('/system/logs/status')
.expect(200);
expect(systemLogServiceMock.status).toHaveBeenCalledWith();
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
app: 'kt-template-online-api',
configured: true,
env: 'test',
host: 'http://loki:3100',
selector: '{app="kt-template-online-api",env="test"}',
},
});
},
'GET /system/logs/summary': async (server) => {
systemLogServiceMock.summary.mockResolvedValue([
{ count: 1, level: 'error' },
]);
const response = await request(server)
.get('/system/logs/summary')
.query({ rangeMinutes: 30 })
.expect(200);
expect(systemLogServiceMock.summary).toHaveBeenCalledWith({
rangeMinutes: '30',
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [{ count: 1, level: 'error' }],
});
},
'GET /minio/check': async (server) => {
minioServiceMock.checkConnection.mockResolvedValue({
bucketName: 'demo-bucket',
exists: true,
});
const response = await request(server)
.get('/minio/check')
.query({ bucketName: 'demo-bucket' })
.expect(200);
expect(minioServiceMock.checkConnection).toHaveBeenCalledWith(
'demo-bucket',
);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: {
bucketName: 'demo-bucket',
exists: true,
},
});
},
'POST /minio/bucket': async (server) => {
minioServiceMock.ensureBucket.mockResolvedValue('demo-bucket');
const response = await request(server)
.post('/minio/bucket')
.query({ bucketName: 'demo-bucket' })
.expect(201);
expect(minioServiceMock.ensureBucket).toHaveBeenCalledWith('demo-bucket');
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: 'demo-bucket',
});
},
'POST /minio/upload': async (server) => {
minioServiceMock.uploadObject.mockResolvedValue(uploadResult);
const response = await request(server)
.post('/minio/upload')
.field('objectName', 'uploads/demo.txt')
.attach('file', Buffer.from('demo'), {
filename: 'demo.txt',
contentType: 'text/plain',
})
.expect(201);
expect(minioServiceMock.uploadObject).toHaveBeenCalledWith(
expect.objectContaining({
objectName: 'uploads/demo.txt',
file: expect.objectContaining({
originalname: 'demo.txt',
mimetype: 'text/plain',
size: 4,
}),
}),
);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: uploadResult,
});
},
'GET /minio/list': async (server) => {
minioServiceMock.listObjects.mockResolvedValue([objectStat]);
const response = await request(server)
.get('/minio/list')
.query({
bucketName: 'demo-bucket',
prefix: 'uploads/',
recursive: 'false',
})
.expect(200);
expect(minioServiceMock.listObjects).toHaveBeenCalledWith({
bucketName: 'demo-bucket',
prefix: 'uploads/',
recursive: false,
});
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: [objectStat],
});
},
'GET /minio/url': async (server) => {
minioServiceMock.getPresignedUrl.mockResolvedValue(
'http://127.0.0.1:9000/kt-template-online/uploads/demo.txt',
);
const response = await request(server)
.get('/minio/url')
.query({
objectName: 'uploads/demo.txt',
bucketName: 'demo-bucket',
expiry: 60,
})
.expect(200);
expect(minioServiceMock.getPresignedUrl).toHaveBeenCalledWith(
'uploads/demo.txt',
'demo-bucket',
60,
);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: 'http://127.0.0.1:9000/kt-template-online/uploads/demo.txt',
});
},
'GET /minio/resource-proxy': async (server) => {
const body = Buffer.from('proxy-content');
const originalFetch = global.fetch;
global.fetch = jest.fn().mockResolvedValue({
ok: true,
headers: {
get: jest.fn().mockReturnValue('text/css; charset=utf-8'),
},
arrayBuffer: jest
.fn()
.mockResolvedValue(
body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength),
),
} as any);
try {
const response = await request(server)
.get('/minio/resource-proxy')
.query({ url: 'https://example.com/assets/style.css' })
.expect(200);
expect(global.fetch).toHaveBeenCalledWith(
'https://example.com/assets/style.css',
expect.objectContaining({
redirect: 'follow',
signal: expect.any(AbortSignal),
}),
);
expect(response.headers['content-type']).toContain('text/css');
expect(response.text).toBe('proxy-content');
} finally {
global.fetch = originalFetch;
}
},
'GET /minio/download': async (server) => {
minioServiceMock.getObject.mockResolvedValue({
stream: Readable.from(['file-content']),
stat: {
size: 12,
etag: 'etag',
lastModified: new Date('2026-05-13T02:30:00.000Z'),
metaData: {
'content-type': 'text/plain',
},
},
bucketName: 'demo-bucket',
objectName: 'uploads/demo.txt',
});
const response = await request(server)
.get('/minio/download')
.query({
objectName: 'uploads/demo.txt',
bucketName: 'demo-bucket',
})
.expect(200);
expect(minioServiceMock.getObject).toHaveBeenCalledWith(
'uploads/demo.txt',
'demo-bucket',
);
expect(response.headers['content-type']).toContain('text/plain');
expect(response.text).toBe('file-content');
},
'DELETE /minio/remove': async (server) => {
minioServiceMock.removeObject.mockResolvedValue(true);
const response = await request(server)
.delete('/minio/remove')
.query({
objectName: 'uploads/demo.txt',
bucketName: 'demo-bucket',
})
.expect(200);
expect(minioServiceMock.removeObject).toHaveBeenCalledWith(
'uploads/demo.txt',
'demo-bucket',
);
expect(response.body).toEqual({
code: 200,
msg: '操作成功',
data: true,
});
},
'GET /wordpress/auth/check': async (server) => {
wordpressServiceMock.checkAuth.mockResolvedValue(wordpressUser);
const response = await request(server)
.get('/wordpress/auth/check')
.expect(200);
expect(wordpressServiceMock.checkAuth).toHaveBeenCalledWith(
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressUser,
});
},
'POST /wordpress/auth/login': async (server) => {
wordpressServiceMock.loginWithConfiguredAdmin.mockResolvedValue(
wordpressLoginResult,
);
const response = await request(server)
.post('/wordpress/auth/login')
.expect(201);
expect(
wordpressServiceMock.loginWithConfiguredAdmin,
).toHaveBeenCalledWith();
expect(wordpressServiceMock.setAuthCookie).toHaveBeenCalledWith(
expect.anything(),
wordpressLoginResult.cookie,
);
expect(response.body).toMatchObject({
code: 200,
data: {
auth: wordpressLoginResult.auth,
user: wordpressUser,
},
});
},
'POST /wordpress/auth/logout': async (server) => {
const response = await request(server)
.post('/wordpress/auth/logout')
.expect(201);
expect(wordpressServiceMock.clearAuthCookie).toHaveBeenCalledWith(
expect.anything(),
);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /wordpress/article/list': async (server) => {
wordpressServiceMock.articleList.mockResolvedValue({
list: [wordpressArticle],
total: 1,
});
const response = await request(server)
.get('/wordpress/article/list')
.query({
pageNo: 1,
pageSize: 10,
search: '文章',
})
.expect(200);
expect(wordpressServiceMock.articleList).toHaveBeenCalledWith(
{
pageNo: '1',
pageSize: '10',
search: '文章',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: {
list: [wordpressArticle],
total: 1,
},
});
},
'GET /wordpress/article/detail': async (server) => {
wordpressServiceMock.articleDetail.mockResolvedValue(wordpressArticle);
const response = await request(server)
.get('/wordpress/article/detail')
.query({ id: 1 })
.expect(200);
expect(wordpressServiceMock.articleDetail).toHaveBeenCalledWith(
'1',
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressArticle,
});
},
'GET /wordpress/article/public/list': async (server) => {
wordpressServiceMock.publicArticleList.mockResolvedValue({
list: [wordpressArticle],
total: 1,
});
const response = await request(server)
.get('/wordpress/article/public/list')
.query({
pageNo: 1,
pageSize: 10,
search: '文章',
})
.expect(200);
expect(wordpressServiceMock.publicArticleList).toHaveBeenCalledWith({
pageNo: '1',
pageSize: '10',
search: '文章',
});
expect(wordpressServiceMock.getAuthContext).not.toHaveBeenCalled();
expect(response.body).toMatchObject({
code: 200,
data: {
list: [wordpressArticle],
total: 1,
},
});
},
'GET /wordpress/article/public/detail': async (server) => {
wordpressServiceMock.publicArticleDetail.mockResolvedValue(
wordpressArticle,
);
const response = await request(server)
.get('/wordpress/article/public/detail')
.query({ slug: 'wordpress-article' })
.expect(200);
expect(wordpressServiceMock.publicArticleDetail).toHaveBeenCalledWith({
id: undefined,
slug: 'wordpress-article',
});
expect(wordpressServiceMock.getAuthContext).not.toHaveBeenCalled();
expect(response.body).toMatchObject({
code: 200,
data: wordpressArticle,
});
},
'POST /wordpress/article/save': async (server) => {
wordpressServiceMock.articleSave.mockResolvedValue(wordpressArticle);
const response = await request(server)
.post('/wordpress/article/save')
.send({
id: 999,
title: 'WordPress 文章',
content: '文章内容',
})
.expect(200);
expect(wordpressServiceMock.articleSave).toHaveBeenCalledWith(
{
title: 'WordPress 文章',
content: '文章内容',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressArticle,
});
},
'POST /wordpress/article/update': async (server) => {
wordpressServiceMock.articleUpdate.mockResolvedValue(wordpressArticle);
const response = await request(server)
.post('/wordpress/article/update')
.send({
id: 1,
title: 'WordPress 文章',
})
.expect(200);
expect(wordpressServiceMock.articleUpdate).toHaveBeenCalledWith(
{
id: 1,
title: 'WordPress 文章',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressArticle,
});
},
'POST /wordpress/article/remove': async (server) => {
wordpressServiceMock.articleRemove.mockResolvedValue(true);
const response = await request(server)
.post('/wordpress/article/remove')
.query({
id: 1,
force: 'false',
})
.expect(200);
expect(wordpressServiceMock.articleRemove).toHaveBeenCalledWith(
'1',
false,
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /wordpress/tag/list': async (server) => {
wordpressServiceMock.tagList.mockResolvedValue({
list: [wordpressTerm],
total: 1,
});
const response = await request(server)
.get('/wordpress/tag/list')
.query({
pageNo: 1,
pageSize: 10,
search: '分类',
})
.expect(200);
expect(wordpressServiceMock.tagList).toHaveBeenCalledWith(
{
pageNo: '1',
pageSize: '10',
search: '分类',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: {
list: [wordpressTerm],
total: 1,
},
});
},
'GET /wordpress/tag/detail': async (server) => {
wordpressServiceMock.tagDetail.mockResolvedValue(wordpressTerm);
const response = await request(server)
.get('/wordpress/tag/detail')
.query({ id: 1 })
.expect(200);
expect(wordpressServiceMock.tagDetail).toHaveBeenCalledWith(
'1',
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/tag/save': async (server) => {
wordpressServiceMock.tagSave.mockResolvedValue(wordpressTerm);
const response = await request(server)
.post('/wordpress/tag/save')
.send({
id: 999,
name: 'WordPress 标签',
})
.expect(200);
expect(wordpressServiceMock.tagSave).toHaveBeenCalledWith(
{
name: 'WordPress 标签',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/tag/update': async (server) => {
wordpressServiceMock.tagUpdate.mockResolvedValue(wordpressTerm);
const response = await request(server)
.post('/wordpress/tag/update')
.send({
id: 1,
name: 'WordPress 标签',
})
.expect(200);
expect(wordpressServiceMock.tagUpdate).toHaveBeenCalledWith(
{
id: 1,
name: 'WordPress 标签',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/tag/remove': async (server) => {
wordpressServiceMock.tagRemove.mockResolvedValue(true);
const response = await request(server)
.post('/wordpress/tag/remove')
.query({ id: 1 })
.expect(200);
expect(wordpressServiceMock.tagRemove).toHaveBeenCalledWith(
'1',
true,
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /wordpress/category/list': async (server) => {
wordpressServiceMock.categoryList.mockResolvedValue({
list: [wordpressTerm],
total: 1,
});
const response = await request(server)
.get('/wordpress/category/list')
.query({
pageNo: 1,
pageSize: 10,
search: '分类',
})
.expect(200);
expect(wordpressServiceMock.categoryList).toHaveBeenCalledWith(
{
pageNo: '1',
pageSize: '10',
search: '分类',
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: {
list: [wordpressTerm],
total: 1,
},
});
},
'GET /wordpress/category/detail': async (server) => {
wordpressServiceMock.categoryDetail.mockResolvedValue(wordpressTerm);
const response = await request(server)
.get('/wordpress/category/detail')
.query({ id: 1 })
.expect(200);
expect(wordpressServiceMock.categoryDetail).toHaveBeenCalledWith(
'1',
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/category/save': async (server) => {
wordpressServiceMock.categorySave.mockResolvedValue(wordpressTerm);
const response = await request(server)
.post('/wordpress/category/save')
.send({
id: 999,
name: 'WordPress 分类',
parent: 0,
})
.expect(200);
expect(wordpressServiceMock.categorySave).toHaveBeenCalledWith(
{
name: 'WordPress 分类',
parent: 0,
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/category/update': async (server) => {
wordpressServiceMock.categoryUpdate.mockResolvedValue(wordpressTerm);
const response = await request(server)
.post('/wordpress/category/update')
.send({
id: 1,
name: 'WordPress 分类',
parent: 0,
})
.expect(200);
expect(wordpressServiceMock.categoryUpdate).toHaveBeenCalledWith(
{
id: 1,
name: 'WordPress 分类',
parent: 0,
},
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: wordpressTerm,
});
},
'POST /wordpress/category/remove': async (server) => {
wordpressServiceMock.categoryRemove.mockResolvedValue(true);
const response = await request(server)
.post('/wordpress/category/remove')
.query({ id: 1 })
.expect(200);
expect(wordpressServiceMock.categoryRemove).toHaveBeenCalledWith(
'1',
true,
wordpressAuthContext,
);
expect(response.body).toMatchObject({
code: 200,
data: true,
});
},
'GET /wordpress/theme/config': async (server) => {
wordpressServiceMock.themeConfig.mockResolvedValue(wordpressThemeConfig);
const response = await request(server)
.get('/wordpress/theme/config')
.expect(200);
expect(wordpressServiceMock.themeConfig).toHaveBeenCalledWith();
expect(wordpressServiceMock.getAuthContext).not.toHaveBeenCalled();
expect(response.body).toMatchObject({
code: 200,
data: wordpressThemeConfig,
});
},
};
describe('KT Template Online API (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
controllers: controllerClasses,
providers: [
AppService,
ToolsService,
Reflector,
{
provide: ComponentService,
useValue: componentServiceMock,
},
{
provide: AdminAuthService,
useValue: authServiceMock,
},
JwtAuthGuard,
{
provide: DictService,
useValue: dictServiceMock,
},
{
provide: SystemLogService,
useValue: systemLogServiceMock,
},
{
provide: BlogArticleService,
useValue: blogArticleServiceMock,
},
{
provide: BlogTermService,
useValue: blogTermServiceMock,
},
{
provide: BlogThemeConfigService,
useValue: blogThemeConfigServiceMock,
},
{
provide: MinioClientService,
useValue: minioServiceMock,
},
{
provide: WordpressService,
useValue: wordpressServiceMock,
},
{
provide: PinoLogger,
useValue: pinoLoggerMock,
},
{
provide: APP_INTERCEPTOR,
useClass: SaveBodyInterceptor,
},
{
provide: APP_FILTER,
useClass: ApiExceptionFilter,
},
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
beforeEach(() => {
jest.clearAllMocks();
authServiceMock.currentUser.mockResolvedValue({
id: '2041739550026043001',
username: 'admin',
});
wordpressServiceMock.getAuthContext.mockReturnValue(wordpressAuthContext);
});
afterAll(async () => {
await app.close();
});
it('keeps generated e2e cases aligned with controller routes', () => {
expect(Object.keys(routeTestCases).sort()).toEqual(
controllerRoutes.map(routeKey).sort(),
);
});
it('keeps Blog Web runtime endpoints public for WordPress replacement', () => {
expect(
Reflect.getMetadata(
IS_PUBLIC_KEY,
BlogArticleController.prototype.publicList,
),
).toBe(true);
expect(
Reflect.getMetadata(
IS_PUBLIC_KEY,
BlogArticleController.prototype.publicDetail,
),
).toBe(true);
expect(
Reflect.getMetadata(
IS_PUBLIC_KEY,
BlogThemeConfigController.prototype.config,
),
).toBe(true);
});
describe('generated route smoke tests', () => {
controllerRoutes.forEach((route) => {
const key = routeKey(route);
it(`${key} -> ${route.controllerName}.${route.handlerName}`, async () => {
const testCase = routeTestCases[key];
expect(testCase).toBeDefined();
await testCase(app.getHttpServer());
});
});
});
it('returns component update failure in a unified response shape', async () => {
componentServiceMock.update.mockResolvedValue(false);
const response = await request(app.getHttpServer())
.post('/component/update')
.send({
id: component.id,
name: component.name,
})
.expect(200);
expect(response.body).toEqual({
code: 400,
msg: '操作失败',
err: 'false',
});
});
it('serializes object error details as a string for frontend parsing', async () => {
wordpressServiceMock.checkAuth.mockRejectedValue(
new HttpException(
{
msg: 'WordPress 请求失败',
err: {
code: 'WORDPRESS_NETWORK_ERROR',
message: 'connect ECONNREFUSED 127.0.0.1:8080',
},
},
HttpStatus.BAD_GATEWAY,
),
);
const response = await request(app.getHttpServer())
.get('/wordpress/auth/check')
.expect(502);
expect(response.body).toEqual({
code: 502,
msg: 'WordPress 请求失败',
err: 'connect ECONNREFUSED 127.0.0.1:8080',
});
});
it('protects dict, minio and wordpress endpoints with jwt auth', async () => {
authServiceMock.currentUser.mockRejectedValue(unauthorizedException());
await request(app.getHttpServer())
.get('/dict/getDictByKey')
.query({ dictKey: 'COMPONENT_TYPE' })
.expect(401)
.expect(({ body }) => {
expect(body).toEqual({
code: 401,
msg: 'Unauthorized Exception',
err: 'Unauthorized Exception',
});
});
expect(dictServiceMock.getDictByKey).not.toHaveBeenCalled();
jest.clearAllMocks();
authServiceMock.currentUser.mockRejectedValue(unauthorizedException());
await request(app.getHttpServer()).get('/minio/check').expect(401);
expect(minioServiceMock.checkConnection).not.toHaveBeenCalled();
jest.clearAllMocks();
authServiceMock.currentUser.mockRejectedValue(unauthorizedException());
await request(app.getHttpServer()).get('/wordpress/auth/check').expect(401);
expect(wordpressServiceMock.checkAuth).not.toHaveBeenCalled();
});
});