Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-a11y-workflows.test.mjs
T

126 lines
7.2 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import assert from "node:assert/strict";
import { describe, it } from "vitest";
import { missingReadinessEnv } from "./run-playwright-a11y.mjs";
const repoRoot = process.cwd();
const readinessWorkflowEnvKeys = missingReadinessEnv({ env: {} });
function readWorkflow(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
}
describe("a11y workflow install cache", () => {
it("the Gitea a11y workflow uses the pnpm store cache before installing", () => {
const workflow = ".gitea/workflows/a11y.yml";
const source = readWorkflow(workflow);
const configureStoreIndex = source.indexOf("pnpm config set store-dir .pnpm-store");
const cacheIndex = source.search(/uses: actions\/cache@[0-9a-f]{40}/);
const installIndex = source.indexOf("pnpm install --frozen-lockfile --prefer-offline");
assert.ok(configureStoreIndex > 0, `${workflow} must configure .pnpm-store`);
assert.ok(cacheIndex > configureStoreIndex, `${workflow} must cache .pnpm-store after configuring it`);
assert.ok(source.includes("path: .pnpm-store"), `${workflow} must cache the pnpm store path`);
assert.ok(source.includes("key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}"), `${workflow} must key cache by pnpm lockfile`);
assert.ok(installIndex > cacheIndex, `${workflow} must install with --prefer-offline after cache restore`);
});
});
describe("e2e readiness workflows", () => {
it("the Gitea readiness workflow runs the browser readiness suite in fail-closed mode", () => {
const workflow = ".gitea/workflows/e2e-readiness.yml";
const source = readWorkflow(workflow);
assert.match(source, /^ workflow_dispatch:\s*$/m, `${workflow} must support manual operator dispatch`);
assert.match(source, /^ schedule:\s*$/m, `${workflow} must run on a schedule`);
assert.ok(source.includes("pnpm config set store-dir .pnpm-store"), `${workflow} must configure the pnpm store`);
assert.ok(source.includes("path: .cache/ms-playwright"), `${workflow} must cache Playwright browsers`);
assert.ok(source.includes("pnpm install --frozen-lockfile --prefer-offline"), `${workflow} must install deterministically`);
assert.ok(
source.includes("pnpm exec playwright install --with-deps chromium webkit"),
`${workflow} must install every browser used by E2E_BROWSER_MATRIX=1`
);
assert.ok(source.includes("pnpm run verify:e2e-a11y"), `${workflow} must run the strict Playwright wrapper`);
assert.ok(source.includes("customer_portal_invitation:"), `${workflow} must expose the disposable VA-P26a invitation opt-in`);
assert.ok(source.includes("plugin_lifecycle_mutation:"), `${workflow} must expose the plugin lifecycle mutation manual dispatch opt-in`);
assert.ok(source.includes("plugin_settings_write:"), `${workflow} must expose the plugin settings write manual dispatch opt-in`);
assert.ok(source.includes("workflow_outbox_drain:"), `${workflow} must expose the workflow outbox-drain manual dispatch opt-in`);
assert.ok(source.includes("workflow_webhook_run:"), `${workflow} must expose the real workflow webhook manual dispatch opt-in`);
assert.ok(source.includes("workflow_webhook_target_url:"), `${workflow} must expose the real workflow webhook target URL input`);
assert.ok(source.includes("subscription_success:"), `${workflow} must expose the checkout success manual dispatch opt-in`);
assert.ok(source.includes("subscription_success_path:"), `${workflow} must expose the checkout success return path input`);
assert.ok(source.includes("Apply manual operator E2E opt-ins"), `${workflow} must apply manual operator opt-ins before running Playwright`);
for (const requiredEnv of [
'REQUIRE_E2E_A11Y: "1"',
'REQUIRE_AUTH_E2E: "1"',
'REQUIRE_E2E_READINESS: "1"',
'E2E_A11Y_STRICT: "1"',
'E2E_BROWSER_MATRIX: "1"',
'E2E_ALLOW_WORKFLOW_RUN: "1"',
"E2E_TENANT_EMAIL: ${{ secrets.E2E_TENANT_EMAIL }}",
"E2E_TENANT_PASSWORD: ${{ secrets.E2E_TENANT_PASSWORD }}",
"E2E_PLUGIN_READONLY_EMAIL: ${{ secrets.E2E_PLUGIN_READONLY_EMAIL }}",
"E2E_PLUGIN_READONLY_PASSWORD: ${{ secrets.E2E_PLUGIN_READONLY_PASSWORD }}",
"E2E_TENANT_API_LOGS_PATH: /e2e-readiness/settings/integrations/api/logs",
"E2E_PLUGIN_SETTINGS_PATH: /e2e-readiness/settings/integrations/plugins",
"E2E_WORKFLOW_ENGINE_PATH: /e2e-readiness/workflow-engine/workflows",
"E2E_WORKFLOW_RUN_API_PATH: /e2e-readiness/workflow-engine/workflows/wf_123/run/api",
"E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123",
"E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: /e2e-readiness/workflow-engine/workflows/wf_webhook_123/run/manual",
"PLUGIN_BINARY_BASE_PATH: /tmp/e2e-plugins",
]) {
assert.ok(source.includes(requiredEnv), `${workflow} missing ${requiredEnv}`);
}
for (const optInExport of [
"E2E_ALLOW_PLUGIN_LIFECYCLE_MUTATION=1",
"E2E_ALLOW_PLUGIN_SETTINGS_WRITE=1",
"E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1",
"E2E_ALLOW_WORKFLOW_WEBHOOK_RUN=1",
"E2E_WORKFLOW_WEBHOOK_TARGET_URL=%s",
"E2E_REQUIRE_SUBSCRIPTION_SUCCESS=1",
"E2E_TENANT_SUBSCRIPTION_SUCCESS_PATH=%s",
]) {
assert.ok(source.includes(optInExport), `${workflow} must export ${optInExport} for manual dispatch`);
}
for (const requiredKey of readinessWorkflowEnvKeys) {
assert.match(source, new RegExp(`^\\s+${requiredKey}:\\s+`, "m"), `${workflow} missing strict readiness env key ${requiredKey}`);
}
assert.doesNotMatch(source, /SKIP_E2E_A11Y:\s*"?1"?/, `${workflow} must not silently skip the browser suite`);
assert.ok(source.includes("path: test-results/"), `${workflow} must upload Playwright traces`);
const invitationStep = source.slice(
source.indexOf("- name: Run operator-gated VA-P26a invitation lifecycle"),
source.indexOf("- name: Upload Playwright traces")
);
assert.match(invitationStep, /github\.event_name == 'workflow_dispatch'/u, `${workflow} must never run the invitation proof on schedule`);
assert.match(invitationStep, /inputs\.customer_portal_invitation/u, `${workflow} must bind the explicit invitation dispatch opt-in`);
for (const contract of [
'E2E_ALLOW_CUSTOMER_PORTAL_P26A_INVITATION_MUTATION: "1"',
'REQUIRE_E2E_MUTATING_READINESS: "1"',
"E2E_PORTAL_INVITATION_ADMIN_EMAIL",
"E2E_PORTAL_INVITATION_ADMIN_PASSWORD",
"E2E_PORTAL_INVITATION_SETUP_PASSWORD",
"E2E_PORTAL_SETUP_MAIL_FIXTURE_URL",
"E2E_PORTAL_SETUP_MAIL_FIXTURE_TOKEN",
"customer-portal-invitation+${correlation_key}@saas-ctrl.com",
"pnpm run verify:e2e-customer-portal-invitation",
]) {
assert.ok(invitationStep.includes(contract), `${workflow} invitation step missing ${contract}`);
}
assert.doesNotMatch(invitationStep, /GITHUB_ENV/u, `${workflow} must keep invitation secrets and recipient scoped to one shell step`);
});
it("the Gitea readiness workflow uses the self-hosted runner and host resolver", () => {
const source = readWorkflow(".gitea/workflows/e2e-readiness.yml");
assert.ok(source.includes("runs-on: toolctrl-ci"));
assert.ok(source.includes("HIVEGRID_GITEA_HOST_IP"));
assert.ok(source.includes("git.tool-ctrl.de"));
});
});