From 65cb5ab547881ea2ec7eee037ee41d9189f73dbf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 2 Oct 2026 07:23:04 -0700 Subject: [PATCH] test(scripts): remove low-value tests (batch d152) (#163549) * test(scripts): deslop s1015 tests * test(scripts): deslop s1023 tests * test(scripts): deslop s1026 tests * test(scripts): deslop s1027 tests * test(scripts): deslop s1020 tests * test(scripts): deslop s1031 tests * test(scripts): deslop s1038 tests * test(scripts): deslop s1025 tests * test(scripts): deslop s1028 tests * test(scripts): deslop s1037 tests * test(scripts): retain rollout ancestry coverage --- scripts/check-line-cap-ratchet.mts | 2 +- test/scripts/check-line-cap-ratchet.test.ts | 14 +- ...enclaw-package-tarball.bundled-mcp.test.ts | 29 +-- test/scripts/ci-architecture-go-env.test.ts | 42 +--- test/scripts/ci-changed-lint.test.ts | 35 ++- ...changed-node-test-plan.integration.test.ts | 35 ++- test/scripts/ci-changed-type-graphs.test.ts | 37 +-- test/scripts/ci-check-family-scope.test.ts | 13 +- test/scripts/compiler-input-snapshot.test.ts | 27 +-- test/scripts/control-ui-i18n-verify.test.ts | 62 ++--- test/scripts/control-ui-icon-grid-fit.test.ts | 104 ++++---- test/scripts/control-ui-mock-plugins.test.ts | 15 +- .../crabbox-hydrate-dependencies.test.ts | 22 -- test/scripts/crabbox-source-capsule.test.ts | 74 +++--- .../doctor-install-switch-transport.test.ts | 14 -- .../extended-stable-active-line.test.ts | 1 - test/scripts/extension-import-profile.test.ts | 2 +- test/scripts/frv.release.test.ts | 94 ++------ test/scripts/install-cli.freebsd.test.ts | 26 +- test/scripts/install-ps1.release.test.ts | 97 +------- test/scripts/inventory-report-cli.test.ts | 35 +-- test/scripts/ios-release-e2e.test.ts | 111 ++------- test/scripts/kitchen-sink-resources.test.ts | 95 ++++---- test/scripts/linux-app-channel.test.ts | 28 +-- test/scripts/linux-updater-manifest.test.ts | 54 ++--- .../managed-child-process.tree.test.ts | 8 +- .../managed-handoff-build-config.test.ts | 29 +-- test/scripts/mobile-release-ci.test.ts | 82 ++----- test/scripts/mobile-release-notes.test.ts | 180 +++++++------- .../npm-onboard-execution-identity.test.ts | 18 +- test/scripts/onboard-first-agent-flow.test.ts | 4 - .../openclaw-performance-crabbox.test.ts | 68 ++---- test/scripts/openclaw-release-ready.test.ts | 86 ++----- test/scripts/pr-merge-admission.test.ts | 131 +++------- test/scripts/pr-merge-auto-recovery.test.ts | 1 - test/scripts/pr-merge-body-provenance.test.ts | 81 +------ test/scripts/pr-merge-correction.test.ts | 8 +- test/scripts/pr-merge-legacy-recovery.test.ts | 17 +- .../pr-merge-pre-dispatch-refusal.test.ts | 115 +++------ .../pr-merge-qualified-refusal.test.ts | 84 ++----- test/scripts/security-review-rollout.test.ts | 45 ++-- test/scripts/security-review-script.test.ts | 223 +++++------------- test/scripts/security-review-workflow.test.ts | 44 +--- .../setup-pnpm-archive-download.test.ts | 32 +-- test/scripts/simslim.test.ts | 2 - .../sqlite-reliability-process.test.ts | 67 +++--- .../update-compat-external-bindings.test.ts | 24 +- 47 files changed, 668 insertions(+), 1749 deletions(-) diff --git a/scripts/check-line-cap-ratchet.mts b/scripts/check-line-cap-ratchet.mts index 960f8b912dcb..f079f2e1ac70 100644 --- a/scripts/check-line-cap-ratchet.mts +++ b/scripts/check-line-cap-ratchet.mts @@ -26,7 +26,7 @@ type OxlintDiagnostic = { help?: string; }; -export function compareLineCapViolations( +function compareLineCapViolations( head: ReadonlyMap, base: ReadonlyMap, renames: readonly { from: string; to: string }[] = [], diff --git a/test/scripts/check-line-cap-ratchet.test.ts b/test/scripts/check-line-cap-ratchet.test.ts index 6492a143ac33..ec72bacfcd9c 100644 --- a/test/scripts/check-line-cap-ratchet.test.ts +++ b/test/scripts/check-line-cap-ratchet.test.ts @@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { compareLineCapViolations, main } from "../../scripts/check-line-cap-ratchet.mts"; +import { main } from "../../scripts/check-line-cap-ratchet.mts"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); @@ -102,18 +102,6 @@ describe("line-cap growth ratchet", () => { expect(errors).not.toHaveBeenCalled(); }); - it.each([ - { label: "over-cap shrinking", before: 705, after: 703, fails: false }, - { label: "over-cap growing", before: 705, after: 706, fails: true }, - { label: "newly over-cap", before: 700, after: 701, fails: true }, - { label: "under-cap growth", before: 698, after: 700, fails: false }, - { label: "unchanged over-cap", before: 705, after: 705, fails: false }, - ])("$label", ({ before, after, fails }) => { - const violations = (count: number) => - new Map(count > 700 ? [["src/file.ts", { count, cap: 700 }]] : []); - expect(compareLineCapViolations(violations(after), violations(before)).length > 0).toBe(fails); - }); - it.skipIf(process.platform === "win32")("preserves native filenames beginning with file:", () => { vi.stubEnv("TERMINAL_EMULATOR", ""); const root = fixture(); diff --git a/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts b/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts index 2f6b44383852..afafccb2f01b 100644 --- a/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts +++ b/test/scripts/check-openclaw-package-tarball.bundled-mcp.test.ts @@ -300,29 +300,16 @@ describe("bundled browser MCP package", () => { change: "modify", error: "bundled chrome-devtools-mcp must be ESM version 1.10.1", }, - ...[ - "build/src/TextSnapshot.js", - "build/src/McpPage.js", - "build/src/third_party/index.js", - "build/src/OPENCLAW_PATCH_NOTICE.md", - ].map((file) => ({ - file, + { + file: "build/src/TextSnapshot.js", change: "modify", - error: `unpatched or changed runtime entry ${file}`, - })), - ...[ - MCP_CLI, - "build/src/bin/chrome-devtools-mcp-main.js", - "build/src/third_party/devtools-formatter-worker.js", - "build/src/third_party/devtools-heap-snapshot-worker.js", - "build/src/third_party/lighthouse-devtools-mcp-bundle.js", - "LICENSE", - "build/src/third_party/THIRD_PARTY_NOTICES", - ].map((file) => ({ - file, + error: "unpatched or changed runtime entry build/src/TextSnapshot.js", + }, + { + file: MCP_CLI, change: "remove", - error: `missing required runtime entry ${file}`, - })), + error: `missing required runtime entry ${MCP_CLI}`, + }, { file: "build/src/third_party/issue-descriptions", change: "remove", diff --git a/test/scripts/ci-architecture-go-env.test.ts b/test/scripts/ci-architecture-go-env.test.ts index afdb084a1bf5..d2d06fd185bd 100644 --- a/test/scripts/ci-architecture-go-env.test.ts +++ b/test/scripts/ci-architecture-go-env.test.ts @@ -12,42 +12,14 @@ import { const tempDirs = useAutoCleanupTempDirTracker(afterEach); describe.skipIf(process.platform === "win32")("architecture CI Go memory environment", () => { - it.each([ - { - name: "defaults", - gogc: undefined, - limit: undefined, - expectedGc: "30", - expectedLimit: "3GiB", - exit: 0, - }, - { - name: "caller overrides", - gogc: "70", - limit: "8GiB", - expectedGc: "70", - expectedLimit: "8GiB", - exit: 0, - }, - { - name: "partial override", - gogc: "off", - limit: undefined, - expectedGc: "off", - expectedLimit: "3GiB", - exit: 0, - }, - { - name: "blocking command failure", - gogc: undefined, - limit: undefined, - expectedGc: "30", - expectedLimit: "3GiB", - exit: 17, - }, + it.each<[string, string | undefined, string | undefined, string, string, number]>([ + ["defaults", undefined, undefined, "30", "3GiB", 0], + ["caller overrides", "70", "8GiB", "70", "8GiB", 0], + ["partial override", "off", undefined, "off", "3GiB", 0], + ["blocking command failure", undefined, undefined, "30", "3GiB", 17], ])( - "passes $name to the architecture command", - ({ gogc, limit, expectedGc, expectedLimit, exit }) => { + "passes %s to the architecture command", + (_name, gogc, limit, expectedGc, expectedLimit, exit) => { const script = readCiWorkflow().jobs["check-additional-shard"].steps.find( (step: WorkflowStep) => step.name === "Run additional check shard", )?.run; diff --git a/test/scripts/ci-changed-lint.test.ts b/test/scripts/ci-changed-lint.test.ts index 6dbb9935e80a..9bbc5fa7a42d 100644 --- a/test/scripts/ci-changed-lint.test.ts +++ b/test/scripts/ci-changed-lint.test.ts @@ -21,6 +21,16 @@ import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); +function fixture(prefix: string) { + const cwd = tempDirs.make(prefix); + const write = (file: string, source: string) => { + const target = path.join(cwd, file); + mkdirSync(path.dirname(target), { recursive: true }); + writeFileSync(target, source); + }; + return { cwd, write }; +} + vi.mock("../../scripts/test-projects.test-support.mts", async (importOriginal) => { const original = await importOriginal(); @@ -39,12 +49,7 @@ vi.mock("../../scripts/test-projects.test-support.mts", async (importOriginal) = describe("CI changed lint", () => { it("includes transitive and aliased type consumers without widening to their packages", async () => { - const cwd = tempDirs.make("ci-file-lint-"); - const write = (file: string, content: string) => { - const target = path.join(cwd, file); - mkdirSync(path.dirname(target), { recursive: true }); - writeFileSync(target, content); - }; + const { cwd, write } = fixture("ci-file-lint-"); write("pnpm-workspace.yaml", "packages:\n - .\n - ui\n - packages/*\n - extensions/*\n"); for (const root of [ ".", @@ -145,23 +150,17 @@ describe("CI changed lint", () => { ])( "retains full lint when an affected consumer augments globals through %s", async (file, source) => { - const cwd = tempDirs.make("ci-ambient-consumer-"); - mkdirSync(path.join(cwd, "src")); - writeFileSync(path.join(cwd, "package.json"), '{"type":"module"}'); - writeFileSync(path.join(cwd, "src/value.ts"), "export type Work = () => Promise;\n"); - writeFileSync(path.join(cwd, "src", file), source); - writeFileSync(path.join(cwd, "src/reader.ts"), "window.work();\n"); + const { cwd, write } = fixture("ci-ambient-consumer-"); + write("package.json", '{"type":"module"}'); + write("src/value.ts", "export type Work = () => Promise;\n"); + write(`src/${file}`, source); + write("src/reader.ts", "window.work();\n"); expect(await resolveChangedOxlintFileScope(["src/value.ts"], cwd)).toBeUndefined(); }, ); it("runs native rules on a changed file and unchanged consumers of its changed type", () => { - const cwd = tempDirs.make("ci-file-lint-native-"); - const write = (file: string, source: string) => { - const target = path.join(cwd, file); - mkdirSync(path.dirname(target), { recursive: true }); - writeFileSync(target, source); - }; + const { cwd, write } = fixture("ci-file-lint-native-"); write("package.json", '{"private":true,"type":"module"}'); write("pnpm-workspace.yaml", "packages: [.]\n"); write( diff --git a/test/scripts/ci-changed-node-test-plan.integration.test.ts b/test/scripts/ci-changed-node-test-plan.integration.test.ts index 4216403c08da..1d5e8741056d 100644 --- a/test/scripts/ci-changed-node-test-plan.integration.test.ts +++ b/test/scripts/ci-changed-node-test-plan.integration.test.ts @@ -32,6 +32,18 @@ function selectedFiles(shards: ReturnType) { ); } +function canonicalOwner(jobs: CompactNodeTestShard[], shardName: string) { + const job = expectDefined( + jobs.find((candidate) => candidate.groups.some((group) => group.shard_name === shardName)), + `canonical job for ${shardName}`, + ); + const group = expectDefined( + job.groups.find((candidate) => candidate.shard_name === shardName), + `canonical group for ${shardName}`, + ); + return { job, group }; +} + it("keeps the aggressive fixed smoke within two Node rows", () => { let smoke: string[] = []; resolveChangedNodeTestTargets(["src/infra/new-unlisted-module.ts"], { @@ -282,16 +294,7 @@ it("keeps UI and core changes with exact owners and direct consumers", () => { const owners = group.configs.includes("test/vitest/vitest.tooling.config.ts") ? canonicalTooling : canonical; - const ownerJob = expectDefined( - owners.find((candidate) => - candidate.groups.some((owner) => owner.shard_name === group.shard_name), - ), - `canonical UI consumer job for ${group.shard_name}`, - ); - const owner = expectDefined( - ownerJob.groups.find((candidate) => candidate.shard_name === group.shard_name), - "canonical UI consumer group", - ); + const { group: owner } = canonicalOwner(owners, group.shard_name); if (group.includePatterns) { expect(group.includePatterns.length).toBeGreaterThan(0); } else { @@ -299,15 +302,9 @@ it("keeps UI and core changes with exact owners and direct consumers", () => { } expect(group.configs.every((config) => owner.configs.includes(config))).toBe(true); // Tooling capacity follows selected files; an excluded compiler can require a larger full job. - const selectedJob = expectDefined( - selectedCanonical.find((candidate) => - candidate.groups.some((selected) => selected.shard_name === group.shard_name), - ), - `selected UI consumer job for ${group.shard_name}`, - ); - const selectedGroup = expectDefined( - selectedJob.groups.find((selected) => selected.shard_name === group.shard_name), - "selected UI consumer group", + const { job: selectedJob, group: selectedGroup } = canonicalOwner( + selectedCanonical, + group.shard_name, ); for (const key of [ "configs", diff --git a/test/scripts/ci-changed-type-graphs.test.ts b/test/scripts/ci-changed-type-graphs.test.ts index fb9ce8e9c5a9..4310f63c0fa8 100644 --- a/test/scripts/ci-changed-type-graphs.test.ts +++ b/test/scripts/ci-changed-type-graphs.test.ts @@ -73,15 +73,10 @@ describe("changed CI compiler graph selection", () => { it.for([ [], - ["src/missing.ts"], ["src/types/runtime.d.ts"], - ["test/tsconfig/tsconfig.test.root.json"], ["package.json"], ["unclassified/module.ts"], - ["docs/plugins/sdk-subpaths.md", "ui/src/styles/chat.css"], [sharedType, "src/config/catalog.json"], - ["extensions/example/value.ts", "extensions/example/types.d.ts"], - ["extensions/example/value.ts", "extensions/example/tsconfig.json"], ])("retains all compilers without discovery for uncertain input %j", async (paths) => { expect(selectChangedCiTsgoGraphs(paths, graphs())).toBeUndefined(); inspectGraphs.mockRejectedValue(new Error("Full plans must not enumerate compiler inputs")); @@ -151,14 +146,13 @@ describe("changed CI compiler graph selection", () => { }); }); - it.each(["leaf", "directory", "extension-alias", "broken", "traversal", "directory-file"])( + it.each(["leaf", "directory", "traversal", "directory-file"])( "retains every compiler for a nonphysical extension input (%s)", async (kind) => { const cwd = tempDirs.make("ci-type-extension-alias-"); mkdirSync(join(cwd, "src")); mkdirSync(join(cwd, "extensions", "example"), { recursive: true }); writeFileSync(join(cwd, "src/value.ts"), "export type Value = number;\n"); - writeFileSync(join(cwd, "extensions/example/other.ts"), "export type Value = number;\n"); const file = kind === "traversal" ? "extensions/example/../../src/value.ts" @@ -170,18 +164,7 @@ describe("changed CI compiler graph selection", () => { } else if (kind === "directory-file") { mkdirSync(join(cwd, file)); } else if (kind !== "traversal") { - symlinkSync( - join( - cwd, - kind === "broken" - ? "src/missing.ts" - : kind === "extension-alias" - ? "extensions/example/other.ts" - : "src/value.ts", - ), - join(cwd, file), - "file", - ); + symlinkSync(join(cwd, "src/value.ts"), join(cwd, file), "file"); } inspectGraphs.mockRejectedValue( new Error("Aliases must retain all graphs without discovery"), @@ -215,7 +198,7 @@ describe("changed CI compiler graph selection", () => { }, ); - it.each(["missing", "duplicate", "unexpected", "unmatched", "mixed", "ambient", "config"])( + it.each(["missing", "duplicate", "unmatched", "mixed"])( "refuses incomplete or inapplicable noncore discovery (%s)", (kind) => { const file = "extensions/example/value.ts"; @@ -227,29 +210,19 @@ describe("changed CI compiler graph selection", () => { inventory.pop(); } else if (kind === "duplicate") { inventory[inventory.length - 1] = inventory[0]!; - } else if (kind === "unexpected") { - inventory[0] = { config: "tsconfig.core.json", files: [file] }; } else if (kind === "unmatched") { paths.push("extensions/example/other.ts"); } else if (kind === "mixed") { paths.push("src/value.ts"); - } else if (kind === "ambient") { - paths.push("extensions/example/types.d.ts"); - } else if (kind === "config") { - paths.push("extensions/example/package.json"); } expect(selectChangedCiTsgoGraphs(paths, inventory, { scope: "noncore" })).toBeUndefined(); }, ); - it.each(["missing", "duplicate"])("refuses an incomplete %s compiler inventory", (kind) => { + it("refuses an incomplete full compiler inventory", () => { const inventory = graphs(); inventory[0]!.files.push(sharedType); - if (kind === "missing") { - inventory.pop(); - } else { - inventory[inventory.length - 1] = inventory[0]!; - } + inventory.pop(); expect(selectChangedCiTsgoGraphs([sharedType], inventory)).toBeUndefined(); }); }); diff --git a/test/scripts/ci-check-family-scope.test.ts b/test/scripts/ci-check-family-scope.test.ts index 561df2a75522..827c9bb550be 100644 --- a/test/scripts/ci-check-family-scope.test.ts +++ b/test/scripts/ci-check-family-scope.test.ts @@ -37,8 +37,9 @@ describe("narrow PR check families", () => { "packages/media-core/src/types.d.ts", ])("keeps runtime and declaration consumers for %s", (path) => { const scope = resolveCiCheckFamilyScope([path]); - expect(scope.checkTasks).not.toContain("bundled-channel-config-metadata"); + expect(scope.checkTasks).toEqual(["guards", "dependencies"]); expect(scope.fastTasks).toEqual([]); + expect(scope.additionalGroups).toContain("boundaries"); expect(scope.additionalGroups).toContain("extension-package-boundary"); expect(scope).toMatchObject({ baselineRatchets: true, @@ -47,15 +48,6 @@ describe("narrow PR check families", () => { }); }); - it("keeps extension correctness checks while exact runtime tests use the Node owner", () => { - const scope = resolveCiCheckFamilyScope(["extensions/telegram/src/send.ts"]); - expect(scope.fastTasks).toEqual([]); - expect(scope.checkTasks).toEqual(["guards", "dependencies"]); - expect(scope).toMatchObject({ types: true, lint: true }); - expect(scope.additionalGroups).toContain("boundaries"); - expect(scope.additionalGroups).toContain("extension-package-boundary"); - }); - it.each(["src/config/zod-schema.core.ts", "extensions/telegram/src/config-schema.ts"])( "selects bundled metadata through its schema owner for %s", (file) => { @@ -70,7 +62,6 @@ describe("narrow PR check families", () => { "src/agents/session.test-support.ts", "scripts/lib/source-file-scan-cache.mts", "config/knip.all-exports.config.ts", - "config/test-timeout-race-baseline.txt", "extensions/telegram/tsconfig.json", "extensions/telegram/package.json", "pnpm-lock.yaml", diff --git a/test/scripts/compiler-input-snapshot.test.ts b/test/scripts/compiler-input-snapshot.test.ts index 0a9fcb87ed79..3ace5e609077 100644 --- a/test/scripts/compiler-input-snapshot.test.ts +++ b/test/scripts/compiler-input-snapshot.test.ts @@ -66,7 +66,6 @@ function sealDiagnostic(before: CompilerInputSnapshot, after: CompilerInputSnaps it.each([ ['{"compilerOptions":{"target":"invalid"},"include":["src/**/*.ts"]}', "TS6046"], ['{"include":"src/**/*.ts"}', "TS5024"], - ['{"files":"src/index.ts"}', "TS5024"], ['{"extends":"./missing.json","include":["src/**/*.ts"]}', "TS5083"], ['{"compilerOptions": {', "TS1005"], ])("rejects invalid native compiler configuration %s", (config, diagnostic) => { @@ -245,12 +244,15 @@ it.each([ ["config-bytes", "base.json", '{ "compilerOptions": {"target":"ES2023","types":[]} }\n'], ])( "identifies a %s rejection without exposing configuration or tool bytes", - (category, file, bytes) => { + async (category, file, bytes) => { const f = fixture(); const before = f.snapshot(); + await before.prepare(); f.signature(before); f.write(file!, bytes!); - expect(sealDiagnostic(before, f.snapshot()).detail).toEqual({ category }); + const after = f.snapshot(); + await after.prepare(); + expect(sealDiagnostic(before, after).detail).toEqual({ category }); }, ); @@ -294,16 +296,6 @@ it.each(["ascii", "unicode", "controls"])( }, ); -it("prepares the same ordered source and installed-alias namespace as synchronous readers", async () => { - const f = fixture(); - const synchronous = f.snapshot(); - const prepared = f.snapshot(); - await prepared.prepare(); - for (const outputRoot of [undefined, path.join(f.root, "packages/local/dist")]) { - expect(f.signature(prepared, outputRoot)).toBe(f.signature(synchronous, outputRoot)); - } -}); - it("ignores checkout scratch packages that disappear during preparation", async () => { const f = fixture(); const original = f.signature(f.snapshot()); @@ -545,21 +537,20 @@ it("preloads sibling subtrees while the ordered visitor waits on a deeper direct expect(active).toBe(0); expect(observed.has(path.join(f.root, ".artifacts"))).toBe(false); expect(observed.has(path.join(f.root, ".cache/vitest"))).toBe(false); - expect(f.signature(snapshot)).toBe(f.signature(f.snapshot())); + const synchronous = f.snapshot(); + for (const outputRoot of [undefined, path.join(f.root, "packages/local/dist")]) { + expect(f.signature(snapshot, outputRoot)).toBe(f.signature(synchronous, outputRoot)); + } }); it.each([ - ["source addition", "src/shadow.ts", "export const shadow = 1;\n"], - ["package addition", "src/package.json", '{"type":"commonjs"}'], ["nested workspace metadata", "packages/local/.tmp/package.json", '{"type":"commonjs"}'], [ "installed package metadata", "packages/local/package.json", '{"name":"fixture-package","type":"commonjs"}', ], - ["inherited config", "base.json", '{"compilerOptions":{"target":"ES2022","types":[]}}'], ["generator input", "scripts/generator.mts", "export const generator = 2;\n"], - ["compiler input", "tools/compiler.js", "export const compiler = 2;\n"], ])("retains invalidation after a %s change", async (_label, filename, bytes) => { const f = fixture(); const before = f.snapshot(); diff --git a/test/scripts/control-ui-i18n-verify.test.ts b/test/scripts/control-ui-i18n-verify.test.ts index 48821cdfea58..9b87d6531e02 100644 --- a/test/scripts/control-ui-i18n-verify.test.ts +++ b/test/scripts/control-ui-i18n-verify.test.ts @@ -169,7 +169,6 @@ afterEach(() => { describe("syncControlUiCatalogFallbackBaseline", () => { it("checks and writes ordered fallback bytes without rewriting a matching baseline", async () => { - const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true); const expected = baseline({ "group.second": ["de"], missing: ["de", "fr"] }); await expect( @@ -179,9 +178,6 @@ describe("syncControlUiCatalogFallbackBaseline", () => { await syncControlUiCatalogFallbackBaseline(writeOptions); expect(fixture.writes).toEqual([{ path: baselinePath, data: expected }]); expect(fixture.files.get(baselinePath)).toBe(expected); - expect(stdout).toHaveBeenCalledWith( - "control-ui-i18n: catalog: fallback_keys=2 fallback_pairs=3\n", - ); fixture.writes.length = 0; await syncControlUiCatalogFallbackBaseline(writeOptions); @@ -231,26 +227,22 @@ describe("syncControlUiCatalogFallbackBaseline", () => { ); }); - it.each([false, true])( - "preserves the first missing/malformed memory error (allowCatalogDrift=%s)", - async (allowCatalogDrift) => { - fixture.files.delete(memoryPath("fr")); - fixture.files.set(memoryPath("de"), "{"); - await expect( - syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }), - ).rejects.toThrow("ui/src/i18n/.i18n/fr.tm.jsonl does not contain fr translations"); + it("preserves the first missing/malformed memory error during scoped sync", async () => { + fixture.files.delete(memoryPath("fr")); + fixture.files.set(memoryPath("de"), "{"); + await expect( + syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }), + ).rejects.toThrow("ui/src/i18n/.i18n/fr.tm.jsonl does not contain fr translations"); - fixture.files.set(memoryPath("fr"), "{"); - fixture.files.delete(memoryPath("de")); - await expect( - syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }), - ).rejects.toBeInstanceOf(SyntaxError); - expect(fixture.writes).toEqual([]); - }, - ); + fixture.files.set(memoryPath("fr"), "{"); + fixture.files.delete(memoryPath("de")); + await expect( + syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }), + ).rejects.toBeInstanceOf(SyntaxError); + expect(fixture.writes).toEqual([]); + }); it("only tolerates analyzer drift during scoped sync", async () => { - const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true); fixture.loadSource.mockReturnValue({ greeting: "Hello {count}" }); setMemory("fr", [ row({ @@ -268,23 +260,17 @@ describe("syncControlUiCatalogFallbackBaseline", () => { expect(fixture.writes).toEqual([]); await syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }); expect(fixture.files.get(baselinePath)).toBe(baseline({ greeting: ["de"] })); - expect(stdout).toHaveBeenCalledWith( - "control-ui-i18n: catalog: tolerated_errors=1 during scoped sync\n", - ); }); - it.each([false, true])( - "rejects terminology errors before later locale errors (allowCatalogDrift=%s)", - async (allowCatalogDrift) => { - fixture.loadSource.mockReturnValue({ sessionsView: { subagentPrefix: "abc" } }); - setMemory("fr", [ - row({ segment_id: "sessionsView.subagentPrefix", segment_ids: [], translated: "Cron" }), - ]); - fixture.files.set(memoryPath("de"), "{"); - await expect( - syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }), - ).rejects.toThrow("fr: sessionsView.subagentPrefix"); - expect(fixture.writes).toEqual([]); - }, - ); + it("rejects terminology errors before later locale errors during scoped sync", async () => { + fixture.loadSource.mockReturnValue({ sessionsView: { subagentPrefix: "abc" } }); + setMemory("fr", [ + row({ segment_id: "sessionsView.subagentPrefix", segment_ids: [], translated: "Cron" }), + ]); + fixture.files.set(memoryPath("de"), "{"); + await expect( + syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }), + ).rejects.toThrow("fr: sessionsView.subagentPrefix"); + expect(fixture.writes).toEqual([]); + }); }); diff --git a/test/scripts/control-ui-icon-grid-fit.test.ts b/test/scripts/control-ui-icon-grid-fit.test.ts index f3cbe235655c..b4c41d48051a 100644 --- a/test/scripts/control-ui-icon-grid-fit.test.ts +++ b/test/scripts/control-ui-icon-grid-fit.test.ts @@ -34,52 +34,54 @@ function scan(css: string) { return scanIconGridFit(css, [fixture], base); } +const cramped = + 'html`
`'; + +function createAuditFixture(sheets: Record = {}, markup = cramped) { + const root = tempDirs.make("openclaw-icon-grid-"); + const styles = path.join(root, "ui/src/styles"); + const source = path.join(root, "ui/src/control.ts"); + fs.mkdirSync(styles, { recursive: true }); + for (const [name, css] of Object.entries({ + "base.css": base, + "components.css": "", + "control.css": original, + ...sheets, + })) { + fs.writeFileSync(path.join(styles, name), css); + } + fs.writeFileSync(source, markup); + return { root, styles, source }; +} + describe("fixed icon-grid fit", () => { it("catches the shipped size override rather than flagging the safe base control", () => { const findings = scan(original).findings; expect(findings).toHaveLength(2); - expect(findings).toEqual( - expect.arrayContaining([ + for (const axis of ["width", "height"]) { + expect(findings).toContainEqual( expect.objectContaining({ kind: "overflow", - axis: "width", + axis, size: 26, padding: 12, border: 2, icon: 17, available: 12, }), - expect.objectContaining({ - kind: "overflow", - axis: "height", - size: 26, - padding: 12, - border: 2, - icon: 17, - available: 12, - }), - ]), - ); + ); + } expect(scan(original + ".toolbar > button {padding:0}").findings).toEqual([]); expect(scan(original.replace("width:26px; height:26px;", "")).findings).toEqual([]); }); it("requires authored padding instead of trusting jsdom's zero native default", () => { const css = original.replace("padding:6px;", ""); - expect(scan(css).findings).toEqual([ - expect.objectContaining({ - kind: "native-padding", - axis: "width", - padding: null, - available: null, - }), - expect.objectContaining({ - kind: "native-padding", - axis: "height", - padding: null, - available: null, - }), - ]); + expect(scan(css).findings).toEqual( + ["width", "height"].map((axis) => + expect.objectContaining({ kind: "native-padding", axis, padding: null, available: null }), + ), + ); expect(scan(css + ".icon {padding:0}").findings).toEqual([]); }); @@ -115,7 +117,6 @@ describe("fixed icon-grid fit", () => { ".toolbar .icon {width:40px!important}.icon {width:24px!important}", ], ["always-applicable media correction", "@media all {.icon {padding:0}}"], - ["supports correction", "@supports (display:grid) {.icon {padding:0}}"], ["nested-only padding", ".icon {&:hover {padding:0}}"], ])("defers %s rather than inventing a resolved geometry", (_name, correction) => { const result = scan(original + correction); @@ -123,9 +124,9 @@ describe("fixed icon-grid fit", () => { expect(result.unresolved).toBeGreaterThan(0); }); - it.each(["hover", "focus-visible", "active"])("defers top-level %s geometry", (state) => { + it("defers top-level hover geometry", () => { const result = scan( - original + ".toolbar > button {padding:0}.toolbar > button:" + state + " {padding:8px}", + original + ".toolbar > button {padding:0}.toolbar > button:hover {padding:8px}", ); expect(result.findings).toHaveLength(0); expect(result.checked).toBe(0); @@ -133,14 +134,8 @@ describe("fixed icon-grid fit", () => { }); it("defers cross-sheet ancestor, tag, ID, attribute, and SVG overrides", () => { - const root = tempDirs.make("openclaw-icon-grid-cross-sheet-"); - const styles = path.join(root, "ui/src/styles"); - fs.mkdirSync(styles, { recursive: true }); - fs.writeFileSync(path.join(styles, "base.css"), base); - fs.writeFileSync(path.join(styles, "components.css"), ""); - fs.writeFileSync(path.join(styles, "control.css"), original); - fs.writeFileSync( - path.join(root, "ui/src/control.ts"), + const { root, styles } = createAuditFixture( + {}, 'html`
`', ); for (const correction of [ @@ -249,17 +244,7 @@ describe("fixed icon-grid fit", () => { }); it("audits current source and shared styles on each manual invocation", () => { - const root = tempDirs.make("openclaw-icon-grid-"); - const styles = path.join(root, "ui/src/styles"); - fs.mkdirSync(styles, { recursive: true }); - fs.writeFileSync(path.join(styles, "base.css"), base); - fs.writeFileSync(path.join(styles, "components.css"), ""); - const source = path.join(root, "ui/src/control.ts"); - fs.writeFileSync( - source, - 'html`
`', - ); - fs.writeFileSync(path.join(styles, "control.css"), original); + const { root, styles, source } = createAuditFixture(); const audit = () => auditIconButtons(root, ["ui/src/styles/control.css"]); const first = audit(); expect(first.findings).toHaveLength(2); @@ -267,8 +252,6 @@ describe("fixed icon-grid fit", () => { fs.writeFileSync(source, 'html``'); expect(audit().findings).toHaveLength(0); const added = path.join(root, "ui/src/added.ts"); - const cramped = - 'html`
`'; fs.writeFileSync(added, cramped); expect(audit().findings).toHaveLength(2); fs.unlinkSync(added); @@ -288,18 +271,11 @@ describe("fixed icon-grid fit", () => { }); it("does not append the base sheet again after component overrides", () => { - const root = tempDirs.make("openclaw-icon-grid-order-"); - const styles = path.join(root, "ui/src/styles"); - fs.mkdirSync(styles, { recursive: true }); - fs.writeFileSync(path.join(styles, "base.css"), base + original); - fs.writeFileSync( - path.join(styles, "components.css"), - ".toolbar > button {width:32px;height:32px}", - ); - fs.writeFileSync( - path.join(root, "ui/src/control.ts"), - 'html`
`', - ); + const { root } = createAuditFixture({ + "base.css": base + original, + "components.css": ".toolbar > button {width:32px;height:32px}", + "control.css": "", + }); const result = auditIconButtons(root, ["ui/src/styles/base.css"]); expect(result.findings).toHaveLength(0); expect(result.checkedAxes).toBe(2); diff --git a/test/scripts/control-ui-mock-plugins.test.ts b/test/scripts/control-ui-mock-plugins.test.ts index 607c5c79fee2..3ab317a4f7be 100644 --- a/test/scripts/control-ui-mock-plugins.test.ts +++ b/test/scripts/control-ui-mock-plugins.test.ts @@ -23,19 +23,6 @@ describe("Control UI plugin discovery preview", () => { const result = buildPluginDiscoveryCategoriesMock(); expect(Value.Check(PluginsCatalogCategoriesResultSchema, result)).toBe(true); - expect(result.categories.map((category) => category.slug)).toEqual([ - "channels", - "models", - "memory", - "context", - "voice", - "media", - "web", - "tools", - "runtime", - "gateway", - "security", - "other", - ]); + expect(result.categories.length).toBeGreaterThan(0); }); }); diff --git a/test/scripts/crabbox-hydrate-dependencies.test.ts b/test/scripts/crabbox-hydrate-dependencies.test.ts index c7ec5cd676de..d0581140ecee 100644 --- a/test/scripts/crabbox-hydrate-dependencies.test.ts +++ b/test/scripts/crabbox-hydrate-dependencies.test.ts @@ -97,10 +97,8 @@ fi ["shared setup action", "fresh"], ["default hydration", "legacy"], ["GitHub hydration", "legacy"], - ["default hydration", "unknown"], ["GitHub hydration", "unknown"], ["default hydration", "unknown-newline"], - ["default hydration", "fallback"], ["default hydration", "fallback-dangling"], ["default hydration", "configured-fallback"], ["default hydration", "unknown-fallback"], @@ -116,7 +114,6 @@ fi const store = path.join(root, "store"); const runnerTemp = path.join(root, "runner"); const usesFallback = [ - "fallback", "fallback-dangling", "configured-fallback", "unknown-fallback", @@ -303,26 +300,7 @@ fi rmSync(path.join(workspace, "node_modules"), { recursive: true, force: true }); symlinkSync(linkedModules, path.join(workspace, "node_modules")); - const handoff = path.join(env.HOME!, ".crabbox/actions/hydration-proof.env"); - const exports = `${handoff}.sh`; - const legacyExports = - job === "hydrate-github" - ? `export PNPM_CONFIG_MODULES_DIR=${shellQuote(externalModules)}\nexport PNPM_CONFIG_VIRTUAL_STORE_DIR=${shellQuote(path.join(externalRoot, "virtual-store"))}\n` - : `export CRABBOX_PNPM_MODULES_DIR=${shellQuote(externalModules)}\n`; - write( - root, - path.relative(root, handoff), - `WORKSPACE=${workspace}\nRUN_ID=fixture\nJOB=${job}\nENV_FILE=${exports}\nSERVICES_FILE=${handoff.replace(/\.env$/u, ".services")}\nREADY_AT=2026-09-14T00:00:00Z\n`, - ); - write( - root, - path.relative(root, exports), - `export CI=true\nexport GITHUB_WORKSPACE=${shellQuote(workspace)}\nexport GITHUB_RUN_ID=fixture\nexport PNPM_CONFIG_STORE_DIR=${shellQuote(legacyStore)}\n${usesFallback ? `export XDG_CACHE_HOME=${shellQuote(cacheRoot)}\n` : ""}${legacyExports}`, - ); // Released Crabbox clears both native handoff markers before starting rehydration. - rmSync(handoff); - rmSync(exports); - expect(existsSync(handoff) || existsSync(exports)).toBe(false); if (initialState === "fallback-dangling") { // Native rehydration recreates the same lease's runner root before workflow steps. rmSync(runnerTemp, { recursive: true }); diff --git a/test/scripts/crabbox-source-capsule.test.ts b/test/scripts/crabbox-source-capsule.test.ts index e803f74608d1..bf04a067fcbe 100644 --- a/test/scripts/crabbox-source-capsule.test.ts +++ b/test/scripts/crabbox-source-capsule.test.ts @@ -247,51 +247,37 @@ describe.skipIf(process.platform === "win32")("persistent Crabbox source capsule } }); - it.each(["empty", "changed source"])( - "reuses unchanged mirror files after a same-ref %s commit and seals the new witness", - (change) => { - const f = fixture(); - const first = f.prepare(); - const stable = fileIdentity(join(first.directory, "stable.txt")); - first.cleanup(); - if (change === "changed source") { - writeFileSync(join(f.repository, "change.txt"), "committed newer bytes\r\n"); - f.git(f.repository, "add", "change.txt"); - } - f.git( - f.repository, - "-c", - "commit.gpgsign=false", - "commit", - "--quiet", - "--allow-empty", - "-m", - "next head", + it("reuses unchanged mirror files after a same-ref source commit and seals the new witness", () => { + const f = fixture(); + const first = f.prepare(); + const stable = fileIdentity(join(first.directory, "stable.txt")); + first.cleanup(); + writeFileSync(join(f.repository, "change.txt"), "committed newer bytes\r\n"); + f.git(f.repository, "add", "change.txt"); + f.git(f.repository, "-c", "commit.gpgsign=false", "commit", "--quiet", "-m", "next head"); + const head = f.git(f.repository, "rev-parse", "HEAD"); + expect(head).not.toBe(first.sourceSha); + const next = f.prepare(); + try { + expect(next.directory).toBe(first.directory); + expect(fileIdentity(join(next.directory, "stable.txt"))).toEqual(stable); + expect(next.sourceSha).toBe(head); + expect(readFileSync(join(next.directory, "change.txt"), "utf8")).toBe( + "committed newer bytes\r\n", ); - const head = f.git(f.repository, "rev-parse", "HEAD"); - expect(head).not.toBe(first.sourceSha); - const next = f.prepare(); - try { - expect(next.directory).toBe(first.directory); - expect(fileIdentity(join(next.directory, "stable.txt"))).toEqual(stable); - expect(next.sourceSha).toBe(head); - expect(readFileSync(join(next.directory, "change.txt"), "utf8")).toBe( - change === "changed source" ? "committed newer bytes\r\n" : "original bytes\n", - ); - const receipt: unknown = JSON.parse( - readFileSync(join(next.staging.root, "staging.json"), "utf8"), - ); - expect(receipt).toHaveProperty("witness", { - gitDir: f.git(f.repository, "rev-parse", "--path-format=absolute", "--git-common-dir"), - ref: "refs/heads/main", - commit: head, - }); - f.expectColdEquivalent(next); - } finally { - next.cleanup(); - } - }, - ); + const receipt: unknown = JSON.parse( + readFileSync(join(next.staging.root, "staging.json"), "utf8"), + ); + expect(receipt).toHaveProperty("witness", { + gitDir: f.git(f.repository, "rev-parse", "--path-format=absolute", "--git-common-dir"), + ref: "refs/heads/main", + commit: head, + }); + f.expectColdEquivalent(next); + } finally { + next.cleanup(); + } + }); it.each(["ref", "Git directory"])( "rebuilds the mirror when its retained source %s changes", diff --git a/test/scripts/doctor-install-switch-transport.test.ts b/test/scripts/doctor-install-switch-transport.test.ts index 18f6d44e71db..7bd9d1e6df92 100644 --- a/test/scripts/doctor-install-switch-transport.test.ts +++ b/test/scripts/doctor-install-switch-transport.test.ts @@ -133,23 +133,9 @@ it.runIf(process.platform === "linux")( { command: "busctl", args: ["--machine", "testuser@", ...versionArgs] }, { command: "systemctl", args: ["--system", "is-system-running"] }, ]); - console.info("original Doctor argv:", JSON.stringify(invocations[0]?.args)); }, ); -it.runIf(process.platform === "linux")("still rejects an unavailable synthetic bus", async () => { - const env = scenarioEnvironment(); - vi.mocked(execFileUtf8).mockResolvedValue({ - code: 1, - termination: "exit", - stdout: "", - stderr: "Failed to connect to bus: No such file or directory", - }); - await expect(resolveSystemdUserTransport(env)).rejects.toMatchObject({ - reason: "systemd-user-bus-unavailable", - }); -}); - it("keeps machine scope, auto-start, and foreign-manager probes outside the shim contract", () => { const env = scenarioEnvironment(); for (const args of [ diff --git a/test/scripts/extended-stable-active-line.test.ts b/test/scripts/extended-stable-active-line.test.ts index 9e27304e8999..0b938d1aca03 100644 --- a/test/scripts/extended-stable-active-line.test.ts +++ b/test/scripts/extended-stable-active-line.test.ts @@ -20,7 +20,6 @@ describe("extended-stable live publication eligibility", () => { }); it.skipIf(process.platform === "win32").each([ - { mainVersion: "2026.8.1", expectedStatus: 42, expectedError: "" }, { mainVersion: "2026.9.1", expectedStatus: 42, expectedError: "" }, { mainVersion: "2026.10.1", diff --git a/test/scripts/extension-import-profile.test.ts b/test/scripts/extension-import-profile.test.ts index e2123547ee6d..574fbfeca2f2 100644 --- a/test/scripts/extension-import-profile.test.ts +++ b/test/scripts/extension-import-profile.test.ts @@ -38,7 +38,7 @@ describe("cold-import resource observations", () => { it.each([ "not a resource record", RESOURCE_MARKER + "{", - ...[undefined, 0, -1, 1.5].map( + ...[undefined, 0, 1.5].map( (pid) => RESOURCE_MARKER + JSON.stringify({ ...observation, pid, userCpuUs: 1, systemCpuUs: 0 }), ), diff --git a/test/scripts/frv.release.test.ts b/test/scripts/frv.release.test.ts index e6e14b9d241e..d6b4a32a9367 100644 --- a/test/scripts/frv.release.test.ts +++ b/test/scripts/frv.release.test.ts @@ -679,7 +679,6 @@ describe("publication status real CLI", () => { "duplicate-name", "duplicate-id", "count-gap", - "attempt-limit", ])("rejects independently observed %s", async (kind) => { const result = await runPublicationCli(publicationFixture(), undefined, (responses) => { const prefix = `repos/${REPOSITORY}/actions/`; @@ -711,32 +710,11 @@ describe("publication status real CLI", () => { if (kind === "count-gap") { list.total_count++; } - if (kind === "attempt-limit") { - Object.assign(responses[`${prefix}runs/101`] as object, { run_attempt: 100000000 }); - } }); expect(result.status, result.stdout).toBe(1); expect(JSON.parse(result.stdout).publication.collection.complete).toBe(false); }); - it.each(["88", "77", "101"])( - "refuses advancing run %s without restarting observation", - async (runId) => { - const result = await runPublicationCli(publicationFixture(), undefined, (responses) => { - const path = `repos/${REPOSITORY}/actions/runs/${runId}`; - const before = responses[path] as { run_attempt: number }; - responses[path] = { - sequence: [before, { ...before, run_attempt: before.run_attempt + 1 }], - }; - }); - expect(result.status).toBe(1); - expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed"); - expect( - result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/${runId}`)), - ).toHaveLength(2); - }, - ); - it.each(["missing", "expired", "legacy-only", "unsupported", "incomplete-link"])( "keeps authenticated historical %s unknown, not failed publication", async (kind) => { @@ -773,24 +751,21 @@ describe("publication status real CLI", () => { }, ); - it.each(["403 quota", "404 unavailable", "network timeout"])( - "classifies %s as unavailable, never absence", - async (failure) => { - const result = await runPublicationCli(publicationFixture(), undefined, (responses) => { - responses[`repos/${REPOSITORY}/actions/runs/88/artifacts?per_page=100&page=1`] = { - failure: `${failure}: /private/fixture/credential synthetic-secret`, - }; - }); - expect(result.status).toBe(1); - expect(JSON.parse(result.stdout).publication.collection).toEqual({ - complete: false, - error: "transport", - }); - expect(result.stdout + result.stderr).not.toMatch( - /synthetic-secret|private\/fixture|quota|404 unavailable/u, - ); - }, - ); + it("classifies failed artifact reads as unavailable, never absence", async () => { + const result = await runPublicationCli(publicationFixture(), undefined, (responses) => { + responses[`repos/${REPOSITORY}/actions/runs/88/artifacts?per_page=100&page=1`] = { + failure: "404 unavailable: /private/fixture/credential synthetic-secret", + }; + }); + expect(result.status).toBe(1); + expect(JSON.parse(result.stdout).publication.collection).toEqual({ + complete: false, + error: "transport", + }); + expect(result.stdout + result.stderr).not.toMatch( + /synthetic-secret|private\/fixture|quota|404 unavailable/u, + ); + }); it("retains partial package successes and truncation without declaring a complete observation", async () => { const fixture = publicationFixture(); @@ -973,6 +948,9 @@ describe("publication status real CLI", () => { expect(result.status).toBe(1); expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed"); expect(JSON.parse(result.stdout).publication.relationship.status).toBe("verified"); + expect( + result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/101`)), + ).toHaveLength(2); }); it.each(["transport", "workflow", "attempt-limit"])( @@ -1077,6 +1055,10 @@ describe("publication status real CLI", () => { }); expect(result.status).toBe(1); expect(JSON.parse(result.stdout).publication.relationship.status).toBe("invalid"); + expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed"); + expect( + result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/${runId}`)), + ).toHaveLength(2); }, ); @@ -1317,11 +1299,11 @@ describe("publication status real CLI", () => { }, ); - it.each(["traversal", "unexpected-entry", "expanded", "truncated", "corrupt", "duplicate-entry"])( + it.each(["unexpected-entry", "expanded"])( "refuses %s archives before projecting diagnostics", async (kind) => { const fixture = publicationFixture(); - const result = await runPublicationCli(fixture, undefined, async (responses, artifact) => { + const result = await runPublicationCli(fixture, undefined, async (_responses, artifact) => { await artifact( 3, fixture.publisher, @@ -1329,41 +1311,14 @@ describe("publication status real CLI", () => { DIAGNOSTIC_FILE, fixture.diagnostic, (zip) => { - if (kind === "traversal") { - zip.file("../escape.json", "{}"); - } if (kind === "unexpected-entry") { zip.file("extra.json", "{}"); } if (kind === "expanded") { zip.file(DIAGNOSTIC_FILE, " ".repeat(128 * 1024 + 1)); } - if (kind === "duplicate-entry") { - zip.file("x".repeat(DIAGNOSTIC_FILE.length), "{}"); - } }, ); - const archive = responses[`repos/${REPOSITORY}/actions/artifacts/3/zip`] as { - binary: string; - }; - let bytes = Buffer.from(archive.binary, "base64"); - if (kind === "truncated") { - bytes = bytes.subarray(0, -10); - } - if (kind === "corrupt") { - bytes[0] = 0; - } - if (kind === "duplicate-entry") { - const needle = Buffer.from("x".repeat(DIAGNOSTIC_FILE.length)); - for (let offset = bytes.indexOf(needle); offset !== -1; offset = bytes.indexOf(needle)) { - bytes.set(Buffer.from(DIAGNOSTIC_FILE), offset); - } - } - archive.binary = bytes.toString("base64"); - Object.assign(responses[`repos/${REPOSITORY}/actions/artifacts/3`] as object, { - size_in_bytes: bytes.length, - digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`, - }); }); expect(result.status).toBe(1); expect(JSON.parse(result.stdout).publication.verification.state).toBe("unknown"); @@ -1418,7 +1373,6 @@ describe("publication status real CLI", () => { }); it.each([ - [1, true], [100, true], [100, false], ] as const)( diff --git a/test/scripts/install-cli.freebsd.test.ts b/test/scripts/install-cli.freebsd.test.ts index fa04d8aa2d6f..839e2e7be21e 100644 --- a/test/scripts/install-cli.freebsd.test.ts +++ b/test/scripts/install-cli.freebsd.test.ts @@ -123,15 +123,6 @@ describe("FreeBSD CLI runtime installation", () => { }, ); - it("refuses private Node recovery before linking or changing packages", () => { - const { bin, prefix } = fixture(); - const result = install(bin, prefix, "NODE_ONLY=1"); - expect(result.status).toBe(1); - expect(result.stdout).toContain("Private Node.js recovery is unavailable on FreeBSD"); - expect(result.stdout).not.toContain("unexpected"); - expect(existsSync(prefix)).toBe(false); - }); - it("explains how to install missing Git without invoking pkg", () => { const result = run(` uname() { printf 'FreeBSD\\n'; } @@ -146,11 +137,14 @@ describe("FreeBSD CLI runtime installation", () => { }); describe("FreeBSD source-install admission", () => { - it.each( - ["--install-method git", "--method git", "--git", "--github", "--npm --git", ""].flatMap( - (args) => [false, true].map((json) => ({ args, json })), - ), - )("refuses $args before installation side effects (JSON: $json)", ({ args, json }) => { + it.each([ + { args: "--install-method git", json: false }, + { args: "--method git", json: true }, + { args: "--git", json: false }, + { args: "--github", json: true }, + { args: "--npm --git", json: true }, + { args: "", json: false }, + ])("refuses $args before installation side effects (JSON: $json)", ({ args, json }) => { const { root, prefix } = fixture(); const oldRuntime = join(root, "old-runtime"); const checkout = join(root, "checkout"); @@ -209,11 +203,7 @@ describe("FreeBSD source-install admission", () => { it.each([ ["freebsd", "--git --npm"], - ["freebsd", "--github --install-method npm"], - ["freebsd", "--method npm"], ["linux", "--git"], - ["darwin", "--git"], - ["linux", "--npm"], ["darwin", "--npm"], ])("keeps %s %s on its selected install route", (os, args) => { const result = run( diff --git a/test/scripts/install-ps1.release.test.ts b/test/scripts/install-ps1.release.test.ts index 6f971e438121..5fa811a256da 100644 --- a/test/scripts/install-ps1.release.test.ts +++ b/test/scripts/install-ps1.release.test.ts @@ -336,37 +336,6 @@ try { `, ].join("\n"), }, - { - name: "openclaw-native-command-exit", - source: [ - scriptWithoutEntryPoint, - "", - "function Get-OpenClawCommandPath { return (Get-Process -Id $PID).Path }", - "$caught = $false", - "try {", - " Invoke-OpenClawCommand -NoLogo -NoProfile -Command 'exit 17'", - "} catch {", - " if ($_.Exception.Message -notmatch 'failed with exit code 17') { throw }", - " $caught = $true", - "}", - "if (-not $caught) { throw 'nonzero native exit was accepted' }", - "", - ].join("\n"), - }, - { - name: "doctor-failure-output", - source: [ - scriptWithoutEntryPoint, - "", - "function Invoke-OpenClawCommand { throw 'doctor failed' }", - "$output = @(Run-Doctor *>&1 | ForEach-Object { $_.ToString() })", - '$text = $output -join "`n"', - "if ($text -match 'Migration complete') { throw 'doctor failure reported success' }", - "if ($text -notmatch 'Migration failed') { throw \"missing error: $text\" }", - "if ($output[-1] -ne $false) { throw 'doctor failure did not propagate' }", - "", - ].join("\n"), - }, { name: "npm-lifecycle-policy", source: [ @@ -894,31 +863,6 @@ try { "", ].join("\n"), }, - { - name: "package-manager-node-validation-failure", - source: [ - scriptWithoutEntryPoint, - "", - "function Get-Command {", - " [CmdletBinding()]", - " param([string]$Name)", - " if ($Name -eq 'choco') { return $true }", - " return $null", - "}", - "filter Out-Host { }", - "function choco {", - " $global:LASTEXITCODE = 0", - " Write-Output 'Chocolatey output'", - "}", - "$script:portableCalled = $false", - "function Install-PortableNode { $script:portableCalled = $true }", - "function Check-Node { return $script:portableCalled }", - "$result = @(Install-Node)", - 'if ($result.Count -ne 1 -or $result[0] -ne $true) { throw "Install-Node returned $result" }', - "if (-not $script:portableCalled) { throw 'Portable Node fallback was not attempted' }", - "", - ].join("\n"), - }, { name: "package-manager-node-command-failures", source: [ @@ -1798,7 +1742,6 @@ try { expectBatchedPowerShellCase("winget-node-delayed-path"); expectBatchedPowerShellCase("chocolatey-node-upgrade"); expectBatchedPowerShellCase("scoop-node-update"); - expectBatchedPowerShellCase("package-manager-node-validation-failure"); }); runIfPowerShell("recovers from package-manager failures and preserves installer refusal", () => { @@ -2090,19 +2033,12 @@ describe("install.ps1 stale Winget repair", () => { repair: true, success: true, }, - { - name: "accepts a normal successful install without repair", - installExit: 0, - afterInstall: "healthy", - success: true, - }, { name: "accepts a healthy no-upgrade result without repair", afterInstall: "healthy", success: true, }, { name: "does not repair generic Winget failure", installExit: 1 }, - { name: "does not repair another HRESULT", installExit: -1978335188 }, { name: "does not repair successful install with missing Node", installExit: 0 }, { name: "recovers unsupported repair through Chocolatey", @@ -2125,13 +2061,6 @@ describe("install.ps1 stale Winget repair", () => { fallback: "portable", success: true, }, - { - name: "rejects unusable Chocolatey fallback after failed repair", - repairExit: 1, - repair: true, - fallback: "choco", - afterFallback: "old-sqlite", - }, { name: "rejects unusable portable fallback after failed repair", repairExit: 1, @@ -2139,24 +2068,6 @@ describe("install.ps1 stale Winget repair", () => { fallback: "portable", afterFallback: "text", }, - { - name: "recovers a generic Winget failure through portable Node", - installExit: 1, - fallback: "portable", - success: true, - }, - { - name: "recovers a thrown Winget invocation through portable Node", - installThrows: true, - fallback: "portable", - success: true, - }, - { - name: "recovers a generic Winget failure through the next package manager", - installExit: 1, - fallback: "choco", - success: true, - }, { name: "rejects failed repair even if Node becomes healthy", repairExit: 1, @@ -2164,8 +2075,6 @@ describe("install.ps1 stale Winget repair", () => { repair: true, }, { name: "rejects repair that leaves Node missing", repair: true }, - { name: "rejects old Node after repair", afterRepair: "old-node", repair: true }, - { name: "rejects old SQLite after repair", afterRepair: "old-sqlite", repair: true }, ...["text", "blob", "json", "probe-error"].map((capability) => ({ name: `rejects broken SQLite ${capability} after repair`, afterRepair: capability, @@ -2186,7 +2095,6 @@ describe("install.ps1 stale Winget repair", () => { afterRepair: "missing", repair: false, success: false, - installThrows: false, fallback: "none", afterFallback: "healthy", ...testCase, @@ -2237,7 +2145,6 @@ function Get-Command { function Invoke-FixtureNode { $global:LASTEXITCODE = 0 if ($args[0] -eq '-v') { - if ($global:State -eq 'old-node') { return 'v22.15.0' } return 'v26.1.0' } $probe = @($input) -join [Environment]::NewLine @@ -2253,7 +2160,6 @@ function winget { $global:Events.Add($args[0]) $global:WingetCalls.Add(@($args)) if ($args[0] -eq 'install') { - if ($case.installThrows) { throw 'fixture Winget invocation failed' } $global:LASTEXITCODE = $case.installExit $global:PendingState = $case.afterInstall } elseif ($args[0] -eq 'repair') { @@ -2347,8 +2253,7 @@ Write-Output ('RESULT:' + (@{ direct = $direct; main = $main; healthy = $healthy expect(run.calls).toEqual(options.repair ? [installArgs, repairArgs] : [installArgs]); const repaired = options.repair && options.repairExit === 0 && options.afterRepair === "healthy"; - const fallbackExpected = - !repaired && (options.installThrows || options.afterInstall !== "healthy"); + const fallbackExpected = !repaired && options.afterInstall !== "healthy"; const fallbacks: string[] = []; if (fallbackExpected) { if (options.fallback === "choco") { diff --git a/test/scripts/inventory-report-cli.test.ts b/test/scripts/inventory-report-cli.test.ts index eaeec24bfa8d..ee25829f2514 100644 --- a/test/scripts/inventory-report-cli.test.ts +++ b/test/scripts/inventory-report-cli.test.ts @@ -71,40 +71,21 @@ describe.each([ expect(stdout).toHaveBeenCalledWith(expect.stringContaining("Usage:")); expect(scan).not.toHaveBeenCalled(); }); +}); +describe("shared inventory argument validation", () => { it.each([ { argv: ["--limit"], error: "--limit expects a non-negative integer" }, - ...[ - "", - " ", - " 1", - "1 ", - "+1", - "-1", - "1.0", - "1e3", - "0x10", - "١", - "1", - "9007199254740992", - "9".repeat(400), - "--", - "-h", - ].map((value) => ({ + ...["-1", "9007199254740992", "--"].map((value) => ({ argv: ["--limit", value], error: "--limit expects a non-negative integer", })), { argv: ["--repo-root"], error: "--repo-root expects a path" }, - ...["", "-", "-h", "--"].map((value) => ({ - argv: ["--repo-root", value], - error: "--repo-root expects a path", + { argv: ["--repo-root", "-h"], error: "--repo-root expects a path" }, + ...["--limit=1", "--repo-root=repo", "--json=true"].map((arg) => ({ + argv: [arg], + error: `Unknown argument: ${arg}`, })), - ...["--limit=1", "--repo-root=repo", "--json=true", "--unknown", "positional", ""].map( - (arg) => ({ - argv: [arg], - error: `Unknown argument: ${arg}`, - }), - ), { argv: ["--limit", "1", "--limit", "bad"], error: "--limit expects a non-negative integer" }, { argv: ["--limit", "bad", "--limit", "1"], error: "--limit expects a non-negative integer" }, { argv: ["--repo-root", "valid", "--repo-root", ""], error: "--repo-root expects a path" }, @@ -115,7 +96,7 @@ describe.each([ const scan = vi.spyOn(fileUtils, "listRepoFilesSync"); const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true); for (const args of [argv, ["--help", ...argv], [...argv, "--help"]]) { - expect(() => main(args)).toThrow(new Error(error)); + expect(() => runEnvReport(args)).toThrow(new Error(error)); } expect(stdout).not.toHaveBeenCalled(); expect(scan).not.toHaveBeenCalled(); diff --git a/test/scripts/ios-release-e2e.test.ts b/test/scripts/ios-release-e2e.test.ts index 571bea92d426..643d2ab45a9c 100644 --- a/test/scripts/ios-release-e2e.test.ts +++ b/test/scripts/ios-release-e2e.test.ts @@ -108,7 +108,6 @@ describe("iOS release test identity", () => { ["skipped", { result: "Skipped" }], ["failed", { result: "Failed" }], ["failed child", { children: [{ nodeType: "Test Case Run", result: "Failed" }] }], - ["wrong class", { nodeIdentifier: "OtherTests/testLiveGatewayPairChatAndRelaunch()" }], [ "retry to green", { @@ -204,7 +203,6 @@ describe("sampled simulator-tree footprint", () => { it.each([ {}, { ...sample, bytes: 0 }, - { ...sample, bytes: -1 }, { ...sample, bytes: "1024" }, { ...sample, processes: 0 }, { ...sample, cpu: Number.NaN }, @@ -234,7 +232,7 @@ describe("sampled simulator-tree footprint", () => { function fixture( options: { - fail?: "prepare" | "test" | "reader" | "cleanup"; + fail?: "test" | "cleanup"; measure?: boolean; invalidMeasurement?: boolean; cancel?: boolean; @@ -262,9 +260,6 @@ function fixture( prepare: async () => { trace.push(`prepare:${index}`); time += 100; - if (index === 1 && options.fail === "prepare") { - throw new Error("private preparation diagnostics"); - } }, test: async (test: TestIdentity) => { trace.push(`test:${index}:${test}`); @@ -278,11 +273,7 @@ function fixture( if (options.cancel) { abort.abort(); } - if ( - index === 1 && - (options.fail === "test" || - (options.fail === "reader" && test === IOS_RELEASE_TESTS[1])) - ) { + if (index === 1 && options.fail === "test") { throw Object.assign(new Error("private timeout diagnostics"), { code: "ETIMEDOUT" }); } return result(test); @@ -361,45 +352,11 @@ describe("fresh trial ownership", () => { } expect(JSON.stringify(report)).not.toContain("private"); }); - it.each(["test", "reader"] as const)( - "fails the arm after its %s failure without repeating either test", - async (fail) => { - const { deps, trace } = fixture({ fail }); - const report = await runTrials("stock", deps); - expect(report.trials).toHaveLength(1); - expect(report.trials[0]).toMatchObject({ - status: "failed", - errors: ["test-timeout"], - tests: - fail === "test" - ? [{ test: IOS_RELEASE_TESTS[0], status: "failed" }] - : [ - { test: IOS_RELEASE_TESTS[0], status: "passed" }, - { test: IOS_RELEASE_TESTS[1], status: "failed" }, - ], - }); - expect(trace.filter((entry) => entry.startsWith("test:"))).toEqual( - (fail === "test" ? [IOS_RELEASE_TESTS[0]] : IOS_RELEASE_TESTS).map( - (test) => `test:1:${test}`, - ), - ); - expect(trace.at(-1)).toBe("cleanup:1"); - }, - ); it("refuses comparison without a meter", async () => { const { deps } = fixture(); await expect(runTrials("compare", deps)).rejects.toThrow("comparison-meter-required"); expect(deps.create).not.toHaveBeenCalled(); }); - it("does not retry failed preparation or start its test/meter", async () => { - const { deps, trace } = fixture({ fail: "prepare" }); - const report = await runTrials("stock", deps); - expect(report.trials[0]?.errors).toEqual(["preparation-failed"]); - expect(trace.filter((entry) => entry === "prepare:1")).toHaveLength(1); - expect(trace.some((entry) => entry.startsWith("test:"))).toBe(false); - expect(report.trials[0]?.tests).toEqual([]); - expect(trace).toContain("cleanup:1"); - }); it("joins the collector and fails incomplete measurement without discarding the trial", async () => { const { deps, trace } = fixture({ measure: true, invalidMeasurement: true }); const report = await runTrials("stock", deps); @@ -521,9 +478,7 @@ describe("release qualification workflow authority", () => { }); it.each([ ["manual current revision", {}, true], - ["CI current revision", { caller: "ci" }, true], ["manual arbitrary target", { target: "b".repeat(40) }, false], - ["CI arbitrary target", { caller: "ci", target: "b".repeat(40) }, false], ["invalid SHA", { target: "main" }, false], ["invalid mode", { mode: "unknown" }, false], ])("checks %s before checkout", (_name, options, admitted) => { @@ -533,7 +488,6 @@ describe("release qualification workflow authority", () => { const target = "target" in options ? options.target : sha; const repository = "openclaw/openclaw"; const ref = "refs/heads/main"; - const caller = "caller" in options ? options.caller : "ios-release-e2e"; const first = workflow.jobs.qualify.steps[0]; expect(first.id).toBe("start"); const execution = spawnSync("/bin/bash", ["-c", first.run], { @@ -546,7 +500,7 @@ describe("release qualification workflow authority", () => { GITHUB_SHA: sha, GITHUB_REPOSITORY: repository, GITHUB_REF: ref, - GITHUB_WORKFLOW_REF: `${repository}/.github/workflows/${caller}.yml@${ref}`, + GITHUB_WORKFLOW_REF: `${repository}/.github/workflows/ios-release-e2e.yml@${ref}`, GITHUB_EVENT_NAME: "workflow_dispatch", TARGET_SHA: target, E2E_MODE: "mode" in options ? options.mode : "stock", @@ -637,7 +591,6 @@ describe("release qualification workflow authority", () => { ["full", "a".repeat(40), true], ["full", "b".repeat(40), false], ["main", "a".repeat(40), false], - ["main", "b".repeat(40), false], ])( "selects %s-tier target %s for required native qualification: %s", (tier, target, selected) => { @@ -671,15 +624,12 @@ describe("release qualification workflow authority", () => { describe("native command adapter", () => { it.each([ - "success", "dirty-tracked", "dirty-untracked", "source-late-dirty", "source-late-head-change", "different-xcode", - "different-xcode-build", "invalid-xcode-output", - "different-runtime", "newest-compatible-runtime", "unavailable-runtime", "unsupported-runtime-device", @@ -709,8 +659,6 @@ describe("native command adapter", () => { "reader-failure", "fixture-exit", "gateway-exit", - "missing-first", - "missing-second", "missing-relaunch", "provider-duplicate", "provider-out-of-order", @@ -718,7 +666,6 @@ describe("native command adapter", () => { "test-timeout-output", "reply-failure-evidence", "reply-failure-history-error", - "reply-failure-submission", "reply-failure-source-only", "reply-failure-app-log-error", ])("owns admission, build, test and cleanup for %s", async (scenario) => { @@ -951,11 +898,9 @@ describe("native command adapter", () => { stdout.write( scenario === "different-xcode" ? "Xcode 26.6\nBuild version 17F113\n" - : scenario === "different-xcode-build" - ? "Xcode 27.0\nBuild version 27A000\n" - : scenario === "invalid-xcode-output" - ? "unrecognized toolchain\n" - : "Xcode 27.0\nBuild version 27A266a\n", + : scenario === "invalid-xcode-output" + ? "unrecognized toolchain\n" + : "Xcode 27.0\nBuild version 27A266a\n", ); } else if (args.includes("--print-path")) { stdout.write(`${developerDir}\n`); @@ -964,13 +909,11 @@ describe("native command adapter", () => { } else if (args.includes("runtimes")) { const runtime = { isAvailable: scenario !== "unavailable-runtime", - version: scenario === "different-runtime" ? "27.0" : "26.5", + version: "26.5", identifier: - scenario === "different-runtime" - ? "com.apple.CoreSimulator.SimRuntime.iOS-27-0" - : scenario === "non-ios-runtime" - ? "com.apple.CoreSimulator.SimRuntime.watchOS-26-5" - : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", + scenario === "non-ios-runtime" + ? "com.apple.CoreSimulator.SimRuntime.watchOS-26-5" + : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", supportedArchitectures: scenario === "unsupported-runtime-architecture" ? ["x86_64"] : ["arm64"], supportedDeviceTypes: [ @@ -1014,11 +957,9 @@ describe("native command adapter", () => { expect(lifecycle).toContain("setup-status-ready"); lifecycle.push("simulator-create"); expect(args.at(-1)).toBe( - scenario === "different-runtime" - ? "com.apple.CoreSimulator.SimRuntime.iOS-27-0" - : scenario === "newest-compatible-runtime" - ? "com.apple.CoreSimulator.SimRuntime.iOS-26-10" - : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", + scenario === "newest-compatible-runtime" + ? "com.apple.CoreSimulator.SimRuntime.iOS-26-10" + : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", ); if (scenario === "gateway-exit-during-create") { gatewayChild.exitCode = 17; @@ -1181,7 +1122,7 @@ describe("native command adapter", () => { const failureMessage = scenario === "reply-failure-source-only" ? "" - : `IOS_RELEASE_CHAT_FAILURE relaunch ${scenario === "reply-failure-submission" ? "submission" : "reply"} draft=false keyboard=true reply=false writing=false jump=true foreground=true input=true transcript=true send=false`; + : "IOS_RELEASE_CHAT_FAILURE relaunch reply draft=false keyboard=true reply=false writing=false jump=true foreground=true input=true transcript=true send=false"; stdout.write( "Test Case '-[OpenClawUITests.OpenClawSnapshotUITests testLiveGatewayPairChatAndRelaunch]' started.\n" + `/private/checkout/OpenClawSnapshotUITests.swift:1913: error: private ${failureMessage}\n` + @@ -1338,24 +1279,12 @@ describe("native command adapter", () => { const native = await admission; expect(proof).toMatchObject({ xcode: scenario === "different-xcode" ? "26.6" : "27.0", - xcodeBuild: - scenario === "different-xcode" - ? "17F113" - : scenario === "different-xcode-build" - ? "27A000" - : "27A266a", - runtime: - scenario === "different-runtime" - ? "27.0" - : scenario === "newest-compatible-runtime" - ? "26.10" - : "26.5", + xcodeBuild: scenario === "different-xcode" ? "17F113" : "27A266a", + runtime: scenario === "newest-compatible-runtime" ? "26.10" : "26.5", runtimeIdentifier: - scenario === "different-runtime" - ? "com.apple.CoreSimulator.SimRuntime.iOS-27-0" - : scenario === "newest-compatible-runtime" - ? "com.apple.CoreSimulator.SimRuntime.iOS-26-10" - : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", + scenario === "newest-compatible-runtime" + ? "com.apple.CoreSimulator.SimRuntime.iOS-26-10" + : "com.apple.CoreSimulator.SimRuntime.iOS-26-5", }); try { if (scenario === "native-build-only") { @@ -1426,7 +1355,7 @@ describe("native command adapter", () => { ? [] : [ "chat-stage:relaunch", - `chat-checkpoint:${scenario === "reply-failure-submission" ? "submission" : "reply"}`, + "chat-checkpoint:reply", "chat-draft-retained:false", "chat-keyboard:true", "chat-reply-present:false", diff --git a/test/scripts/kitchen-sink-resources.test.ts b/test/scripts/kitchen-sink-resources.test.ts index 22dab21b13b8..8799ee49ebfd 100644 --- a/test/scripts/kitchen-sink-resources.test.ts +++ b/test/scripts/kitchen-sink-resources.test.ts @@ -81,7 +81,7 @@ describe("Kitchen Sink resource phase receipts", () => { expect(phase.processCpuMsPerCompletedOperation).toBeCloseTo(1.21 / 20); }); - it.each([0, 1, 3])( + it.each([0, 3])( "retains partial split counts when original operation %i fails", async (failedIndex) => { let step = 0; @@ -183,49 +183,46 @@ describe("Kitchen Sink resource phase receipts", () => { }, ); - it.each(invalidSamples)( - "preserves the first receipt and all completions on a %s final sample", - async (_name, invalidSample) => { - const before = snapshot(1); - const midpoint = snapshot(2, 90); - const sample = vi - .fn() - .mockResolvedValueOnce(before) - .mockResolvedValueOnce(midpoint) - .mockImplementationOnce(invalidSample); - const run = vi.fn(async () => {}); - const phase = await measureResourceOperations({ - name: "plugin-tool", - count: 20, - splitFirst: true, - sample, - run, - }); - expect(run).toHaveBeenCalledTimes(20); - expect(sample).toHaveBeenCalledTimes(3); - expect(phase).toMatchObject({ - status: "failed", - operations: { attempted: 20, completed: 20, failed: 0 }, - after: null, - cpu: null, - }); - expect(phase.before).toBe(before); - expect(phase.breakdown![0]).toEqual( - summarizeResourcePhase("plugin-tool-first", before, midpoint, { - attempted: 1, - completed: 1, - failed: 0, - }), - ); - expect(phase.breakdown![1]).toMatchObject({ - name: "plugin-tool-warm", - status: "failed", - operations: { attempted: 19, completed: 19, failed: 0 }, - after: null, - cpu: null, - }); - }, - ); + it("preserves the first receipt and all completions on a missing final sample", async () => { + const before = snapshot(1); + const midpoint = snapshot(2, 90); + const sample = vi + .fn() + .mockResolvedValueOnce(before) + .mockResolvedValueOnce(midpoint) + .mockRejectedValueOnce(new Error("sample unavailable")); + const run = vi.fn(async () => {}); + const phase = await measureResourceOperations({ + name: "plugin-tool", + count: 20, + splitFirst: true, + sample, + run, + }); + expect(run).toHaveBeenCalledTimes(20); + expect(sample).toHaveBeenCalledTimes(3); + expect(phase).toMatchObject({ + status: "failed", + operations: { attempted: 20, completed: 20, failed: 0 }, + after: null, + cpu: null, + }); + expect(phase.before).toBe(before); + expect(phase.breakdown![0]).toEqual( + summarizeResourcePhase("plugin-tool-first", before, midpoint, { + attempted: 1, + completed: 1, + failed: 0, + }), + ); + expect(phase.breakdown![1]).toMatchObject({ + name: "plugin-tool-warm", + status: "failed", + operations: { attempted: 19, completed: 19, failed: 0 }, + after: null, + cpu: null, + }); + }); it("counts only asserted responses and stops on the first failure without retrying", async () => { const sample = vi @@ -327,16 +324,6 @@ describe("Kitchen Sink resource phase receipts", () => { ).toEqual([]); }); - it("rejects mixed process identities and unavailable memory rather than reporting zero", () => { - const ops = { attempted: 0, completed: 0, failed: 0 }; - expect(() => - summarizeResourcePhase("idle", snapshot(1), { ...snapshot(2), pid: 999 }, ops), - ).toThrow("one process"); - const invalid = snapshot(2); - invalid.memory.rss = Number.NaN; - expect(() => summarizeResourcePhase("idle", snapshot(1), invalid, ops)).toThrow("invalid rss"); - }); - it("retains signed resource changes and rejects missing observations", () => { const before = { ...snapshot(1), activeResources: { Timeout: 2, TCPServerWrap: 1 } }; const after = { ...snapshot(2), activeResources: { Timeout: 1, Immediate: 1 } }; diff --git a/test/scripts/linux-app-channel.test.ts b/test/scripts/linux-app-channel.test.ts index 8846358f808c..2406254f39bf 100644 --- a/test/scripts/linux-app-channel.test.ts +++ b/test/scripts/linux-app-channel.test.ts @@ -1028,27 +1028,20 @@ it("accepts a successful completed publisher for finalization without a Linux ch it("rejects retired Tideclaw alpha finalization before writes", () => { const f = fixture("tideclaw/alpha/2026-09-13-0400Z"); - const tag = "v2026.9.4-alpha.1"; - f.addDraft(tag, true); - const result = f.run("finalize-core", tag, "false"); + const alphaTag = "v2026.9.4-alpha.1"; + f.addDraft(alphaTag, true); + const result = f.run("finalize-core", alphaTag, "false"); expect(result.status, result.stderr).toBe(1); expect(result.stderr).toContain("Alpha releases are retired;"); expect(f.mutations()).toEqual([]); }); -it.each([ - { ref: "unreviewed/branch", tag: "v2026.9.4-alpha.1", latest: "false" }, - { ref: "tideclaw/alpha/2026-09-13-0400Z", tag: nextTag, latest: "false" }, - { ref: "tideclaw/alpha/2026-09-13-0400Z", tag: "v2026.9.4-alpha.1", latest: "true" }, -])( - "rejects unapproved branch finalization $ref/$tag/$latest", - ({ ref, tag: selectedTag, latest }) => { - const f = fixture(ref); - f.addDraft(selectedTag, selectedTag.includes("-alpha.")); - expect(f.run("finalize-core", selectedTag, latest).status).toBe(1); - expect(f.mutations()).toEqual([]); - }, -); +it("rejects a retired alpha workflow even when finalizing a stable tag", () => { + const f = fixture("tideclaw/alpha/2026-09-13-0400Z"); + f.addDraft(nextTag); + failed(f.run("finalize-core", nextTag, "false"), "Alpha releases are retired;"); + expect(f.mutations()).toEqual([]); +}); it("publishes opt-in desktop metadata only after successful Linux and legacy readback", () => { const f = fixture(toolingRef, true); @@ -1485,7 +1478,7 @@ it("refuses mirroring before canonical initialization without creating a release expect(f.mutations()).toEqual([]); }); -it.each(["v2026.9.3-alpha.1", "v2026.9.3-beta.1", "v2026.6.33"])( +it.each(["v2026.9.3-beta.1", "v2026.6.33"])( "rejects non-regular target %s before touching GitHub", (releaseTag) => { const f = fixture(); @@ -1556,7 +1549,6 @@ it.each([null, tag])( it.each([ { releaseTag: nextTag, prerelease: false }, - { releaseTag: "v2026.6.33", prerelease: false }, { releaseTag: "v2026.8.35", prerelease: false }, { releaseTag: "v2026.9.4-beta.1", prerelease: true }, ])("honors explicit non-latest finalization of $releaseTag", ({ releaseTag, prerelease }) => { diff --git a/test/scripts/linux-updater-manifest.test.ts b/test/scripts/linux-updater-manifest.test.ts index 1614a6f2e144..330e6039f899 100644 --- a/test/scripts/linux-updater-manifest.test.ts +++ b/test/scripts/linux-updater-manifest.test.ts @@ -46,7 +46,7 @@ type Remote = { requestReadRejected?: boolean; uploadBehavior?: "accepted-error" | "rejected-error" | "deleted-error" | "success-noop"; dispatchRejected?: boolean; - dispatchResponse?: "missing-id" | "null-id"; + missingDispatchId?: boolean; toolingStatus?: string; parentAttempt?: number; currentRun?: ActionRun; @@ -175,8 +175,7 @@ if (args[0] === 'api') { (state.requestRuns ||= []).unshift({id: 456, head_sha: '${toolingSha}', head_branch: request.ref, event: 'workflow_dispatch', status: 'queued', conclusion: null, display_title: 'Linux App Release Request [' + request.inputs.tag + '] desktop=' + request.inputs['desktop-test-bundles']}); - result({...(state.dispatchResponse === 'missing-id' ? {} : - {workflow_run_id: state.dispatchResponse === 'null-id' ? null : 456}), + result({...(state.missingDispatchId ? {} : {workflow_run_id: 456}), html_url: 'https://github.com/${repository}/actions/runs/456'}); } else if (endpoint?.startsWith('repos/${repository}/commits/')) { result('${sourceSha}'); @@ -380,7 +379,7 @@ process.stdout.write('${sourceSha}\\trefs/tags/v2026.9.4\\n'); }; } -it.each(["2026.9.3", "2026.9.4", "2026.9.5"])( +it.each(["2026.9.3", "2026.9.5"])( "preserves an equal or newer valid target manifest byte-for-byte (%s)", (version) => { const remote = fixture(); @@ -418,7 +417,6 @@ it.each(["missing-manifest", "missing-release"])( it.each([ "cross-repository", - "wrong-version-url", "empty-signature", "invalid-base64", "invalid-json", @@ -436,9 +434,6 @@ it.each([ if (kind === "cross-repository") { platform.url = platform.url.replace(repository, "foreign/repository"); } - if (kind === "wrong-version-url") { - platform.url = platform.url.replaceAll("2026.9.3", "2026.9.2"); - } if (kind === "empty-signature") { platform.signature = ""; } @@ -565,7 +560,7 @@ it("preserves identity CLI behavior when no writer tuple is requested", () => { }); it.each([ - ...writerCliFields.map(([name, value]) => ({ name, arguments: [name, value] })), + { name: "incomplete writer tuple", arguments: ["--writer-run-id", "200"] }, { name: "missing writer value", arguments: ["--writer-run-id"] }, ])("refuses partial identity CLI writer arguments: $name", (scenario) => { const remote = fixture(); @@ -811,10 +806,7 @@ it.each(["absent", "complete", "partial", "propagation"])( ); it("reports a refused Linux workflow dispatch without a success receipt or retry", () => { - const remote = fixture({ dispatchRejected: true }); - remote.update((state) => { - state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined }); - }); + const remote = pendingLinuxFixture({ dispatchRejected: true }); const result = remote.dispatch(); expect(result.status).toBe(1); expect(result.evidence.state).toBe("dispatch-unconfirmed"); @@ -847,7 +839,7 @@ function linuxRequest(overrides: Partial = {}): ActionRun { }; } -it.each(["requested", "waiting", "pending", "queued", "in_progress", "completed"])( +it.each(["queued", "completed"])( "reuses a manual same-tag Linux request in %s state without another build request", (status) => { const remote = pendingLinuxFixture({ @@ -894,7 +886,6 @@ it("finds a desktop-inclusive Linux request beyond the first history page", () = it.each([ { reason: "failed", overrides: { conclusion: "failure" } }, - { reason: "cancelled", overrides: { conclusion: "cancelled" } }, { reason: "another branch", overrides: { head_branch: "feature" } }, { reason: "another event", overrides: { event: "push" } }, { @@ -919,10 +910,7 @@ it("refuses another Linux request when request history cannot be read", () => { }); it("refuses a moved tag even when the dispatch caller suppresses shell errexit", () => { - const remote = fixture(); - remote.update((state) => { - state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined }); - }); + const remote = pendingLinuxFixture(); const result = remote.dispatch("c".repeat(40)); expect(result.status).toBe(1); expect(result.stderr).toContain("Release tag v2026.9.4 moved"); @@ -933,20 +921,14 @@ it("refuses a moved tag even when the dispatch caller suppresses shell errexit", ).toEqual([]); }); -it.each(["missing-id", "null-id"] as const)( - "does not confirm or repeat an accepted workflow dispatch with %s", - (dispatchResponse) => { - const remote = fixture({ dispatchResponse }); - remote.update((state) => { - state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined }); - }); - const result = remote.dispatch(); - expect(result.status).toBe(1); - expect(result.evidence.state).toBe("dispatch-unconfirmed"); - expect(remote.read().requests).toHaveLength(1); - expect(remote.read().calls.filter((args) => args[0] === "run")).toEqual([]); - expect( - remote.read().calls.filter((args) => args.some((arg) => arg.endsWith("/dispatches"))), - ).toHaveLength(1); - }, -); +it("does not confirm or repeat an accepted workflow dispatch without a run ID", () => { + const remote = pendingLinuxFixture({ missingDispatchId: true }); + const result = remote.dispatch(); + expect(result.status).toBe(1); + expect(result.evidence.state).toBe("dispatch-unconfirmed"); + expect(remote.read().requests).toHaveLength(1); + expect(remote.read().calls.filter((args) => args[0] === "run")).toEqual([]); + expect( + remote.read().calls.filter((args) => args.some((arg) => arg.endsWith("/dispatches"))), + ).toHaveLength(1); +}); diff --git a/test/scripts/managed-child-process.tree.test.ts b/test/scripts/managed-child-process.tree.test.ts index c759cee7f400..829319eb5e73 100644 --- a/test/scripts/managed-child-process.tree.test.ts +++ b/test/scripts/managed-child-process.tree.test.ts @@ -208,7 +208,6 @@ it.each([false, true])( it.each([ ["win32", true, true], ["win32", false, true], - ["darwin", true, true], ["darwin", false, true], ["win32", true, false], ] as const)( @@ -297,12 +296,7 @@ it.each([ if (terminates && closes) { expect(descendantOutput.destroyed).toBe(true); owner.assertReleased(); - // POSIX strict normal-exit policy still reports unexpected group survivors. - if (platform === "win32") { - expect(outcome).toBe(0); - } else { - expect(outcome).toMatchObject({ processTreeState: "terminated" }); - } + expect(outcome).toBe(0); } else { expect(hasUnjoinedWork(outcome)).toBe(true); expect(outcome).toMatchObject({ code: "EPROCESSGROUP_CLEANUP_FAILED" }); diff --git a/test/scripts/managed-handoff-build-config.test.ts b/test/scripts/managed-handoff-build-config.test.ts index 988167284639..51d2a0d1ebc9 100644 --- a/test/scripts/managed-handoff-build-config.test.ts +++ b/test/scripts/managed-handoff-build-config.test.ts @@ -1,4 +1,4 @@ -import { execFileSync, spawnSync } from "node:child_process"; +import { spawnSync } from "node:child_process"; import { randomUUID } from "node:crypto"; import { mkdirSync, readdirSync, realpathSync, readFileSync, lstatSync } from "node:fs"; import path from "node:path"; @@ -73,23 +73,6 @@ const tempDirs = useAutoCleanupTempDirTracker(afterEach); afterEach(() => vi.restoreAllMocks()); -it("loads the worker compiler with native Node before preparing artifacts", () => { - const output = execFileSync( - process.execPath, - [ - "--input-type=module", - "--eval", - ` -await import("./scripts/lib/vitest-worker-compiler.mts"); -console.log("native worker compiler import verified"); -`, - ], - { encoding: "utf8", timeout: 30_000 }, - ); - - expect(output.trim()).toBe("native worker compiler import verified"); -}); - it.each( (["managed", "package"] as const).filter( (kind) => kind === "managed" || process.platform !== "win32", @@ -246,9 +229,6 @@ it.each( "createManagedHandoffLeaseStore", "resolveUpdateRestartNoticeMeta", "shouldPublishUpdateRestartNotice", - "extractSqliteTableSchema", - "readRestartSentinelRowSync", - "writeRestartSentinelRowIfRevisionSync", ]) { assert.equal(typeof runtime[name], "function", name); } @@ -330,12 +310,7 @@ it.each( expect(stale.error).toBeUndefined(); expect(stale.status).toBe(1); expect(stale.stderr).toContain("different operation"); - const after = snapshot(); - expect(after).toHaveLength(before.length); - for (const [index, original] of before.entries()) { - expect(after[index]!.ino).toBe(original.ino); - expect(after[index]!.bytes.equals(original.bytes)).toBe(true); - } + expect(snapshot()).toEqual(before); } // The replacement's temporary command is deliberately one-phase, never // another locator for the next operation after its helper has moved. diff --git a/test/scripts/mobile-release-ci.test.ts b/test/scripts/mobile-release-ci.test.ts index 2a01e6124a8e..b4018ea31eb1 100644 --- a/test/scripts/mobile-release-ci.test.ts +++ b/test/scripts/mobile-release-ci.test.ts @@ -755,16 +755,6 @@ describe("mobile release CI tools", () => { expect(JSON.stringify(steps)).not.toContain("candidate/"); const tooling = steps[toolingIndex]?.run ?? ""; - expect(tooling).toContain('apt_source="/etc/apt/sources.list.d/ubuntu.sources"'); - expect(tooling).toContain( - 'apt_source_parts="$RUNNER_TEMP/openclaw-android-apt-sourceparts-disabled"', - ); - expect(tooling).toContain('test -s "$apt_source"'); - expect(tooling).toContain('[[ -e "$apt_source_parts" || -L "$apt_source_parts" ]]'); - expect(tooling).toContain('/usr/bin/apt-get "${apt_options[@]}" update'); - expect(tooling).toMatch( - /\/usr\/bin\/apt-get "\$\{apt_options\[@\]\}" install \\\n\s+-y --no-install-recommends imagemagick/u, - ); expect(tooling).toContain( 'test "$(git -C "$trusted_root" rev-parse HEAD)" = "$GITHUB_WORKFLOW_SHA"', ); @@ -955,18 +945,10 @@ describe("mobile release CI tools", () => { expect(diagnostic).toContain( 'emulator_args=(-avd "$AVD_NAME" -no-window -no-audio -no-boot-anim -verbose -show-kernel)', ); - expect(diagnostic).toContain("capture_accel_check() {"); expect(diagnostic).toContain("accel_check_timeout_seconds=10"); - expect(diagnostic).toContain('emulator -accel-check >"$accel_raw" 2>&1 &'); expect(diagnostic).toContain( 'head -c 16384 "$accel_raw" >"$DIAGNOSTIC_DIR/emulator-accel-check.txt"', ); - expect(diagnostic).toContain( - 'printf \'exit_status=%s\\n\' "$accel_status" >>"$DIAGNOSTIC_DIR/emulator-accel-check.txt"', - ); - expect(diagnostic).toContain( - 'printf \'timed_out=%s\\n\' "$accel_timed_out" >>"$DIAGNOSTIC_DIR/emulator-accel-check.txt"', - ); expect(diagnostic).toContain("sample_owned_qemu() {"); expect(diagnostic).toContain( 'printf \'\\n[%s] owned_emulator_pid=%s\\n\' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$emulator_pid"', @@ -1021,18 +1003,15 @@ describe("mobile release CI tools", () => { expect(timedOutAccel.output).toContain("exit_status=124"); expect(timedOutAccel.output).toContain("timed_out=true"); - expect(diagnostic).toContain("observe_after_readiness_timeout() {"); expect(diagnostic).toContain("final_cold_boot_observation_seconds=900"); expect(diagnostic).toContain("probe_timeout_seconds=5"); expect(diagnostic).toContain("final_snapshot_lead_seconds=15"); expect(diagnostic).toContain("snapshot_properties_max_bytes=65536"); expect(diagnostic).toContain("snapshot_logcat_max_bytes=262144"); - expect(diagnostic).toContain("capture_cold_boot_snapshot() {"); expect(diagnostic).toContain( "emulator_observation_deadline=$((emulator_launch_seconds + final_cold_boot_observation_seconds))", ); expect(diagnostic).not.toContain("post_deadline_observation_seconds"); - expect(diagnostic).toContain("fail_after_readiness_timeout() {"); const observationFunctionStart = diagnostic.indexOf("run_bounded_probe() {"); const observationFunctionEnd = diagnostic.indexOf( "\n\nfail_after_readiness_timeout()", @@ -1127,34 +1106,11 @@ describe("mobile release CI tools", () => { }; }; - const lateReady = await runPostDeadlineObservation(`#!/bin/bash -set -euo pipefail -if [[ "\${1:-}" == "devices" ]]; then - printf 'List of devices attached\\nemulator-5554\\tdevice product:sdk model:sdk\\n' -elif [[ "\${1:-}" == "-s" && "\${3:-}" == "shell" ]]; then - printf '1\\n' -elif [[ "\${1:-}" == "-s" && "\${3:-}" == "emu" ]]; then - printf '%s\\nOK\\n' "\${AVD_NAME:?}" -fi -`); - expect(lateReady.result.status).toBe(1); - expect(lateReady.result.stderr).toContain("::error::latched readiness failure"); - expect(lateReady.observations).toContain("late_adb_online_at="); - expect(lateReady.observations).toContain("late_boot_completed_at="); - expect(lateReady.observations).toContain("observation_stop=late-boot-completed"); - expect(lateReady.snapshots).toEqual(["first-online"]); - expect( - fs.readFileSync( - path.join(lateReady.snapshotsRoot, "first-online", "boot-properties.txt"), - "utf8", - ), - ).toContain("probe_exit_status=0"); - const lateReadyNearCeilingFunctions = observationFunctions.replace( "final_snapshot_lead_seconds=4", "final_snapshot_lead_seconds=60", ); - const lateReadyNearCeiling = await runPostDeadlineObservation( + const lateReady = await runPostDeadlineObservation( `#!/bin/bash set -euo pipefail if [[ "\${1:-}" == "devices" ]]; then @@ -1167,10 +1123,18 @@ fi `, { functions: lateReadyNearCeilingFunctions }, ); - expect(lateReadyNearCeiling.result.status).toBe(1); - expect(lateReadyNearCeiling.observations).toContain("late_boot_completed_at="); - expect(lateReadyNearCeiling.observations).toContain("observation_stop=late-boot-completed"); - expect(lateReadyNearCeiling.snapshots).toEqual(["first-online"]); + expect(lateReady.result.status).toBe(1); + expect(lateReady.result.stderr).toContain("::error::latched readiness failure"); + expect(lateReady.observations).toContain("late_adb_online_at="); + expect(lateReady.observations).toContain("late_boot_completed_at="); + expect(lateReady.observations).toContain("observation_stop=late-boot-completed"); + expect(lateReady.snapshots).toEqual(["first-online"]); + expect( + fs.readFileSync( + path.join(lateReady.snapshotsRoot, "first-online", "boot-properties.txt"), + "utf8", + ), + ).toContain("probe_exit_status=0"); const [failedBootProbeResult, boundedSnapshotsResult] = await Promise.allSettled([ runPostDeadlineObservation( @@ -1256,21 +1220,6 @@ fi expect(changedDevice.observations).toContain("observation_stop=unexpected-device-change"); expect(changedDevice.snapshots).toEqual([]); - const capped = await runPostDeadlineObservation( - `#!/bin/bash -set -euo pipefail -if [[ "\${1:-}" == "devices" ]]; then - printf 'List of devices attached\\n\\n' -fi -`, - { deadlineSeconds: 2 }, - ); - expect(capped.result.status).toBe(1); - expect(capped.elapsedSeconds).toBe(2); - expect(capped.result.stderr).toContain("::error::latched readiness failure"); - expect(capped.observations).toContain("observation_cap_seconds=900"); - expect(capped.observations).toContain("observation_stop=observation-cap-reached"); - const absoluteCap = await runPostDeadlineObservation( `#!/bin/bash set -euo pipefail @@ -1282,6 +1231,8 @@ fi ); expect(absoluteCap.result.status).toBe(1); expect(absoluteCap.elapsedSeconds).toBe(3); + expect(absoluteCap.result.stderr).toContain("::error::latched readiness failure"); + expect(absoluteCap.observations).toContain("observation_cap_seconds=900"); expect(absoluteCap.durationMs).toBeLessThan(5_000); expect(absoluteCap.observations).toContain("observation_stop=observation-cap-reached"); @@ -1454,7 +1405,6 @@ fi expect(diagnostic).toContain("adb devices -l"); expect(diagnostic).toContain('>>"$DIAGNOSTIC_DIR/adb-observations.log" 2>&1'); expect(diagnostic).toContain('ps -p "$emulator_pid"'); - expect(diagnostic).toContain('kill "$emulator_pid"'); expect(diagnostic).toContain("adb kill-server"); expect(diagnostic).toContain("trap cleanup EXIT"); expect(diagnostic).toMatch( @@ -1906,8 +1856,6 @@ fi "bundle:_4.0.21_ exec fastlane ios signing_check", "probe:root-cwd", ]); - expect(signingProof).toContain("source ./scripts/lib/ios-fastlane.sh"); - expect(signingProof).toContain("(cd apps/ios && run_ios_fastlane ios signing_check)"); const failedCheck = runSigningProof({ FIXTURE_FAIL_CHECK: "1" }); expect(failedCheck.result.status).not.toBe(0); diff --git a/test/scripts/mobile-release-notes.test.ts b/test/scripts/mobile-release-notes.test.ts index baf7ed9dac82..ad864281ca2d 100644 --- a/test/scripts/mobile-release-notes.test.ts +++ b/test/scripts/mobile-release-notes.test.ts @@ -179,104 +179,90 @@ describe("generated mobile store notes", () => { await expect(generateMobileReleaseNotes(f)).rejects.toThrow("different production baseline"); }); - it.each(["legacy", "v2"])( - "keeps phone and Wear baselines and text separate with %s source records", - async (scheme) => { - const f = fixture("android"); - const sourceRef = - "refs/openclaw/mobile-releases/android/v2/2026.7.3/0/1/2026070449-2026070450"; - const wearBase = f.sourceSha; - git( - f.rootDir, - "update-ref", - "refs/openclaw/mobile-releases/android/2026.7.3-2026070302", - wearBase, - ); - f.plan.releaseNotesBaselines[1] = { - audience: "wear", - version: "2026.7.3", - build: "2026070352", - }; - const wearFile = "apps/android/wear/src/main/java/Watch.kt"; - f.write(wearFile, 'val label = "Start talking"\n'); - git(f.rootDir, "add", wearFile); - git(f.rootDir, "commit", "-m", "Clarify watch voice action"); - f.sourceSha = git(f.rootDir, "rev-parse", "HEAD"); - f.plan.sourceSha = f.sourceSha; - fs.writeFileSync(f.planPath, JSON.stringify(f.plan)); - if (scheme === "v2") { - git(f.rootDir, "update-ref", sourceRef, f.base); - f.plan.releaseNotesBaselines[0] = { - audience: "phone", - version: "2026.7.30", - build: "2026070449", - }; - fs.writeFileSync( - f.planPath, - JSON.stringify({ - ...f.plan, - releaseNotesBaselines: [ - { ...f.plan.releaseNotesBaselines[0], sourceRef }, - f.plan.releaseNotesBaselines[1], - ], - }), - ); - } - accept([{ file: f.file, focus: ["label"] }]); - accept( - [{ file: wearFile, focus: ["label"] }], - [{ text: "Clearer watch voice controls.", evidenceIds: ["e1"] }], - ); - const saved = await generateMobileReleaseNotes(f); - const wearInventory = JSON.parse(api.parse.mock.calls[3]![0].input); - expect(wearInventory.files).toEqual( - expect.arrayContaining([expect.objectContaining({ file: wearFile })]), - ); - expect(wearInventory.files).not.toEqual( - expect.arrayContaining([expect.objectContaining({ file: f.file })]), - ); - for (const index of [2, 5]) { - const review = JSON.parse(api.parse.mock.calls[index]![0].input); - expect(review.evidence).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - file: "apps/android/app/src/play/java/ai/openclaw/app/SensitiveFeatureConfig.kt", - kind: "context", - patch: expect.stringContaining("smsEnabled = false"), - }), - ]), - ); - } - expect( - saved.entries.map((entry) => [ - entry.audience, - entry.baseline.build, - entry.baseline.sourceSha, - entry.text, - ]), - ).toEqual([ - [ - "phone", - scheme === "v2" ? "2026070449" : "2026070301", - f.base, - "- Clearer labels when sending messages.", + it("keeps v2 phone and legacy Wear baselines and text separate", async () => { + const f = fixture("android"); + const sourceRef = "refs/openclaw/mobile-releases/android/v2/2026.7.3/0/1/2026070449-2026070450"; + const wearBase = f.sourceSha; + git( + f.rootDir, + "update-ref", + "refs/openclaw/mobile-releases/android/2026.7.3-2026070302", + wearBase, + ); + f.plan.releaseNotesBaselines[1] = { + audience: "wear", + version: "2026.7.3", + build: "2026070352", + }; + const wearFile = "apps/android/wear/src/main/java/Watch.kt"; + f.write(wearFile, 'val label = "Start talking"\n'); + git(f.rootDir, "add", wearFile); + git(f.rootDir, "commit", "-m", "Clarify watch voice action"); + f.sourceSha = git(f.rootDir, "rev-parse", "HEAD"); + f.plan.sourceSha = f.sourceSha; + git(f.rootDir, "update-ref", sourceRef, f.base); + f.plan.releaseNotesBaselines[0] = { + audience: "phone", + version: "2026.7.30", + build: "2026070449", + }; + fs.writeFileSync( + f.planPath, + JSON.stringify({ + ...f.plan, + releaseNotesBaselines: [ + { ...f.plan.releaseNotesBaselines[0], sourceRef }, + f.plan.releaseNotesBaselines[1], ], - ["wear", "2026070352", wearBase, "- Clearer watch voice controls."], - ]); - vi.stubEnv("OPENAI_API_KEY", ""); - expect(await generateMobileReleaseNotes(f)).toEqual(saved); - if (scheme === "v2") { - fs.rmSync(f.outputPath); - vi.stubEnv("OPENAI_API_KEY", "synthetic-key"); - const plan = JSON.parse(fs.readFileSync(f.planPath, "utf8")); - plan.releaseNotesBaselines[0].build = "2026070450"; - fs.writeFileSync(f.planPath, JSON.stringify(plan)); - await expect(generateMobileReleaseNotes(f)).rejects.toThrow( - "does not match its recorded store identity", - ); - } - }, - ); + }), + ); + accept([{ file: f.file, focus: ["label"] }]); + accept( + [{ file: wearFile, focus: ["label"] }], + [{ text: "Clearer watch voice controls.", evidenceIds: ["e1"] }], + ); + const saved = await generateMobileReleaseNotes(f); + const wearInventory = JSON.parse(api.parse.mock.calls[3]![0].input); + expect(wearInventory.files).toEqual( + expect.arrayContaining([expect.objectContaining({ file: wearFile })]), + ); + expect(wearInventory.files).not.toEqual( + expect.arrayContaining([expect.objectContaining({ file: f.file })]), + ); + for (const index of [2, 5]) { + const review = JSON.parse(api.parse.mock.calls[index]![0].input); + expect(review.evidence).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + file: "apps/android/app/src/play/java/ai/openclaw/app/SensitiveFeatureConfig.kt", + kind: "context", + patch: expect.stringContaining("smsEnabled = false"), + }), + ]), + ); + } + expect( + saved.entries.map((entry) => [ + entry.audience, + entry.baseline.build, + entry.baseline.sourceSha, + entry.text, + ]), + ).toEqual([ + ["phone", "2026070449", f.base, "- Clearer labels when sending messages."], + ["wear", "2026070352", wearBase, "- Clearer watch voice controls."], + ]); + vi.stubEnv("OPENAI_API_KEY", ""); + expect(await generateMobileReleaseNotes(f)).toEqual(saved); + fs.rmSync(f.outputPath); + vi.stubEnv("OPENAI_API_KEY", "synthetic-key"); + const plan = JSON.parse(fs.readFileSync(f.planPath, "utf8")); + plan.releaseNotesBaselines[0].build = "2026070450"; + fs.writeFileSync(f.planPath, JSON.stringify(plan)); + await expect(generateMobileReleaseNotes(f)).rejects.toThrow( + "does not match its recorded store identity", + ); + }); it("does not invent notes for a fully reverted app change", async () => { const f = fixture(); diff --git a/test/scripts/npm-onboard-execution-identity.test.ts b/test/scripts/npm-onboard-execution-identity.test.ts index 4097d77eb6b8..477fc6231f96 100644 --- a/test/scripts/npm-onboard-execution-identity.test.ts +++ b/test/scripts/npm-onboard-execution-identity.test.ts @@ -55,13 +55,6 @@ describe("installed-package execution identity proof", () => { expect(existsSync(path.join(state, "state/openclaw.sqlite"))).toBe(false); }); - it("accepts the persisted run id when it differs from the caller session id", () => { - const result = projection(); - expect( - assertIdentityProjection(result, JSON.stringify(result.identity.context), [privateMarker]), - ).toBe("execution-1"); - }); - it.each([ [ "missing identity", @@ -109,15 +102,10 @@ describe("installed-package execution identity proof", () => { ).toThrow(); }); - it.each(["export", "storage", "receipt"])("rejects a private canary in %s", (where) => { + it.each(["export", "storage"])("rejects a private canary in %s", (where) => { const result = projection(); const stored = JSON.stringify(result.identity.context); - const exported = - where === "export" - ? { ...result, prompt: privateMarker } - : where === "receipt" - ? { ...result, decisions: [{ body: privateMarker }] } - : result; + const exported = where === "export" ? { ...result, prompt: privateMarker } : result; expect(() => assertIdentityProjection(exported, where === "storage" ? stored + privateMarker : stored, [ privateMarker, @@ -152,7 +140,7 @@ describe("installed-package execution identity proof", () => { insert.run(JSON.stringify(before.identity.context)); writeFileSync(beforePath, JSON.stringify(before)); writeFileSync(afterPath, JSON.stringify(before)); - expect(verify().status).toBe(0); + expect(verify()).toMatchObject({ status: 0, stdout: "execution-1" }); const after = projection(); after.identity.context.runtimeInstance.runtimeRef = domainRef.replace(/b/gu, "c"); db.prepare("UPDATE execution_identity_contexts SET context_json = ?").run( diff --git a/test/scripts/onboard-first-agent-flow.test.ts b/test/scripts/onboard-first-agent-flow.test.ts index 2eca41ee4b57..5d06d9e13647 100644 --- a/test/scripts/onboard-first-agent-flow.test.ts +++ b/test/scripts/onboard-first-agent-flow.test.ts @@ -8,13 +8,9 @@ const helper = "scripts/e2e/lib/onboard/first-agent-flow.sh"; describe.skipIf(process.platform === "win32")("guided first-agent prompt handshake", () => { it.each([ ["legacy", "plain", "provider", 5], - ["legacy", "fragmented", "provider", 5], - ["team", "plain", "provider", 6], ["team", "fragmented", "provider", 6], - ["legacy", "plain", "configured", 4], ["legacy", "fragmented", "configured", 4], ["team", "plain", "configured", 5], - ["team", "fragmented", "configured", 5], ] as const)( "drives the real guided sender through %s %s %s prompts", (layout, rendering, modelPrompt, count) => { diff --git a/test/scripts/openclaw-performance-crabbox.test.ts b/test/scripts/openclaw-performance-crabbox.test.ts index 8ab2647d0134..2079028fc128 100644 --- a/test/scripts/openclaw-performance-crabbox.test.ts +++ b/test/scripts/openclaw-performance-crabbox.test.ts @@ -292,12 +292,8 @@ describe("OpenClaw performance Crabbox boundary", () => { }, ); - it.each([ - { name: "IPv6 rule readback", fault: "6:-C" }, - { name: "IPv4 reachable metadata", family: 4, code: 0, output: "401" }, - { name: "IPv6 reachable metadata", family: 6, code: 0, output: "401" }, - ])("blocks runner handoff without its own metadata denial: $name", (options) => { - const run = prepareSut({ ...options, runner: true }); + it("blocks runner handoff when metadata is reachable", () => { + const run = prepareSut({ runner: true, family: 6, code: 0, output: "401" }); expect(run.result.error).toBeUndefined(); expect(run.result.status, run.result.stderr).not.toBe(0); expect(run.handoff).toBe(false); @@ -310,21 +306,19 @@ describe("OpenClaw performance Crabbox boundary", () => { fault: `missing:${tool}`, })), ...["http", "ipv6", "version"].map((fault) => ({ name: `curl ${fault}`, fault })), - ...[4, 6].flatMap((family) => - ["401", "404"].map((output) => ({ - name: `IPv${family} HTTP ${output}`, - family, - output, - code: 0, - })), - ), - ...[0, 1, 2, 3, 4, 5, 6, 22, 23, 26, 28, 52, 55, 56, 126, 127, 143].map((code) => ({ + ...[4, 6].map((family) => ({ + name: `IPv${family} HTTP 401`, + family, + output: "401", + code: 0, + })), + ...[0, 22, 28, 127].map((code) => ({ name: `IPv6 curl exit ${code}`, code, })), { name: "IPv4 timeout", family: 4, code: 28 }, { name: "HTTP response despite connection failure", output: "401", code: 7 }, - ...["", "00", "000000", "000\n401", " 000", "000\n"].map((output) => ({ + ...["", "000\n401", "000\n"].map((output) => ({ name: `malformed status ${JSON.stringify(output)}`, output, })), @@ -446,7 +440,6 @@ collect_diagnostics "$SOURCE" "$DESTINATION" "$SUBTREE" it.each([ { name: "success", expected: 0 }, - { name: "advisory matrix 17", matrixExit: 17, expected: 0 }, { name: "adapted gated matrix 17", matrixExit: 17, gated: true, adapted: true, expected: 0 }, { name: "rejected gated matrix 17", matrixExit: 17, gated: true, expected: 17 }, { name: "setup failure", setupExit: 23, expected: 23 }, @@ -823,75 +816,58 @@ fi it.each([ { name: "advisory BLOCKED", - gated: false, sutExit: 17, - records: true, - planFilter: "scenario:probe", expected: 0, }, { name: "unadaptable gated BLOCKED", gated: true, sutExit: 17, - records: true, - planFilter: "scenario:probe", expected: 17, }, { name: "missing requested records", - gated: false, - sutExit: 0, records: false, - planFilter: "scenario:probe", expected: 1, }, { name: "wrong plan filters", - gated: false, - sutExit: 0, - records: true, planFilter: "scenario:wrong", expected: 1, }, { name: "ambiguous full reports", - gated: false, - sutExit: 0, - records: true, - planFilter: "scenario:probe", ambiguous: true, expected: 1, }, { name: "custom Kova diagnostics", - gated: false, - sutExit: 0, - records: true, - planFilter: "scenario:probe", admitted: false, expected: 0, }, { name: "custom Kova cannot approve a gate", gated: true, - sutExit: 0, - records: true, - planFilter: "scenario:probe", admitted: false, expected: 1, }, { name: "custom Kova invalid evidence", - gated: false, - sutExit: 0, records: false, - planFilter: "scenario:probe", admitted: false, expected: 1, }, ])( "enforces native Kova evidence and gate semantics: $name", - ({ gated, sutExit, records, planFilter, expected, admitted = true, ambiguous = false }) => { + ({ + gated = false, + sutExit = 0, + records = true, + planFilter = "scenario:probe", + expected, + admitted = true, + ambiguous = false, + }) => { const root = tempDirs.make("openclaw-performance-kova-contract-"); const openclaw = join(root, "openclaw"); mkdirSync(join(openclaw, ".artifacts/kova/reports/mock-provider"), { recursive: true }); @@ -1051,10 +1027,8 @@ validate_kova mock-provider "$ROOT" diagnostic 1 scenario:probe - "$GATED" "$HEL ); chmodSync(crabbox, 0o755); - for (let attempt = 0; attempt < 2; attempt += 1) { - const result = spawnSync("bash", [SCRIPT, "confirm-stop", crabbox, "cbx_0123456789ab"]); - expect(result.status).toBe(status); - } + const result = spawnSync("bash", [SCRIPT, "confirm-stop", crabbox, "cbx_0123456789ab"]); + expect(result.status).toBe(status); }, ); diff --git a/test/scripts/openclaw-release-ready.test.ts b/test/scripts/openclaw-release-ready.test.ts index 010cc299d683..074310e8310b 100644 --- a/test/scripts/openclaw-release-ready.test.ts +++ b/test/scripts/openclaw-release-ready.test.ts @@ -157,7 +157,6 @@ describe("release readiness contract", () => { ["retired soak waiver", { stable_soak_waiver: "2026.9.2 approved" }], ["retired lane waiver", { lane_waiver: "2026.9.2 approved" }], ["moving source", { tag: "main" }], - ["missing source", { tag: "" }], ["wrong beta channel", { npm_dist_tag: "latest" }], ["alpha owner", { tag: "v2026.9.2-alpha.1", npm_dist_tag: "alpha" }], ["extended-stable owner", { tag: "v2026.9.33", npm_dist_tag: "latest" }], @@ -1844,28 +1843,25 @@ describe("release preparation recovery", () => { expect(existsSync(fixture.env.GITHUB_OUTPUT)).toBe(false); }); - it.each([1, 2])( - "adopts exact identified preparations without dispatch on attempt %s", - async (attempt) => { - const fixture = await preparationFixture(); - const request = preparationRequest(); - const result = fixture.prepare(request, attempt); - expect(result.status, result.stderr).toBe(0); - expect(JSON.parse(readFileSync(fixture.requestPath, "utf8"))).toEqual(request); - expect(fixture.trace().filter((entry) => entry.args?.includes("POST"))).toEqual([]); - expect( - fixture - .trace() - .filter((entry) => /\/actions\/runs\/(?:300|400)$/u.test(entry.args?.[1] ?? "")), - ).toEqual([ - { event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/300`] }, - { event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/400`] }, - ]); - expect(readFileSync(fixture.env.GITHUB_OUTPUT, "utf8")).toContain( - `request=${JSON.stringify(request)}\n`, - ); - }, - ); + it("adopts exact identified preparations without dispatch on a rerun", async () => { + const fixture = await preparationFixture(); + const request = preparationRequest(); + const result = fixture.prepare(request, 2); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(readFileSync(fixture.requestPath, "utf8"))).toEqual(request); + expect(fixture.trace().filter((entry) => entry.args?.includes("POST"))).toEqual([]); + expect( + fixture + .trace() + .filter((entry) => /\/actions\/runs\/(?:300|400)$/u.test(entry.args?.[1] ?? "")), + ).toEqual([ + { event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/300`] }, + { event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/400`] }, + ]); + expect(readFileSync(fixture.env.GITHUB_OUTPUT, "utf8")).toContain( + `request=${JSON.stringify(request)}\n`, + ); + }); it.each([ ["schema", { schema: "different" }], @@ -1893,9 +1889,6 @@ describe("release preparation recovery", () => { it.each([ ["workflow", { path: ".github/workflows/openclaw-release-publish.yml" }], - ["event", { event: "push" }], - ["protected ref", { head_branch: "main" }], - ["tooling SHA", { head_sha: "c".repeat(40) }], ["attempt", { run_attempt: 0 }], ])("rejects a recovered producer with the wrong %s", async (_label, preparationProducer) => { const fixture = await preparationFixture({ preparationProducer }); @@ -1952,7 +1945,6 @@ process.exitCode = 1; ] as const)( "preserves sealed inputs with resume override %s (sealed=%s)", (resumeRunId, sealedResumeRunId) => { - const fixture = finalizationFixture(); const ready = readyRelease(); ready.inputs = validateReleaseButtonInputs( inputs({ @@ -1967,38 +1959,9 @@ process.exitCode = 1; ...(sealedResumeRunId ? { openclaw_npm_resume_run_id: sealedResumeRunId } : {}), }), ); - writeFixtureFile( - fixture.scripts, - "lib/actions-artifact-archive.mjs", - ` - export { readBoundedRegularFile } from ${JSON.stringify(pathToFileURL(resolve("scripts/lib/actions-artifact-archive.mjs")).href)}; - export async function downloadActionsArtifactArchive() { return { archiveBytes: Buffer.from('verified fixture archive') }; } - export function inspectActionsArtifactZipWithPolicy() { - return new Map([['release-ready.json', Buffer.from(${JSON.stringify(JSON.stringify(ready))})]]); - } - `, - ); - const artifact = { - ...descriptor("npm"), - workflowPath: ".github/workflows/openclaw-release-prepare.yml", - artifactName: readyArtifactName(SOURCE_SHA, 300, 1), - }; - const workflow = parse( - readFileSync(".github/workflows/openclaw-release-promote.yml", "utf8"), - ); - const dispatch = workflow.jobs.publish.steps.find( - (step: { id?: string }) => step.id === "dispatch", - ); - const result = spawnSync("bash", ["-c", dispatch.run], { - cwd: dirname(fixture.scripts), - env: { - ...fixture.env, - PREPARED_ARTIFACT: JSON.stringify(artifact), - OPENCLAW_NPM_RESUME_RUN_ID: resumeRunId, - }, - encoding: "utf8", - timeout: 10_000, - }); + const fixture = publicationFixture({}, ready); + const { workflow } = fixture; + const result = fixture.publish({ OPENCLAW_NPM_RESUME_RUN_ID: resumeRunId }); expect(result.status, result.stderr).toBe(0); const outputs = Object.fromEntries( readFileSync(fixture.env.GITHUB_OUTPUT, "utf8") @@ -2063,7 +2026,7 @@ process.exitCode = 1; }, ); - it.each(["0", "-1", "1.5", " 800", "9007199254740992"])( + it.each(["0", " 800", "9007199254740992"])( "rejects malformed resume run %s without dispatching publication", (resumeRunId) => { const fixture = finalizationFixture(); @@ -2133,7 +2096,6 @@ process.exitCode = 1; it.each([ ["v2026.9.2-beta.1", "beta", true], - ["v2026.9.2", "beta", false], ["v2026.9.2", "latest", false], ] as const)( "refuses parent activation of %s on %s with a mismatched draft classification", @@ -2168,12 +2130,10 @@ process.exitCode = 1; describe("prepared Windows handoff", () => { it.each([ ["stable on beta", "v2026.9.2", "beta", "success", true, true, false, true], - ["stable on latest", "v2026.9.2", "latest", "success", true, true, false, true], ["absent", "v2026.9.2", "beta", "success", false, false, false, false], ["incomplete", "v2026.9.2", "beta", "success", true, false, false, true], ["beta", "v2026.9.2-beta.1", "beta", "success", true, true, false, false], ["failed activation", "v2026.9.2", "beta", "failure", true, true, false, false], - ["skipped activation", "v2026.9.2", "beta", "skipped", true, true, false, false], ["dispatch failed", "v2026.9.2", "beta", "success", true, true, true, true], ] as const)( "uses the frozen optional selection after activation: %s", diff --git a/test/scripts/pr-merge-admission.test.ts b/test/scripts/pr-merge-admission.test.ts index 77a05f036d7b..32209c4482af 100644 --- a/test/scripts/pr-merge-admission.test.ts +++ b/test/scripts/pr-merge-admission.test.ts @@ -127,8 +127,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery", it.each([ { auto: false, mergeStateStatus: "CLEAN", route: "immediate" }, - { auto: true, mergeStateStatus: "CLEAN", route: "immediate" }, - { auto: true, mergeStateStatus: "BEHIND", route: "auto" }, { auto: true, mergeStateStatus: "BLOCKED", route: "auto" }, { auto: false, mergeStateStatus: "CLEAN", route: "immediate", statusFirst: true }, ])( @@ -209,18 +207,14 @@ describePosix("native merge outcome with real Git and supervised lock recovery", }); it.each([ - "persistent UNKNOWN", "persistent UNKNOWN mergeable", "persistent UNKNOWN status", "known mergeable reverts", "known status reverts", - "known status changes", "invalid metadata", "API error", "PR identity", "head", - "base", - "closed", "merged", "draft", "auto request", @@ -231,74 +225,31 @@ describePosix("native merge outcome with real Git and supervised lock recovery", "known HAS_HOOKS", "final UNKNOWN mergeable", "final UNKNOWN status", - "final changed status", ])("stops initial settlement without dispatch on %s", (fault) => { const f = fixture(); const next = f.state(); const { author: _author, headRefName: _headRefName, ...observedPr } = next.pr; - const step: (typeof next.observations)[number] = {}; - switch (fault) { - case "invalid metadata": - step.invalid = true; - break; - case "API error": - step.unavailable = true; - break; - case "PR identity": - step.pr = { id: "other-pr" }; - break; - case "head": - step.pr = { headRefOid: f.base }; - break; - case "base": - step.pr = { baseRefName: "release" }; - break; - case "closed": - step.pr = { state: "CLOSED" }; - break; - case "merged": - step.main = f.commit(f.tree("after\n"), [f.base]); - step.pr = { state: "MERGED", mergeCommit: { oid: step.main } }; - break; - case "draft": - step.pr = { isDraft: true }; - break; - case "auto request": - step.pr = { autoMergeRequest: { mergeMethod: "SQUASH" } }; - break; - case "queue policy": - step.pr = { isMergeQueueEnabled: true }; - break; - case "queue membership": - step.pr = { isInMergeQueue: true }; - break; - case "invalid receipt": - step.pr = { mergeCommit: { oid: f.head } }; - break; - case "conflicting": - step.pr = { mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" }; - break; - case "known HAS_HOOKS": - step.pr = { mergeable: "MERGEABLE", mergeStateStatus: "HAS_HOOKS" }; - break; - case "known mergeable reverts": - step.pr = { mergeable: "UNKNOWN" }; - break; - case "known status reverts": - step.pr = { mergeStateStatus: "UNKNOWN" }; - break; - case "known status changes": - step.pr = { mergeStateStatus: "BEHIND" }; - break; - case "final UNKNOWN mergeable": - step.pr = { mergeable: "UNKNOWN" }; - break; - case "final UNKNOWN status": - step.pr = { mergeStateStatus: "UNKNOWN" }; - break; - case "final changed status": - step.pr = { mergeStateStatus: "BEHIND" }; - break; + const steps: Record = { + "invalid metadata": { invalid: true }, + "API error": { unavailable: true }, + "PR identity": { pr: { id: "other-pr" } }, + head: { pr: { headRefOid: f.base } }, + draft: { pr: { isDraft: true } }, + "auto request": { pr: { autoMergeRequest: { mergeMethod: "SQUASH" } } }, + "queue policy": { pr: { isMergeQueueEnabled: true } }, + "queue membership": { pr: { isInMergeQueue: true } }, + "invalid receipt": { pr: { mergeCommit: { oid: f.head } } }, + conflicting: { pr: { mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" } }, + "known HAS_HOOKS": { pr: { mergeable: "MERGEABLE", mergeStateStatus: "HAS_HOOKS" } }, + "known mergeable reverts": { pr: { mergeable: "UNKNOWN" } }, + "known status reverts": { pr: { mergeStateStatus: "UNKNOWN" } }, + "final UNKNOWN mergeable": { pr: { mergeable: "UNKNOWN" } }, + "final UNKNOWN status": { pr: { mergeStateStatus: "UNKNOWN" } }, + }; + const step = steps[fault] ?? {}; + if (fault === "merged") { + step.main = f.commit(f.tree("after\n"), [f.base]); + step.pr = { state: "MERGED", mergeCommit: { oid: step.main } }; } next.observations = [{ pr: unknownProjection }]; const persistent = fault.startsWith("persistent "); @@ -348,7 +299,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery", expect(run.output).toContain( fault === "final UNKNOWN mergeable" ? 'mergeable: observed="UNKNOWN"; expected="MERGEABLE"' - : `mergeStateStatus: observed="${fault === "final UNKNOWN status" ? "UNKNOWN" : "BEHIND"}"; expected="CLEAN"`, + : 'mergeStateStatus: observed="UNKNOWN"; expected="CLEAN"', ); for (const [label, expected] of [ ["observation", { main: f.base, pr: observedPr, transport: "graphql" }], @@ -448,7 +399,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery", f.git(["merge-base", "--is-ancestor", f.head, f.record().landed]); }); - it.each(["review", "ready", "checks", "pending", "existing-auto", "auto-ineligible"])( + it.each(["review", "ready", "checks", "pending"])( "keeps %s admission ahead of intent", (gate) => { const f = fixture(); @@ -465,12 +416,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery", if (gate === "pending") { next.gates = "pending"; } - if (gate === "existing-auto") { - next.pr.autoMergeRequest = { mergeMethod: "MERGE" }; - } - if (gate === "auto-ineligible") { - next.pr.mergeStateStatus = "HAS_HOOKS"; - } f.save(next); const run = f.run(true); expect(run.status, run.output).toBe(1); @@ -499,39 +444,23 @@ describePosix("native merge outcome with real Git and supervised lock recovery", expect(() => f.record()).toThrow(); }); - it.each([ - { - name: "removed", - reason: "completed review is missing.", - comments: [ + it("revalidates removed review evidence immediately before intent", () => { + const f = fixture(); + f.save({ + ...f.state(), + issueCommentsAfterFirst: [ { id: 2, body: "", user: { id: 274271284, login: "clawsweeper[bot]", type: "Bot" }, }, ], - }, - { - name: "malformed", - reason: "trusted review-version field values are invalid.", - comments: [ - { - id: 2, - body: ` - -`, - user: { id: 274271284, login: "clawsweeper[bot]", type: "Bot" }, - }, - ], - }, - ])("revalidates $name review evidence immediately before intent", ({ comments, reason }) => { - const f = fixture(); - f.save({ ...f.state(), issueCommentsAfterFirst: comments }); + }); const run = f.run(); expect(run.status, run.output).toBe(1); - expect(run.output).toContain(`ClawSweeper review gate failed: ${reason}`); + expect(run.output).toContain("ClawSweeper review gate failed: completed review is missing."); expect(f.state().issueCommentReads).toBe(2); expect(f.state().mutations).toBe(0); expect(() => f.record()).toThrow(); diff --git a/test/scripts/pr-merge-auto-recovery.test.ts b/test/scripts/pr-merge-auto-recovery.test.ts index 0d5404469219..1594db24f671 100644 --- a/test/scripts/pr-merge-auto-recovery.test.ts +++ b/test/scripts/pr-merge-auto-recovery.test.ts @@ -41,7 +41,6 @@ describePosix("native auto-merge recovery", () => { { mode: "pending", cancellation: "lost", absent: false }, { mode: "pending-error", cancellation: "success", absent: false }, { mode: "pending", cancellation: "success", absent: true }, - { mode: "pending-error", cancellation: "success", absent: true }, ])( "retires auto before recovering a reviewed replacement (submission=$mode, cancellation=$cancellation, absent=$absent)", ({ mode, cancellation, absent }) => { diff --git a/test/scripts/pr-merge-body-provenance.test.ts b/test/scripts/pr-merge-body-provenance.test.ts index f695a9b98655..0c62f1d0d014 100644 --- a/test/scripts/pr-merge-body-provenance.test.ts +++ b/test/scripts/pr-merge-body-provenance.test.ts @@ -28,7 +28,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery", expect(f.state().graphqlMergePayloads[0]?.expectedHeadOid).toBe(f.head); }); - it.each(["tamper", "head", "queue", "merge", "review"])( + it.each(["tamper", "head", "queue", "merge"])( "keeps explicit body admission closed for %s", (fault) => { const f = fixture(); @@ -44,9 +44,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery", if (fault === "queue") { state.observations = [{ pr: { isMergeQueueEnabled: true } }]; } - if (fault === "review") { - state.ready = false; - } f.save(state); const result = f.run(false, f.repo, fault === "merge" ? "merge" : "squash", "", "", body); expect(result.status, result.output).not.toBe(0); @@ -55,60 +52,18 @@ describePosix("native merge outcome with real Git and supervised lock recovery", }, ); - it.each([ - { - route: "immediate", - access: "external", - auto: false, - admin: false, - queue: false, - mergeStateStatus: "CLEAN", - }, - { - route: "auto", - access: "unknown", - auto: true, - admin: false, - queue: false, - mergeStateStatus: "BEHIND", - }, - { - route: "queue", - access: "external", - auto: false, - admin: false, - queue: true, - mergeStateStatus: "CLEAN", - }, - { - route: "admin", - access: "unknown", - auto: false, - admin: true, - queue: false, - mergeStateStatus: "BLOCKED", - }, - ])( - "blocks rewritten $access squash before $route intent", - ({ access, auto, admin, queue, mergeStateStatus }) => { - const f = fixture(); - f.setPrivacyProvenance("true", access); - f.save({ - ...f.state(), - admin, - gates: admin ? "fail" : "pass", - pr: { ...f.state().pr, isMergeQueueEnabled: queue, mergeStateStatus }, - }); + it.each(["external", "unknown"])("blocks rewritten %s squash before intent", (access) => { + const f = fixture(); + f.setPrivacyProvenance("true", access); - const run = f.run(auto); + const run = f.run(); - expect(run.status, run.output).toBe(1); - expect(run.output).toContain("maintainer-owned replacement PR"); - expect(f.state().mutations).toBe(0); - expect(f.captures()).toEqual([]); - expect(() => f.record()).toThrow(); - }, - ); + expect(run.status, run.output).toBe(1); + expect(run.output).toContain("maintainer-owned replacement PR"); + expect(f.state().mutations).toBe(0); + expect(f.captures()).toEqual([]); + expect(() => f.record()).toThrow(); + }); it.each([ { rewrite: "true", access: "maintainer" }, @@ -126,11 +81,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery", it.each([ ["missing", "PREP_REPLACED_HOSTED_ANCESTRY=false\n"], - [ - "malformed rewrite", - "PREP_REPLACED_HOSTED_ANCESTRY=false", - "PREP_REPLACED_HOSTED_ANCESTRY=yes", - ], ["malformed access", "PREP_AUTHOR_ACCESS=external", "PREP_AUTHOR_ACCESS=write"], ])("requires prepare rerun for %s squash provenance", (_label, from, to = "") => { const f = fixture(); @@ -145,11 +95,11 @@ describePosix("native merge outcome with real Git and supervised lock recovery", expect(() => f.record()).toThrow(); }); - it.each(["merge", "rebase"])("leaves %s mechanics independent of squash provenance", (method) => { + it("leaves merge mechanics independent of squash provenance", () => { const f = fixture(); f.setPrivacyProvenance(null, null); - const run = f.run(false, f.repo, method); + const run = f.run(false, f.repo, "merge"); expect(run.status, run.output).toBe(0); expect(f.state().mutations).toBe(1); @@ -170,14 +120,9 @@ describePosix("native merge outcome with real Git and supervised lock recovery", }); it.each([ - { auto: false, admin: false, mergeState: "CLEAN", route: "immediate" }, { auto: false, admin: false, mergeState: "HAS_HOOKS", route: "immediate" }, - { auto: false, admin: false, mergeState: "UNSTABLE", route: "immediate" }, - { auto: false, admin: true, mergeState: "CLEAN", route: "admin" }, { auto: false, admin: true, mergeState: "BLOCKED", route: "admin" }, - { auto: false, admin: true, mergeState: "BEHIND", route: "admin" }, { auto: true, admin: false, mergeState: "BEHIND", route: "auto" }, - { auto: true, admin: false, mergeState: "BLOCKED", route: "auto" }, { auto: true, admin: false, mergeState: "CLEAN", route: "immediate", pendingGates: true }, ])( "submits verified attribution with pinned head for %j", diff --git a/test/scripts/pr-merge-correction.test.ts b/test/scripts/pr-merge-correction.test.ts index 31d2c624bef5..4db0366dd868 100644 --- a/test/scripts/pr-merge-correction.test.ts +++ b/test/scripts/pr-merge-correction.test.ts @@ -63,13 +63,7 @@ function correctionFixture() { } describePosix("correction authority through native merge admission", () => { - it("merges an exactly reviewed and qualified correction while retaining original NEEDS WORK", () => { - const f = correctionFixture(); - const result = f.run(); - expect(result.status, result.output).toBe(0); - expect(f.state().mutations).toBe(1); - }); - it.each(["correction-review.json", "prep-context.env", "gates.env", "prep.env", "pr-meta.env"])( + it.each(["correction-review.json", "prep-context.env", "gates.env"])( "refuses changed %s after CI checks and before intent", (artifact) => { const f = correctionFixture(); diff --git a/test/scripts/pr-merge-legacy-recovery.test.ts b/test/scripts/pr-merge-legacy-recovery.test.ts index a48c61fe81a2..20a780381f20 100644 --- a/test/scripts/pr-merge-legacy-recovery.test.ts +++ b/test/scripts/pr-merge-legacy-recovery.test.ts @@ -101,7 +101,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery", }); it.each([ - "empty", "timeout", "extra-capture", "wrong-hash", @@ -109,10 +108,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery", "wrong-base", "missing-proof", "symlink", - "auto", "queue", - "closed", - "pending", "ci-proof", "changed-capture", "no-head", @@ -122,8 +118,8 @@ describePosix("native merge outcome with real Git and supervised lock recovery", const approvedHead = f.replacePreparedHead(); const next = f.state(); const capture = join(f.worktree, ".local/merge-output.log"); - if (fault === "empty" || fault === "timeout") { - const text = fault === "empty" ? "" : "502 after dispatch\n"; + if (fault === "timeout") { + const text = "502 after dispatch\n"; writeFileSync(capture, text); writeFileSync(join(legacy.directory, "merge-output.log"), text); legacy.oid = f.git(["hash-object", "--no-filters", capture]); @@ -151,18 +147,9 @@ describePosix("native merge outcome with real Git and supervised lock recovery", rmSync(capture); symlinkSync(join(legacy.directory, "merge-output.log"), capture); } - if (fault === "auto") { - next.pr.autoMergeRequest = { mergeMethod: "SQUASH" }; - } if (fault === "queue") { next.pr.isMergeQueueEnabled = true; } - if (fault === "closed") { - next.pr.state = "CLOSED"; - } - if (fault === "pending") { - next.gates = "pending"; - } if (fault === "ci-proof") { next.ciExit = 15; } diff --git a/test/scripts/pr-merge-pre-dispatch-refusal.test.ts b/test/scripts/pr-merge-pre-dispatch-refusal.test.ts index 92a4b517e093..dfb167e50303 100644 --- a/test/scripts/pr-merge-pre-dispatch-refusal.test.ts +++ b/test/scripts/pr-merge-pre-dispatch-refusal.test.ts @@ -45,10 +45,27 @@ const hash = (text: string) => execFileSync("git", ["hash-object", "--stdin"], { input: text, encoding: "utf8" }).trim(); const describePosix = process.platform === "win32" ? describe.skip : describe; +function refusalEvidence(contents: string, proof: unknown) { + const root = temps.make("pr-refusal-evidence-"); + const directory = join(root, "evidence"); + mkdirSync(directory); + mkdirSync(join(root, ".local")); + writeFileSync(join(root, ".local", capture), contents); + writeFileSync(join(directory, capture), contents); + writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof)); + return { + root, + directory, + run: (attempt = record) => + spawnSync(node, [helper, directory, outcome, JSON.stringify(attempt)], { + cwd: root, + encoding: "utf8", + }), + }; +} + describePosix("operator-qualified pre-dispatch evidence", () => { it.each([ - "approved", - "uninspected", "diagnostics-not-requested", "wrong-producer", "wrong-command", @@ -57,20 +74,13 @@ describePosix("operator-qualified pre-dispatch evidence", () => { "wrong-executable", "missing-source", "extra-source", - ...Object.keys(policyTimeoutQualification.sourceSha256).map((path) => `source:${path}`), - "policy-denial", + "source:cmd/octopool/string_rewrites_pr.go", "other-class", - "extra-text", "extra-newline", "missing-newline", "diagnostic", - "child-started", "response-headers", ])("bounds the inspected initial policy timeout: %s", (fault) => { - const root = temps.make("pr-policy-timeout-evidence-"); - const directory = join(root, "evidence"); - mkdirSync(directory); - mkdirSync(join(root, ".local")); const sourceSha256: Record = { ...policyTimeoutQualification.sourceSha256 }; if (fault.startsWith("source:")) { sourceSha256[fault.slice("source:".length)] = "0".repeat(64); @@ -81,36 +91,19 @@ describePosix("operator-qualified pre-dispatch evidence", () => { if (fault === "extra-source") { sourceSha256["cmd/octopool/unknown.go"] = "0".repeat(64); } - let contents = policyTimeoutCapture; - if (fault === "policy-denial") { - contents = refusal; - } - if (fault === "other-class") { - contents = contents.replace("class=timeout", "class=server_validation"); - } - if (fault === "extra-text") { - contents += "mutation accepted\n"; - } - if (fault === "extra-newline") { - contents += "\n"; - } - if (fault === "missing-newline") { - contents = contents.trimEnd(); - } - if (fault === "diagnostic" || fault === "child-started") { - contents += - fault === "diagnostic" - ? diagnostic - : diagnostic.replace("child_started=false", "child_started=true"); - } - if (fault === "response-headers") { - contents = contents.replace(")\n", " http_status=504)\n"); - } + const captures: Record = { + "other-class": policyTimeoutCapture.replace("class=timeout", "class=server_validation"), + "extra-newline": policyTimeoutCapture + "\n", + "missing-newline": policyTimeoutCapture.trimEnd(), + diagnostic: policyTimeoutCapture + diagnostic, + "response-headers": policyTimeoutCapture.replace(")\n", " http_status=504)\n"), + }; + const contents = captures[fault] ?? policyTimeoutCapture; const proof = { ...policyTimeoutQualification, outcome, capture: hash(contents), - inspected: fault !== "uninspected", + inspected: true, diagnosticsEnabled: fault !== "diagnostics-not-requested", producer: fault === "wrong-producer" ? "gh" : policyTimeoutQualification.producer, command: fault === "wrong-command" ? "pr view" : policyTimeoutQualification.command, @@ -121,38 +114,20 @@ describePosix("operator-qualified pre-dispatch evidence", () => { fault === "wrong-executable" ? "a".repeat(64) : policyTimeoutQualification.executableSha256, sourceSha256, }; - writeFileSync(join(root, ".local", capture), contents); - writeFileSync(join(directory, capture), contents); - writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof)); - const result = spawnSync(node, [helper, directory, outcome, JSON.stringify(record)], { - cwd: root, - encoding: "utf8", - }); - if (fault === "approved") { - expect(result.status, result.stderr).toBe(0); - expect(JSON.parse(result.stdout)).toMatchObject({ - kind: proof.kind, - capture, - files: { [capture]: proof.capture }, - }); - } else { - expect(result.status, result.stderr).toBe(1); - expect(result.stdout).toBe(""); - } + const result = refusalEvidence(contents, proof).run(); + expect(result.status, result.stderr).toBe(1); + expect(result.stdout).toBe(""); }); it.each([ - "historical", "historical-0.7.1", "0.7.1-wrong-version", "0.7.1-wrong-source", "0.7.1-wrong-parser", - "0.7.1-subject", "0.7.1-subject-equals", "0.7.1-altered-stderr", "diagnostic", "generic-only", - "wrong-source", "changed-capture", "symlink", "accepted", @@ -163,10 +138,6 @@ describePosix("operator-qualified pre-dispatch evidence", () => { "extra-evidence", "wrong-outcome", ])("qualifies only intact, inspected no-dispatch evidence: %s", (fault) => { - const root = temps.make("pr-refusal-evidence-"); - const directory = join(root, "evidence"); - mkdirSync(directory); - mkdirSync(join(root, ".local")); const diagnostics = ["diagnostic", "started", "duplicate"].includes(fault); const historical = historicalRefusals[ @@ -195,9 +166,6 @@ describePosix("operator-qualified pre-dispatch evidence", () => { "--body-file", ".local/merge-body.fixture", ]; - if (fault === "0.7.1-subject") { - args.push("--subject", "Fixture subject"); - } if (fault === "0.7.1-subject-equals") { args.push("--subject=Fixture subject"); } @@ -212,16 +180,14 @@ describePosix("operator-qualified pre-dispatch evidence", () => { version: fault === "0.7.1-wrong-version" ? "0.7.0" : historical.version, sourceRevision: fault === "0.7.1-wrong-source" ? "a".repeat(40) : historical.sourceRevision, - parserSha256: - fault === "wrong-source" || fault === "0.7.1-wrong-parser" - ? "a".repeat(64) - : historical.parserSha256, + parserSha256: fault === "0.7.1-wrong-parser" ? "a".repeat(64) : historical.parserSha256, args, }), }; - writeFileSync(join(root, ".local", capture), contents); - writeFileSync(join(directory, capture), fault === "changed-capture" ? "changed\n" : contents); - writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof)); + const { root, directory, run } = refusalEvidence(contents, proof); + if (fault === "changed-capture") { + writeFileSync(join(directory, capture), "changed\n"); + } if (fault === "symlink") { rmSync(join(directory, capture)); symlinkSync(join(root, ".local", capture), join(directory, capture)); @@ -237,11 +203,8 @@ describePosix("operator-qualified pre-dispatch evidence", () => { accepted: fault === "accepted", route: fault === "queue" ? "queue" : record.route, }; - const result = spawnSync(node, [helper, directory, outcome, JSON.stringify(attempt)], { - cwd: root, - encoding: "utf8", - }); - if (fault === "historical" || fault === "historical-0.7.1" || fault === "diagnostic") { + const result = run(attempt); + if (fault === "historical-0.7.1" || fault === "diagnostic") { expect(result.status, result.stderr).toBe(0); expect(JSON.parse(result.stdout)).toMatchObject({ kind: proof.kind, diff --git a/test/scripts/pr-merge-qualified-refusal.test.ts b/test/scripts/pr-merge-qualified-refusal.test.ts index 47ee03726451..3ccf0b2e096f 100644 --- a/test/scripts/pr-merge-qualified-refusal.test.ts +++ b/test/scripts/pr-merge-qualified-refusal.test.ts @@ -9,24 +9,9 @@ import { const { fixture, outcomeRef, describePosix } = createMergeOutcomeFixtureHarness(); -const historicalRefusals = { - "0.6.10": { - kind: "octopool-0.6.10-auto-refusal", - version: "0.6.10", - sourceRevision: "00c442d8084ad26eb5a5003f7372170e75a20c8a", - parserSha256: "f6ff8cd7e59503f71f94fefd561b671193df11b3aac9ba0986a0dc3ba91ca32b", - }, - "0.7.1": { - kind: "octopool-0.7.1-missing-subject-refusal", - version: "0.7.1", - sourceRevision: "7ab9b348c99a7be4fdc82c75cb06ebce44e0007e", - parserSha256: "b32cb960537f5ffa1336a7689674afba9b4a2485b05e449acd2684a251ff8970", - }, -} as const; - function qualifiedAutoRefusal( f: ReturnType, - version: keyof typeof historicalRefusals | "policy-timeout" = "0.6.10", + version: "0.6.10" | "policy-timeout" = "0.6.10", ) { f.save({ ...f.state(), @@ -49,7 +34,10 @@ function qualifiedAutoRefusal( ...(version === "policy-timeout" ? policyTimeoutQualification : { - ...historicalRefusals[version], + kind: "octopool-0.6.10-auto-refusal", + version, + sourceRevision: "00c442d8084ad26eb5a5003f7372170e75a20c8a", + parserSha256: "f6ff8cd7e59503f71f94fefd561b671193df11b3aac9ba0986a0dc3ba91ca32b", args: [ "pr", "merge", @@ -68,14 +56,18 @@ function qualifiedAutoRefusal( writeFileSync(join(directory, "qualification.json"), JSON.stringify(qualification)); f.recover(); f.save({ ...f.state(), mode: "success", pr: { ...f.state().pr, mergeStateStatus: "CLEAN" } }); - return { outcome, directory, capture, qualification }; + return { + outcome, + capture, + qualification, + runRecovery: (replacement = "") => + f.run(false, f.repo, "squash", outcome, replacement, "", "", "", false, directory), + }; } describePosix("qualified pre-dispatch merge recovery", () => { it.each([ { version: "0.6.10", replaceHead: false }, - { version: "0.6.10", replaceHead: true }, - { version: "0.7.1", replaceHead: true }, { version: "policy-timeout", replaceHead: true }, ] as const)( "recovers a qualified $version pre-dispatch refusal with retained evidence (replacement=$replaceHead)", @@ -89,18 +81,7 @@ describePosix("qualified pre-dispatch merge recovery", () => { `PR_NUMBER=123\nGATES_MODE=github_pending\nHOSTED_GATES_TARGET_HEAD_SHA=${preparedHead}\n`, ); f.save({ ...f.state(), requiredCheckName: "openclaw/ci-gate" }); - const run = f.run( - false, - f.repo, - "squash", - proof.outcome, - replacement, - "", - "", - "", - false, - proof.directory, - ); + const run = proof.runRecovery(replacement); expect(run.status, run.output).toBe(0); expect(f.state().mutations).toBe(1); expect(f.record()).toMatchObject({ @@ -125,18 +106,7 @@ describePosix("qualified pre-dispatch merge recovery", () => { ); expect(f.run().status).toBe(0); expect(f.state().mutations).toBe(1); - const replay = f.run( - false, - f.repo, - "squash", - proof.outcome, - replacement, - "", - "", - "", - false, - proof.directory, - ); + const replay = proof.runRecovery(replacement); expect(replay.status, replay.output).toBe(1); expect(f.state().mutations).toBe(1); }, @@ -176,18 +146,7 @@ describePosix("qualified pre-dispatch merge recovery", () => { next.duringChecks = { preparedHead: movedHead }; } f.save(next); - const run = f.run( - false, - f.repo, - "squash", - proof.outcome, - "", - "", - "", - "", - false, - proof.directory, - ); + const run = proof.runRecovery(); expect(run.error, run.output).toBeUndefined(); expect(run.status, run.output).toBe(1); expect(f.state()).toMatchObject({ mutations: 0, posts: 0 }); @@ -226,18 +185,7 @@ describePosix("qualified pre-dispatch merge recovery", () => { next.pr.mergeStateStatus = "BEHIND"; } f.save(next); - const run = f.run( - false, - f.repo, - "squash", - proof.outcome, - replacement, - "", - "", - "", - false, - proof.directory, - ); + const run = proof.runRecovery(replacement); expect(run.status, `${fault}: ${run.output}`).toBe(1); expect(f.state().mutations).toBe(0); expect(f.git(["rev-parse", outcomeRef])).toBe(proof.outcome); diff --git a/test/scripts/security-review-rollout.test.ts b/test/scripts/security-review-rollout.test.ts index 87f3bdbf3e83..6d8f228a4ff5 100644 --- a/test/scripts/security-review-rollout.test.ts +++ b/test/scripts/security-review-rollout.test.ts @@ -128,25 +128,19 @@ describe("security review rollout", () => { expect(result.requests).toEqual([`/repos/openclaw/openclaw/pulls/${rolloutNumber}`]); }); - it.each(["2026-09-19T12:00:00Z", "2026-09-20T00:00:00Z"])( - "enforces PRs created at or after rollout (%s), even on an old branch", - (createdAt) => { - const result = evaluate({ pullRequest: { ...pullRequest, created_at: createdAt } }); - expect(result.mode).toBe("enforced"); - expect(result.requests).toHaveLength(1); - }, - ); + it("enforces PRs created at rollout, even on an old branch", () => { + const result = evaluate({ pullRequest: { ...pullRequest, created_at: mergedAt } }); + expect(result.mode).toBe("enforced"); + expect(result.requests).toHaveLength(1); + }); - it.each(["behind", "diverged"])( - "exempts an older PR whose head has not incorporated the rollout (%s)", - (status) => { - const result = evaluate({ comparison: { ...comparison, status } }); - expect(result.mode).toBe("grandfathered"); - expect(result.requests[1]).toBe( - `/repos/openclaw/openclaw/compare/${mergeCommit}...${head}?per_page=1&page=2`, - ); - }, - ); + it("exempts an older PR whose head is behind the rollout", () => { + const result = evaluate({ comparison: { ...comparison, status: "behind" } }); + expect(result.mode).toBe("grandfathered"); + expect(result.requests[1]).toBe( + `/repos/openclaw/openclaw/compare/${mergeCommit}...${head}?per_page=1&page=2`, + ); + }); it("enforces an older PR after a rebase or merge incorporates the rollout", () => { const result = evaluate({ @@ -181,12 +175,10 @@ describe("security review rollout", () => { { ...rollout, number: 1 }, { ...rollout, base: { ref: "stable", repo: { full_name: "openclaw/openclaw" } } }, { ...rollout, base: { ref: "main", repo: { full_name: "contributor/fork" } } }, - { ...rollout, merged: undefined }, { ...rollout, merged: false }, { ...rollout, state: "open" }, { ...rollout, merged_at: null }, { ...rollout, merged_at: "2026-02-30T12:00:00Z" }, - { ...rollout, merge_commit_sha: "main" }, ])("does not grant an exemption for invalid rollout metadata (%j)", (value) => { const result = evaluate({ rollout: value }); expect(result.status).toBe(1); @@ -216,12 +208,9 @@ describe("security review rollout", () => { expect(result.error).toContain("Cannot determine security review rollout"); }); - it.each(["rollout", "comparison"] as const)( - "does not convert a GitHub %s error into an exemption", - (apiError) => { - const result = evaluate({ apiError }); - expect(result.status).toBe(1); - expect(result.error).toBe("GitHub unavailable"); - }, - ); + it("does not convert a GitHub comparison error into an exemption", () => { + const result = evaluate({ apiError: "comparison" }); + expect(result.status).toBe(1); + expect(result.error).toBe("GitHub unavailable"); + }); }); diff --git a/test/scripts/security-review-script.test.ts b/test/scripts/security-review-script.test.ts index cfaa9009bb37..5d35cfd1c7c7 100644 --- a/test/scripts/security-review-script.test.ts +++ b/test/scripts/security-review-script.test.ts @@ -63,6 +63,26 @@ const otherReview = { creator: { login: "github-actions[bot]", type: "Bot" }, }; +const lockfilePr = { + ...pr, + changed_files: 1, + head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } }, +}; +const lockfileContents = { + type: "file", + encoding: "base64", + content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"), +}; +const lockfileRoutes = { + [`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }], + [rolePath]: { role_name: "read" }, + [`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [], + [`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: { + base_commit: { sha: pr.base.sha }, + merge_base_commit: { sha: pr.base.sha }, + }, +}; + function evaluate(routes: Record = {}, mode = "enforce", deadline?: number) { const root = tempDirs.make("security-review-"); const logPath = path.join(root, "requests.jsonl"); @@ -148,20 +168,9 @@ function evaluate(routes: Record = {}, mode = "enforce", deadli } describe("combined security review entry point", () => { - it("recovers a temporary HTTP 500 without failing the review", () => { - const result = evaluate({ - [`GET ${pullPath}`]: { responses: [{ httpError: 500 }, pr] }, - }); - expect(result.status, result.stderr).toBe(0); - expect(result.waits).toEqual([1_000]); - expect(result.combined.at(-1)).toBe("success"); - expect(result.reviews.filter((entry) => entry.body?.state === "success")).toHaveLength(2); - }); - it.each([ { route: `GET ${pullPath}`, response: pr, requestTimeout: "fetch" }, { route: rolePath, response: { role_name: "maintain" }, requestTimeout: "body" }, - { route: jobsPath, response: jobs, requestTimeout: "fetch" }, ])( "restarts evaluation after a $requestTimeout deadline on $route", ({ route, response, requestTimeout }) => { @@ -213,27 +222,10 @@ describe("combined security review entry point", () => { it("recovers a lockfile read deadline before submitting one cleanup commit", () => { const result = evaluate( { - [`GET ${pullPath}`]: { - ...pr, - changed_files: 1, - head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } }, - }, - [`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }], - [rolePath]: { role_name: "read" }, - [`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [], - [`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: { - base_commit: { sha: pr.base.sha }, - merge_base_commit: { sha: pr.base.sha }, - }, + ...lockfileRoutes, + [`GET ${pullPath}`]: lockfilePr, [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml?ref=${pr.base.sha}`]: { - responses: [ - { requestTimeout: "fetch" }, - { - type: "file", - encoding: "base64", - content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"), - }, - ], + responses: [{ requestTimeout: "fetch" }, lockfileContents], }, "POST /graphql": { data: { createCommitOnBranch: { commit: { oid: "e".repeat(40) } } } }, }, @@ -268,8 +260,6 @@ describe("combined security review entry point", () => { unavailable: true, }, { name: "unknown HTTP 403", response: { httpError: 403 }, unavailable: false }, - { name: "invalid credentials", response: { httpError: 401 }, unavailable: false }, - { name: "missing repository", response: { httpError: 404 }, unavailable: false }, { name: "stale head", response: { errors: [{ type: "STALE_DATA", message: "Expected branch head to match" }] }, @@ -307,26 +297,15 @@ describe("combined security review entry point", () => { const { response, unavailable } = scenario; const baseReadDenied = "baseReadDenied" in scenario && scenario.baseReadDenied; const routes = { + ...lockfileRoutes, [`GET ${pullPath}`]: { - ...pr, - changed_files: 1, + ...lockfilePr, maintainer_can_modify: true, head: { ...pr.head, repo: { id: 2, full_name: "contributor/openclaw" } }, }, - [`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }], - [rolePath]: { role_name: "read" }, - [`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [], - [`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: { - base_commit: { sha: pr.base.sha }, - merge_base_commit: { sha: pr.base.sha }, - }, [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: baseReadDenied ? response - : { - type: "file", - encoding: "base64", - content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"), - }, + : lockfileContents, "POST /graphql": response, }; const cleanup = evaluate(routes, "autoscrub"); @@ -357,34 +336,19 @@ describe("combined security review entry point", () => { }); it("preserves a failed cleanup mutation when the PR then closes", () => { - const cleanupPr = { - ...pr, - changed_files: 1, - head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } }, - }; const result = evaluate( { + ...lockfileRoutes, [`GET ${pullPath}`]: { responses: [ - cleanupPr, - cleanupPr, - cleanupPr, - cleanupPr, - { ...cleanupPr, state: "closed" }, + lockfilePr, + lockfilePr, + lockfilePr, + lockfilePr, + { ...lockfilePr, state: "closed" }, ], }, - [`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }], - [rolePath]: { role_name: "read" }, - [`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [], - [`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: { - base_commit: { sha: pr.base.sha }, - merge_base_commit: { sha: pr.base.sha }, - }, - [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: { - type: "file", - encoding: "base64", - content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"), - }, + [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: lockfileContents, "POST /graphql": { httpError: 500 }, }, "autoscrub", @@ -444,32 +408,6 @@ describe("combined security review entry point", () => { expect(result.combined).not.toContain("success"); }); - it("exhausts HTTP 500 retries without publishing approval", () => { - const result = evaluate({ [`GET ${pullPath}`]: { httpError: 500 } }); - expect(result.status).toBe(1); - expect(result.waits).toEqual([1_000, 2_000, 4_000]); - expect(result.requests.filter((entry) => entry.path === pullPath)).toHaveLength(4); - expect(result.stderr).toContain(`GitHub API GET ${pullPath} failed: 500`); - expect( - result.requests.every((entry) => entry.method === "GET" || entry.method === "WAIT"), - ).toBe(true); - }); - - it.each([ - { name: "partial file list", initialPr: pr, initialFiles: files.slice(0, 1) }, - { name: "stale file count", initialPr: { ...pr, changed_files: 3 }, initialFiles: files }, - ])("recovers a $name before evaluating either guard", ({ initialPr, initialFiles }) => { - const result = evaluate({ - [`GET ${pullPath}`]: { responses: [initialPr, pr] }, - [`GET ${pullPath}/files`]: { responses: [initialFiles, files] }, - }); - expect(result.status, result.stderr).toBe(0); - expect(result.waits).toEqual([30_000, 30_000]); - expect(result.requests.filter((entry) => entry.path === `${pullPath}/files`)).toHaveLength(2); - expect(result.combined.at(-1)).toBe("success"); - expect(result.reviews.filter((entry) => entry.body?.state === "success")).toHaveLength(2); - }); - it.each(["detect", "enforce"])( "recovers diff data that takes longer than seven seconds to settle in %s mode", (mode) => { @@ -724,22 +662,19 @@ describe("combined security review entry point", () => { }, ); - it.each([ - { httpError: 500 }, - { httpError: 502 }, - { httpError: 503 }, - { httpError: 504 }, - { transportError: "ECONNRESET" }, - ])("restarts evaluation after a transient status publication failure: %j", (failure) => { - const result = evaluate({ [statusPath]: { responses: [failure, {}] } }); - expect(result.status, result.stderr).toBe(0); - expect(result.waits).toEqual([1_000]); - const afterWait = result.requests.slice( - result.requests.findIndex((entry) => entry.method === "WAIT") + 1, - ); - expect(afterWait[0]).toMatchObject({ method: "GET", path: pullPath }); - expect(result.combined.at(-1)).toBe("success"); - }); + it.each([{ httpError: 500 }, { transportError: "ECONNRESET" }])( + "restarts evaluation after a transient status publication failure: %j", + (failure) => { + const result = evaluate({ [statusPath]: { responses: [failure, {}] } }); + expect(result.status, result.stderr).toBe(0); + expect(result.waits).toEqual([1_000]); + const afterWait = result.requests.slice( + result.requests.findIndex((entry) => entry.method === "WAIT") + 1, + ); + expect(afterWait[0]).toMatchObject({ method: "GET", path: pullPath }); + expect(result.combined.at(-1)).toBe("success"); + }, + ); it.each([ { httpError: 429 }, @@ -1129,29 +1064,14 @@ describe("combined security review entry point", () => { it.each(["before cleanup", "before commit"])( "preserves merged lockfiles %s and still reports their missing approval", (phase) => { - const cleanupPr = { - ...pr, - changed_files: 1, - head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } }, - }; - const merged = { ...cleanupPr, state: "closed", merged: true }; + const merged = { ...lockfilePr, state: "closed", merged: true }; const routes = { + ...lockfileRoutes, [`GET ${pullPath}`]: { responses: - phase === "before cleanup" ? [merged] : [cleanupPr, cleanupPr, cleanupPr, merged], - }, - [`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }], - [rolePath]: { role_name: "read" }, - [`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [], - [`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: { - base_commit: { sha: pr.base.sha }, - merge_base_commit: { sha: pr.base.sha }, - }, - [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: { - type: "file", - encoding: "base64", - content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"), + phase === "before cleanup" ? [merged] : [lockfilePr, lockfilePr, lockfilePr, merged], }, + [`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: lockfileContents, }; const cleanup = evaluate(routes, "autoscrub"); expect(cleanup.status, cleanup.stderr).toBe(0); @@ -1216,15 +1136,12 @@ describe("combined security review entry point", () => { expect(result.combined).not.toContain("success"); }); - it.each([undefined, "", "main"])( - "does not treat an invalid live head %s as superseded", - (sha) => { - const result = evaluate({ [`GET ${pullPath}`]: { ...pr, head: { ...pr.head, sha } } }); - expect(result.status).toBe(1); - expect(result.stdout).not.toContain("Superseded"); - expect(result.requests.some((entry) => entry.method !== "GET")).toBe(false); - }, - ); + it.each([undefined, "main"])("does not treat an invalid live head %s as superseded", (sha) => { + const result = evaluate({ [`GET ${pullPath}`]: { ...pr, head: { ...pr.head, sha } } }); + expect(result.status).toBe(1); + expect(result.stdout).not.toContain("Superseded"); + expect(result.requests.some((entry) => entry.method !== "GET")).toBe(false); + }); it("accepts the existing exact-head CI fallback without a manual guard run", () => { const fallback = { @@ -1329,7 +1246,6 @@ describe("combined security review entry point", () => { it.each([ { status: "completed", conclusion: "failure", expected: "failure" }, - { status: "completed", conclusion: "cancelled", expected: "failure" }, { status: "in_progress", conclusion: null, expected: "pending" }, ])( "keeps newer substantive CI authoritative before a skipped PR workflow: $status/$conclusion", @@ -1401,7 +1317,6 @@ describe("combined security review entry point", () => { }); it.each([ - { field: "status", value: undefined }, { field: "status", value: "unknown" }, { field: "run_attempt", value: undefined }, { field: "run_attempt", value: 0 }, @@ -1419,7 +1334,7 @@ describe("combined security review entry point", () => { } }); - it.each([0, -1, undefined, "invalid"])( + it.each([0, undefined])( "rejects malformed eligible run ID %s before selecting older successful CI", (id) => { const result = evaluate({ @@ -1434,14 +1349,11 @@ describe("combined security review entry point", () => { }, ); - it.each(["failure", "cancelled", "skipped", "neutral"])( - "does not hide a %s CI gate", - (conclusion) => { - const result = evaluate({ [jobsPath]: { ...jobs, jobs: [{ ...jobs.jobs[0], conclusion }] } }); - expect(result.status, result.stderr).toBe(0); - expect(result.combined).not.toContain("success"); - }, - ); + it.each(["failure", "skipped"])("does not hide a %s CI gate", (conclusion) => { + const result = evaluate({ [jobsPath]: { ...jobs, jobs: [{ ...jobs.jobs[0], conclusion }] } }); + expect(result.status, result.stderr).toBe(0); + expect(result.combined).not.toContain("success"); + }); it("does not accept a previously successful CI attempt while a new attempt runs", () => { const result = evaluate({ @@ -1460,17 +1372,6 @@ describe("combined security review entry point", () => { expect(result.combined).toEqual(["pending", "failure"]); }); - it("settles after CI completes without leaving either evaluation failed", () => { - const waiting = evaluate({ - [runsPath]: { total_count: 1, workflow_runs: [{ ...run, status: "in_progress" }] }, - }); - const completed = evaluate(); - expect(waiting.status, waiting.stderr).toBe(0); - expect(waiting.combined.at(-1)).toBe("pending"); - expect(completed.status, completed.stderr).toBe(0); - expect(completed.combined.at(-1)).toBe("success"); - }); - it.each([ { name: "CI API failure", routes: { [runsPath]: { httpError: 403 } } }, { name: "invalid CI metadata", routes: { [runsPath]: { workflow_runs: null } } }, diff --git a/test/scripts/security-review-workflow.test.ts b/test/scripts/security-review-workflow.test.ts index a06e376a4b01..8bb17bade5f1 100644 --- a/test/scripts/security-review-workflow.test.ts +++ b/test/scripts/security-review-workflow.test.ts @@ -1,15 +1,6 @@ import { execFileSync, spawnSync } from "node:child_process"; -import { - cpSync, - existsSync, - mkdirSync, - readFileSync, - realpathSync, - rmSync, - writeFileSync, -} from "node:fs"; +import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; -import { pathToFileURL } from "node:url"; import { runInNewContext } from "node:vm"; import ignore from "ignore"; import { afterEach, describe, expect, it } from "vitest"; @@ -592,26 +583,6 @@ describe("security review workflow trust boundaries", () => { expect(result.stderr.trim()).toBe( "GitHub token, event, event name, and repository are required.", ); - - rmSync(join(workspace, "scripts/lib/bounded-response.mjs")); - const entryUrl = pathToFileURL(join(workspace, "scripts/github", entry)).href; - const missingModule = spawnSync( - process.execPath, - [ - "--input-type=module", - "--eval", - `await import(${JSON.stringify(entryUrl)}).catch(error => { - console.error(JSON.stringify({ code: error.code, message: error.message })); - process.exitCode = 1; - });`, - ], - { cwd: workspace, env: {}, encoding: "utf8" }, - ); - expect(missingModule.status).toBe(1); - expect(JSON.parse(missingModule.stderr)).toMatchObject({ - code: "ERR_MODULE_NOT_FOUND", - message: expect.stringContaining("bounded-response.mjs"), - }); }); it.skipIf(process.platform === "win32")( @@ -695,16 +666,6 @@ for (const [name, target] of Object.entries(${JSON.stringify(packages)})) { expect(loaded.stderr.trim()).toBe( "GITHUB_TOKEN, GITHUB_EVENT_PATH, and GITHUB_REPOSITORY are required.", ); - - rmSync(join(workspace, ".github/security-review-policy.yml")); - const missingPolicy = spawnSync(process.execPath, [probe], { - cwd: workspace, - env: {}, - encoding: "utf8", - }); - expect(missingPolicy.status).toBe(1); - expect(missingPolicy.stderr).toContain("ENOENT"); - expect(missingPolicy.stderr).toContain("security-review-policy.yml"); }, ); @@ -777,14 +738,11 @@ describe("security review ownership", () => { it.each([ ".github/CODEOWNERS", "SECURITY.md", - ".github/codeql/codeql-core-auth-secrets-critical-security.yml", ".github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql", ".github/workflows/codeql-macos-critical-security.yml", ".github/workflows/security-review.yml", ".github/security-review-policy.yml", ".github/actions/setup-security-review/action.yml", - ".github/actions/setup-security-review/package.json", - ".github/actions/setup-security-review/package-lock.json", "scripts/github/security-review-policy.mjs", "scripts/github/security-review-event.mjs", "scripts/github/security-review.mjs", diff --git a/test/scripts/setup-pnpm-archive-download.test.ts b/test/scripts/setup-pnpm-archive-download.test.ts index 64756967ec8c..2cb28010ad6f 100644 --- a/test/scripts/setup-pnpm-archive-download.test.ts +++ b/test/scripts/setup-pnpm-archive-download.test.ts @@ -29,7 +29,7 @@ describe("pinned pnpm cold bootstrap", () => { for (const name of archives) { fs.copyFileSync(path.join(f.registry, name), path.join(f.image, name)); } - for (const source of ["image", "store", "registry"]) { + for (const [index, source] of ["image", "store", "registry"].entries()) { if (source === "store") { for (const name of archives) { fs.writeFileSync(path.join(f.image, name), "corrupt image"); @@ -46,10 +46,13 @@ describe("pinned pnpm cold bootstrap", () => { expect( fs.readFileSync(path.join(root, "node_modules/@pnpm/exe.linux-x64/pnpm"), "utf8"), ).toBe("native-fixture\n"); - expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8")).hash).toBe( - f.spec.split("+")[1], - ); + expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8"))).toEqual({ + locator: { name: "pnpm", reference: f.spec.slice(5) }, + bin: { pnpm: "./bin/pnpm.mjs", pnpx: "./bin/pnpx.mjs" }, + hash: f.spec.split("+")[1], + }); expect(fs.existsSync(f.calls)).toBe(source === "registry"); + expect(fs.readdirSync(f.runner)).toHaveLength(index + 1); for (const name of archives) { expect(fs.readFileSync(path.join(f.store, "toolchain", name))).toEqual( fs.readFileSync(path.join(f.registry, name)), @@ -151,27 +154,6 @@ describe("pinned pnpm cold bootstrap", () => { }); }); - it("downloads authenticated registry archives when both the store and image are empty", async ({ - command, - }) => { - const f = createPnpmArchiveFixture(command); - const result = await f.run(); - expect(result.status, result.stderr).toBe(0); - expect(result.stdout.trim()).not.toBe(""); - const root = path.join(result.stdout.trim(), "v1/pnpm/12.5.1"); - expect(fs.readFileSync(path.join(root, "pnpm"), "utf8")).toBe("wrapper-fixture\n"); - expect(fs.readFileSync(path.join(root, "node_modules/@pnpm/exe.linux-x64/pnpm"), "utf8")).toBe( - "native-fixture\n", - ); - expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8"))).toEqual({ - locator: { name: "pnpm", reference: f.spec.slice(5) }, - bin: { pnpm: "./bin/pnpm.mjs", pnpx: "./bin/pnpx.mjs" }, - hash: f.spec.split("+")[1], - }); - expect(fs.readFileSync(f.calls, "utf8").trim().split("\n")).toHaveLength(2); - expect(fs.readdirSync(f.runner)).toHaveLength(1); - }); - it("uses authenticated image bytes without making a network request", async ({ command }) => { const f = createPnpmArchiveFixture(command); for (const name of fs.readdirSync(f.registry)) { diff --git a/test/scripts/simslim.test.ts b/test/scripts/simslim.test.ts index d6fcab6f4f1d..eb8de02159a1 100644 --- a/test/scripts/simslim.test.ts +++ b/test/scripts/simslim.test.ts @@ -192,8 +192,6 @@ describe.skipIf(process.platform === "win32")("iOS simulator preparation", () => { env: { OPENCLAW_CI_SIMSLIM_BINARY: "/missing-simslim" } }, { args: [] }, { args: ["booted"] }, - { args: ["all"] }, - { args: [simulatorId, "extra"] }, ])("rejects invalid admission before any tool call: %j", (options) => { const { result, commands } = runFixture("ios-simulator-prepare.sh", options); expect(result.status).not.toBe(0); diff --git a/test/scripts/sqlite-reliability-process.test.ts b/test/scripts/sqlite-reliability-process.test.ts index b08edc931e2c..e927ec7d0f29 100644 --- a/test/scripts/sqlite-reliability-process.test.ts +++ b/test/scripts/sqlite-reliability-process.test.ts @@ -32,26 +32,6 @@ describe("SQLite reliability worker messages", () => { vi.useRealTimers(); }); - it("listens before sending, ignores other messages, and preserves other listeners", async () => { - const child = new ChildProcess(); - const observed: unknown[] = []; - const observe = (message: unknown) => observed.push(message); - child.on("message", observe); - const ready = waitForReady(child, { - action: () => { - child.emit("message", "unrelated"); - expect(child.listenerCount("message")).toBe(2); - child.emit("message", "ready"); - }, - }); - - await expect(ready).resolves.toBe("ready"); - expect(observed).toEqual(["unrelated", "ready"]); - expect(child.listeners("message")).toEqual([observe]); - child.off("message", observe); - expectWaitCleanedUp(child); - }); - it("rejects child errors without replacing the original error", async () => { const child = new ChildProcess(); const error = new Error("IPC failed"); @@ -75,25 +55,23 @@ describe("SQLite reliability worker messages", () => { expectWaitCleanedUp(child); }); - it.each([30_000, 120_000])( - "honors a %i ms timeout and reads final diagnostics", - async (timeoutMs) => { - const child = new ChildProcess(); - let stderr = "before"; - const ready = waitForReady(child, { - timeoutMs, - timeoutMessage: () => `timeout: ${stderr}`, - }); - const rejected = expect(ready).rejects.toThrow("timeout: last stderr"); - await vi.advanceTimersByTimeAsync(timeoutMs - 1); - expect(child.listenerCount("message")).toBe(1); - stderr = "last stderr"; - await vi.advanceTimersByTimeAsync(1); + it("honors the configured timeout and reads final diagnostics", async () => { + const timeoutMs = 120_000; + const child = new ChildProcess(); + let stderr = "before"; + const ready = waitForReady(child, { + timeoutMs, + timeoutMessage: () => `timeout: ${stderr}`, + }); + const rejected = expect(ready).rejects.toThrow("timeout: last stderr"); + await vi.advanceTimersByTimeAsync(timeoutMs - 1); + expect(child.listenerCount("message")).toBe(1); + stderr = "last stderr"; + await vi.advanceTimersByTimeAsync(1); - await rejected; - expectWaitCleanedUp(child); - }, - ); + await rejected; + expectWaitCleanedUp(child); + }); it.each(["action", "matches"] as const)("cleans up when %s throws", async (source) => { const child = new ChildProcess(); @@ -123,19 +101,28 @@ describe("SQLite reliability worker messages", () => { expectWaitCleanedUp(child); }); - it("returns the requested writer payload from a synchronous action reply", async () => { + it("waits for the synchronous writer reply while preserving other listeners", async () => { const child = new ChildProcess(); + const observed: unknown[] = []; + const observe = (message: unknown) => observed.push(message); + child.on("message", observe); + const payload = { kind: "result", batchesCommitted: 2, rowsCommitted: 16 }; const result = await waitForWriterMessage( { child, stderr: [], stopped: false }, "result", () => { child.emit("message", { kind: "ready" }); - child.emit("message", { kind: "result", batchesCommitted: 2, rowsCommitted: 16 }); + expect(child.listenerCount("message")).toBe(2); + child.emit("message", payload); }, ); expect(result.batchesCommitted).toBe(2); expect(result.rowsCommitted).toBe(16); + expect(result).toBe(payload); + expect(observed).toEqual([{ kind: "ready" }, payload]); + expect(child.listeners("message")).toEqual([observe]); + child.off("message", observe); expectWaitCleanedUp(child); }); }); diff --git a/test/scripts/update-compat-external-bindings.test.ts b/test/scripts/update-compat-external-bindings.test.ts index 12795a51fe4a..70470d8dba15 100644 --- a/test/scripts/update-compat-external-bindings.test.ts +++ b/test/scripts/update-compat-external-bindings.test.ts @@ -16,20 +16,19 @@ const { createTempDir } = createScriptTestHarness(); const chunk = previousReleaseInventory.releases[0]!.chunks.find((entry) => /-[A-Za-z0-9_-]{8}\.m?js$/.test(entry.path), )!; -const externalSources = [ - { kind: "named export", source: 'export { helper as NAME } from "external-package";' }, - { kind: "namespace export", source: 'export * as NAME from "external-package";' }, - { - kind: "namespace import", - source: 'import * as NAME from "external-package"; export { NAME };', - }, -]; +const namedExport = { + kind: "named export", + source: 'export { helper as NAME } from "external-package";', +}; +const namespaceImport = { + kind: "namespace import", + source: 'import * as NAME from "external-package"; export { NAME };', +}; it.each([ 'import { safePath as helper } from "@openclaw/fs-safe/path"; export { helper };', 'import helper from "external-package"; export { helper };', 'export { safePath as helper } from "@openclaw/fs-safe/path";', - 'import { basename as helper } from "node:path"; export { helper };', 'export * as helper from "external-package";', 'import * as helper from "external-package"; export { helper };', ])("keeps published updater bridges usable beside %s", async (external) => { @@ -51,7 +50,7 @@ it.each([ } }); -it.each(externalSources)( +it.each([namedExport, namespaceImport])( "refuses a required updater implementation replaced with an external $kind", ({ source: template }) => { const root = createTempDir("update-compat-required-external-"); @@ -71,7 +70,10 @@ it.each(externalSources)( }, ); -it.each(externalSources)("refuses to omit a published external $kind", ({ source }) => { +it.each([ + namedExport, + { kind: "namespace export", source: 'export * as NAME from "external-package";' }, +])("refuses to omit a published external $kind", ({ source }) => { const root = createTempDir("update-compat-published-external-"); fs.mkdirSync(path.join(root, "dist")); fs.writeFileSync(