From cdb2b401ced4b357c882e81b02db684ccc20934d Mon Sep 17 00:00:00 2001 From: dumbmoron Date: Tue, 9 Jul 2024 18:30:24 +0000 Subject: [PATCH] ci: test services in github actions --- .github/workflows/test.yml | 24 +++++++++++ src/modules/test.js | 42 ++++++++++++++++++++ src/util/test-ci.js | 81 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 src/modules/test.js create mode 100644 src/util/test-ci.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 193ddd01..4ac2daf3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,3 +36,27 @@ jobs: uses: actions/checkout@v4 - name: Run test script run: .github/test.sh api + + check-services: + name: test service functionality + runs-on: ubuntu-latest + outputs: + services: ${{ steps.checkServices.outputs.service_list }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - id: checkServices + run: npm ci && echo "service_list=$(node src/util/test-ci get-services)" >> "$GITHUB_OUTPUT" + + test-services: + needs: check-services + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + service: ${{ fromJson(needs.check-services.outputs.services) }} + name: "test service: ${{ matrix.service }}" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - run: npm ci && node src/util/test-ci run-tests-for ${{ matrix.service }} \ No newline at end of file diff --git a/src/modules/test.js b/src/modules/test.js new file mode 100644 index 00000000..75bdab26 --- /dev/null +++ b/src/modules/test.js @@ -0,0 +1,42 @@ +import { normalizeRequest } from "../modules/processing/request.js"; +import match from "./processing/match.js"; +import { extract } from "./processing/url.js"; + +export async function runTest(url, params, expect) { + const normalized = normalizeRequest({ url, ...params }); + if (!normalized) { + throw "invalid request"; + } + + const parsed = extract(normalized.url); + if (parsed === null) { + throw `invalid url: ${normalized.url}`; + } + + const result = await match( + parsed.host, parsed.patternMatch, "en", normalized + ); + + let error = []; + if (expect.status !== result.body.status) { + const detail = `${expect.status} (expected) != ${result.body.status} (actual)`; + error.push(`status mismatch: ${detail}`); + } + + if (expect.code !== result.status) { + const detail = `${expect.code} (expected) != ${result.status} (actual)`; + error.push(`status code mismatch: ${detail}`); + } + + if (error.length) { + if (result.body.text) { + error.push(`error message: ${result.body.text}`); + } + + throw error.join('\n'); + } + + if (result.body.status === 'stream') { + // TODO: stream testing + } +} \ No newline at end of file diff --git a/src/util/test-ci.js b/src/util/test-ci.js new file mode 100644 index 00000000..ed6962c7 --- /dev/null +++ b/src/util/test-ci.js @@ -0,0 +1,81 @@ +import { env } from "../modules/config.js"; +import { runTest } from "../modules/test.js"; +import { loadLoc } from "../localization/manager.js"; +import { loadJSON } from "../modules/sub/loadFromFs.js"; +import { Red, Bright } from "../modules/sub/consoleText.js"; + +const tests = loadJSON('./src/util/tests.json'); +const services = loadJSON('./src/modules/processing/servicesConfig.json'); + +// services that are known to frequently fail due to external +// factors (e.g. rate limiting) +const finnicky = new Set(['bilibili', 'instagram', 'youtube']) + +const action = process.argv[2]; +switch (action) { + case "get-services": + const fromConfig = Object.keys(services.config); + + const missingTests = fromConfig.filter( + service => !tests[service] || tests[service].length === 0 + ); + + if (missingTests.length) { + console.error('services have no tests:', missingTests); + console.log('[]'); + process.exitCode = 1; + break; + } + + console.log(JSON.stringify(fromConfig)); + break; + + case "run-tests-for": + const service = process.argv[3]; + let failed = false; + + if (!tests[service]) { + console.error('no such service:', service); + } + + await loadLoc(); + env.streamLifespan = 10000; + env.apiURL = 'http://x'; + + for (const test of tests[service]) { + const { name, url, params, expected } = test; + const canFail = test.canFail || finnicky.has(service); + + try { + await runTest(url, params, expected); + console.log(`${service}/${name}: ok`); + + } catch(e) { + failed = !canFail; + + let failText = canFail ? `${Red('FAIL')} (ignored)` : Bright(Red('FAIL')); + if (canFail && process.env.GITHUB_ACTION) { + console.log(`::warning title=${service}/${name.replace(/,/g, ';')}::failed and was ignored`); + } + + console.error(`${service}/${name}: ${failText}`); + const errorString = e.toString().split('\n'); + let c = '┃'; + errorString.forEach((line, index) => { + line = line.replace('!=', Red('!=')); + + if (index === errorString.length - 1) { + c = '┗'; + } + + console.error(` ${c}`, line); + }); + } + } + + process.exitCode = Number(failed); + break; + default: + console.error('invalid action:', action); + process.exitCode = 1; +} \ No newline at end of file