426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
247 lines
9.1 KiB
TypeScript
247 lines
9.1 KiB
TypeScript
// Exercises tests e2e coverage.test automation behavior with deterministic script fixtures.
|
|
import { describe, expect, test } from "bun:test";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
buildCoverageMatrix,
|
|
discoverRoutePlugins,
|
|
discoverZeroTestPlugins,
|
|
resolveCoverage,
|
|
} from "../e2e-coverage/inventory.ts";
|
|
import {
|
|
COMMAND_COVERAGE,
|
|
LARP_TEST_ARTIFACTS,
|
|
PLUGIN_ROUTE_COVERAGE,
|
|
VIEW_COVERAGE_GATES,
|
|
ZERO_TEST_EXEMPT,
|
|
} from "../e2e-coverage/manifest.ts";
|
|
|
|
/**
|
|
* The e2e coverage ship-gate (issue #8802). This is the umbrella enforcement:
|
|
* every slash command, plugin route, and view surface that ships a real effect
|
|
* must have a real recorded e2e (or a justified exemption), and the coverage
|
|
* manifest must stay in lock-step with what is actually wired in source.
|
|
*
|
|
* It mirrors the existing static ship-gates (route-coverage.test.ts,
|
|
* view-interaction-coverage.test.ts): a curated manifest diffed against a
|
|
* discovered inventory, failing CI when something new ships uncovered.
|
|
*
|
|
* Issue #8802 prescribes the gate "start advisory for one cycle (like
|
|
* coverage-gate.yml), then flip to required once the baseline is green."
|
|
* `E2E_COVERAGE_GATE_ENFORCE=1` makes the develop-landscape-sensitive ratchets
|
|
* (route-wiring drift, blocking gaps, zero-test documentation) hard failures;
|
|
* by default they log a warning and pass, so a PR is never red merely because
|
|
* the develop base it merges against churned its own plugin/test landscape
|
|
* (e.g. a sibling PR adding/removing a route plugin or a plugin's first test).
|
|
* The stable structural checks (larp rejection, exemption reasons, view gates,
|
|
* the command contract) stay hard regardless of this flag.
|
|
*/
|
|
const ENFORCE = process.env.E2E_COVERAGE_GATE_ENFORCE === "1";
|
|
|
|
/** Hard-fail under ENFORCE; otherwise warn and pass (advisory ratchet). */
|
|
function expectRatchet(findings: string[], message: string): void {
|
|
if (findings.length > 0 && !ENFORCE) {
|
|
console.warn(
|
|
`[e2e-coverage][advisory] ${message}\n ${findings.join("\n ")}`,
|
|
);
|
|
}
|
|
expect(ENFORCE ? findings : [], message).toEqual([]);
|
|
}
|
|
|
|
describe("e2e coverage ship-gate", () => {
|
|
test("the route-plugin manifest stays in lock-step with discovered route wiring", () => {
|
|
const discovered = discoverRoutePlugins().map((info) => info.plugin);
|
|
const discoveredSet = new Set(discovered);
|
|
const manifestKeys = new Set(Object.keys(PLUGIN_ROUTE_COVERAGE));
|
|
|
|
const missingFromManifest = discovered
|
|
.filter((plugin) => !manifestKeys.has(plugin))
|
|
.sort();
|
|
const staleInManifest = [...manifestKeys]
|
|
.filter((plugin) => !discoveredSet.has(plugin))
|
|
.sort();
|
|
|
|
expectRatchet(
|
|
missingFromManifest,
|
|
"route-wiring plugins with no coverage manifest entry — add a covered/exempt entry in e2e-coverage/manifest.ts:",
|
|
);
|
|
expectRatchet(
|
|
staleInManifest,
|
|
"manifest entries for plugins that no longer wire routes — remove them:",
|
|
);
|
|
});
|
|
|
|
test("no command, plugin-route, or view surface ships without a real e2e", () => {
|
|
const matrix = buildCoverageMatrix({
|
|
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
});
|
|
const blocking = matrix.blockingGaps.map(
|
|
(gap) => `${gap.id} — ${gap.detail}`,
|
|
);
|
|
expectRatchet(
|
|
blocking,
|
|
"blocking e2e coverage gaps (close with a real e2e or a justified exemption):",
|
|
);
|
|
});
|
|
|
|
test("every slash command in the served catalog is covered by the real contract", () => {
|
|
const resolution = resolveCoverage(COMMAND_COVERAGE);
|
|
expect(resolution.status, resolution.detail).toBe("covered");
|
|
|
|
const matrix = buildCoverageMatrix({
|
|
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
});
|
|
// The served catalog must be non-trivial and fully covered.
|
|
expect(matrix.summary.commands.total).toBeGreaterThanOrEqual(20);
|
|
expect(matrix.summary.commands.covered).toBe(matrix.summary.commands.total);
|
|
});
|
|
|
|
test("every manifested command and route plugin resolves to real coverage (hard)", () => {
|
|
// This is the hard ship-gate enforcement (independent of ENFORCE): every
|
|
// surface the manifest CLAIMS to cover must actually resolve — its artifact
|
|
// exists AND carries the anti-larp signal. Unlike the discovery ratchets,
|
|
// this is not develop-landscape-sensitive (it only reads the committed
|
|
// manifest's own artifacts), so it stays hard and fails when a claimed
|
|
// command/route e2e is deleted, renamed, or downgraded to a larp test.
|
|
const failures: string[] = [];
|
|
const command = resolveCoverage(COMMAND_COVERAGE);
|
|
if (command.status !== "covered") {
|
|
failures.push(`commands — ${command.detail}`);
|
|
}
|
|
for (const [plugin, entry] of Object.entries(PLUGIN_ROUTE_COVERAGE)) {
|
|
const resolution = resolveCoverage(entry);
|
|
if (resolution.status === "missing") {
|
|
failures.push(`plugin-route:${plugin} — ${resolution.detail}`);
|
|
}
|
|
}
|
|
expect(
|
|
failures,
|
|
`manifested coverage is broken (a claimed artifact is missing or fails its anti-larp signal):\n ${failures.join("\n ")}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
test("the existing view ship-gates are referenced, not regressed", () => {
|
|
const matrix = buildCoverageMatrix({
|
|
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
});
|
|
const viewGaps = matrix.items.filter(
|
|
(item) => item.kind === "view" && item.status !== "covered",
|
|
);
|
|
expect(
|
|
viewGaps.map((gap) => gap.id),
|
|
"a referenced view ship-gate file is missing",
|
|
).toEqual([]);
|
|
expect(matrix.summary.views.gates).toBe(VIEW_COVERAGE_GATES.length);
|
|
});
|
|
|
|
test("a shape-only larp test is never accepted as coverage", () => {
|
|
// No covered entry may cite a known larp artifact.
|
|
const allCovered = [
|
|
COMMAND_COVERAGE,
|
|
...Object.values(PLUGIN_ROUTE_COVERAGE),
|
|
].filter((entry) => entry.status === "covered");
|
|
for (const entry of allCovered) {
|
|
if (entry.status !== "covered") continue;
|
|
for (const artifact of entry.artifacts) {
|
|
expect(
|
|
LARP_TEST_ARTIFACTS.has(artifact),
|
|
`${artifact} is a shape-only larp test and must not be cited as coverage`,
|
|
).toBe(false);
|
|
}
|
|
}
|
|
// And resolveCoverage rejects a larp artifact even if it exists.
|
|
const rejected = resolveCoverage({
|
|
status: "covered",
|
|
artifacts: [...LARP_TEST_ARTIFACTS][0]
|
|
? [[...LARP_TEST_ARTIFACTS][0]]
|
|
: [],
|
|
signals: [],
|
|
});
|
|
if ([...LARP_TEST_ARTIFACTS].length > 0) {
|
|
expect(rejected.status).toBe("missing");
|
|
}
|
|
});
|
|
|
|
test("every zero-test plugin gains a test or a documented exemption", () => {
|
|
const zeroTest = discoverZeroTestPlugins();
|
|
const documented = new Set(Object.keys(ZERO_TEST_EXEMPT));
|
|
|
|
const undocumented = zeroTest
|
|
.filter((plugin) => !documented.has(plugin))
|
|
.sort();
|
|
expectRatchet(
|
|
undocumented,
|
|
"plugins with no test file and no documented exemption — add a real test or a ZERO_TEST_EXEMPT entry:",
|
|
);
|
|
|
|
// A stale exemption (the plugin now has a test) must be removed.
|
|
const zeroTestSet = new Set(zeroTest);
|
|
const stale = [...documented]
|
|
.filter((plugin) => !zeroTestSet.has(plugin))
|
|
.sort();
|
|
expectRatchet(
|
|
stale,
|
|
"ZERO_TEST_EXEMPT lists plugins that now have tests — remove them:",
|
|
);
|
|
|
|
// Reasons are stable structure — always required.
|
|
for (const [plugin, reason] of Object.entries(ZERO_TEST_EXEMPT)) {
|
|
expect(
|
|
reason.length,
|
|
`zero-test exemption for ${plugin} needs a written reason`,
|
|
).toBeGreaterThan(20);
|
|
}
|
|
});
|
|
|
|
test("zero-test discovery ignores generated asset-only plugin directories", () => {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "e2e-coverage-"));
|
|
try {
|
|
mkdirSync(path.join(root, "plugins", "plugin-generated", "assets"), {
|
|
recursive: true,
|
|
});
|
|
writeFileSync(
|
|
path.join(root, "plugins", "plugin-generated", "assets", "hero.png"),
|
|
"",
|
|
);
|
|
mkdirSync(path.join(root, "plugins", "plugin-real", "src"), {
|
|
recursive: true,
|
|
});
|
|
mkdirSync(path.join(root, "plugins", "plugin-placeholder"), {
|
|
recursive: true,
|
|
});
|
|
writeFileSync(
|
|
path.join(root, "plugins", "plugin-real", "package.json"),
|
|
JSON.stringify({ name: "@elizaos/plugin-real" }),
|
|
);
|
|
writeFileSync(
|
|
path.join(root, "plugins", "plugin-real", "src", "index.ts"),
|
|
"export const plugin = {};\n",
|
|
);
|
|
writeFileSync(
|
|
path.join(root, "plugins", "plugin-placeholder", "bun.lock"),
|
|
"",
|
|
);
|
|
|
|
expect(discoverZeroTestPlugins(root)).toEqual([
|
|
"plugin-placeholder",
|
|
"plugin-real",
|
|
]);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("every exemption carries a written justification", () => {
|
|
for (const [plugin, entry] of Object.entries(PLUGIN_ROUTE_COVERAGE)) {
|
|
if (entry.status === "exempt") {
|
|
expect(
|
|
entry.reason.length,
|
|
`exemption for ${plugin} needs a written reason`,
|
|
).toBeGreaterThan(20);
|
|
}
|
|
}
|
|
});
|
|
});
|