import { createServer, type Server } from 'node:http'; import { ConfigService } from '@nestjs/config'; import { MarkdownService, ToolsService } from '../../src/common'; import { WordpressService } from '../../src/modules/wordpress/application/wordpress.service'; describe('WordpressService theme config', () => { const rootPayload = { description: '', home: 'https://blog.kwitsukasa.top', name: 'KwiTsukasa的小站', url: 'https://blog.kwitsukasa.top', }; const html = `
KwiTsukasa
`; let service: WordpressService; let fetchSpy: jest.SpyInstance; let markdownService: MarkdownService; beforeEach(() => { markdownService = new MarkdownService(); jest .spyOn(markdownService, 'renderToHtml') .mockResolvedValue('

标题

\n

正文

'); service = new WordpressService( new ConfigService({ WORDPRESS_BASE_URL: 'https://blog.kwitsukasa.top', }), markdownService, new ToolsService(), ); fetchSpy = jest .spyOn(globalThis, 'fetch') .mockImplementation(async (url: URL | RequestInfo) => { const target = `${url}`; if (target.includes('rest_route=/')) { return new Response(JSON.stringify(rootPayload), { headers: { 'Content-Type': 'application/json' }, status: 200, }); } return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 200, }); }); }); afterEach(() => { fetchSpy.mockRestore(); }); it('extracts Argon theme config from WordPress REST root and homepage html', async () => { const config = await service.themeConfig(); expect(config).toMatchObject({ backgroundDarkBrightness: 0.65, backgroundDarkImage: 'https://s3.kwitsukasa.top/images/bg-冬滚滚.png', backgroundDarkOpacity: 1, backgroundImage: 'https://s3.kwitsukasa.top/images/bg-冬滚滚.png', backgroundOpacity: 1, bodyClass: ['home', 'blog', 'wp-theme-argon'], darkmodeAutoSwitch: 'alwayson', enableCustomThemeColor: true, htmlClass: [ 'triple-column', 'immersion-color', 'toolbar-blur', 'article-header-style-default', ], sidebarMenu: [ { external: true, href: 'https://blog.kwitsukasa.top', icon: 'fa-home', label: '首页', }, { external: true, href: 'http://blog.kwitsukasa.top/wp-admin/', icon: 'fa-user', label: '管理', }, ], site: { authorAvatar: 'http://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg', authorName: 'KwiTsukasa', 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', }); expect(config.argonConfig).toMatchObject({ codeHighlight: { breakLine: false, enable: true, hideLinenumber: false, transparentLinenumber: false, }, dateFormat: 'YMD', disablePjax: false, headroom: 'false', language: 'zh_CN', lazyload: { effect: 'fadeIn', threshold: 800, }, pangu: 'article', pjaxAnimationDuration: 600, waterflowColumns: '1', wpPath: '/', zoomify: false, }); }); it('uses configured WordPress host header for raw theme html requests', async () => { const hosts: string[] = []; const server = await new Promise((resolve) => { const nextServer = createServer((request, response) => { hosts.push(request.headers.host || ''); if (request.headers.host !== 'blog.kwitsukasa.top') { response.writeHead(301, { Location: 'http://127.0.0.1/', }); response.end(); return; } if (request.url?.includes('rest_route=')) { response.writeHead(200, { 'Content-Type': 'application/json', }); response.end(JSON.stringify(rootPayload)); return; } response.writeHead(200, { 'Content-Type': 'text/html', }); response.end(html); }); nextServer.listen(0, '127.0.0.1', () => resolve(nextServer)); }); try { const address = server.address(); if (!address || typeof address === 'string') { throw new Error('测试服务启动失败'); } service = new WordpressService( new ConfigService({ WORDPRESS_BASE_URL: `http://127.0.0.1:${address.port}`, WORDPRESS_HOST_HEADER: 'blog.kwitsukasa.top', }), markdownService, new ToolsService(), ); const config = await service.themeConfig(); expect(config.themeColor).toBe('#c3a1ed'); expect(config.site.title).toBe('KwiTsukasa的小站'); expect(hosts).toEqual([ 'blog.kwitsukasa.top', 'blog.kwitsukasa.top', ]); expect(fetchSpy).not.toHaveBeenCalled(); } finally { await new Promise((resolve) => server.close(() => resolve())); } }); it('renders markdown article content before saving to WordPress', async () => { fetchSpy.mockImplementation(async (url: URL | RequestInfo, init?: any) => { const target = `${url}`; if (target.includes('/wp-json/wp/v2/posts')) { const body = JSON.parse(init.body); return new Response( JSON.stringify({ content: { raw: body.content }, id: 1, title: { raw: body.title }, }), { headers: { 'Content-Type': 'application/json' }, status: 200, }, ); } return new Response(JSON.stringify({ id: 1 }), { status: 200 }); }); const result = await service.articleSave( { content: '# 标题\n\n正文', contentFormat: 'markdown', title: 'Markdown 文章', }, { nonce: 'rest-nonce', cookie: 'wordpress_logged_in_demo=1', }, ); expect(fetchSpy).toHaveBeenCalledWith( expect.stringContaining('/wp-json/wp/v2/posts'), expect.objectContaining({ method: 'POST', }), ); const [, init] = fetchSpy.mock.calls[0]; const body = JSON.parse(init.body); expect(body.content).toContain('

标题

'); expect(body.content).not.toContain('', ); expect(result.contentMarkdown).toBe( '# 标题\n\n正文', ); }); it('normalizes public article list from WordPress view responses', async () => { fetchSpy.mockImplementation(async (_url: URL | RequestInfo, init?: any) => { expect(init?.headers?.Authorization).toBeUndefined(); expect(init?.headers?.Cookie).toBeUndefined(); return new Response( JSON.stringify([ { _embedded: { author: [{ name: '作者' }], 'wp:featuredmedia': [{ source_url: 'https://img.demo/cover.jpg' }], 'wp:term': [ [ { count: 2, id: 10, name: '技术', slug: 'tech', taxonomy: 'category', }, ], [ { count: 1, id: 20, name: 'Milkdown', slug: 'milkdown', taxonomy: 'post_tag', }, ], ], }, content: { rendered: '

标题

', }, excerpt: { rendered: '

摘要

', }, id: 1, slug: 'demo', title: { rendered: '公开文章', }, }, ]), { headers: { 'Content-Type': 'application/json', 'x-wp-total': '1', }, status: 200, }, ); }); const result = await service.publicArticleList({ pageNo: 1, pageSize: 10, }); expect(fetchSpy).toHaveBeenCalledWith( expect.stringContaining('/wp-json/wp/v2/posts'), expect.objectContaining({ method: 'GET', }), ); expect(result.total).toBe(1); expect(result.list[0]).toMatchObject({ authorName: '作者', contentHtml: '

标题

', cover: 'https://img.demo/cover.jpg', excerptText: '摘要', categoriesResolved: [{ id: 10, name: '技术', slug: 'tech' }], tagsResolved: [{ id: 20, name: 'Milkdown', slug: 'milkdown' }], }); }); });