diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 25004b0423f8ea..0a5d8d95b335a4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,16 +12,8 @@ updates: - dependency-name: jsrsasign versions: ['>=11.0.0'] # no longer includes KJUR.crypto.Cipher for RSA # ESM only packages - - dependency-name: fanfou-sdk - versions: ['>=5.0.0'] - dependency-name: got versions: ['>=12.0.0'] - - dependency-name: ip-regex - versions: ['>=5.0.0'] - - dependency-name: query-string - versions: ['>=8.0.0'] - - dependency-name: rand-user-agent - versions: ['>=2.0.1'] - dependency-name: remark-parse versions: ['>=10.0.0'] - dependency-name: remark-preset-prettier diff --git a/lib/middleware/access-control.test.ts b/lib/middleware/access-control.test.ts index d5bfc476e519a5..639895a0ea769a 100644 --- a/lib/middleware/access-control.test.ts +++ b/lib/middleware/access-control.test.ts @@ -1,15 +1,11 @@ import { describe, expect, it, jest, afterEach } from '@jest/globals'; -import supertest from 'supertest'; import md5 from '@/utils/md5'; -import type { serve } from '@hono/node-server'; - -let server: ReturnType; process.env.NODE_NAME = 'mock'; -function checkBlock(response) { +async function checkBlock(response) { expect(response.status).toBe(403); - expect(response.text).toMatch(/Access denied\./); + expect(await response.text()).toMatch(/Access denied\./); } afterEach(() => { @@ -17,7 +13,6 @@ afterEach(() => { delete process.env.DENYLIST; delete process.env.ALLOWLIST; jest.resetModules(); - server.close(); }); describe('access-control', () => { @@ -26,46 +21,77 @@ describe('access-control', () => { const code = md5('/test/2' + key); process.env.DENYLIST = 'est/1,233.233.233.,black'; process.env.ACCESS_KEY = key; - server = (await import('@/index')).default; - const request = supertest(server); - - const response11 = await request.get('/test/1'); - checkBlock(response11); - - const response12 = await request.get('/test/1').set('X-Mock-IP', '233.233.233.233'); - checkBlock(response12); - - const response13 = await request.get('/test/1').set('user-agent', 'blackua'); - checkBlock(response13); - - const response21 = await request.get('/test/2'); + const app = (await import('@/app')).default; + + const response11 = await app.request('/test/1'); + await checkBlock(response11); + + const response12 = await app.request('/test/1', { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); + await checkBlock(response12); + + const response13 = await app.request('/test/1', { + headers: { + 'user-agent': 'blackua', + }, + }); + await checkBlock(response13); + + const response21 = await app.request('/test/2'); expect(response21.status).toBe(200); - const response22 = await request.get('/test/2').set('X-Mock-IP', '233.233.233.233'); - checkBlock(response22); + const response22 = await app.request('/test/2', { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); + await checkBlock(response22); - const response23 = await request.get('/test/2').set('user-agent', 'blackua'); - checkBlock(response23); + const response23 = await app.request('/test/2', { + headers: { + 'user-agent': 'blackua', + }, + }); + await checkBlock(response23); // wrong key/code, not on denylist - const response311 = await request.get(`/test/2?key=wrong+${key}`); + const response311 = await app.request(`/test/2?key=wrong+${key}`); expect(response311.status).toBe(200); - const response312 = await request.get(`/test/2?code=wrong+${code}`); + const response312 = await app.request(`/test/2?code=wrong+${code}`); expect(response312.status).toBe(200); // wrong key/code, on denylist - const response321 = await request.get(`/test/2?key=wrong+${key}`).set('X-Mock-IP', '233.233.233.233'); - checkBlock(response321); - - const response322 = await request.get(`/test/2?code=wrong+${code}`).set('X-Mock-IP', '233.233.233.233'); - checkBlock(response322); + const response321 = await app.request(`/test/2?key=wrong+${key}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); + await checkBlock(response321); + + const response322 = await app.request(`/test/2?code=wrong+${code}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); + await checkBlock(response322); // right key/code, on denylist - const response331 = await request.get(`/test/2?key=${key}`).set('X-Mock-IP', '233.233.233.233'); + const response331 = await app.request(`/test/2?key=${key}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response331.status).toBe(200); - const response332 = await request.get(`/test/2?code=${code}`).set('X-Mock-IP', '233.233.233.233'); + const response332 = await app.request(`/test/2?code=${code}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response332.status).toBe(200); }); @@ -74,61 +100,96 @@ describe('access-control', () => { const code = md5('/test/2' + key); process.env.ALLOWLIST = 'est/1,233.233.233.,103.31.4.0/22,white'; process.env.ACCESS_KEY = key; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response01 = await request.get('/'); + const response01 = await app.request('/'); expect(response01.status).toBe(200); - const response02 = await request.get('/robots.txt'); + const response02 = await app.request('/robots.txt'); expect(response02.status).toBe(404); - const response11 = await request.get('/test/1'); + const response11 = await app.request('/test/1'); expect(response11.status).toBe(200); - const response12 = await request.get('/test/1').set('X-Mock-IP', '233.233.233.233'); + const response12 = await app.request('/test/1', { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response12.status).toBe(200); - const response13 = await request.get('/test/1').set('user-agent', 'whiteua'); + const response13 = await app.request('/test/1', { + headers: { + 'user-agent': 'whiteua', + }, + }); expect(response13.status).toBe(200); - const response21 = await request.get('/test/2'); - checkBlock(response21); + const response21 = await app.request('/test/2'); + await checkBlock(response21); - const response22 = await request.get('/test/2').set('X-Mock-IP', '233.233.233.233'); + const response22 = await app.request('/test/2', { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response22.status).toBe(200); - const response221 = await request.get('/test/2').set('X-Mock-IP', '103.31.4.0'); + const response221 = await app.request('/test/2', { + headers: { + 'X-Mock-IP': '103.31.4.0', + }, + }); expect(response221.status).toBe(200); - const response222 = await request.get('/test/2').set('X-Mock-IP', '103.31.7.255'); + const response222 = await app.request('/test/2', { + headers: { + 'X-Mock-IP': '103.31.7.255', + }, + }); expect(response222.status).toBe(200); - const response223 = await request.get('/test/2').set('X-Mock-IP', '103.31.8.0'); - checkBlock(response223); - - const response23 = await request.get('/test/2').set('user-agent', 'whiteua'); + const response223 = await app.request('/test/2', { + headers: { + 'X-Mock-IP': '103.31.8.0', + }, + }); + await checkBlock(response223); + + const response23 = await app.request('/test/2', { + headers: { + 'user-agent': 'whiteua', + }, + }); expect(response23.status).toBe(200); // wrong key/code, not on allowlist - const response311 = await request.get(`/test/2?code=wrong+${code}`); - checkBlock(response311); + const response311 = await app.request(`/test/2?code=wrong+${code}`); + await checkBlock(response311); - const response312 = await request.get(`/test/2?key=wrong+${key}`); - checkBlock(response312); + const response312 = await app.request(`/test/2?key=wrong+${key}`); + await checkBlock(response312); // wrong key/code, on allowlist - const response321 = await request.get(`/test/2?code=wrong+${code}`).set('X-Mock-IP', '233.233.233.233'); + const response321 = await app.request(`/test/2?code=wrong+${code}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response321.status).toBe(200); - const response322 = await request.get(`/test/2?key=wrong+${key}`).set('X-Mock-IP', '233.233.233.233'); + const response322 = await app.request(`/test/2?key=wrong+${key}`, { + headers: { + 'X-Mock-IP': '233.233.233.233', + }, + }); expect(response322.status).toBe(200); // right key/code - const response331 = await request.get(`/test/2?code=${code}`); + const response331 = await app.request(`/test/2?code=${code}`); expect(response331.status).toBe(200); - const response332 = await request.get(`/test/2?key=${key}`); + const response332 = await app.request(`/test/2?key=${key}`); expect(response332.status).toBe(200); }); @@ -136,33 +197,32 @@ describe('access-control', () => { const key = '1L0veRSSHub'; const code = md5('/test/2' + key); process.env.ACCESS_KEY = key; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response01 = await request.get('/'); + const response01 = await app.request('/'); expect(response01.status).toBe(200); - const response02 = await request.get('/robots.txt'); + const response02 = await app.request('/robots.txt'); expect(response02.status).toBe(404); - const response11 = await request.get('/test/1'); - checkBlock(response11); + const response11 = await app.request('/test/1'); + await checkBlock(response11); - const response21 = await request.get('/test/2'); - checkBlock(response21); + const response21 = await app.request('/test/2'); + await checkBlock(response21); // wrong key/code - const response321 = await request.get(`/test/2?key=wrong+${key}`); - checkBlock(response321); + const response321 = await app.request(`/test/2?key=wrong+${key}`); + await checkBlock(response321); - const response322 = await request.get(`/test/2?code=wrong+${code}`); - checkBlock(response322); + const response322 = await app.request(`/test/2?code=wrong+${code}`); + await checkBlock(response322); // right key/code - const response331 = await request.get(`/test/2?key=${key}`); + const response331 = await app.request(`/test/2?key=${key}`); expect(response331.status).toBe(200); - const response332 = await request.get(`/test/2?code=${code}`); + const response332 = await app.request(`/test/2?code=${code}`); expect(response332.status).toBe(200); }); }); diff --git a/lib/middleware/anti-hotlink.test.ts b/lib/middleware/anti-hotlink.test.ts index f3b463b14d9ee1..40240e9090b02a 100644 --- a/lib/middleware/anti-hotlink.test.ts +++ b/lib/middleware/anti-hotlink.test.ts @@ -1,12 +1,8 @@ import { describe, expect, it, jest, afterEach, afterAll } from '@jest/globals'; -import supertest from 'supertest'; import Parser from 'rss-parser'; -import querystring from 'query-string'; -import type { serve } from '@hono/node-server'; const parser = new Parser(); jest.setTimeout(50000); -let server: ReturnType; afterAll(() => { delete process.env.HOTLINK_TEMPLATE; @@ -20,7 +16,6 @@ afterEach(() => { delete process.env.HOTLINK_INCLUDE_PATHS; delete process.env.HOTLINK_EXCLUDE_PATHS; delete process.env.ALLOW_USER_HOTLINK_TEMPLATE; - server.close(); jest.resetModules(); }); @@ -122,17 +117,21 @@ const expects = { }; const testAntiHotlink = async (path, expectObj, query?: string | Record) => { - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; let queryStr; if (query) { - queryStr = typeof query === 'string' ? query : querystring.stringify(query); + queryStr = + typeof query === 'string' + ? query + : Object.entries(query) + .map(([key, value]) => `${key}=${value}`) + .join('&'); } path = path + (queryStr ? `?${queryStr}` : ''); - const response = await request.get(path); - const parsed = await parser.parseString(response.text); + const response = await app.request(path); + const parsed = await parser.parseString(await response.text()); expect({ items: parsed.items.slice(0, expectObj.items.length).map((i) => i.content), desc: parsed.description, @@ -247,9 +246,8 @@ describe('anti-hotlink', () => { it('invalid-property', async () => { process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${createObjectURL}'; - server = (await import('@/index')).default; - const request = supertest(server); - const response = await request.get('/test/complicated'); - expect(response.text).toContain('Error: Invalid URL property: createObjectURL'); + const app = (await import('@/app')).default; + const response = await app.request('/test/complicated'); + expect(await response.text()).toContain('Error: Invalid URL property: createObjectURL'); }); }); diff --git a/lib/middleware/cache.test.ts b/lib/middleware/cache.test.ts index 7989b591e94719..c7187c766f67cd 100644 --- a/lib/middleware/cache.test.ts +++ b/lib/middleware/cache.test.ts @@ -1,11 +1,8 @@ import { describe, expect, it, jest, afterEach, afterAll, beforeAll } from '@jest/globals'; -import supertest from 'supertest'; import Parser from 'rss-parser'; import wait from '@/utils/wait'; -import type { serve } from '@hono/node-server'; const parser = new Parser(); -let server: ReturnType; beforeAll(() => { process.env.CACHE_EXPIRE = '1'; @@ -15,7 +12,6 @@ beforeAll(() => { afterEach(() => { delete process.env.CACHE_TYPE; jest.resetModules(); - server?.close(); }); afterAll(() => { @@ -25,14 +21,13 @@ afterAll(() => { describe('cache', () => { it('memory', async () => { process.env.CACHE_TYPE = 'memory'; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response1 = await request.get('/test/cache'); - const response2 = await request.get('/test/cache'); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); - const parsed1 = await parser.parseString(response1.text); - const parsed2 = await parser.parseString(response2.text); + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); delete parsed1.lastBuildDate; delete parsed2.lastBuildDate; @@ -43,29 +38,29 @@ describe('cache', () => { expect(parsed2).toMatchObject(parsed1); expect(response2.status).toBe(200); - expect(response2.headers['rsshub-cache-status']).toBe('HIT'); + expect(response2.headers.get('rsshub-cache-status')).toBe('HIT'); await wait(1 * 1000 + 100); - const response3 = await request.get('/test/cache'); + const response3 = await app.request('/test/cache'); expect(response3.headers).not.toHaveProperty('rsshub-cache-status'); - const parsed3 = await parser.parseString(response3.text); + const parsed3 = await parser.parseString(await response3.text()); await wait(3 * 1000 + 100); - const response4 = await request.get('/test/cache'); - const parsed4 = await parser.parseString(response4.text); + const response4 = await app.request('/test/cache'); + const parsed4 = await parser.parseString(await response4.text()); expect(parsed1.items[0].content).toBe('Cache1'); expect(parsed2.items[0].content).toBe('Cache1'); expect(parsed3.items[0].content).toBe('Cache1'); expect(parsed4.items[0].content).toBe('Cache2'); - await request.get('/test/refreshCache'); + await app.request('/test/refreshCache'); await wait(1 * 1000 + 100); - const response5 = await request.get('/test/refreshCache'); - const parsed5 = await parser.parseString(response5.text); + const response5 = await app.request('/test/refreshCache'); + const parsed5 = await parser.parseString(await response5.text()); await wait(2 * 1000 + 100); - const response6 = await request.get('/test/refreshCache'); - const parsed6 = await parser.parseString(response6.text); + const response6 = await app.request('/test/refreshCache'); + const parsed6 = await parser.parseString(await response6.text()); expect(parsed5.items[0].content).toBe('1 1'); expect(parsed6.items[0].content).toBe('1 0'); @@ -73,14 +68,14 @@ describe('cache', () => { it('redis', async () => { process.env.CACHE_TYPE = 'redis'; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response1 = await request.get('/test/cache'); - const response2 = await request.get('/test/cache'); + await wait(500); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); - const parsed1 = await parser.parseString(response1.text); - const parsed2 = await parser.parseString(response2.text); + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); delete parsed1.lastBuildDate; delete parsed2.lastBuildDate; @@ -91,29 +86,29 @@ describe('cache', () => { expect(parsed2).toMatchObject(parsed1); expect(response2.status).toBe(200); - expect(response2.headers['rsshub-cache-status']).toBe('HIT'); + expect(response2.headers.get('rsshub-cache-status')).toBe('HIT'); await wait(1 * 1000 + 100); - const response3 = await request.get('/test/cache'); + const response3 = await app.request('/test/cache'); expect(response3.headers).not.toHaveProperty('rsshub-cache-status'); - const parsed3 = await parser.parseString(response3.text); + const parsed3 = await parser.parseString(await response3.text()); await wait(3 * 1000 + 100); - const response4 = await request.get('/test/cache'); - const parsed4 = await parser.parseString(response4.text); + const response4 = await app.request('/test/cache'); + const parsed4 = await parser.parseString(await response4.text()); expect(parsed1.items[0].content).toBe('Cache1'); expect(parsed2.items[0].content).toBe('Cache1'); expect(parsed3.items[0].content).toBe('Cache1'); expect(parsed4.items[0].content).toBe('Cache2'); - await request.get('/test/refreshCache'); + await app.request('/test/refreshCache'); await wait(1 * 1000 + 100); - const response5 = await request.get('/test/refreshCache'); - const parsed5 = await parser.parseString(response5.text); + const response5 = await app.request('/test/refreshCache'); + const parsed5 = await parser.parseString(await response5.text()); await wait(2 * 1000 + 100); - const response6 = await request.get('/test/refreshCache'); - const parsed6 = await parser.parseString(response6.text); + const response6 = await app.request('/test/refreshCache'); + const parsed6 = await parser.parseString(await response6.text()); expect(parsed5.items[0].content).toBe('1 1'); expect(parsed6.items[0].content).toBe('1 0'); @@ -124,16 +119,15 @@ describe('cache', () => { it('redis with quit', async () => { process.env.CACHE_TYPE = 'redis'; - server = (await import('@/index')).default; const cache = (await import('@/utils/cache')).default; await cache.clients.redisClient!.quit(); - const request = supertest(server); + const app = (await import('@/app')).default; - const response1 = await request.get('/test/cache'); - const response2 = await request.get('/test/cache'); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); - const parsed1 = await parser.parseString(response1.text); - const parsed2 = await parser.parseString(response2.text); + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); expect(response2.status).toBe(200); expect(response2.headers).not.toHaveProperty('rsshub-cache-status'); @@ -145,14 +139,13 @@ describe('cache', () => { it('redis with error', async () => { process.env.CACHE_TYPE = 'redis'; process.env.REDIS_URL = 'redis://wrongpath:6379'; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response1 = await request.get('/test/cache'); - const response2 = await request.get('/test/cache'); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); - const parsed1 = await parser.parseString(response1.text); - const parsed2 = await parser.parseString(response2.text); + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); expect(response2.status).toBe(200); expect(response2.headers).not.toHaveProperty('rsshub-cache-status'); @@ -166,14 +159,13 @@ describe('cache', () => { it('no cache', async () => { process.env.CACHE_TYPE = 'NO'; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; - const response1 = await request.get('/test/cache'); - const response2 = await request.get('/test/cache'); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); - const parsed1 = await parser.parseString(response1.text); - const parsed2 = await parser.parseString(response2.text); + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); expect(response2.status).toBe(200); expect(response2.headers).not.toHaveProperty('rsshub-cache-status'); @@ -184,11 +176,10 @@ describe('cache', () => { it('throws URL key', async () => { process.env.CACHE_TYPE = 'memory'; - server = (await import('@/index')).default; - const request = supertest(server); + const app = (await import('@/app')).default; try { - const response = await request.get('/test/cacheUrlKey'); + const response = await app.request('/test/cacheUrlKey'); expect(response).toThrow(Error); } catch (error: any) { expect(error.message).toContain('Cache key must be a string'); diff --git a/lib/middleware/debug.test.ts b/lib/middleware/debug.test.ts index bf7071b1ceaf50..49cf06a6520444 100644 --- a/lib/middleware/debug.test.ts +++ b/lib/middleware/debug.test.ts @@ -1,11 +1,9 @@ -import { describe, expect, it, afterAll } from '@jest/globals'; -import supertest from 'supertest'; -import server from '@/index'; +import { describe, expect, it } from '@jest/globals'; +import app from '@/app'; import { load } from 'cheerio'; process.env.NODE_NAME = 'mock'; -const request = supertest(server); let gitHash; try { gitHash = require('git-rev-sync').short(); @@ -13,22 +11,22 @@ try { gitHash = (process.env.HEROKU_SLUG_COMMIT && process.env.HEROKU_SLUG_COMMIT.slice(0, 7)) || 'unknown'; } -afterAll(() => { - server.close(); -}); - describe('debug', () => { it('debug', async () => { - const response1 = await request.get('/test/1'); - const etag = response1.headers.etag; - await request.get('/test/1').set('If-None-Match', etag); - await request.get('/test/2'); - await request.get('/test/empty'); - await request.get('/test/empty'); + const response1 = await app.request('/test/1'); + const etag = response1.headers.get('etag'); + await app.request('/test/1', { + headers: { + 'If-None-Match': etag!, + }, + }); + await app.request('/test/2'); + await app.request('/test/empty'); + await app.request('/test/empty'); - const response = await request.get('/'); + const response = await app.request('/'); - const $ = load(response.text); + const $ = load(await response.text()); $('.debug-item').each((index, item) => { const key = $(item).find('.debug-key').html()?.trim(); const value = $(item).find('.debug-value').html()?.trim(); diff --git a/lib/middleware/filter-engine.test.ts b/lib/middleware/filter-engine.test.ts index a7677650ba7205..689ff215191ec9 100644 --- a/lib/middleware/filter-engine.test.ts +++ b/lib/middleware/filter-engine.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it, afterAll, jest, afterEach } from '@jest/globals'; -import supertest from 'supertest'; jest.setTimeout(50000); @@ -14,29 +13,29 @@ afterEach(() => { describe('filter-engine', () => { it(`filter RE2 engine ReDoS attack`, async () => { - const request = supertest((await import('@/index')).default); + const app = (await import('@/app')).default; - const response = await request.get('/test/1?filter=abc(%3F%3Ddef)'); + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); expect(response.status).toBe(404); - expect(response.text).toMatch(/RE2JSSyntaxException/); + expect(await response.text()).toMatch(/RE2JSSyntaxException/); }); it(`filter Regexp engine backward compatibility`, async () => { process.env.FILTER_REGEX_ENGINE = 'regexp'; - const request = supertest((await import('@/index')).default); + const app = (await import('@/app')).default; - const response = await request.get('/test/1?filter=abc(%3F%3Ddef)'); + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); expect(response.status).toBe(200); }); it(`filter Regexp engine test config`, async () => { process.env.FILTER_REGEX_ENGINE = 'somethingelse'; - const request = supertest((await import('@/index')).default); + const app = (await import('@/app')).default; - const response = await request.get('/test/1?filter=abc(%3F%3Ddef)'); + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); expect(response.status).toBe(404); - expect(response.text).toMatch(/somethingelse/); + expect(await response.text()).toMatch(/somethingelse/); }); }); diff --git a/lib/middleware/header.test.ts b/lib/middleware/header.test.ts index 237e851edf5b42..34f9bc58bee3af 100644 --- a/lib/middleware/header.test.ts +++ b/lib/middleware/header.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it, afterAll } from '@jest/globals'; -import supertest from 'supertest'; process.env.NODE_NAME = 'mock'; process.env.ALLOW_ORIGIN = 'rsshub.mock'; @@ -13,24 +12,28 @@ afterAll(() => { describe('header', () => { it(`header`, async () => { - const request = supertest((await import('@/index')).default); + const app = (await import('@/app')).default; const { config } = await import('@/config'); - const response = await request.get('/test/1'); - expect(response.headers['access-control-allow-origin']).toBe('rsshub.mock'); - expect(response.headers['access-control-allow-methods']).toBe('GET'); - expect(response.headers['content-type']).toBe('application/xml; charset=utf-8'); - expect(response.headers['cache-control']).toBe(`public, max-age=${config.cache.routeExpire}`); - expect(response.headers['last-modified']).toBe(response.text.match(/(.*)<\/lastBuildDate>/)?.[1]); - expect(response.headers['rsshub-node']).toBe('mock'); - expect(response.headers.etag).not.toBe(undefined); - etag = response.headers.etag; + const response = await app.request('/test/1'); + expect(response.headers.get('access-control-allow-origin')).toBe('rsshub.mock'); + expect(response.headers.get('access-control-allow-methods')).toBe('GET'); + expect(response.headers.get('content-type')).toBe('application/xml; charset=utf-8'); + expect(response.headers.get('cache-control')).toBe(`public, max-age=${config.cache.routeExpire}`); + expect(response.headers.get('last-modified')).toBe((await response.text()).match(/(.*)<\/lastBuildDate>/)?.[1]); + expect(response.headers.get('rsshub-node')).toBe('mock'); + expect(response.headers.get('etag')).not.toBe(undefined); + etag = response.headers.get('etag'); }); it(`etag`, async () => { - const request = supertest((await import('@/index')).default); - const response = await request.get('/test/1').set('If-None-Match', etag); + const app = (await import('@/app')).default; + const response = await app.request('/test/1', { + headers: { + 'If-None-Match': etag, + }, + }); expect(response.status).toBe(304); - expect(response.text).toBe(''); - expect(response.headers['last-modified']).toBe(undefined); + expect(await response.text()).toBe(''); + expect(response.headers.get('last-modified')).toBe(null); }); }); diff --git a/lib/middleware/header.ts b/lib/middleware/header.ts index f8badd78532ad6..f43d8614548f81 100644 --- a/lib/middleware/header.ts +++ b/lib/middleware/header.ts @@ -39,7 +39,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => { const ifNoneMatch = ctx.req.header('If-None-Match') ?? null; if (etagMatches(etag, ifNoneMatch)) { ctx.status(304); - return ctx.body(null); + ctx.set('no-content', true); } else { ctx.header('Last-Modified', lastBuildDate); } diff --git a/lib/middleware/parameter.test.ts b/lib/middleware/parameter.test.ts index 5bf88247617bd5..4250943118462a 100644 --- a/lib/middleware/parameter.test.ts +++ b/lib/middleware/parameter.test.ts @@ -1,138 +1,132 @@ -import { describe, expect, it, afterAll, jest } from '@jest/globals'; -import supertest from 'supertest'; -import server from '@/index'; +import { describe, expect, it, jest } from '@jest/globals'; +import app from '@/app'; import Parser from 'rss-parser'; import { config } from '@/config'; import nock from 'nock'; -const request = supertest(server); const parser = new Parser(); -afterAll(() => { - server.close(); -}); - describe('filter', () => { it(`filter`, async () => { - const response = await request.get('/test/1?filter=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Title4'); expect(parsed.items[1].title).toBe('Title5'); }); it(`filter filter_case_sensitive default`, async () => { - const response = await request.get('/test/1?filter=description4|title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter=description4|title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(0); }); it(`filter filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filter=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Title4'); expect(parsed.items[1].title).toBe('Title5'); }); it(`filter_title`, async () => { - const response = await request.get('/test/1?filter_title=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_title=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Title5'); }); it(`filter_title filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filter_title=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_title=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Title5'); }); it(`filter_description`, async () => { - const response = await request.get('/test/1?filter_description=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_description=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Title4'); }); it(`filter_description filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filter_description=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_description=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Title4'); }); it(`filter_author`, async () => { - const response = await request.get('/test/1?filter_author=DIYgod4|DIYgod5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_author=DIYgod4|DIYgod5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Title4'); expect(parsed.items[1].title).toBe('Title5'); }); it(`filter_author filter_case_sensitive default`, async () => { - const response = await request.get('/test/1?filter_author=diygod4|diygod5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_author=diygod4|diygod5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(0); }); it(`filter_author filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filter_author=diygod4|diygod5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filter_author=diygod4|diygod5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Title4'); expect(parsed.items[1].title).toBe('Title5'); }); it(`filter_category`, async () => { - const response = await request.get('/test/filter?filter_category=Category0|Category1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filter_category=Category0|Category1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Filter Title1'); expect(parsed.items[1].title).toBe('Filter Title2'); }); it(`filter_category filter_case_sensitive default`, async () => { - const response = await request.get('/test/filter?filter_category=category0|category1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filter_category=category0|category1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(0); }); it(`filter_category filter_case_sensitive=false`, async () => { - const response = await request.get('/test/filter?filter_category=category0|category1&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filter_category=category0|category1&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Filter Title1'); expect(parsed.items[1].title).toBe('Filter Title2'); }); it(`filter_category filter_case_sensitive=false category string`, async () => { - const response = await request.get('/test/filter?filter_category=category3&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filter_category=category3&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Filter Title3'); }); it(`filter_category illegal_category`, async () => { - const response = await request.get('/test/filter-illegal-category?filter_category=CategoryIllegal'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter-illegal-category?filter_category=CategoryIllegal'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].categories?.length).toBe(1); expect(parsed.items[0].categories?.[0]).toBe('CategoryIllegal'); }); it(`filter_time`, async () => { - const response = await request.get('/test/current_time?filter_time=25'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/current_time?filter_time=25'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(2); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); }); it(`filterout`, async () => { - const response = await request.get('/test/1?filterout=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(3); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -140,8 +134,8 @@ describe('filter', () => { }); it(`filterout filter_case_sensitive default`, async () => { - const response = await request.get('/test/1?filterout=description4|title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout=description4|title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(5); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -149,8 +143,8 @@ describe('filter', () => { }); it(`filterout filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filterout=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(3); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -158,8 +152,8 @@ describe('filter', () => { }); it(`filterout_title`, async () => { - const response = await request.get('/test/1?filterout_title=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_title=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(4); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -168,8 +162,8 @@ describe('filter', () => { }); it(`filterout_title filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filterout_title=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_title=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(4); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -178,8 +172,8 @@ describe('filter', () => { }); it(`filterout_description`, async () => { - const response = await request.get('/test/1?filterout_description=Description4|Title5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_description=Description4|Title5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(4); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -188,8 +182,8 @@ describe('filter', () => { }); it(`filterout_description filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filterout_description=description4|title5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_description=description4|title5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(4); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -198,8 +192,8 @@ describe('filter', () => { }); it(`filterout_author`, async () => { - const response = await request.get('/test/1?filterout_author=DIYgod4|DIYgod5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_author=DIYgod4|DIYgod5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(3); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -207,8 +201,8 @@ describe('filter', () => { }); it(`filterout_author filter_case_sensitive default`, async () => { - const response = await request.get('/test/1?filterout_author=diygod4|diygod5'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_author=diygod4|diygod5'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(5); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -216,8 +210,8 @@ describe('filter', () => { }); it(`filterout_author filter_case_sensitive=false`, async () => { - const response = await request.get('/test/1?filterout_author=diygod4|diygod5&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?filterout_author=diygod4|diygod5&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(3); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -225,8 +219,8 @@ describe('filter', () => { }); it(`filterout_category`, async () => { - const response = await request.get('/test/filter?filterout_category=Category0|Category1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filterout_category=Category0|Category1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(6); expect(parsed.items[0].title).toBe('Filter Title3'); expect(parsed.items[1].title).toBe('Title1'); @@ -234,8 +228,8 @@ describe('filter', () => { }); it(`filterout_category filter_case_sensitive default`, async () => { - const response = await request.get('/test/filter?filterout_category=category0|category1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filterout_category=category0|category1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(8); expect(parsed.items[0].title).toBe('Filter Title1'); expect(parsed.items[1].title).toBe('Filter Title2'); @@ -244,8 +238,8 @@ describe('filter', () => { }); it(`filterout_category filter_case_sensitive=false`, async () => { - const response = await request.get('/test/filter?filterout_category=category0|category1&filter_case_sensitive=false'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filterout_category=category0|category1&filter_case_sensitive=false'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(6); expect(parsed.items[0].title).toBe('Filter Title3'); expect(parsed.items[1].title).toBe('Title1'); @@ -253,15 +247,15 @@ describe('filter', () => { }); it(`filter combination`, async () => { - const response = await request.get('/test/filter?filter_title=Filter&filter_description=Description1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filter_title=Filter&filter_description=Description1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(1); expect(parsed.items[0].title).toBe('Filter Title1'); }); it(`filterout combination`, async () => { - const response = await request.get('/test/filter?filterout_title=Filter&filterout_description=Description1'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/filter?filterout_title=Filter&filterout_description=Description1'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(4); expect(parsed.items[0].title).toBe('Title2'); }); @@ -269,8 +263,8 @@ describe('filter', () => { describe('limit', () => { it(`limit`, async () => { - const response = await request.get('/test/1?limit=3'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?limit=3'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(3); expect(parsed.items[0].title).toBe('Title1'); expect(parsed.items[1].title).toBe('Title2'); @@ -280,9 +274,9 @@ describe('limit', () => { describe('sorted', () => { it('sorted', async () => { - const response = await request.get('/test/sort?sorted=false'); + const response = await app.request('/test/sort?sorted=false'); expect(response.status).toBe(200); - const parsed = await parser.parseString(response.text); + const parsed = await parser.parseString(await response.text()); expect(parsed.items[0].title).toBe('Sort Title 0'); expect(parsed.items[1].title).toBe('Sort Title 1'); expect(parsed.items[2].title).toBe('Sort Title 2'); @@ -292,8 +286,8 @@ describe('sorted', () => { describe('tgiv', () => { it(`tgiv`, async () => { - const response = await request.get('/test/1?tgiv=test'); - const parsed = await parser.parseString(response.text); + const response = await app.request('/test/1?tgiv=test'); + const parsed = await parser.parseString(await response.text()); expect(parsed.items[0].link).toBe(`https://t.me/iv?url=https%3A%2F%2Fgithub.com%2FDIYgod%2FRSSHub%2Fissues%2F1&rhash=test`); expect(parsed.items[1].link).toBe(`https://t.me/iv?url=https%3A%2F%2Fgithub.com%2FDIYgod%2FRSSHub%2Fissues%2F2&rhash=test`); }); @@ -301,49 +295,49 @@ describe('tgiv', () => { describe('empty', () => { it(`empty`, async () => { - const response1 = await request.get('/test/empty'); + const response1 = await app.request('/test/empty'); expect(response1.status).toBe(404); - expect(response1.text).toMatch(/Error: this route is empty/); + expect(await response1.text()).toMatch(/Error: this route is empty/); - const response2 = await request.get('/test/1?limit=0'); + const response2 = await app.request('/test/1?limit=0'); expect(response2.status).toBe(200); - const parsed = await parser.parseString(response2.text); + const parsed = await parser.parseString(await response2.text()); expect(parsed.items.length).toBe(0); }); }); describe('allow_empty', () => { it(`allow_empty`, async () => { - const response = await request.get('/test/allow_empty'); + const response = await app.request('/test/allow_empty'); expect(response.status).toBe(200); - const parsed = await parser.parseString(response.text); + const parsed = await parser.parseString(await response.text()); expect(parsed.items.length).toBe(0); }); }); describe('wrong_path', () => { it(`wrong_path`, async () => { - const response = await request.get('/wrong'); + const response = await app.request('/wrong'); expect(response.status).toBe(404); - expect(response.headers['cache-control']).toBe(`public, max-age=${config.cache.routeExpire}`); - expect(response.text).toMatch(/Error message: wrong path/); + expect(response.headers.get('cache-control')).toBe(`public, max-age=${config.cache.routeExpire}`); + expect(await response.text()).toMatch(/Error message: wrong path/); }); }); describe('fulltext_mode', () => { it(`fulltext`, async () => { - const response = await request.get('/test/1?mode=fulltext'); + const response = await app.request('/test/1?mode=fulltext'); expect(response.status).toBe(200); - const parsed = await parser.parseString(response.text); + const parsed = await parser.parseString(await response.text()); expect(parsed.items[0].content).not.toBe(undefined); }, 60000); }); describe('complicated_description', () => { it(`complicated_description`, async () => { - const response = await request.get('/test/complicated'); + const response = await app.request('/test/complicated'); expect(response.status).toBe(200); - const parsed = await parser.parseString(response.text); + const parsed = await parser.parseString(await response.text()); expect(parsed.items[0].content).toBe(` @@ -361,9 +355,9 @@ describe('complicated_description', () => { describe('multimedia_description', () => { it(`multimedia_description`, async () => { - const response = await request.get('/test/multimedia'); + const response = await app.request('/test/multimedia'); expect(response.status).toBe(200); - const parsed = await parser.parseString(response.text); + const parsed = await parser.parseString(await response.text()); expect(parsed.items[0].content).toBe(`