Files
RSSHub/lib/utils/request-rewriter/fetch-retry.test.ts
T
DIYgod 81ff849337 feat(tests): add comprehensive unit tests for various utility functions and views (#20779)
* feat(tests): add comprehensive unit tests for various utility functions and views

- Implement tests for cache utility to ensure proper behavior with no cache and TTL keys.
- Add tests for common utilities including string manipulation and path handling.
- Introduce tests for directory import functionality to validate file imports and pattern matching.
- Create tests for git hash retrieval to handle fallback scenarios.
- Develop tests for deprecated got utility to verify response handling and retry logic.
- Enhance got utility tests to include request hooks and search parameter handling.
- Mock header generator tests to validate user agent handling.
- Expand helpers tests to cover current path retrieval and duration parsing.
- Implement tests for ofetch utility to ensure proper proxy handling and logging.
- Add OpenTelemetry metric tests to validate metric serialization.
- Create proxy tests to verify multi-proxy selection and failure handling.
- Introduce request rewriter tests to validate fetch and get wrapper functionality.
- Add timezone utility tests to handle various input types.
- Implement view tests for Atom and RSS rendering to ensure correct output.
- Create index view tests to validate debug information display based on configuration.

* refactor: remove deprecated got implementation and associated tests

* test: expand coverage for api, middleware, and utils
2025-12-29 17:34:48 +08:00

119 lines
3.6 KiB
TypeScript

import undici from 'undici';
import { afterEach, describe, expect, it, vi } from 'vitest';
const buildProxyState = () => [
{
uri: 'http://proxy1.test',
isActive: true,
failureCount: 0,
urlHandler: new URL('http://proxy1.test'),
},
{
uri: 'http://proxy2.test',
isActive: true,
failureCount: 0,
urlHandler: new URL('http://proxy2.test'),
},
];
const loadWrappedFetch = async (proxyMock: any) => {
vi.resetModules();
vi.doMock('@/utils/logger', () => ({
default: {
debug: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
http: vi.fn(),
},
}));
vi.doMock('@/utils/proxy', () => ({
default: proxyMock,
}));
return (await import('@/utils/request-rewriter/fetch')).default;
};
afterEach(() => {
vi.restoreAllMocks();
vi.resetModules();
vi.unmock('@/utils/logger');
vi.unmock('@/utils/proxy');
});
describe('request-rewriter fetch retry', () => {
it('retries with the next proxy when prefer-proxy header is set', async () => {
const proxies = buildProxyState();
let index = 0;
const proxyMock = {
proxyObj: {
strategy: 'on_retry',
url_regex: 'example.com',
},
proxyUrlHandler: null,
multiProxy: {
allProxies: proxies,
},
getCurrentProxy: vi.fn(() => proxies[index]),
markProxyFailed: vi.fn(() => {
index = 1;
}),
getDispatcherForProxy: vi.fn((proxyState) => ({
proxy: proxyState.uri,
})),
};
const wrappedFetch = await loadWrappedFetch(proxyMock);
const fetchSpy = vi.spyOn(undici, 'fetch');
fetchSpy.mockRejectedValueOnce(new Error('boom'));
fetchSpy.mockResolvedValueOnce(new Response('ok'));
const response = await wrappedFetch('http://example.com/resource', {
headers: new Headers({
'x-prefer-proxy': '1',
}),
});
expect(response).toBeInstanceOf(Response);
expect(fetchSpy).toHaveBeenCalledTimes(2);
expect(proxyMock.markProxyFailed).toHaveBeenCalledWith('http://proxy1.test');
expect(proxyMock.getDispatcherForProxy).toHaveBeenCalledWith(proxies[1]);
const requestArg = fetchSpy.mock.calls[0][0] as Request;
expect(requestArg.headers.get('x-prefer-proxy')).toBeNull();
});
it('drops dispatcher when no next proxy is available', async () => {
const proxies = buildProxyState();
const proxyMock = {
proxyObj: {
strategy: 'on_retry',
url_regex: 'example.com',
},
proxyUrlHandler: null,
multiProxy: {
allProxies: proxies,
},
getCurrentProxy: vi.fn(() => proxies[0]),
markProxyFailed: vi.fn(),
getDispatcherForProxy: vi.fn((proxyState) => ({
proxy: proxyState.uri,
})),
};
const wrappedFetch = await loadWrappedFetch(proxyMock);
const fetchSpy = vi.spyOn(undici, 'fetch');
fetchSpy.mockRejectedValueOnce(new Error('boom'));
fetchSpy.mockResolvedValueOnce(new Response('ok'));
await wrappedFetch('http://example.com/resource', {
headers: {
'x-prefer-proxy': '1',
},
});
expect(fetchSpy).toHaveBeenCalledTimes(2);
expect(fetchSpy.mock.calls[1][1]?.dispatcher).toBeUndefined();
});
});