diff --git a/.github/scripts/csharp-workflow-policy.test.mjs b/.github/scripts/csharp-workflow-policy.test.mjs index 1a9837f..d7388ba 100755 --- a/.github/scripts/csharp-workflow-policy.test.mjs +++ b/.github/scripts/csharp-workflow-policy.test.mjs @@ -135,6 +135,15 @@ test("builds PDF and API documentation in parallel before publishing both", () = ); }); +test("validates generated API pages before upload and after download", () => { + const documentation = jobs.get("buildDocumentation"); + const publisher = jobs.get("publishDocumentation"); + + assert.match(documentation, /docfx" docfx\.json --warningsAsErrors/); + assert.match(documentation, /validate-csharp-docs\.sh _site/); + assert.match(publisher, /validate-csharp-docs\.sh _site/); +}); + test("aggregates every job result so skipped dependents cannot hide failures", () => { const gate = jobs.get("pipelineStatus"); assert.ok(gate, "pipelineStatus job should exist"); diff --git a/.github/scripts/validate-csharp-docs.sh b/.github/scripts/validate-csharp-docs.sh new file mode 100755 index 0000000..1043036 --- /dev/null +++ b/.github/scripts/validate-csharp-docs.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euo pipefail + +site=${1:?Usage: validate-csharp-docs.sh SITE_DIRECTORY} + +for file in \ + index.html \ + api/Platform.Interfaces.html \ + api/Platform.Interfaces.IFactory-1.html \ + xrefmap.yml; do + if [[ ! -s "$site/$file" ]]; then + echo "Documentation site is missing $site/$file." >&2 + exit 1 + fi +done + +echo "Validated DocFX home page, API pages, and cross-reference map." diff --git a/.github/scripts/validate-csharp-docs.test.mjs b/.github/scripts/validate-csharp-docs.test.mjs new file mode 100644 index 0000000..c8b54e5 --- /dev/null +++ b/.github/scripts/validate-csharp-docs.test.mjs @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; +import test from "node:test"; + +const validator = new URL("./validate-csharp-docs.sh", import.meta.url).pathname; + +const withSite = (files, check) => { + const site = mkdtempSync(join(tmpdir(), "csharp-docs-")); + try { + for (const file of files) { + const path = join(site, file); + mkdirSync(join(path, ".."), { recursive: true }); + writeFileSync(path, "generated content"); + } + check(spawnSync("bash", [validator, site], { encoding: "utf8" })); + } finally { + rmSync(site, { recursive: true, force: true }); + } +}; + +const requiredFiles = [ + "index.html", + "api/Platform.Interfaces.html", + "api/Platform.Interfaces.IFactory-1.html", + "xrefmap.yml", +]; + +test("accepts a complete DocFX site", () => { + withSite(requiredFiles, ({ status, stderr }) => { + assert.equal(status, 0, stderr); + }); +}); + +for (const missingFile of requiredFiles) { + test(`rejects a site missing ${missingFile}`, () => { + withSite(requiredFiles.filter((file) => file !== missingFile), ({ status, stderr }) => { + assert.notEqual(status, 0); + assert.ok(stderr.includes(missingFile), stderr); + }); + }); +} diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 887c273..7f74576 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -230,7 +230,7 @@ jobs: cp _site/README.html _site/index.html - name: Validate documentation output - run: test -s _site/index.html + run: ../.github/scripts/validate-csharp-docs.sh _site - name: Upload documentation site timeout-minutes: 5 @@ -263,7 +263,7 @@ jobs: - name: Validate documentation site run: | - test -s _site/index.html + ../.github/scripts/validate-csharp-docs.sh _site test -s _site/Platform.Interfaces.pdf - name: Publish documentation to gh-pages