Fixes: Improved unit tests for fetcher and display

- display.test.js: Added vitest import to afterEach
- fetcher.test.js: Changed from mock axios to mock child_process.execSync
- fetcher.test.js: Added mock cache.js to avoid caching interference with tests
- fetcher.test.js: Changed network failure test to expect null return
This commit is contained in:
gdjdkid committed 2026-03-22 12:12:00 +08:00
1 parent fd517c95d1
commit e2932476a7
3 files changed
+55 -57

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "javdict",
"version": "1.2.5",
"version": "1.2.6",
"description": "AV番号命令行查询工具",
"main": "./index.js",
"type": "module",
+1 -4
View File
@@ -1,7 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { display } from '../lib/display.js';
// 完整的 mock 数据
const MOCK_INFO = {
id: 'SSIS-001',
title: '测试标题',
@@ -20,7 +19,6 @@ const MOCK_INFO = {
describe('display.js', () => {
beforeEach(() => {
// 拦截 console.log,不真正打印,但可以断言调用内容
vi.spyOn(console, 'log').mockImplementation(() => {});
});
@@ -47,7 +45,6 @@ describe('display.js', () => {
it('display:raw 模式输出 JSON 字符串', () => {
display(MOCK_INFO, true);
const allOutput = console.log.mock.calls.flat().join(' ');
// raw 模式应该输出可解析的 JSON
expect(() => JSON.parse(allOutput)).not.toThrow();
});
+53 -52
View File
@@ -1,49 +1,55 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// mock axios,避免真实网络请求
vi.mock('axios', () => ({
default: {
get: vi.fn(),
},
// mock execSync,避免真实的 curl 网络请求
vi.mock('child_process', () => ({
execSync: vi.fn(),
spawnSync: vi.fn(),
}));
import axios from 'axios';
// mock cache,避免缓存干扰测试结果
vi.mock('../lib/cache.js', () => ({
getCache: vi.fn().mockReturnValue(null),
setCache: vi.fn(),
getConfig: vi.fn().mockReturnValue({ session: 'mock_session' }),
}));
import { execSync } from 'child_process';
import { search } from '../lib/fetcher.js';
// 模拟搜索结果页的 HTML
const MOCK_SEARCH_HTML = `
<div class="movie-list">
<div class="item">
<a href="/v/abc123">
<div class="video-title">SSIS-001</div>
</a>
// 模拟 JAVBUS 详情页 HTML
const MOCK_JAVBUS_HTML = `
<html>
<head><title>SSIS-001</title></head>
<body>
<div class="container">
<div class="row">
<h3>SSIS-001 测试标题</h3>
<div class="screencap">
<img src="https://example.com/cover.jpg" />
</div>
<div class="info">
<p><span class="header">發行日期:</span> 2021-01-01</p>
<p><span class="header">長度:</span> 150分鐘</p>
<p><span class="header">導演:</span> <a>导演A</a></p>
<p><span class="header">製作商:</span> <a>SOD Create</a></p>
<p><span class="header">發行商:</span> <a>SOD</a></p>
<p><span class="header">系列:</span> <a>系列A</a></p>
</div>
<span class="genre"><a>独占</a></span>
<span class="genre"><a>美少女</a></span>
<div class="star-name"><a>天使もえ</a></div>
</div>
</div>
</div>
</body>
</html>
`;
// 模拟详情页的 HTML
const MOCK_DETAIL_HTML = `
<h2 class="title"><strong class="current-title">测试标题</strong></h2>
<div class="video-cover"><img src="https://example.com/cover.jpg" /></div>
<div class="score"><span class="value">4.5分</span></div>
<div class="movie-panel-info">
<div class="panel-block">
<strong>日期:</strong>
<span class="value">2021-01-01</span>
</div>
<div class="panel-block">
<strong>演員:</strong>
<span class="value"><a>天使もえ</a></span>
</div>
<div class="panel-block">
<strong>片商:</strong>
<span class="value">SOD Create</span>
</div>
<div class="panel-block">
<strong>時長:</strong>
<span class="value">120分钟</span>
</div>
</div>
// 模拟 404 页面
const MOCK_404_HTML = `
<html>
<head><title>404</title></head>
<body></body>
</html>
`;
describe('fetcher.js', () => {
@@ -52,41 +58,36 @@ describe('fetcher.js', () => {
});
it('search:正常番号能返回结构完整的对象', async () => {
// 第一次调用返回搜索页,第二次返回详情页
axios.get
.mockResolvedValueOnce({ data: MOCK_SEARCH_HTML })
.mockResolvedValueOnce({ data: MOCK_DETAIL_HTML });
execSync.mockReturnValue(Buffer.from(MOCK_JAVBUS_HTML));
const result = await search('SSIS-001');
expect(result).not.toBeNull();
expect(result.id).toBe('SSIS-001');
expect(result.title).toBe('测试标题');
expect(result.source).toBe('JAVBUS');
expect(result.actresses).toContain('天使もえ');
expect(result.releaseDate).toBe('2021-01-01');
expect(result.studio).toBe('SOD Create');
expect(result.duration).toBe('120分钟');
expect(result.coverUrl).toBe('https://example.com/cover.jpg');
});
it('search:搜索结果为空时返回 null', async () => {
// 返回一个没有结果的搜索页
axios.get.mockResolvedValueOnce({ data: '<div class="movie-list"></div>' });
execSync.mockReturnValue(Buffer.from(MOCK_404_HTML));
const result = await search('INVALID-999');
expect(result).toBeNull();
});
it('search:网络请求失败时抛出错误', async () => {
axios.get.mockRejectedValueOnce(new Error('Network Error'));
it('search:网络请求失败时返回 null', async () => {
execSync.mockImplementation(() => {
throw new Error('Network Error');
});
await expect(search('SSIS-001')).rejects.toThrow('Network Error');
const result = await search('SSIS-001');
expect(result).toBeNull();
});
it('search:返回对象包含所有预期字段', async () => {
axios.get
.mockResolvedValueOnce({ data: MOCK_SEARCH_HTML })
.mockResolvedValueOnce({ data: MOCK_DETAIL_HTML });
execSync.mockReturnValue(Buffer.from(MOCK_JAVBUS_HTML));
const result = await search('SSIS-001');
const expectedFields = [