From 44ae3a8ab5c178b9e40fd2cd3df90ede764e6d6f Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Wed, 22 Jul 2026 14:27:50 +0200 Subject: [PATCH] ci: sync tests/README.md to docs (#3164) --- .gitignore | 3 + .woodpecker.star | 48 +++++++++++++- tests/README.md | 2 +- tests/scripts/generate-docs.mjs | 109 ++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 tests/scripts/generate-docs.mjs diff --git a/.gitignore b/.gitignore index 917b48e04c..55833657e7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ tests/acceptance/filesForUpload/filesWithVirus/ # QA activity reports tests/qa-activity-report/reports/ +# Generated docs page (published to the server-testing-docs branch by CI) +tests/.docs-dist/ + # drone CI is in .drone.star, do not let someone accidentally commit a local .drone.yml .drone.yml diff --git a/.woodpecker.star b/.woodpecker.star index a832ba7229..87e8776fa6 100644 --- a/.woodpecker.star +++ b/.woodpecker.star @@ -610,7 +610,7 @@ def main(ctx): ), ) - pipelines = test_pipelines + build_release_pipelines + genDocsPr(ctx) + notifyMatrixCheckSteps(ctx, getPipelineNames(testPipelines(ctx), optional = True)) + pipelines = test_pipelines + build_release_pipelines + genDocsPr(ctx) + serverTestingDocs(ctx) + notifyMatrixCheckSteps(ctx, getPipelineNames(testPipelines(ctx), optional = True)) pipelineSanityChecks(pipelines) return savePipelineNumber(ctx) + pipelines @@ -2343,6 +2343,52 @@ def genDocsPr(ctx): ], }] +def serverTestingDocs(ctx): + return [{ + "name": "server-testing-docs", + "steps": [ + { + "name": "build", + "image": OC_CI_NODEJS, + "commands": [ + "node tests/scripts/generate-docs.mjs", + # add dummy woodpecker config to disable CI on push to the docs branch + "printf 'def main(ctx):\n return [{\n \"name\":\"dummy-pipeline\",\n" + + " \"steps\":[{\"name\":\"dummy-step\",\"image\":\"alpine:latest\"}],\n" + + " \"when\":[{\"branch\":[\"main\"]}],\n }]\n' > tests/.docs-dist/.woodpecker.star", + ], + }, + { + "name": "publish", + "image": "plugins/gh-pages:1", + "settings": { + "username": { + "from_secret": "github_username", + }, + "password": { + "from_secret": "github_token", + }, + "pages_directory": "tests/.docs-dist/", + "copy_contents": True, + "target_branch": "server-testing-docs", + "delete": True, + }, + "when": [ + { + "event": ["push"], + "branch": "${CI_REPO_DEFAULT_BRANCH}", + "path": "tests/README.md", + }, + ], + }, + ], + "when": [ + { + "event": ["push", "pull_request"], + }, + ], + }] + def notifyMatrixCheckSteps(ctx, depends_on): result = [{ "name": "all-checks-finished", diff --git a/tests/README.md b/tests/README.md index 04df56e24f..ba8d5a1d11 100644 --- a/tests/README.md +++ b/tests/README.md @@ -174,7 +174,7 @@ TEST_GROUP=BaseWopiViewing docker compose -f tests/acceptance/docker/src/wopi-va Use the arm image for macOS to run the validator tests. ```bash -WOPI_VALIDATOR_IMAGE=scharfvi/wopi-validator \ +WOPI_VALIDATOR_IMAGE=opencloudeu/wopi-validator \ TEST_GROUP=BaseWopiViewing \ docker compose -f tests/acceptance/docker/src/wopi-validator-test.yml up -d ``` diff --git a/tests/scripts/generate-docs.mjs b/tests/scripts/generate-docs.mjs new file mode 100644 index 0000000000..8d87379126 --- /dev/null +++ b/tests/scripts/generate-docs.mjs @@ -0,0 +1,109 @@ +// Transforms tests/README.md into a Docusaurus-ready markdown page and writes +// it to tests/.docs-dist/. CI publishes that directory to the +// `server-testing-docs` branch, which the docs site (opencloud-eu/docs) pulls +// into docs/dev/server/testing via git-batch (.batchfile). +// +// Run locally with: node tests/scripts/generate-docs.mjs + +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const HERE = path.dirname(fileURLToPath(import.meta.url)) +const TESTS_DIR = path.resolve(HERE, '..') +const REPO_ROOT = path.resolve(TESTS_DIR, '..') +const INPUT = path.join(TESTS_DIR, 'README.md') +const OUT_DIR = path.join(TESTS_DIR, '.docs-dist') + +// tests, relative to the repo root, used to resolve README-relative links. +const TESTS_REL = path.relative(REPO_ROOT, TESTS_DIR).split(path.sep).join('/') +const GH_BLOB = 'https://github.com/opencloud-eu/opencloud/blob/main' + +const FRONT_MATTER = `--- +title: 'Acceptance Testing' +sidebar_position: 1 +id: acceptance-testing +--- +` + +const CATEGORY = { + label: 'Testing', + position: 4 +} + +// GitHub alert syntax -> Docusaurus admonitions. +const ALERT_TYPES = { + NOTE: 'note', + TIP: 'tip', + IMPORTANT: 'info', + WARNING: 'warning', + CAUTION: 'danger' +} + +function stripTitleAndToc(md) { + // Docusaurus renders the title from front matter, so drop the leading H1. + md = md.replace(/^#\s+.*\r?\n+/, '') + // Drop the hand-maintained "Table of Contents" section (auto-generated by Docusaurus). + md = md.replace(/^##\s+Table of Contents\s*\r?\n[\s\S]*?(?=^##\s)/m, '') + return md +} + +function stripHugoShortcodes(md) { + // Hugo shortcodes ({{< ref "x" >}}) are not valid in Docusaurus MDX and raw + // "{{ }}" would break the build. Keep the link text, drop the broken target. + md = md.replace(/\[([^\]]+)\]\(\s*\{\{[<%][\s\S]*?[%>]\}\}\s*\)/g, '$1') + // Remove any remaining bare shortcodes. + md = md.replace(/\{\{[<%][\s\S]*?[%>]\}\}/g, '') + return md +} + +function convertAlerts(md) { + const lines = md.split('\n') + const out = [] + for (let i = 0; i < lines.length; i++) { + const match = lines[i].match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/) + if (!match) { + out.push(lines[i]) + continue + } + const type = ALERT_TYPES[match[1]] + const body = [] + while (i + 1 < lines.length && lines[i + 1].startsWith('>')) { + i++ + body.push(lines[i].replace(/^>\s?/, '')) + } + out.push(`:::${type}`, ...body, ':::') + } + return out.join('\n') +} + +function rewriteRelativeLinks(md) { + // ](./foo) or ](../foo) -> absolute GitHub blob URL, resolved against tests/. + return md.replace(/\]\((\.{1,2}\/[^)]+)\)/g, (_full, rel) => { + const repoRelative = path.posix.join(TESTS_REL, rel) + return `](${GH_BLOB}/${repoRelative})` + }) +} + +async function main() { + const readme = await readFile(INPUT, 'utf8') + const body = rewriteRelativeLinks( + convertAlerts(stripHugoShortcodes(stripTitleAndToc(readme))) + ) + const page = `${FRONT_MATTER}\n${body.replace(/^\n+/, '')}` + + if (page.includes('{{')) { + console.warn('Warning: unresolved "{{" remains in the output; Docusaurus MDX may fail.') + } + + await rm(OUT_DIR, { recursive: true, force: true }) + await mkdir(OUT_DIR, { recursive: true }) + await writeFile(path.join(OUT_DIR, 'index.md'), page, 'utf8') + await writeFile(path.join(OUT_DIR, '_category_.json'), JSON.stringify(CATEGORY, null, 2) + '\n', 'utf8') + console.log(`Wrote ${path.relative(REPO_ROOT, OUT_DIR)}/{index.md,_category_.json}`) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +})