| | import http from 'node:http'; |
| | import https from 'node:https'; |
| |
|
| | import got from 'got'; |
| | import undici from 'undici'; |
| | import { afterAll, afterEach, describe, expect, it, vi } from 'vitest'; |
| |
|
| | import { PRESETS } from '@/utils/header-generator'; |
| |
|
| | const originalGlobals = { |
| | fetch: globalThis.fetch, |
| | Headers: globalThis.Headers, |
| | FormData: globalThis.FormData, |
| | Request: globalThis.Request, |
| | Response: globalThis.Response, |
| | }; |
| | const originalHttp = { |
| | get: http.get, |
| | request: http.request, |
| | }; |
| | const originalHttps = { |
| | get: https.get, |
| | request: https.request, |
| | }; |
| | const originalEnv = { |
| | PROXY_URI: process.env.PROXY_URI, |
| | PROXY_AUTH: process.env.PROXY_AUTH, |
| | PROXY_URL_REGEX: process.env.PROXY_URL_REGEX, |
| | }; |
| |
|
| | process.env.PROXY_URI = 'http://rsshub.proxy:2333/'; |
| | process.env.PROXY_AUTH = 'rsshubtest'; |
| | process.env.PROXY_URL_REGEX = 'headers'; |
| |
|
| | await import('@/utils/request-rewriter'); |
| | const { config } = await import('@/config'); |
| | const { default: ofetch } = await import('@/utils/ofetch'); |
| |
|
| | const createJsonResponse = () => |
| | Response.json( |
| | { ok: true }, |
| | { |
| | headers: { |
| | 'content-type': 'application/json', |
| | }, |
| | } |
| | ); |
| |
|
| | describe('request-rewriter', () => { |
| | afterAll(() => { |
| | globalThis.fetch = originalGlobals.fetch; |
| | globalThis.Headers = originalGlobals.Headers; |
| | globalThis.FormData = originalGlobals.FormData; |
| | globalThis.Request = originalGlobals.Request; |
| | globalThis.Response = originalGlobals.Response; |
| |
|
| | http.get = originalHttp.get; |
| | http.request = originalHttp.request; |
| | https.get = originalHttps.get; |
| | https.request = originalHttps.request; |
| |
|
| | if (originalEnv.PROXY_URI === undefined) { |
| | delete process.env.PROXY_URI; |
| | } else { |
| | process.env.PROXY_URI = originalEnv.PROXY_URI; |
| | } |
| | if (originalEnv.PROXY_AUTH === undefined) { |
| | delete process.env.PROXY_AUTH; |
| | } else { |
| | process.env.PROXY_AUTH = originalEnv.PROXY_AUTH; |
| | } |
| | if (originalEnv.PROXY_URL_REGEX === undefined) { |
| | delete process.env.PROXY_URL_REGEX; |
| | } else { |
| | process.env.PROXY_URL_REGEX = originalEnv.PROXY_URL_REGEX; |
| | } |
| | }); |
| |
|
| | afterEach(() => { |
| | vi.restoreAllMocks(); |
| | }); |
| |
|
| | it('fetch', async () => { |
| | const fetchSpy = vi.spyOn(undici, 'fetch').mockImplementation(() => Promise.resolve(createJsonResponse())); |
| |
|
| | try { |
| | await (await fetch('http://rsshub.test/headers')).json(); |
| | } catch { |
| | |
| | } |
| |
|
| | |
| | const headers: Headers = fetchSpy.mock.lastCall?.[0].headers; |
| | expect(headers.get('user-agent')).toBe(config.ua); |
| | expect(headers.get('accept')).toBeDefined(); |
| | expect(headers.get('referer')).toBe('http://rsshub.test'); |
| | expect(headers.get('sec-ch-ua')).toBeDefined(); |
| | expect(headers.get('sec-ch-ua-mobile')).toBeDefined(); |
| | expect(headers.get('sec-ch-ua-platform')).toBeDefined(); |
| | expect(headers.get('sec-fetch-site')).toBeDefined(); |
| | expect(headers.get('sec-fetch-mode')).toBeDefined(); |
| | expect(headers.get('sec-fetch-user')).toBeDefined(); |
| | expect(headers.get('sec-fetch-dest')).toBeDefined(); |
| |
|
| | |
| | const options = fetchSpy.mock.lastCall?.[1]; |
| | const agentKey = Object.getOwnPropertySymbols(options?.dispatcher).find((s) => s.description === 'proxy agent options'); |
| | const agentUri = agentKey ? options?.dispatcher?.[agentKey].uri : null; |
| | expect(agentUri).toBe(process.env.PROXY_URI); |
| |
|
| | |
| | const headersKey = Object.getOwnPropertySymbols(options?.dispatcher).find((s) => s.description === 'proxy headers'); |
| | const agentHeaders = headersKey ? options?.dispatcher?.[headersKey] : null; |
| | expect(agentHeaders['proxy-authorization']).toBe(`Basic ${process.env.PROXY_AUTH}`); |
| |
|
| | |
| | { |
| | try { |
| | await (await fetch('http://rsshub.test/rss')).json(); |
| | } catch { |
| | |
| | } |
| | const options = fetchSpy.mock.lastCall?.[1]; |
| | expect(options?.dispatcher).toBeUndefined(); |
| | } |
| | }); |
| |
|
| | it('ofetch', async () => { |
| | const fetchSpy = vi.spyOn(undici, 'fetch').mockImplementation(() => Promise.resolve(createJsonResponse())); |
| |
|
| | try { |
| | await ofetch('http://rsshub.test/headers', { |
| | retry: 0, |
| | }); |
| | } catch { |
| | |
| | } |
| |
|
| | |
| | const headers: Headers = fetchSpy.mock.lastCall?.[0].headers; |
| | expect(headers.get('user-agent')).toBe(config.ua); |
| | expect(headers.get('accept')).toBeDefined(); |
| | expect(headers.get('referer')).toBe('http://rsshub.test'); |
| | expect(headers.get('sec-ch-ua')).toBeDefined(); |
| | expect(headers.get('sec-ch-ua-mobile')).toBeDefined(); |
| | expect(headers.get('sec-ch-ua-platform')).toBeDefined(); |
| | expect(headers.get('sec-fetch-site')).toBeDefined(); |
| | expect(headers.get('sec-fetch-mode')).toBeDefined(); |
| | expect(headers.get('sec-fetch-user')).toBeDefined(); |
| | expect(headers.get('sec-fetch-dest')).toBeDefined(); |
| |
|
| | |
| | const options = fetchSpy.mock.lastCall?.[1]; |
| | const agentKey = Object.getOwnPropertySymbols(options?.dispatcher).find((s) => s.description === 'proxy agent options'); |
| | const agentUri = agentKey ? options?.dispatcher?.[agentKey].uri : null; |
| | expect(agentUri).toBe(process.env.PROXY_URI); |
| |
|
| | |
| | const headersKey = Object.getOwnPropertySymbols(options?.dispatcher).find((s) => s.description === 'proxy headers'); |
| | const agentHeaders = headersKey ? options?.dispatcher?.[headersKey] : null; |
| | expect(agentHeaders['proxy-authorization']).toBe(`Basic ${process.env.PROXY_AUTH}`); |
| |
|
| | |
| | { |
| | try { |
| | await ofetch('http://rsshub.test/rss', { |
| | retry: 0, |
| | }); |
| | } catch { |
| | |
| | } |
| | const options = fetchSpy.mock.lastCall?.[1]; |
| | expect(options?.dispatcher).toBeUndefined(); |
| | } |
| | }); |
| |
|
| | it('ofetch custom ua', async () => { |
| | const fetchSpy = vi.spyOn(undici, 'fetch').mockImplementation(() => Promise.resolve(createJsonResponse())); |
| | const userAgent = config.trueUA; |
| |
|
| | try { |
| | await ofetch('http://rsshub.test/headers', { |
| | retry: 0, |
| | headers: { |
| | 'user-agent': userAgent, |
| | }, |
| | }); |
| | } catch { |
| | |
| | } |
| |
|
| | |
| | const headers: Headers = fetchSpy.mock.lastCall?.[0].headers; |
| | expect(headers.get('user-agent')).toBe(userAgent); |
| | }); |
| |
|
| | it('ofetch header preset', async () => { |
| | const fetchSpy = vi.spyOn(undici, 'fetch').mockImplementation(() => Promise.resolve(createJsonResponse())); |
| |
|
| | try { |
| | await ofetch('http://rsshub.test/headers', { |
| | retry: 0, |
| | headerGeneratorOptions: PRESETS.MODERN_WINDOWS_CHROME, |
| | }); |
| | } catch { |
| | |
| | } |
| |
|
| | |
| | const headers: Headers = fetchSpy.mock.lastCall?.[0].headers; |
| | expect(headers.get('user-agent')).toBeDefined(); |
| | expect(headers.get('accept')).toBeDefined(); |
| | expect(headers.get('referer')).toBe('http://rsshub.test'); |
| | expect(headers.get('sec-ch-ua')).toBeDefined(); |
| | expect(headers.get('sec-ch-ua-mobile')).toBe('?0'); |
| | expect(headers.get('sec-ch-ua-platform')).toBe('"Windows"'); |
| | expect(headers.get('sec-fetch-site')).toBeDefined(); |
| | expect(headers.get('sec-fetch-mode')).toBeDefined(); |
| | expect(headers.get('sec-fetch-user')).toBeDefined(); |
| | expect(headers.get('sec-fetch-dest')).toBeDefined(); |
| | }); |
| |
|
| | it('http', async () => { |
| | const httpSpy = vi.spyOn(http, 'request'); |
| |
|
| | try { |
| | await got.get('http://rsshub.test/headers', { |
| | headers: { |
| | 'user-agent': undefined, |
| | accept: undefined, |
| | }, |
| | }); |
| | } catch { |
| | |
| | } |
| |
|
| | |
| | const options = httpSpy.mock.lastCall?.[1]; |
| | const headers = options?.headers; |
| | expect(headers?.['user-agent']).toBe(config.ua); |
| | expect(headers?.accept).toBeDefined(); |
| | expect(headers?.referer).toBe('http://rsshub.test'); |
| |
|
| | |
| | const agentUri = options?.agent?.proxy?.href; |
| | expect(agentUri).toBe(process.env.PROXY_URI); |
| | expect(options?.agent?.proxyHeaders['proxy-authorization']).toBe(`Basic ${process.env.PROXY_AUTH}`); |
| |
|
| | |
| | { |
| | try { |
| | await got.get('http://rsshub.test/rss', { |
| | headers: { |
| | 'user-agent': undefined, |
| | accept: undefined, |
| | }, |
| | }); |
| | } catch { |
| | |
| | } |
| | const options = httpSpy.mock.lastCall?.[1]; |
| | expect(options?.agent).toBeUndefined(); |
| | } |
| | }); |
| |
|
| | it('rate limiter', async () => { |
| | vi.useFakeTimers(); |
| | const fetchSpy = vi.spyOn(undici, 'fetch').mockImplementation(() => Promise.resolve(createJsonResponse())); |
| |
|
| | try { |
| | const { default: wrappedFetch } = await import('@/utils/request-rewriter/fetch'); |
| | const time = Date.now(); |
| | const tasks = Array.from({ length: 20 }).map(() => wrappedFetch('http://rsshub.test/headers')); |
| |
|
| | await vi.advanceTimersByTimeAsync(3000); |
| | await Promise.all(tasks); |
| |
|
| | expect(fetchSpy).toHaveBeenCalledTimes(20); |
| | expect(Date.now() - time).toBeGreaterThan(1500); |
| | } finally { |
| | vi.useRealTimers(); |
| | fetchSpy.mockRestore(); |
| | } |
| | }, 20000); |
| | }); |
| |
|