Skip to content

Commit

Permalink
test: add full routes test (DIYgod#14934)
Browse files Browse the repository at this point in the history
* test: add critical routes test

* fix: youtube channels config

* test: add full routes test

* test: output full routes test result

* test: build routes before test

* test: use one reporter

* test: fix cache test errors

* test: fix cache test errors

* chore: puppeteerrc.cjs

* chore: puppeteerrc.cjs

* chore: puppeteerrc.cjs

* fix: build script

* chore: puppeteerrc rename

* test: route test names

* chore: pass test result to docs
  • Loading branch information
DIYgod authored Mar 24, 2024
1 parent 2fc3b3a commit b774c60
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 198 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/build-assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ jobs:
- name: Install dependencies (yarn)
run: pnpm i
- name: Build assets
run: npm run build
run: pnpm build
- name: Build full routes test result
run: pnpm vitest:fullroutes
- name: Build docs
run: pnpm build:docs
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
branches-ignore:
- 'dependabot/**'
paths:
- 'test/**'
- 'lib/**'
- 'package.json'
- 'pnpm-lock.yaml'
Expand Down Expand Up @@ -43,6 +42,8 @@ jobs:
run: pnpm i
- name: Run postinstall script for dependencies
run: pnpm rb
- name: Build routes
run: pnpm build
- name: Test all and generate coverage
run: pnpm run vitest:coverage
env:
Expand Down Expand Up @@ -84,6 +85,8 @@ jobs:
run: pnpm i
- name: Run postinstall script for dependencies
run: pnpm rb
- name: Build routes
run: pnpm build
- name: Install Chromium
if: ${{ matrix.chromium.dependency != '' }}
# 'chromium-browser' from Ubuntu APT repo is a dummy package. Its version (85.0.4183.83) means
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ FROM node:21-bookworm-slim AS chromium-downloader
# Yeah, downloading Chromium never needs those dependencies below.

WORKDIR /app
COPY ./.puppeteerrc.js /app/
COPY ./.puppeteerrc.cjs /app/
COPY --from=dep-version-parser /ver/.puppeteer_version /app/.puppeteer_version

ARG TARGETPLATFORM
Expand Down
15 changes: 4 additions & 11 deletions lib/middleware/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { describe, expect, it, vi, afterEach, afterAll, beforeAll } from 'vitest';
import { describe, expect, it, vi, afterEach } from 'vitest';
import Parser from 'rss-parser';
import wait from '@/utils/wait';

const parser = new Parser();
process.env.CACHE_EXPIRE = '1';
process.env.CACHE_CONTENT_EXPIRE = '3';

beforeAll(() => {
process.env.CACHE_EXPIRE = '1';
process.env.CACHE_CONTENT_EXPIRE = '3';
});
const parser = new Parser();

afterEach(() => {
delete process.env.CACHE_TYPE;
vi.resetModules();
});

afterAll(() => {
delete process.env.CACHE_EXPIRE;
});

describe('cache', () => {
it('memory', async () => {
process.env.CACHE_TYPE = 'memory';
Expand Down
59 changes: 2 additions & 57 deletions lib/registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,8 @@
import { describe, expect, it, afterAll } from 'vitest';
process.env.SOCKET = 'socket';

import { describe, expect, it } from 'vitest';
import app from '@/app';
import Parser from 'rss-parser';
const parser = new Parser();
import { config } from '@/config';

afterAll(() => {
delete process.env.SOCKET;
});

async function checkRSS(response) {
const checkDate = (date) => {
expect(date).toEqual(expect.any(String));
expect(Date.parse(date)).toEqual(expect.any(Number));
expect(Date.now() - +new Date(date)).toBeGreaterThan(-1000 * 60 * 60 * 24 * 5);
expect(Date.now() - +new Date(date)).toBeLessThan(1000 * 60 * 60 * 24 * 30 * 12 * 10);
};

const parsed = await parser.parseString(await response.text());

expect(parsed).toEqual(expect.any(Object));
expect(parsed.title).toEqual(expect.any(String));
expect(parsed.title).not.toBe('RSSHub');
expect(parsed.description).toEqual(expect.any(String));
expect(parsed.link).toEqual(expect.any(String));
expect(parsed.lastBuildDate).toEqual(expect.any(String));
expect(parsed.ttl).toEqual(Math.trunc(config.cache.routeExpire / 60) + '');
expect(parsed.items).toEqual(expect.any(Array));
checkDate(parsed.lastBuildDate);

// check items
const guids: (string | undefined)[] = [];
for (const item of parsed.items) {
expect(item).toEqual(expect.any(Object));
expect(item.title).toEqual(expect.any(String));
expect(item.link).toEqual(expect.any(String));
expect(item.content).toEqual(expect.any(String));
expect(item.guid).toEqual(expect.any(String));
if (item.pubDate) {
expect(item.pubDate).toEqual(expect.any(String));
checkDate(item.pubDate);
}

// guid must be unique
expect(guids).not.toContain(item.guid);
guids.push(item.guid);
}
}

describe('router', () => {
describe('registry', () => {
// root
it(`/`, async () => {
const response = await app.request('/');
Expand All @@ -58,14 +11,6 @@ describe('router', () => {
expect(response.headers.get('cache-control')).toBe('no-cache');
});

// route
it(`/test/1`, async () => {
const response = await app.request('/test/1');
expect(response.status).toBe(200);

await checkRSS(response);
});

// robots.txt
it('/robots.txt', async () => {
config.disallowRobot = false;
Expand Down
15 changes: 2 additions & 13 deletions lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { serveStatic } from '@hono/node-server/serve-static';

import index from '@/routes/index';
import robotstxt from '@/routes/robots.txt';
import { namespace as testNamespace } from './routes/test/namespace';
import { route as testRoute } from '@/routes/test/index';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

Expand All @@ -27,18 +25,9 @@ let namespaces: Record<

switch (process.env.NODE_ENV) {
case 'test':
modules = {
'/test/namespace.ts': {
namespace: testNamespace,
},
'/test/index.ts': {
route: testRoute,
},
};
break;
case 'production':
// eslint-disable-next-line n/no-unpublished-require
namespaces = require('../assets/build/routes.json');
// @ts-expect-error
namespaces = await import('../assets/build/routes.json');
break;
default:
modules = directoryImport({
Expand Down
78 changes: 78 additions & 0 deletions lib/routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, expect, it } from 'vitest';
import app from '@/app';
import Parser from 'rss-parser';
const parser = new Parser();
import { config } from '@/config';

process.env.ALLOW_USER_SUPPLY_UNSAFE_DOMAIN = 'true';

const routes = {
'/test/:id': '/test/1',
};
if (process.env.FULL_ROUTES_TEST) {
const { namespaces } = await import('@/registry');
for (const namespace in namespaces) {
for (const route in namespaces[namespace].routes) {
const requireConfig = namespaces[namespace].routes[route].features?.requireConfig;
let configs;
if (typeof requireConfig !== 'boolean') {
configs = requireConfig
?.filter((config) => !config.optional)
.map((config) => config.name)
.filter((name) => name !== 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN');
}
if (namespaces[namespace].routes[route].example && !configs?.length) {
routes[`/${namespace}${route}`] = namespaces[namespace].routes[route].example;
}
}
}
}

async function checkRSS(response) {
const checkDate = (date) => {
expect(date).toEqual(expect.any(String));
expect(Date.parse(date)).toEqual(expect.any(Number));
expect(Date.now() - +new Date(date)).toBeGreaterThan(-1000 * 60 * 60 * 24 * 5);
expect(Date.now() - +new Date(date)).toBeLessThan(1000 * 60 * 60 * 24 * 30 * 12 * 10);
};

const parsed = await parser.parseString(await response.text());

expect(parsed).toEqual(expect.any(Object));
expect(parsed.title).toEqual(expect.any(String));
expect(parsed.title).not.toBe('RSSHub');
expect(parsed.description).toEqual(expect.any(String));
expect(parsed.link).toEqual(expect.any(String));
expect(parsed.lastBuildDate).toEqual(expect.any(String));
expect(parsed.ttl).toEqual(Math.trunc(config.cache.routeExpire / 60) + '');
expect(parsed.items).toEqual(expect.any(Array));
checkDate(parsed.lastBuildDate);

// check items
const guids: (string | undefined)[] = [];
for (const item of parsed.items) {
expect(item).toEqual(expect.any(Object));
expect(item.title).toEqual(expect.any(String));
expect(item.link).toEqual(expect.any(String));
expect(item.content).toEqual(expect.any(String));
expect(item.guid).toEqual(expect.any(String));
if (item.pubDate) {
expect(item.pubDate).toEqual(expect.any(String));
checkDate(item.pubDate);
}

// guid must be unique
expect(guids).not.toContain(item.guid);
guids.push(item.guid);
}
}

describe('routes', () => {
for (const route in routes) {
it.concurrent(route, async () => {
const response = await app.request(routes[route]);
expect(response.status).toBe(200);
await checkRSS(response);
});
}
});
1 change: 1 addition & 0 deletions lib/routes/afdian/dynamic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import got from '@/utils/got';
import type { Route } from '@/types';

export const route: Route = {
path: '/dynamic/:uid?',
Expand Down
1 change: 0 additions & 1 deletion lib/routes/bilibili/dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ async function handler(ctx) {
headers: {
Referer: `https://space.bilibili.com/${uid}/`,
},
transformResponse: [(data) => data],
});
const cards = JSONbig.parse(response.body).data.cards;

Expand Down
10 changes: 3 additions & 7 deletions lib/routes/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ async function handler(ctx) {
break;

case 'cache': {
const description = await cache.tryGet(
'test',
() => ({
text: `Cache${++cacheIndex}`,
}),
config.cache.routeExpire * 2
);
const description = await cache.tryGet('test', () => ({
text: `Cache${++cacheIndex}`,
}));
item.push({
title: 'Cache Title',
description: description.text,
Expand Down
7 changes: 6 additions & 1 deletion lib/routes/youtube/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export const route: Route = {
example: '/youtube/channel/UCDwDMPOZfxVV0x_dz0eQ8KQ',
parameters: { id: 'YouTube channel id', embed: 'Default to embed the video, set to any value to disable embedding' },
features: {
requireConfig: false,
requireConfig: [
{
name: 'YOUTUBE_KEY',
description: ' YouTube API Key, support multiple keys, split them with `,`, [API Key application](https://console.developers.google.com/)',
},
],
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
Expand Down
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
"files": [
"lib"
],
"type": "module",
"scripts": {
"build": "tsx scripts/workflow/build-routes.ts",
"dev": "cross-env NODE_ENV=dev tsx watch --no-cache lib/index.ts",
"dev:cache": "cross-env NODE_ENV=production tsx watch lib/index.ts",
"build:docs": "tsx scripts/workflow/build-docs.ts",
"dev": "NODE_ENV=dev tsx watch --no-cache lib/index.ts",
"dev:cache": "NODE_ENV=production tsx watch lib/index.ts",
"format": "eslint --cache --fix \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --write",
"format:check": "eslint --cache \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --check",
"format:staged": "lint-staged",
"vitest": "cross-env NODE_ENV=test vitest",
"vitest:coverage": "cross-env NODE_ENV=test vitest --coverage.enabled --reporter=junit",
"vitest:watch": "cross-env NODE_ENV=test vitest --watch",
"vitest": "NODE_ENV=test vitest",
"vitest:fullroutes": "NODE_ENV=test FULL_ROUTES_TEST=true vitest --reporter=json --reporter=default --outputFile=\"./assets/build/test-full-routes.json\" routes",
"vitest:coverage": "NODE_ENV=test vitest --coverage.enabled --reporter=junit",
"vitest:watch": "NODE_ENV=test vitest --watch",
"lint": "eslint --cache .",
"prepare": "husky || true",
"profiling": "NODE_ENV=production tsx --prof lib/index.ts",
"start": "cross-env NODE_ENV=production tsx lib/index.ts",
"start": "NODE_ENV=production tsx lib/index.ts",
"test": "npm run format:check && npm run vitest:coverage"
},
"lint-staged": {
Expand Down Expand Up @@ -147,7 +150,6 @@
"@typescript-eslint/parser": "7.3.1",
"@vercel/nft": "0.26.4",
"@vitest/coverage-v8": "1.4.0",
"cross-env": "7.0.3",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-nibble": "8.1.0",
Expand Down
11 changes: 0 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b774c60

Please sign in to comment.