diff --git a/apps/android/scripts/build-release-artifacts.ts b/apps/android/scripts/build-release-artifacts.ts index 86362d32ea1d..ed9807433141 100644 --- a/apps/android/scripts/build-release-artifacts.ts +++ b/apps/android/scripts/build-release-artifacts.ts @@ -320,10 +320,16 @@ function verifyApkSignature(path: string, expectedCertificateSha256: string): vo throw new Error(`apksigner verification failed for ${path}`); } - const fingerprints = Array.from( - output.matchAll(/^Signer #[0-9]+ certificate SHA-256 digest: ([a-fA-F0-9:]+)$/gmu), - (match) => match[1].replaceAll(":", "").toLowerCase(), - ); + const fingerprints: string[] = []; + for (const match of output.matchAll( + /^Signer #[0-9]+ certificate SHA-256 digest: ([a-fA-F0-9:]+)$/gmu, + )) { + const fingerprint = match[1]; + if (!fingerprint) { + throw new Error(`Malformed SHA-256 signing certificate output for ${path}`); + } + fingerprints.push(fingerprint.replaceAll(":", "").toLowerCase()); + } if (fingerprints.length !== 1 || !/^[a-f0-9]{64}$/u.test(fingerprints[0] ?? "")) { throw new Error(`Expected exactly one SHA-256 signing certificate for ${path}`); } diff --git a/scripts/e2e/parallels/linux-smoke.ts b/scripts/e2e/parallels/linux-smoke.ts index b096385a449d..ac94daf4a574 100755 --- a/scripts/e2e/parallels/linux-smoke.ts +++ b/scripts/e2e/parallels/linux-smoke.ts @@ -69,12 +69,15 @@ function compareOpenClawPackageVersions(left: string, right: string): number { if (!match) { return [0, 0, 0]; } - return [Number(match[1]), Number(match[2]), Number(match[3])]; + const [, year, month, patch] = match; + if (!year || !month || !patch) { + return [0, 0, 0]; + } + return [Number(year), Number(month), Number(patch)]; }; - const leftParts = parse(left); - const rightParts = parse(right); - for (let index = 0; index < leftParts.length; index++) { - const delta = leftParts[index] - rightParts[index]; + const [leftYear, leftMonth, leftPatch] = parse(left); + const [rightYear, rightMonth, rightPatch] = parse(right); + for (const delta of [leftYear - rightYear, leftMonth - rightMonth, leftPatch - rightPatch]) { if (delta !== 0) { return delta; } diff --git a/scripts/e2e/parallels/parallels-vm.ts b/scripts/e2e/parallels/parallels-vm.ts index b62c6ed65ac7..cb044b3e0856 100644 --- a/scripts/e2e/parallels/parallels-vm.ts +++ b/scripts/e2e/parallels/parallels-vm.ts @@ -91,7 +91,7 @@ export function resolveUbuntuVmName(requested: string, explicit = false): string names .map((name) => ({ name, parts: parseUbuntuVersionParts(name) })) .filter((item): item is { name: string; parts: number[] } => Boolean(item.parts)) - .filter((item) => item.parts[0] >= 24) + .filter((item) => item.parts[0] !== undefined && item.parts[0] >= 24) .toSorted((a, b) => compareVersions(b.parts, a.parts))[0]?.name ?? names.find(isSafeUbuntuFallbackName); if (!fallback) { diff --git a/scripts/e2e/parallels/snapshots.ts b/scripts/e2e/parallels/snapshots.ts index 4c62f69d681b..3e216eb2b172 100644 --- a/scripts/e2e/parallels/snapshots.ts +++ b/scripts/e2e/parallels/snapshots.ts @@ -1,4 +1,5 @@ // Snapshots script supports OpenClaw repository automation. +import { expectDefined } from "@openclaw/normalization-core"; import { die, run } from "./host-command.ts"; import type { Mode } from "./types.ts"; import type { SnapshotInfo } from "./types.ts"; @@ -96,20 +97,29 @@ function stringSimilarity(a: string, b: string): number { const cols = b.length + 1; const matrix = Array.from({ length: rows }, () => Array(cols).fill(0)); for (let i = 0; i < rows; i++) { - matrix[i][0] = i; + expectDefined(matrix[i], `snapshot similarity matrix row ${i}`)[0] = i; } + const firstRow = expectDefined(matrix[0], "snapshot similarity first matrix row"); for (let j = 0; j < cols; j++) { - matrix[0][j] = j; + firstRow[j] = j; } for (let i = 1; i < rows; i++) { + const row = expectDefined(matrix[i], `snapshot similarity matrix row ${i}`); + const previousRow = expectDefined(matrix[i - 1], `snapshot similarity matrix row ${i - 1}`); for (let j = 1; j < cols; j++) { - matrix[i][j] = Math.min( - matrix[i - 1][j] + 1, - matrix[i][j - 1] + 1, - matrix[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1), + row[j] = Math.min( + expectDefined(previousRow[j], `snapshot similarity deletion cell ${i},${j}`) + 1, + expectDefined(row[j - 1], `snapshot similarity insertion cell ${i},${j - 1}`) + 1, + expectDefined( + previousRow[j - 1], + `snapshot similarity substitution cell ${i - 1},${j - 1}`, + ) + (a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1), ); } } - const distance = matrix[a.length][b.length]; + const distance = expectDefined( + expectDefined(matrix[a.length], "snapshot similarity final matrix row")[b.length], + "snapshot similarity final distance", + ); return 1 - distance / Math.max(a.length, b.length, 1); } diff --git a/scripts/e2e/parallels/windows-smoke.ts b/scripts/e2e/parallels/windows-smoke.ts index 17751d9019eb..9b7ded291b70 100755 --- a/scripts/e2e/parallels/windows-smoke.ts +++ b/scripts/e2e/parallels/windows-smoke.ts @@ -215,6 +215,9 @@ export function parseArgs(argv: string[]): WindowsOptions { }; for (let i = 0; i < args.length; i++) { const arg = args[i]; + if (arg === undefined) { + die(`missing argument at index ${i}`); + } if (arg === "--") { break; } diff --git a/scripts/e2e/telegram-user-crabbox-proof.ts b/scripts/e2e/telegram-user-crabbox-proof.ts index db9e18bb5a0a..03d9aed11eac 100644 --- a/scripts/e2e/telegram-user-crabbox-proof.ts +++ b/scripts/e2e/telegram-user-crabbox-proof.ts @@ -240,7 +240,7 @@ function trimToValue(value: string | undefined) { const positiveIntegerPattern = /^[1-9]\d*$/u; const SHORT_OPTION_TOKENS = new Set(["-h"]); -function isMissingOptionValue(value: string | undefined) { +function isMissingOptionValue(value: string) { return !value || SHORT_OPTION_TOKENS.has(value) || value.startsWith("--"); } @@ -330,9 +330,12 @@ export function parseArgs(argvInput: string[]): Options { const seenSingleValueOptions = new Set(); for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; + if (arg === undefined) { + usage(); + } const readValue = (options: { repeatable?: boolean } = {}) => { const value = argv[index + 1]; - if (isMissingOptionValue(value)) { + if (value === undefined || isMissingOptionValue(value)) { usage(); } if (!options.repeatable) { diff --git a/test/appcast.test.ts b/test/appcast.test.ts index 0a1cd29aa8c1..9aa1f9f89bde 100644 --- a/test/appcast.test.ts +++ b/test/appcast.test.ts @@ -1,5 +1,6 @@ // Appcast tests validate generated update appcast metadata. import { readFileSync } from "node:fs"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { canonicalSparkleBuildFromVersion } from "../scripts/sparkle-build.ts"; @@ -69,10 +70,13 @@ describe("appcast.xml", () => { ); expect(stableItems.length).toBeGreaterThan(0); - const firstStable = stableItems[0]; - const newestStable = [...stableItems].toSorted( - (left, right) => (right.sparkleVersion ?? 0) - (left.sparkleVersion ?? 0), - )[0]; + const firstStable = expectDefined(stableItems[0], "first stable appcast item"); + const newestStable = expectDefined( + [...stableItems].toSorted( + (left, right) => (right.sparkleVersion ?? 0) - (left.sparkleVersion ?? 0), + )[0], + "newest stable appcast item", + ); expect(firstStable.sparkleVersion).toBe(newestStable.sparkleVersion); expect(firstStable.shortVersion).toBe(newestStable.shortVersion); diff --git a/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts b/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts index 1171c44464cf..087bc386831f 100644 --- a/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts +++ b/test/e2e/qa-lab/runtime/qa-otel-smoke-runtime.ts @@ -446,7 +446,11 @@ class ProtoReader { let result = 0; let shift = 0; while (this.offset < this.buffer.length) { - const byte = this.buffer[this.offset++]; + const byte = this.buffer.at(this.offset); + if (byte === undefined) { + throw new Error("truncated protobuf varint"); + } + this.offset += 1; result += (byte & 0x7f) * 2 ** shift; if ((byte & 0x80) === 0) { return result; diff --git a/test/extension-test-boundary.test.ts b/test/extension-test-boundary.test.ts index 8351286df181..8d9c54ac621b 100644 --- a/test/extension-test-boundary.test.ts +++ b/test/extension-test-boundary.test.ts @@ -94,7 +94,7 @@ function findExtensionImports(source: string): string[] { return [ ...source.matchAll(/from\s+["']((?:\.\.\/)+extensions\/[^"']+)["']/g), ...source.matchAll(/import\(\s*["']((?:\.\.\/)+extensions\/[^"']+)["']\s*\)/g), - ].map((match) => match[1]); + ].flatMap((match) => (match[1] === undefined ? [] : [match[1]])); } function isAllowedExtensionPublicImport(specifier: string): boolean { @@ -107,7 +107,7 @@ function findPluginSdkImports(source: string): string[] { return [ ...source.matchAll(/from\s+["']((?:\.\.\/)+plugin-sdk\/[^"']+)["']/g), ...source.matchAll(/import\(\s*["']((?:\.\.\/)+plugin-sdk\/[^"']+)["']\s*\)/g), - ].map((match) => match[1]); + ].flatMap((match) => (match[1] === undefined ? [] : [match[1]])); } function findBundledPluginPublicSurfaceImports(source: string): string[] { @@ -124,7 +124,7 @@ function findRelativeSrcImports(source: string): string[] { ...source.matchAll(/from\s+["']((?:\.\.?\/)+src\/[^"']+)["']/g), ...source.matchAll(/import\(\s*["']((?:\.\.?\/)+src\/[^"']+)["']\s*\)/g), ...source.matchAll(/vi\.(?:mock|doMock)\s*\(\s*["']((?:\.\.?\/)+src\/[^"']+)["']/g), - ].map((match) => match[1]); + ].flatMap((match) => (match[1] === undefined ? [] : [match[1]])); } function getImportBasename(importPath: string): string { diff --git a/test/helpers/sqlite-sessions-transcripts-flip-proof.ts b/test/helpers/sqlite-sessions-transcripts-flip-proof.ts index c88caa7d29c4..2c0efcb3fdd4 100644 --- a/test/helpers/sqlite-sessions-transcripts-flip-proof.ts +++ b/test/helpers/sqlite-sessions-transcripts-flip-proof.ts @@ -8,6 +8,7 @@ import path from "node:path"; import { DatabaseSync, type SQLInputValue } from "node:sqlite"; import type { Readable } from "node:stream"; import { fileURLToPath } from "node:url"; +import { expectDefined } from "@openclaw/normalization-core"; import { readSessionArchiveContentSync, stripSessionArchiveCompressionSuffix, @@ -500,11 +501,19 @@ export async function runSqliteSessionsTranscriptsFlipProof( await deleteSession(postResetClient, context.deleteSessionKey); await record("after-sessions-delete"); - await deleteSession(postResetClient, context.sharedSessionKeys[0]); - await requireTrackedSession(postResetClient, context.sharedSessionKeys[1]); + const firstSharedSessionKey = expectDefined( + context.sharedSessionKeys[0], + "first shared SQLite proof session key", + ); + const secondSharedSessionKey = expectDefined( + context.sharedSessionKeys[1], + "second shared SQLite proof session key", + ); + await deleteSession(postResetClient, firstSharedSessionKey); + await requireTrackedSession(postResetClient, secondSharedSessionKey); await record("after-shared-first-delete"); - await deleteSession(postResetClient, context.sharedSessionKeys[1]); + await deleteSession(postResetClient, secondSharedSessionKey); await record("after-shared-final-delete"); } finally { await disconnectGatewayClient(postResetClient); @@ -737,14 +746,22 @@ async function seedLegacySessionStore(context: ProofContext): Promise { await fs.mkdir(context.legacySessionsDir, { recursive: true }); await fs.mkdir(path.join(context.stateDir, "agent"), { recursive: true }); const now = Date.now(); + const firstSharedSessionKey = expectDefined( + context.sharedSessionKeys[0], + "first legacy shared SQLite proof session key", + ); + const secondSharedSessionKey = expectDefined( + context.sharedSessionKeys[1], + "second legacy shared SQLite proof session key", + ); const entries: Record> = { [context.concurrentDeleteSessionKey]: legacyEntry("sqlite-concurrent-delete", now - 8_000), [context.concurrentResetSessionKey]: legacyEntry("sqlite-concurrent-reset", now - 9_000), [context.deleteSessionKey]: legacyEntry("sqlite-delete-session", now - 1_000), - [context.sharedSessionKeys[0]]: legacyEntry("sqlite-shared-session", now - 2_000, { + [firstSharedSessionKey]: legacyEntry("sqlite-shared-session", now - 2_000, { sessionFile: "sqlite-shared-a.jsonl", }), - [context.sharedSessionKeys[1]]: legacyEntry("sqlite-shared-session", now - 3_000, { + [secondSharedSessionKey]: legacyEntry("sqlite-shared-session", now - 3_000, { sessionFile: "sqlite-shared-b.jsonl", }), }; diff --git a/test/package-scripts.test.ts b/test/package-scripts.test.ts index a53c2cc22289..f9100eeca3e1 100644 --- a/test/package-scripts.test.ts +++ b/test/package-scripts.test.ts @@ -1,5 +1,6 @@ // Package script tests validate root package script invariants. import fs from "node:fs"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; type RootPackageJson = { @@ -124,7 +125,10 @@ describe("package scripts", () => { { scriptName: "build:plugin-sdk:strict-smoke", expectedCount: 1 }, { scriptName: "build:strict-smoke", expectedCount: 1 }, ])("runs TypeScript steps in $scriptName through tsx", ({ scriptName, expectedCount }) => { - const script = readPackageJson().scripts[scriptName]; + const script = expectDefined( + readPackageJson().scripts[scriptName], + `package script ${scriptName}`, + ); expect(script).not.toContain("--experimental-strip-types"); expect(script.match(/node --import tsx scripts\/[^\s]+\.ts/gu)).toHaveLength(expectedCount); diff --git a/test/plugin-clawhub-release.test.ts b/test/plugin-clawhub-release.test.ts index d5bf8cf81086..dd8ef9e3430f 100644 --- a/test/plugin-clawhub-release.test.ts +++ b/test/plugin-clawhub-release.test.ts @@ -1990,8 +1990,9 @@ function createClawHubPlanFetch(config: { requests.push(url.pathname); const packageMatch = url.pathname.match(/^\/api\/v1\/packages\/([^/]+)$/u); - if (packageMatch) { - const packageName = decodeURIComponent(packageMatch[1]); + const encodedPackageName = packageMatch?.[1]; + if (encodedPackageName !== undefined) { + const packageName = decodeURIComponent(encodedPackageName); const packageResponse = config.packages[packageName]; if (!packageResponse) { throw new Error(`Unexpected package detail request for ${packageName}`); @@ -2004,8 +2005,9 @@ function createClawHubPlanFetch(config: { const trustedPublisherMatch = url.pathname.match( /^\/api\/v1\/packages\/([^/]+)\/trusted-publisher$/u, ); - if (trustedPublisherMatch) { - const packageName = decodeURIComponent(trustedPublisherMatch[1]); + const encodedTrustedPublisherPackageName = trustedPublisherMatch?.[1]; + if (encodedTrustedPublisherPackageName !== undefined) { + const packageName = decodeURIComponent(encodedTrustedPublisherPackageName); const trustedPublisherResponse = config.trustedPublishers?.[packageName]; if (!trustedPublisherResponse) { throw new Error(`Unexpected trusted-publisher request for ${packageName}`); @@ -2016,9 +2018,11 @@ function createClawHubPlanFetch(config: { } const versionMatch = url.pathname.match(/^\/api\/v1\/packages\/([^/]+)\/versions\/([^/]+)$/u); - if (versionMatch) { - const packageName = decodeURIComponent(versionMatch[1]); - const version = decodeURIComponent(versionMatch[2]); + const encodedVersionPackageName = versionMatch?.[1]; + const encodedVersion = versionMatch?.[2]; + if (encodedVersionPackageName !== undefined && encodedVersion !== undefined) { + const packageName = decodeURIComponent(encodedVersionPackageName); + const version = decodeURIComponent(encodedVersion); const status = config.versions?.[`${packageName}@${version}`]; if (!status) { throw new Error(`Unexpected version detail request for ${packageName}@${version}`); diff --git a/test/scripts/barnacle-auto-response.test.ts b/test/scripts/barnacle-auto-response.test.ts index 009429e50d33..6eac88e94560 100644 --- a/test/scripts/barnacle-auto-response.test.ts +++ b/test/scripts/barnacle-auto-response.test.ts @@ -1,5 +1,6 @@ // Barnacle Auto Response tests cover barnacle auto response script behavior. import { readFileSync } from "node:fs"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { candidateLabels, @@ -236,19 +237,23 @@ function expectedAddLabels(issue_number: number, labels: string[]) { }; } +function managedLabelDescription(label: string): string { + return expectDefined(managedLabelSpecs[label], `managed label ${label}`).description; +} + describe("barnacle-auto-response", () => { it("keeps Barnacle-owned labels documented and ClawHub spelled correctly", () => { - expect(managedLabelSpecs["r: skill"].description).toContain("ClawHub"); - expect(managedLabelSpecs["r: skill"].description).not.toContain("Clawdhub"); - expect(managedLabelSpecs.dirty.description).toContain("dirty/unrelated"); - expect(managedLabelSpecs["r: support"].description).toContain("support requests"); - expect(managedLabelSpecs["r: false-positive"].description).toContain("false positive"); - expect(managedLabelSpecs["r: third-party-extension"].description).toContain("ClawHub"); - expect(managedLabelSpecs["r: bluebubbles"].description).toContain("deprecated"); - expect(managedLabelSpecs["r: too-many-prs"].description).toContain("twenty active PRs"); + expect(managedLabelDescription("r: skill")).toContain("ClawHub"); + expect(managedLabelDescription("r: skill")).not.toContain("Clawdhub"); + expect(managedLabelDescription("dirty")).toContain("dirty/unrelated"); + expect(managedLabelDescription("r: support")).toContain("support requests"); + expect(managedLabelDescription("r: false-positive")).toContain("false positive"); + expect(managedLabelDescription("r: third-party-extension")).toContain("ClawHub"); + expect(managedLabelDescription("r: bluebubbles")).toContain("deprecated"); + expect(managedLabelDescription("r: too-many-prs")).toContain("twenty active PRs"); for (const label of Object.values(candidateLabels)) { expect(managedLabelSpecs).toHaveProperty(label); - expect(managedLabelSpecs[label].description).toMatch(/^Candidate:/); + expect(managedLabelDescription(label)).toMatch(/^Candidate:/); } }); diff --git a/test/scripts/build-all.test.ts b/test/scripts/build-all.test.ts index 2b1590c278c7..de880ad5f248 100644 --- a/test/scripts/build-all.test.ts +++ b/test/scripts/build-all.test.ts @@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it, vi } from "vitest"; import { BUILD_ALL_PROFILES, @@ -327,13 +328,20 @@ describe("resolveBuildAllSteps", () => { }); it("skips bundled tsdown declarations for runtime-only profiles", () => { - for (const profile of ["gatewayWatch", "qaRuntime", "sourcePerformance", "cliStartup"]) { + for (const profile of [ + "gatewayWatch", + "qaRuntime", + "sourcePerformance", + "cliStartup", + ] as const) { const tsdown = resolveBuildAllSteps(profile).find((step) => step.label === "tsdown"); if (!tsdown) { throw new Error(`Missing ${profile} tsdown step`); } - expect(BUILD_ALL_PROFILE_STEP_ENV[profile].tsdown).toMatchObject({ + expect( + expectDefined(BUILD_ALL_PROFILE_STEP_ENV[profile], `${profile} build step env`).tsdown, + ).toMatchObject({ OPENCLAW_RUN_NODE_SKIP_DTS_BUILD: "1", }); expect( @@ -429,7 +437,7 @@ describe("resolveBuildAllSteps", () => { }); it("skips generated static plugin assets for minimal backend-only profiles", () => { - for (const profile of ["gatewayWatch", "cliStartup"]) { + for (const profile of ["gatewayWatch", "cliStartup"] as const) { const runtimePostbuild = resolveBuildAllSteps(profile).find( (step) => step.label === "runtime-postbuild", ); @@ -437,7 +445,11 @@ describe("resolveBuildAllSteps", () => { throw new Error(`Missing ${profile} runtime-postbuild step`); } - expect(BUILD_ALL_PROFILE_STEP_ENV[profile]["runtime-postbuild"]).toEqual({ + expect( + expectDefined(BUILD_ALL_PROFILE_STEP_ENV[profile], `${profile} build step env`)[ + "runtime-postbuild" + ], + ).toEqual({ OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS: "0", }); expect( @@ -451,7 +463,7 @@ describe("resolveBuildAllSteps", () => { }); it("keeps generated static plugin assets enabled for QA-backed profiles", () => { - for (const profile of ["qaRuntime", "sourcePerformance"]) { + for (const profile of ["qaRuntime", "sourcePerformance"] as const) { const runtimePostbuild = resolveBuildAllSteps(profile).find( (step) => step.label === "runtime-postbuild", ); @@ -459,7 +471,11 @@ describe("resolveBuildAllSteps", () => { throw new Error(`Missing ${profile} runtime-postbuild step`); } - expect(BUILD_ALL_PROFILE_STEP_ENV[profile]["runtime-postbuild"]).toBeUndefined(); + expect( + expectDefined(BUILD_ALL_PROFILE_STEP_ENV[profile], `${profile} build step env`)[ + "runtime-postbuild" + ], + ).toBeUndefined(); expect( resolveBuildAllStep(runtimePostbuild, { env: { OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS: "1" }, diff --git a/test/scripts/build-diffs-viewer-runtime.test.ts b/test/scripts/build-diffs-viewer-runtime.test.ts index 4b17c41c14a3..bc5d3a9a066b 100644 --- a/test/scripts/build-diffs-viewer-runtime.test.ts +++ b/test/scripts/build-diffs-viewer-runtime.test.ts @@ -1,4 +1,5 @@ // Build Diffs Viewer Runtime tests cover build diffs viewer runtime script behavior. +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { createPierreDiffsSideEffectImportPlugin } from "../../scripts/build-diffs-viewer-runtime.mjs"; @@ -25,9 +26,11 @@ describe("build diffs viewer runtime", () => { const { loadCallbacks, resolveCallbacks } = collectPluginCallbacks(); expect(resolveCallbacks).toHaveLength(1); expect(loadCallbacks).toHaveLength(1); + const resolveCallback = expectDefined(resolveCallbacks[0], "Pierre Diffs resolve callback"); + const loadCallback = expectDefined(loadCallbacks[0], "Pierre Diffs load callback"); expect( - resolveCallbacks[0]({ + resolveCallback({ path: "diff", importer: "/repo/node_modules/@pierre/diffs/dist/utils/parseDiffDecorations.js", }), @@ -37,12 +40,12 @@ describe("build diffs viewer runtime", () => { sideEffects: true, }); expect( - resolveCallbacks[0]({ + resolveCallback({ path: "diff", importer: "/repo/node_modules/@pierre/diffs/dist/utils/renderDiffWithHighlighter.js", }), ).toBeUndefined(); - expect(loadCallbacks[0]()).toEqual({ + expect(loadCallback()).toEqual({ contents: "export {};\n", loader: "js", }); diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index 6e18282502a0..ec7818723e2a 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -2,6 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it } from "vitest"; import { createEmptyChangedLanes, @@ -102,7 +103,12 @@ function runChangedFormatLaneWithRepoOxfmt(cwd: string, changedPaths: string[]) const packageJson = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8")) as { scripts: Record; }; - const [scriptBin, ...scriptArgs] = packageJson.scripts["format:check"].split(" "); + const formatScript = expectDefined( + packageJson.scripts["format:check"], + "format:check package script", + ); + const [rawScriptBin, ...scriptArgs] = formatScript.split(" "); + const scriptBin = expectDefined(rawScriptBin, "format:check script binary"); expect(scriptBin).toBe("oxfmt"); const invocation = resolveOxfmtInvocation( [...scriptArgs, ...(formatCommand?.args.slice(1) ?? [])], @@ -955,7 +961,10 @@ describe("scripts/changed-lanes", () => { { name: "conflict markers", args: ["check:no-conflict-markers"] }, { CI: "1", PATH: "/usr/bin" }, ); - const [shimDir] = (command.env?.PATH ?? "").split(path.delimiter); + const shimDir = expectDefined( + (command.env?.PATH ?? "").split(path.delimiter)[0], + "CI Corepack pnpm shim directory", + ); expect(path.basename(shimDir)).toMatch(/^openclaw-corepack-pnpm-/u); expect(existsSync(path.join(shimDir, "pnpm"))).toBe(true); diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 05240f7a2b55..4980be6c7315 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -13,6 +13,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; import { NATIVE_I18N_LOCALES } from "../../scripts/native-app-i18n.ts"; @@ -273,14 +274,18 @@ function runMaturityArtifactCopyScenario( if (options.extraFile) { writeFileSync(path.join(staging, "unexpected.txt"), "unexpected\n", "utf8"); } + const firstGeneratedPath = expectDefined( + MATURITY_GENERATED_PR_PATHS[0], + "first maturity generated PR path", + ); if (options.sourceSymlink) { - const staged = path.join(staging, MATURITY_GENERATED_PR_PATHS[0]); + const staged = path.join(staging, firstGeneratedPath); rmSync(staged); symlinkSync("missing-score-source", staged); } const escaped = path.join(root, "escaped.txt"); if (options.destinationSymlink) { - const selected = path.join(root, "selected", MATURITY_GENERATED_PR_PATHS[0]); + const selected = path.join(root, "selected", firstGeneratedPath); writeFileSync(escaped, "outside\n", "utf8"); rmSync(selected); symlinkSync(escaped, selected); @@ -1399,7 +1404,7 @@ describe("ci workflow guards", () => { [".github/workflows/ci-check-arm-testbox.yml", "120s"], [".github/workflows/ci-build-artifacts-testbox.yml", "120s"], [".github/workflows/crabbox-hydrate.yml", "30s"], - ]; + ] as const; for (const [workflowPath, timeoutSeconds] of workflowPaths) { const workflow = readFileSync(workflowPath, "utf8"); @@ -1685,7 +1690,14 @@ describe("ci workflow guards", () => { expect(legacy.outputs.run_qa_smoke_ci).toBe("false"); expect(legacy.outputs.run_channel_contracts_shards).toBe("false"); expect(legacy.outputs.run_protocol_event_coverage).toBe("false"); - expect(JSON.parse(legacy.outputs.checks_node_core_nondist_matrix).include).toContainEqual( + expect( + JSON.parse( + expectDefined( + legacy.outputs.checks_node_core_nondist_matrix, + "legacy node core nondist matrix output", + ), + ).include, + ).toContainEqual( expect.objectContaining({ check_name: "legacy-node-plan", shard_name: "legacy-node-plan", @@ -1700,7 +1712,14 @@ describe("ci workflow guards", () => { expect(current.outputs.run_channel_contracts_shards).toBe("true"); expect(current.outputs.run_protocol_event_coverage).toBe("true"); expect(current.outputs.run_format_check).toBe("true"); - expect(JSON.parse(current.outputs.checks_node_core_nondist_matrix).include).toContainEqual( + expect( + JSON.parse( + expectDefined( + current.outputs.checks_node_core_nondist_matrix, + "current node core nondist matrix output", + ), + ).include, + ).toContainEqual( expect.objectContaining({ check_name: "bundled-node-plan", shard_name: "bundled-node-plan", @@ -1776,7 +1795,12 @@ describe("ci workflow guards", () => { expect(legacyReleaseCandidate.status, legacyReleaseCandidate.output).toBe(0); expect(legacyReleaseCandidate.outputs.compatibility_target).toBe("true"); expect( - JSON.parse(legacyReleaseCandidate.outputs.checks_node_core_nondist_matrix).include, + JSON.parse( + expectDefined( + legacyReleaseCandidate.outputs.checks_node_core_nondist_matrix, + "release candidate node core nondist matrix output", + ), + ).include, ).toContainEqual(expect.objectContaining({ check_name: "legacy-node-plan" })); const currentMissingProtocolCoverage = runCiManifestFixture({ diff --git a/test/scripts/codesign-mac-app.test.ts b/test/scripts/codesign-mac-app.test.ts index bfb7266abc2f..f52763de33da 100644 --- a/test/scripts/codesign-mac-app.test.ts +++ b/test/scripts/codesign-mac-app.test.ts @@ -12,6 +12,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it } from "vitest"; const tempDirs: string[] = []; @@ -194,9 +195,14 @@ describe("codesign-mac-app temp file hygiene", () => { expect(signLines[2]).toContain(`${app}\t`); for (const line of signLines) { const [, , entitlementPath, copiedEntitlementsPath] = line.split("\t"); - const copiedEntitlements = readFileSync(copiedEntitlementsPath, "utf8"); - expect(entitlementPath).toContain("openclaw-entitlements"); - expect(existsSync(entitlementPath)).toBe(false); + const entitlementSource = expectDefined(entitlementPath, "codesign entitlement source path"); + const copiedEntitlementSource = expectDefined( + copiedEntitlementsPath, + "copied codesign entitlement path", + ); + const copiedEntitlements = readFileSync(copiedEntitlementSource, "utf8"); + expect(entitlementSource).toContain("openclaw-entitlements"); + expect(existsSync(entitlementSource)).toBe(false); expect(copiedEntitlements).toContain("com.apple.security.automation.apple-events"); expect(copiedEntitlements).toContain("com.apple.security.device.camera"); } diff --git a/test/scripts/crabbox-wrapper.test.ts b/test/scripts/crabbox-wrapper.test.ts index 88903cc38b81..e8d1e46365ea 100644 --- a/test/scripts/crabbox-wrapper.test.ts +++ b/test/scripts/crabbox-wrapper.test.ts @@ -1598,7 +1598,7 @@ describe("scripts/crabbox-wrapper", () => { "env -i bash scripts/package-mac-app.sh >out.log 2>&1", "openclaw_crabbox_env -i bash scripts/package-mac-app.sh >out.log 2>&1", ], - ]) { + ] as const) { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", ["run", "--provider", "aws", "--target", "macos", "--shell", "--", shellCommand], @@ -1654,7 +1654,7 @@ describe("scripts/crabbox-wrapper", () => { "time env -i bash scripts/package-mac-app.sh", "time openclaw_crabbox_env -i bash scripts/package-mac-app.sh", ], - ]) { + ] as const) { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", ["run", "--provider", "aws", "--target", "macos", "--shell", "--", shellCommand], @@ -1681,7 +1681,7 @@ describe("scripts/crabbox-wrapper", () => { "{ env -i bash scripts/package-mac-app.sh; }", "{ openclaw_crabbox_env -i bash scripts/package-mac-app.sh; }", ], - ]) { + ] as const) { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", ["run", "--provider", "aws", "--target", "macos", "--shell", "--", shellCommand], @@ -1773,7 +1773,7 @@ describe("scripts/crabbox-wrapper", () => { "PATH=/usr/bin:/bin env -i bash scripts/package-mac-app.sh", "PATH=/usr/bin:/bin openclaw_crabbox_env -i bash scripts/package-mac-app.sh", ], - ]) { + ] as const) { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", ["run", "--provider", "aws", "--target", "macos", "--shell", "--", shellCommand], diff --git a/test/scripts/dependency-guard-script.test.ts b/test/scripts/dependency-guard-script.test.ts index 0788520e60a2..9e2be44cb0eb 100644 --- a/test/scripts/dependency-guard-script.test.ts +++ b/test/scripts/dependency-guard-script.test.ts @@ -580,7 +580,7 @@ describe("dependency guard script", () => { "base:/repos/openclaw/openclaw/contents/pnpm-lock.yaml?ref=base-sha", "write:graphql", ]); - expect(calls[1].variables).toMatchObject({ + expect(calls[1]?.variables).toMatchObject({ input: { branch: { repositoryNameWithOwner: "contributor/openclaw", diff --git a/test/scripts/dependency-guard-workflow.test.ts b/test/scripts/dependency-guard-workflow.test.ts index df0667dbbb20..b3586a5673c4 100644 --- a/test/scripts/dependency-guard-workflow.test.ts +++ b/test/scripts/dependency-guard-workflow.test.ts @@ -1,5 +1,6 @@ // Dependency Guard Workflow tests cover dependency guard workflow script behavior. import { readFileSync } from "node:fs"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; @@ -34,6 +35,14 @@ function readWorkflow(): Workflow { return parse(readFileSync(WORKFLOW, "utf8")) as Workflow; } +function workflowStep( + steps: readonly WorkflowStep[], + index: number, + context: string, +): WorkflowStep { + return expectDefined(steps[index], context); +} + describe("dependency guard workflow", () => { it("uses the dependency guard check name", () => { const parsed = readWorkflow(); @@ -88,11 +97,12 @@ describe("dependency guard workflow", () => { parsed.jobs?.["dependency-guard-autoscrub"], parsed.jobs?.["dependency-guard"], ]; - for (const job of jobs) { + for (const [index, job] of jobs.entries()) { const steps = job?.steps ?? []; - expect(steps[0].uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"); - expect(steps[0].with?.ref).toBe("${{ github.event.pull_request.base.sha }}"); - expect(steps[0].with?.["persist-credentials"]).toBe(false); + const checkoutStep = workflowStep(steps, 0, `dependency guard checkout step ${index}`); + expect(checkoutStep.uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"); + expect(checkoutStep.with?.ref).toBe("${{ github.event.pull_request.base.sha }}"); + expect(checkoutStep.with?.["persist-credentials"]).toBe(false); expect(steps.at(-1)?.run).toBe("node scripts/github/dependency-guard.mjs"); } }); @@ -118,48 +128,59 @@ describe("dependency guard workflow", () => { const detectSteps = detectJob?.steps ?? []; const autoscrubSteps = autoscrubJob?.steps ?? []; const finalSteps = finalJob?.steps ?? []; - expect(detectSteps[1].env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("detect"); - expect(autoscrubSteps[1].uses).toBe( + const detectRunStep = workflowStep(detectSteps, 1, "dependency guard detect run step"); + const primaryTokenStep = workflowStep(autoscrubSteps, 1, "dependency guard primary token step"); + const fallbackTokenStep = workflowStep( + autoscrubSteps, + 2, + "dependency guard fallback token step", + ); + const autoscrubRunStep = workflowStep(autoscrubSteps, 3, "dependency guard autoscrub run step"); + const finalRunStep = workflowStep(finalSteps, 1, "dependency guard final run step"); + expect(detectRunStep.env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("detect"); + expect(primaryTokenStep.uses).toBe( "actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3", ); - expect(autoscrubSteps[1].with).toMatchObject({ + expect(primaryTokenStep.with).toMatchObject({ "app-id": "2729701", owner: "${{ needs.dependency-guard-detect.outputs.autoscrub-owner }}", repositories: "${{ needs.dependency-guard-detect.outputs.autoscrub-repository }}", "permission-contents": "write", }); - expect(autoscrubSteps[1]["continue-on-error"]).toBe(true); - expect(autoscrubSteps[2].uses).toBe( + expect(primaryTokenStep["continue-on-error"]).toBe(true); + expect(fallbackTokenStep.uses).toBe( "actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3", ); - expect(autoscrubSteps[2].with).toMatchObject({ + expect(fallbackTokenStep.with).toMatchObject({ "app-id": "2971289", owner: "${{ needs.dependency-guard-detect.outputs.autoscrub-owner }}", repositories: "${{ needs.dependency-guard-detect.outputs.autoscrub-repository }}", "permission-contents": "write", }); - expect(autoscrubSteps[2]["continue-on-error"]).toBe(true); - expect(autoscrubSteps[3].env?.GITHUB_TOKEN).toBe("${{ github.token }}"); - expect(autoscrubSteps[3].env?.OPENCLAW_DEPENDENCY_GUARD_AUTOSCRUB_TOKEN).toBe( + expect(fallbackTokenStep["continue-on-error"]).toBe(true); + expect(autoscrubRunStep.env?.GITHUB_TOKEN).toBe("${{ github.token }}"); + expect(autoscrubRunStep.env?.OPENCLAW_DEPENDENCY_GUARD_AUTOSCRUB_TOKEN).toBe( "${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}", ); - expect(autoscrubSteps[3].env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("autoscrub"); - expect(finalSteps[1].env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("enforce"); + expect(autoscrubRunStep.env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("autoscrub"); + expect(finalRunStep.env?.OPENCLAW_DEPENDENCY_GUARD_MODE).toBe("enforce"); }); it("preserves dependency-guard as the final required check", () => { const steps = readWorkflow().jobs?.["dependency-guard"]?.steps ?? []; expect(steps).toHaveLength(2); - expect(steps[0].uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"); - expect(steps[0].with?.ref).toBe("${{ github.event.pull_request.base.sha }}"); - expect(steps[0].with?.["persist-credentials"]).toBe(false); - expect(steps[1].run).toBe("node scripts/github/dependency-guard.mjs"); + const checkoutStep = workflowStep(steps, 0, "final dependency guard checkout step"); + const runStep = workflowStep(steps, 1, "final dependency guard run step"); + expect(checkoutStep.uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"); + expect(checkoutStep.with?.ref).toBe("${{ github.event.pull_request.base.sha }}"); + expect(checkoutStep.with?.["persist-credentials"]).toBe(false); + expect(runStep.run).toBe("node scripts/github/dependency-guard.mjs"); }); it("uses a dedicated checked-in script and bounded sticky comments", () => { const workflow = readFileSync(WORKFLOW, "utf8"); const detectSteps = readWorkflow().jobs?.["dependency-guard-detect"]?.steps ?? []; - const runStep = detectSteps[1]; + const runStep = workflowStep(detectSteps, 1, "dependency guard bounded comment run step"); const script = readFileSync("scripts/github/dependency-guard.mjs", "utf8"); expect(runStep.env?.OPENCLAW_SECURITY_TEAM_SLUG).toBe("openclaw-secops"); diff --git a/test/scripts/dev-tooling-safety.test.ts b/test/scripts/dev-tooling-safety.test.ts index 8173a81fba5d..9140940d16da 100644 --- a/test/scripts/dev-tooling-safety.test.ts +++ b/test/scripts/dev-tooling-safety.test.ts @@ -130,8 +130,8 @@ describe("dev tooling safety helpers", () => { nested: [{ message: `Authorization: Bearer ${token}` }], }) as { nested: Array<{ message: string }> }; - expect(redacted.nested[0].message).not.toContain(token); - expect(redacted.nested[0].message).toContain("Authorization"); + expect(redacted.nested[0]?.message).not.toContain(token); + expect(redacted.nested[0]?.message).toContain("Authorization"); }); it("parses boolean env values explicitly", () => { diff --git a/test/scripts/docker-all-scheduler.test.ts b/test/scripts/docker-all-scheduler.test.ts index fd132d72a84c..5513fba70e2b 100644 --- a/test/scripts/docker-all-scheduler.test.ts +++ b/test/scripts/docker-all-scheduler.test.ts @@ -50,7 +50,9 @@ function expectDeclaredDispatchInputs(command: string): void { on?: { workflow_dispatch?: { inputs?: Record } }; }; const declared = new Set(Object.keys(workflow.on?.workflow_dispatch?.inputs ?? {})); - const emitted = [...command.matchAll(/(?:^|\s)-f\s+([a-z0-9_]+)=/gu)].map((match) => match[1]); + const emitted = [...command.matchAll(/(?:^|\s)-f\s+([a-z0-9_]+)=/gu)].flatMap((match) => + match[1] === undefined ? [] : [match[1]], + ); expect(emitted.length).toBeGreaterThan(0); for (const input of emitted) { expect(declared.has(input), `undeclared workflow_dispatch input: ${input}`).toBe(true); diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index b71f2e9fe97b..ba71a3371eaa 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -143,7 +143,11 @@ function cleanupSmokeLogTailHelpers(): string { if (!match) { throw new Error("cleanup smoke log helpers were not found"); } - return match[1]; + const helpers = match[1]; + if (helpers === undefined) { + throw new Error("cleanup smoke log helper capture was not found"); + } + return helpers; } function runCleanupDefaultPlatform(env: Record, hostArch: string): string { diff --git a/test/scripts/docker-e2e-helper-cli.test.ts b/test/scripts/docker-e2e-helper-cli.test.ts index 48001b68a926..0b8989a6638d 100644 --- a/test/scripts/docker-e2e-helper-cli.test.ts +++ b/test/scripts/docker-e2e-helper-cli.test.ts @@ -58,7 +58,9 @@ function expectDeclaredDispatchInputs(command: string): void { on?: { workflow_dispatch?: { inputs?: Record } }; }; const declared = new Set(Object.keys(workflow.on?.workflow_dispatch?.inputs ?? {})); - const emitted = [...command.matchAll(/(?:^|\s)-f\s+([a-z0-9_]+)=/gu)].map((match) => match[1]); + const emitted = [...command.matchAll(/(?:^|\s)-f\s+([a-z0-9_]+)=/gu)].flatMap((match) => + match[1] === undefined ? [] : [match[1]], + ); expect(emitted.length).toBeGreaterThan(0); for (const input of emitted) { expect(declared.has(input), `undeclared workflow_dispatch input: ${input}`).toBe(true); diff --git a/test/scripts/docker-e2e-plan.test.ts b/test/scripts/docker-e2e-plan.test.ts index 41881d697d09..0b6d304448e7 100644 --- a/test/scripts/docker-e2e-plan.test.ts +++ b/test/scripts/docker-e2e-plan.test.ts @@ -587,10 +587,9 @@ describe("scripts/lib/docker-e2e-plan", () => { const scripts = packageJson.scripts ?? {}; const missing = plan.lanes .flatMap((lane) => - Array.from(lane.command.matchAll(/\bpnpm\s+(test:docker:[\w:-]+)/gu), (match) => ({ - lane: lane.name, - script: match[1], - })), + Array.from(lane.command.matchAll(/\bpnpm\s+(test:docker:[\w:-]+)/gu)).flatMap((match) => + match[1] === undefined ? [] : [{ lane: lane.name, script: match[1] }], + ), ) .filter(({ script }) => !scripts[script]); diff --git a/test/scripts/find-reusable-release-validation.test.ts b/test/scripts/find-reusable-release-validation.test.ts index ee3f90216c67..45cf6d769335 100644 --- a/test/scripts/find-reusable-release-validation.test.ts +++ b/test/scripts/find-reusable-release-validation.test.ts @@ -3,6 +3,7 @@ import { execFileSync, spawnSync } from "node:child_process"; import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it } from "vitest"; import { createTempDirTracker } from "../helpers/temp-dir.js"; @@ -708,20 +709,22 @@ describe("scripts/github/find-reusable-release-validation.sh", () => { { label: "duplicate child run id", mutate(record: NormalizedEvidence) { - record.children[1].runId = record.children[0].runId; + const firstChild = expectDefined(record.children[0], "first reusable release child"); + const secondChild = expectDefined(record.children[1], "second reusable release child"); + secondChild.runId = firstChild.runId; }, }, { label: "failed child", mutate(record: NormalizedEvidence) { - record.children[0].conclusion = "failure"; + expectDefined(record.children[0], "failed reusable release child").conclusion = "failure"; }, }, { label: "extra child role", mutate(record: NormalizedEvidence) { record.children.push({ - ...record.children[0], + ...expectDefined(record.children[0], "base reusable release child"), role: "npmTelegram", runId: "205", }); diff --git a/test/scripts/generate-dependency-release-evidence.test.ts b/test/scripts/generate-dependency-release-evidence.test.ts index a9990b206075..163b4f183df4 100644 --- a/test/scripts/generate-dependency-release-evidence.test.ts +++ b/test/scripts/generate-dependency-release-evidence.test.ts @@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { DEPENDENCY_EVIDENCE_REPORTS, @@ -241,7 +242,12 @@ describe("generate-dependency-release-evidence", () => { }), ).toBe("v2026.5.1"); expect(calls.map(({ args }) => args[0])).toEqual(["describe", "fetch", "describe"]); - expect(calls[1].args).toEqual(["fetch", "--tags", "--force", "origin"]); + expect(expectDefined(calls[1], "release tag fetch call").args).toEqual([ + "fetch", + "--tags", + "--force", + "origin", + ]); }); it("collects report counts and renders human summaries", async () => { diff --git a/test/scripts/install-sh.test.ts b/test/scripts/install-sh.test.ts index 356ad77a4bc3..6b1d79b9a542 100644 --- a/test/scripts/install-sh.test.ts +++ b/test/scripts/install-sh.test.ts @@ -2340,7 +2340,8 @@ describe("install.sh doctor cancellation and dashboard guard", () => { const bareDoctor = /^\s+run_doctor\s*$/m; const lines = script.split("\n"); for (let i = 0; i < lines.length; i++) { - if (bareDoctor.test(lines[i])) { + const line = lines[i]; + if (line !== undefined && bareDoctor.test(line)) { // A bare run_doctor is only acceptable inside the run_doctor // function definition itself, not at a call site const context = lines.slice(Math.max(0, i - 3), i + 3).join("\n"); diff --git a/test/scripts/kitchen-sink-plugin-assertions.test.ts b/test/scripts/kitchen-sink-plugin-assertions.test.ts index 402e05f8cae8..b1629c6fd792 100644 --- a/test/scripts/kitchen-sink-plugin-assertions.test.ts +++ b/test/scripts/kitchen-sink-plugin-assertions.test.ts @@ -260,8 +260,12 @@ function toGitBashPath(value: string) { if (!match) { return value; } - - return `/${match[1].toLowerCase()}/${match[2].replaceAll("\\", "/")}`; + const drive = match[1]; + const suffix = match[2]; + if (drive === undefined || suffix === undefined) { + return value; + } + return `/${drive.toLowerCase()}/${suffix.replaceAll("\\", "/")}`; } describe("kitchen-sink plugin assertions", () => { diff --git a/test/scripts/kitchen-sink-rpc-walk.test.ts b/test/scripts/kitchen-sink-rpc-walk.test.ts index ec311e67eee8..b589540457d4 100644 --- a/test/scripts/kitchen-sink-rpc-walk.test.ts +++ b/test/scripts/kitchen-sink-rpc-walk.test.ts @@ -13,6 +13,7 @@ import fs, { import { tmpdir } from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it, vi } from "vitest"; import { appendBoundedOutput, @@ -922,7 +923,7 @@ setInterval(() => {}, 1000); expect(samples[0]).toMatchObject({ aggregateRssMiB: 640, label: "plugins install", - processId: seenPids[0] + 1, + processId: expectDefined(seenPids[0], "sampled kitchen sink process id") + 1, rssMiB: 512, }); expect(samples[0]?.elapsedMs).toBeGreaterThanOrEqual(0); diff --git a/test/scripts/lint-suppressions.test.ts b/test/scripts/lint-suppressions.test.ts index 088dda088a6c..91aa6bafbaf3 100644 --- a/test/scripts/lint-suppressions.test.ts +++ b/test/scripts/lint-suppressions.test.ts @@ -94,9 +94,13 @@ function collectProductionLintSuppressions(): SuppressionEntry[] { if (!match) { continue; } + const rule = match[1]; + if (rule === undefined) { + continue; + } entries.push({ file: relativePath, - rule: match[1], + rule, }); } } @@ -134,15 +138,19 @@ function collectProductionLintSuppressionsFromGit(): SuppressionEntry[] | null { if (!match) { continue; } - const [, file, sourceLine] = match; - if (!isProductionCodeFile(file)) { + const file = match[1]; + const sourceLine = match[2]; + if (file === undefined || sourceLine === undefined || !isProductionCodeFile(file)) { continue; } const suppression = sourceLine.match(SUPPRESSION_PATTERN); if (!suppression) { continue; } - entries.push({ file, rule: suppression[1] }); + const rule = suppression[1]; + if (rule !== undefined) { + entries.push({ file, rule }); + } } return entries; } @@ -166,7 +174,7 @@ function summarizeSuppressions(entries: readonly SuppressionEntry[]): string[] { function filterExpectedSuppressionsForPresentFiles(entries: readonly string[]): string[] { return entries.filter((entry) => { const [file] = entry.split("|", 1); - return fs.existsSync(path.join(repoRoot, file)); + return file !== undefined && fs.existsSync(path.join(repoRoot, file)); }); } diff --git a/test/scripts/mantis-publish-pr-evidence.test.ts b/test/scripts/mantis-publish-pr-evidence.test.ts index 0c0bfcdd6afd..d57e48b496b3 100644 --- a/test/scripts/mantis-publish-pr-evidence.test.ts +++ b/test/scripts/mantis-publish-pr-evidence.test.ts @@ -153,7 +153,7 @@ describe("scripts/mantis/publish-pr-evidence", () => { "content-type": "image/png", "x-amz-date": expect.any(String), }); - expect((requests[0].headers as Record).authorization).toContain( + expect((requests[0]?.headers as Record | undefined)?.authorization).toContain( "Credential=access/", ); expect(String(requests[4]?.body)).toContain( diff --git a/test/scripts/native-app-i18n.test.ts b/test/scripts/native-app-i18n.test.ts index a7e03fe324fc..3999c1d6847b 100644 --- a/test/scripts/native-app-i18n.test.ts +++ b/test/scripts/native-app-i18n.test.ts @@ -1,6 +1,7 @@ import { createHash } from "node:crypto"; import { readFile, stat } from "node:fs/promises"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { assignNativeI18nIds, @@ -22,6 +23,14 @@ type NativeTranslationArtifact = { version: 1; }; +function artifactEntry( + artifact: NativeTranslationArtifact, + index: number, + context: string, +): NativeTranslationArtifact["entries"][number] { + return expectDefined(artifact.entries[index], context); +} + describe("native app i18n inventory", () => { it("keeps IDs stable across extractor classification changes", () => { const candidate = { @@ -720,6 +729,8 @@ describe("native app i18n inventory", () => { surface: "apple", }, ]; + const greeting = expectDefined(inventory[0], "native greeting inventory entry"); + const other = expectDefined(inventory[1], "native other inventory entry"); const emptyGlossaryHash = createHash("sha256").update(JSON.stringify([])).digest("hex"); const createArtifact = (): NativeTranslationArtifact => ({ version: 1, @@ -727,13 +738,13 @@ describe("native app i18n inventory", () => { glossaryHash: emptyGlossaryHash, entries: [ { - id: inventory[0].id, - source: inventory[0].source, + id: greeting.id, + source: greeting.source, translated: "Hej ${name}\nNästa", }, { - id: inventory[1].id, - source: inventory[1].source, + id: other.id, + source: other.source, translated: "Annat", }, ], @@ -766,21 +777,36 @@ describe("native app i18n inventory", () => { expected: "entries[0].source does not match inventory", mutate: (artifact) => ({ ...artifact, - entries: [{ ...artifact.entries[0], source: "Changed" }, artifact.entries[1]], + entries: [ + { ...artifactEntry(artifact, 0, "first native translation entry"), source: "Changed" }, + artifactEntry(artifact, 1, "second native translation entry"), + ], }), }, { expected: 'duplicate id "native.android.greeting"', mutate: (artifact) => ({ ...artifact, - entries: [artifact.entries[0], { ...artifact.entries[1], id: artifact.entries[0].id }], + entries: [ + artifactEntry(artifact, 0, "first duplicate native translation entry"), + { + ...artifactEntry(artifact, 1, "second duplicate native translation entry"), + id: artifactEntry(artifact, 0, "duplicate native translation source entry").id, + }, + ], }), }, { expected: "entries[1].translated must be nonempty", mutate: (artifact) => ({ ...artifact, - entries: [artifact.entries[0], { ...artifact.entries[1], translated: " " }], + entries: [ + artifactEntry(artifact, 0, "first nonempty native translation entry"), + { + ...artifactEntry(artifact, 1, "second nonempty native translation entry"), + translated: " ", + }, + ], }), }, { @@ -795,8 +821,11 @@ describe("native app i18n inventory", () => { mutate: (artifact) => ({ ...artifact, entries: [ - { ...artifact.entries[0], translated: "Hej ${name} Nästa" }, - artifact.entries[1], + { + ...artifactEntry(artifact, 0, "first structural native translation entry"), + translated: "Hej ${name} Nästa", + }, + artifactEntry(artifact, 1, "second structural native translation entry"), ], }), }, @@ -845,29 +874,33 @@ describe("native app i18n inventory", () => { surface: "android", }, ]; + const languagePicker = expectDefined(inventory[0], "native language picker inventory entry"); + const androidInspect = expectDefined(inventory[1], "native Android inspect inventory entry"); + const appleInspect = expectDefined(inventory[2], "native Apple inspect inventory entry"); + const voiceNote = expectDefined(inventory[3], "native voice note inventory entry"); const artifact: NativeTranslationArtifact = { version: 1, locale: "id", glossaryHash: createHash("sha256").update(JSON.stringify([])).digest("hex"), entries: [ { - id: inventory[0].id, - source: inventory[0].source, - translated: inventory[0].source, + id: languagePicker.id, + source: languagePicker.source, + translated: languagePicker.source, }, { - id: inventory[1].id, - source: inventory[1].source, - translated: inventory[1].source, + id: androidInspect.id, + source: androidInspect.source, + translated: androidInspect.source, }, { - id: inventory[2].id, - source: inventory[2].source, + id: appleInspect.id, + source: appleInspect.source, translated: "Periksa", }, { - id: inventory[3].id, - source: inventory[3].source, + id: voiceNote.id, + source: voiceNote.source, translated: "Ghi ghi chú thoại", }, ], diff --git a/test/scripts/openclaw-e2e-instance.test.ts b/test/scripts/openclaw-e2e-instance.test.ts index 3d0365169f1f..d58a5824412d 100644 --- a/test/scripts/openclaw-e2e-instance.test.ts +++ b/test/scripts/openclaw-e2e-instance.test.ts @@ -3,6 +3,7 @@ import { execFileSync, spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; const helperPath = path.resolve("scripts/lib/openclaw-e2e-instance.sh"); @@ -1103,7 +1104,7 @@ exit 1 expect(result.status).toBe(1); expect(result.stdout).toContain("recent command tail"); expect(result.stdout).not.toContain("DO_NOT_PRINT_OLD_COMMAND_LOG"); - const [logFile] = fs.readdirSync(logDir); + const logFile = expectDefined(fs.readdirSync(logDir)[0], "OpenClaw E2E command log file"); expect(fs.readFileSync(path.join(logDir, logFile), "utf8")).toContain( "DO_NOT_PRINT_OLD_COMMAND_LOG", ); diff --git a/test/scripts/openclaw-performance-workflow.test.ts b/test/scripts/openclaw-performance-workflow.test.ts index a1d178eadb2a..5dafdc25bbce 100644 --- a/test/scripts/openclaw-performance-workflow.test.ts +++ b/test/scripts/openclaw-performance-workflow.test.ts @@ -12,6 +12,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; @@ -659,7 +660,9 @@ esac const plan = findStep("Kova version and plan sanity"); const runKova = findStep("Run Kova"); const matrixEntries = kovaMatrixEntries(); - const includeFilters = matrixEntries.map((entry) => entry.include_filters); + const includeFilters = matrixEntries.map((entry, index) => + expectDefined(entry.include_filters, `Kova matrix include filters ${index}`), + ); const expectedReleaseEntries = matrixEntries.map((entry) => entry.expected_release_entries); expect(includeFilters).toEqual([ diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 8d1c744771e7..6c2619104cca 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -2099,7 +2099,7 @@ describe("package artifact reuse", () => { ["run_live_discord", "Upload Discord QA artifacts", "always()"], ["run_live_whatsapp", "Upload WhatsApp QA artifacts", "always()"], ["run_live_slack", "Upload Slack QA artifacts", "always()"], - ]; + ] as const; for (const [jobName, stepName, uploadCondition] of cases) { const uploadStep = workflowStep(workflowJob(QA_LIVE_TRANSPORTS_WORKFLOW, jobName), stepName); @@ -2130,7 +2130,7 @@ describe("package artifact reuse", () => { ["qa_live_discord_release_checks", "Upload Discord QA artifacts"], ["qa_live_whatsapp_release_checks", "Upload WhatsApp QA artifacts"], ["qa_live_slack_release_checks", "Upload Slack QA artifacts"], - ]; + ] as const; for (const [jobName, stepName] of cases) { const uploadStep = workflowStep(workflowJob(RELEASE_CHECKS_WORKFLOW, jobName), stepName); diff --git a/test/scripts/plugin-gateway-gauntlet.test.ts b/test/scripts/plugin-gateway-gauntlet.test.ts index 26e1a695afa7..8dbde182b7db 100644 --- a/test/scripts/plugin-gateway-gauntlet.test.ts +++ b/test/scripts/plugin-gateway-gauntlet.test.ts @@ -5,6 +5,7 @@ import os from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { pathToFileURL } from "node:url"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { buildObservationGuardFailures, @@ -234,10 +235,11 @@ describe("plugin gateway gauntlet helpers", () => { runtimeSlashAliases: [{ name: "alpha", kind: "runtime-slash", cliCommand: "plugins" }], skills: [], }); - expect(matrix[1].runtimeSlashAliases).toEqual([ + const beta = expectDefined(matrix[1], "beta bundled plugin manifest"); + expect(beta.runtimeSlashAliases).toEqual([ { name: "dreaming", kind: "runtime-slash", cliCommand: null }, ]); - expect(matrix[1].buildId).toBe("beta"); + expect(beta.buildId).toBe("beta"); }); it("keeps manifest ids separate from bounded build entry ids", async () => { @@ -251,7 +253,8 @@ describe("plugin gateway gauntlet helpers", () => { id: "kimi", }), ]); - expect(buildGauntletPrebuildEnv({}, { buildIds: [matrix[0].buildId] })).toEqual({ + const kimi = expectDefined(matrix[0], "Kimi bundled plugin manifest"); + expect(buildGauntletPrebuildEnv({}, { buildIds: [kimi.buildId] })).toEqual({ OPENCLAW_BUNDLED_PLUGIN_BUILD_IDS: "kimi-coding", PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false", }); @@ -332,9 +335,9 @@ describe("plugin gateway gauntlet helpers", () => { { id: "beta", requiredPlugins: ["alpha"] }, ]; - expect(() => collectRequiredPluginEntries(entries, [entries[0]])).toThrow( - "Bundled plugin dependency cycle detected: alpha -> beta -> alpha", - ); + expect(() => + collectRequiredPluginEntries(entries, [expectDefined(entries[0], "alpha plugin entry")]), + ).toThrow("Bundled plugin dependency cycle detected: alpha -> beta -> alpha"); }); it("detects required schema fields recursively", () => { diff --git a/test/scripts/plugin-publication-artifact.test.ts b/test/scripts/plugin-publication-artifact.test.ts index 1dbb5651de1d..9aaf68784601 100644 --- a/test/scripts/plugin-publication-artifact.test.ts +++ b/test/scripts/plugin-publication-artifact.test.ts @@ -965,7 +965,7 @@ describe("plugin publication artifact", () => { const tamperFixture = createFixture(); const tampered = Buffer.from(tamperFixture.zip); - tampered[35] ^= 0xff; + tampered[35] = tampered.readUInt8(35) ^ 0xff; writeFileSync(tamperFixture.zipPath, tampered); expect(() => verifyFixture(tamperFixture)).toThrow(/digest/u); }); diff --git a/test/scripts/plugin-update-unchanged-docker.test.ts b/test/scripts/plugin-update-unchanged-docker.test.ts index c045bf77f8cf..1b1aafcc6a99 100644 --- a/test/scripts/plugin-update-unchanged-docker.test.ts +++ b/test/scripts/plugin-update-unchanged-docker.test.ts @@ -4,6 +4,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "no import { tmpdir } from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; const PLUGIN_UPDATE_DOCKER_SCRIPT = "scripts/e2e/plugin-update-unchanged-docker.sh"; @@ -257,7 +258,10 @@ describe("plugin update unchanged Docker E2E", () => { pluginId: CORRUPT_PLUGIN_ID, message: `Plugin "${CORRUPT_PLUGIN_ID}" could not be processed after the core update: ` + - disabledAfterFailure.npm.outcomes[0].message + + expectDefined( + disabledAfterFailure.npm.outcomes[0], + "corrupt plugin update failure outcome", + ).message + " Run openclaw update repair to retry post-update plugin repair. " + `Run openclaw plugins inspect ${CORRUPT_PLUGIN_ID} --runtime --json for details.`, }, diff --git a/test/scripts/pr-operation-lock.test.ts b/test/scripts/pr-operation-lock.test.ts index e162b0880081..5ebb5b1e03b2 100644 --- a/test/scripts/pr-operation-lock.test.ts +++ b/test/scripts/pr-operation-lock.test.ts @@ -19,6 +19,7 @@ import { } from "node:fs"; import { delimiter, dirname, join } from "node:path"; import { pathToFileURL } from "node:url"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it } from "vitest"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; @@ -803,8 +804,11 @@ describePosix("scripts/pr per-PR operation lock", () => { ]); expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0); - const [blockedLine] = result.stdout.trim().split("\n"); - const [, ownerOid] = blockedLine.split("\t"); + const blockedLine = expectDefined( + result.stdout.trim().split("\n")[0], + "blocked PR operation lock output", + ); + const ownerOid = expectDefined(blockedLine.split("\t")[1], "blocked PR operation owner oid"); expect(blockedLine).toMatch(/^2\t[0-9a-f]{40}$/u); expect(result.stderr).toContain("operation lock is orphaned"); expect(result.stderr).toContain( diff --git a/test/scripts/profile-extension-memory.test.ts b/test/scripts/profile-extension-memory.test.ts index 9ae8eb6afecf..e3f1286b44d6 100644 --- a/test/scripts/profile-extension-memory.test.ts +++ b/test/scripts/profile-extension-memory.test.ts @@ -99,7 +99,7 @@ describe("scripts/profile-extension-memory", () => { ["--timeout-ms", "1e3"], ["--combined-timeout-ms", "90000ms"], ["--top", "0x10"], - ]; + ] as const; for (const [flag, value] of cases) { const result = runProfileExtensionMemory([flag, value]); diff --git a/test/scripts/release-beta-verifier.test.ts b/test/scripts/release-beta-verifier.test.ts index d2b3ec68f989..6cc4890defd2 100644 --- a/test/scripts/release-beta-verifier.test.ts +++ b/test/scripts/release-beta-verifier.test.ts @@ -1,6 +1,7 @@ // Release Beta Verifier tests cover release beta verifier script behavior. /* oxlint-disable typescript/no-base-to-string -- fetch mock normalizes standard RequestInfo inputs for URL assertions. */ import { createHash } from "node:crypto"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it, vi } from "vitest"; import { downloadClawHubBootstrapReadback, @@ -372,9 +373,10 @@ describe("validateClawHubBootstrapEvidence", () => { ...evidence, packages: [ { - ...evidence.packages[0], + ...expectDefined(evidence.packages[0], "first beta release package evidence"), artifactMetadata: { - ...evidence.packages[0].artifactMetadata, + ...expectDefined(evidence.packages[0], "first beta release package evidence") + .artifactMetadata, npmIntegrity: "sha512-different", }, }, diff --git a/test/scripts/release-candidate-checklist.test.ts b/test/scripts/release-candidate-checklist.test.ts index 043fcd5c3e1e..bb190a351cb1 100644 --- a/test/scripts/release-candidate-checklist.test.ts +++ b/test/scripts/release-candidate-checklist.test.ts @@ -710,7 +710,9 @@ describe("release candidate checklist", () => { ) as { on: { workflow_dispatch: { inputs: Record } }; }; - const emittedInputs = [...command.matchAll(/'-f' '([^=']+)=/gu)].map((match) => match[1]); + const emittedInputs = [...command.matchAll(/'-f' '([^=']+)=/gu)].flatMap((match) => + match[1] === undefined ? [] : [match[1]], + ); for (const input of emittedInputs) { expect(workflow.on.workflow_dispatch.inputs).toHaveProperty(input); } diff --git a/test/scripts/release-ci-summary.test.ts b/test/scripts/release-ci-summary.test.ts index 64f80610bd2b..b07f09814e66 100644 --- a/test/scripts/release-ci-summary.test.ts +++ b/test/scripts/release-ci-summary.test.ts @@ -4,6 +4,7 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "nod import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { expectedChildDispatches, @@ -1178,10 +1179,10 @@ describe("release CI summary child correlation", () => { id: pageIndex * 100 + runIndex, })), ); - pages[9][99] = exact; + expectDefined(pages[9], "last child run page")[99] = exact; expect(selectExactChildRunFromPages(pages, expected, "main")).toBe(exact); - pages[0][0] = { ...exact, id: 1001 }; + expectDefined(pages[0], "first child run page")[0] = { ...exact, id: 1001 }; expect(() => selectExactChildRunFromPages(pages, expected, "main")).toThrow( "multiple child runs have exact dispatch title and branch", ); @@ -1492,7 +1493,10 @@ describe("release CI summary child correlation", () => { }); it("validates manifest child workflow, dispatch tuple, branch, and attempt", () => { - const child = expectedChildDispatches("29090000000", 3, "main")[0]; + const child = expectDefined( + expectedChildDispatches("29090000000", 3, "main")[0], + "expected CI child dispatch", + ); const parentManifest = { runAttempt: 3, runId: "29090000000", diff --git a/test/scripts/release-prepare.test.ts b/test/scripts/release-prepare.test.ts index d5424256783f..6c3953cf0079 100644 --- a/test/scripts/release-prepare.test.ts +++ b/test/scripts/release-prepare.test.ts @@ -1,4 +1,5 @@ // Release prepare tests cover shadow planning, cutover commands, and candidate manifests. +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it, vi } from "vitest"; import { buildReleasePreparationManifest, @@ -57,7 +58,7 @@ describe("release preparation plan", () => { version: "2026.7.2-beta.1", }); - expect(steps[0].args).toEqual([ + expect(expectDefined(steps[0], "release version preparation step").args).toEqual([ "--import", "tsx", "scripts/release-version.ts", @@ -68,7 +69,7 @@ describe("release preparation plan", () => { "--android", "--write", ]); - expect(steps[1].args).toEqual([ + expect(expectDefined(steps[1], "release preflight preparation step").args).toEqual([ "scripts/release-preflight.mjs", "--fix", "--scope", diff --git a/test/scripts/release-workflow-matrix-plan.test.ts b/test/scripts/release-workflow-matrix-plan.test.ts index 30db956746b4..704ab588eacd 100644 --- a/test/scripts/release-workflow-matrix-plan.test.ts +++ b/test/scripts/release-workflow-matrix-plan.test.ts @@ -1,5 +1,6 @@ // Release Workflow Matrix Plan tests cover release workflow matrix plan script behavior. import { readFileSync } from "node:fs"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; import { createReleaseWorkflowMatrixPlan } from "../../scripts/plan-release-workflow-matrix.mjs"; @@ -50,6 +51,10 @@ type WorkflowDocument = { }; }; +function requiredJob(definition: WorkflowDocument, name: string): WorkflowJob { + return expectDefined(definition.jobs[name], `release workflow job ${name}`); +} + // Direct dispatches build from the selected ref. Only trusted workflow callers // may provide the complete immutable package artifact tuple. const WORKFLOW_CALL_ONLY_INPUTS = new Set([ @@ -179,14 +184,15 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { expect(definition.env.OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG).toBe( "${{ inputs.allow_unreleased_changelog }}", ); - const packageStep = definition.jobs.prepare_docker_e2e_image.steps.find( + const packageStep = requiredJob(definition, "prepare_docker_e2e_image").steps.find( (step: WorkflowStep) => step.name === "Pack OpenClaw package for Docker E2E", ); - expect(packageStep!.env!.ALLOW_UNRELEASED_CHANGELOG).toBe( + const requiredPackageStep = expectDefined(packageStep, "Docker E2E package step"); + expect(requiredPackageStep.env?.ALLOW_UNRELEASED_CHANGELOG).toBe( "${{ inputs.allow_unreleased_changelog }}", ); - expect(packageStep!.run).toContain("package_args+=(--allow-unreleased-changelog)"); - expect(packageStep!.run).toContain("grep -Fq"); + expect(requiredPackageStep.run).toContain("package_args+=(--allow-unreleased-changelog)"); + expect(requiredPackageStep.run).toContain("grep -Fq"); }); it.each(PROFILE_EXPECTATIONS)( @@ -260,7 +266,10 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { it("keeps stable Anthropic Docker proof blocking and full proof advisory", () => { const jobs = workflow().jobs; - const dockerLiveJob = jobs.validate_live_docker_provider_suites; + const dockerLiveJob = expectDefined( + jobs.validate_live_docker_provider_suites, + "live Docker provider suites job", + ); const anthropicEntries = dockerLiveJob.strategy.matrix.include .filter((entry: MatrixEntry) => entry.suite_group === "live-gateway-anthropic-docker") .map((entry: MatrixEntry) => ({ @@ -309,24 +318,28 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => { it("wires filtered matrices into the reusable live and E2E workflow", () => { const jobs = workflow().jobs; - const planner = jobs.plan_release_workflow_matrices; + const planner = expectDefined( + jobs.plan_release_workflow_matrices, + "release matrix planner job", + ); + const dockerE2e = expectDefined(jobs.validate_docker_e2e, "Docker E2E validation job"); + const liveModels = expectDefined( + jobs.validate_live_models_docker, + "live Docker models validation job", + ); expect(planner.outputs.docker_e2e_matrix).toBe("${{ steps.plan.outputs.docker_e2e_matrix }}"); expect(planner.outputs.live_models_matrix).toBe("${{ steps.plan.outputs.live_models_matrix }}"); - expect(jobs.validate_docker_e2e.needs).toContain("plan_release_workflow_matrices"); - expect(jobs.validate_live_models_docker.needs).toContain("plan_release_workflow_matrices"); - expect(jobs.validate_docker_e2e.strategy.matrix).toBe( + expect(dockerE2e.needs).toContain("plan_release_workflow_matrices"); + expect(liveModels.needs).toContain("plan_release_workflow_matrices"); + expect(dockerE2e.strategy.matrix).toBe( "${{ fromJson(needs.plan_release_workflow_matrices.outputs.docker_e2e_matrix) }}", ); - expect(jobs.validate_live_models_docker.strategy.matrix).toBe( + expect(liveModels.strategy.matrix).toBe( "${{ fromJson(needs.plan_release_workflow_matrices.outputs.live_models_matrix) }}", ); - expect(jobs.validate_live_models_docker.env.OPENCLAW_LIVE_MODELS).toBe( - "${{ matrix.models || 'modern' }}", - ); - expect(jobs.validate_live_models_docker.env.OPENCLAW_LIVE_MAX_MODELS).toBe( - "${{ matrix.max_models || '6' }}", - ); + expect(liveModels.env.OPENCLAW_LIVE_MODELS).toBe("${{ matrix.models || 'modern' }}"); + expect(liveModels.env.OPENCLAW_LIVE_MAX_MODELS).toBe("${{ matrix.max_models || '6' }}"); }); it("requires new release-profile matrices to use a planner or an explicit allowlist", () => { diff --git a/test/scripts/shared-image-artifact.test.ts b/test/scripts/shared-image-artifact.test.ts index c0632450a544..1945459c758e 100644 --- a/test/scripts/shared-image-artifact.test.ts +++ b/test/scripts/shared-image-artifact.test.ts @@ -3,6 +3,7 @@ import { createHash } from "node:crypto"; import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; const HELPER = resolve("scripts/docker/shared-image-artifact.sh"); @@ -342,7 +343,10 @@ describe("shared Docker image artifacts", () => { }, { env: expectedArchiveEnv(fixture), - imageRefs: [IMAGE_REFS[0], "openclaw-docker-e2e-functional:wrong"], + imageRefs: [ + expectDefined(IMAGE_REFS[0], "first shared Docker image reference"), + "openclaw-docker-e2e-functional:wrong", + ], expected: "image ref 1", }, { diff --git a/test/scripts/test-group-report.test.ts b/test/scripts/test-group-report.test.ts index e153387e19c6..0ec65acd1711 100644 --- a/test/scripts/test-group-report.test.ts +++ b/test/scripts/test-group-report.test.ts @@ -4,6 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { expectDefined } from "@openclaw/normalization-core"; import { beforeAll, describe, expect, it, vi } from "vitest"; import { buildGroupedTestComparison, @@ -803,12 +804,24 @@ describe("scripts/test-group-report arg parsing", () => { ["--timeout-ms", ["1000", "2000"]], ["--kill-grace-ms", ["100", "200"]], ["--concurrency", ["2", "3"]], - ]) { + ] as const) { const args = flag === "--compare" - ? [flag, values[0], values[1], flag, values[2], values[3]] - : [flag, values[0], flag, values[1]]; - expect(() => parseTestGroupReportArgs(args as string[])).toThrow( + ? [ + flag, + expectDefined(values[0], "first compare report path"), + expectDefined(values[1], "first compare baseline path"), + flag, + expectDefined(values[2], "second compare report path"), + expectDefined(values[3], "second compare baseline path"), + ] + : [ + flag, + expectDefined(values[0], `first ${flag} value`), + flag, + expectDefined(values[1], `second ${flag} value`), + ]; + expect(() => parseTestGroupReportArgs(args)).toThrow( `${String(flag)} was provided more than once`, ); } diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index 2f88a6116d4f..d41ed0e36f70 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -13,6 +13,7 @@ import { import { tmpdir } from "node:os"; import path, { join } from "node:path"; import { runInNewContext } from "node:vm"; +import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, describe, expect, it } from "vitest"; import { parse } from "yaml"; import { createTempDirTracker } from "../helpers/temp-dir.js"; @@ -55,7 +56,7 @@ function extractNonrootNodePreflight(): string { if (!match) { throw new Error("non-root smoke Node preflight was not found"); } - return match[1]; + return expectDefined(match[1], "non-root smoke Node preflight capture"); } function runNonrootNodePreflight(version: string, options: { sqlite?: boolean } = {}) { @@ -128,7 +129,7 @@ function extractInstallE2eAgentJsonParser(): string { if (!match) { throw new Error("install E2E agent JSON parser was not found"); } - return match[1]; + return expectDefined(match[1], "install E2E agent JSON parser capture"); } function normalizeInstallE2eAgentOutput(output: string) { @@ -158,7 +159,7 @@ function extractInstallSmokeUpdateJsonParser(): string { if (!match) { throw new Error("install smoke update JSON parser was not found"); } - return match[1]; + return expectDefined(match[1], "install smoke update JSON parser capture"); } function validateInstallSmokeUpdateJson(doctorStep?: Record) { @@ -240,7 +241,7 @@ function extractReadPackTarballFilename(): string { if (!match) { throw new Error("read_pack_tarball_filename helper was not found"); } - return match[1]; + return expectDefined(match[1], "pack tarball filename helper capture"); } function runReadPackTarballFilename(filename: string) { @@ -275,7 +276,7 @@ function extractEnsureLocalUpdateDistImportClosure(): string { if (!match) { throw new Error("ensure_local_update_dist_import_closure helper was not found"); } - return match[1]; + return expectDefined(match[1], "local update import closure helper capture"); } type RestorePathEscape = "packages" | "ai"; @@ -290,7 +291,7 @@ function runRestoreLocalDistFixture( ["dist/root.txt", "old-root"], ["packages/ai/dist/ai.txt", "old-ai"], ["packages/ai/package.json", "{}"], - ]) { + ] as const) { const target = join(fixtureRoot, relativePath); mkdirSync(path.dirname(target), { recursive: true }); writeFileSync(target, contents); @@ -298,7 +299,7 @@ function runRestoreLocalDistFixture( for (const [relativePath, contents] of [ ["app/dist/root.txt", "new-root"], ["app/node_modules/@openclaw/ai/dist/ai.txt", "new-ai"], - ]) { + ] as const) { const target = join(imageRoot, relativePath); mkdirSync(path.dirname(target), { recursive: true }); writeFileSync(target, contents); diff --git a/test/scripts/test-report-utils.test.ts b/test/scripts/test-report-utils.test.ts index f051c55dc40e..4ac249be6fdc 100644 --- a/test/scripts/test-report-utils.test.ts +++ b/test/scripts/test-report-utils.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { collectVitestAssertionDurations, @@ -159,7 +160,7 @@ describe("scripts/test-report-utils runVitestJsonReport", () => { const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1234567890); spawnSyncMock.mockImplementation((_command: string, args: string[]) => { const outputFileIndex = args.indexOf("--outputFile") + 1; - const outputFile = args[outputFileIndex]; + const outputFile = expectDefined(args[outputFileIndex], "Vitest JSON report output path"); reportPaths.push(outputFile); fs.writeFileSync(outputFile, `${JSON.stringify({ testResults: [] })}\n`, "utf8"); return { status: 0 }; diff --git a/test/scripts/verify-pr-hosted-gates.test.ts b/test/scripts/verify-pr-hosted-gates.test.ts index 60e5baef785e..255b25d0be11 100644 --- a/test/scripts/verify-pr-hosted-gates.test.ts +++ b/test/scripts/verify-pr-hosted-gates.test.ts @@ -1,3 +1,4 @@ +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { collectHostedGateEvidence as collectHostedGateEvidenceRaw, @@ -106,7 +107,7 @@ describe("verify-pr-hosted-gates", () => { successfulRun(name, index + 1, `2026-06-17T10:4${index}:00Z`), ); workflowRuns[2] = { - ...workflowRuns[2], + ...expectDefined(workflowRuns[2], "Blacksmith ARM Testbox workflow run"), conclusion: "failure", updated_at: "2026-06-17T10:50:00Z", }; diff --git a/test/scripts/zai-fallback-repro.test.ts b/test/scripts/zai-fallback-repro.test.ts index 7f54caf09dac..8f68eb68603c 100644 --- a/test/scripts/zai-fallback-repro.test.ts +++ b/test/scripts/zai-fallback-repro.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { appendBoundedReproOutput, @@ -112,7 +113,11 @@ describe("zai fallback repro command resolution", () => { expect(exitCode).toBe(0); expect(calls).toEqual(["run1", "run2"]); expect(tempRoots).toHaveLength(1); - await expect(fs.stat(tempRoots[0])).rejects.toMatchObject({ code: "ENOENT" }); + await expect( + fs.stat(expectDefined(tempRoots[0], "Z.AI fallback temp root")), + ).rejects.toMatchObject({ + code: "ENOENT", + }); }); it("fails when run 1 does not leave tool result evidence", async () => { diff --git a/test/test-env.ts b/test/test-env.ts index 65204c572301..a944b41c792f 100644 --- a/test/test-env.ts +++ b/test/test-env.ts @@ -147,14 +147,19 @@ function loadProfileEnv(homeDir = os.homedir()): void { if (!match) { return ""; } - let value = match[2].trim(); + const name = match[1]; + const rawValue = match[2]; + if (name === undefined || rawValue === undefined) { + return ""; + } + let value = rawValue.trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } - return `${match[1]}=${value}`; + return `${name}=${value}`; }) .filter(Boolean); const applied = countAppliedEntries(fallbackEntries); diff --git a/test/tsconfig/tsconfig.test.root.json b/test/tsconfig/tsconfig.test.root.json index 1dbcd89f37b9..ac47fd0bb378 100644 --- a/test/tsconfig/tsconfig.test.root.json +++ b/test/tsconfig/tsconfig.test.root.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.test.json", "compilerOptions": { + "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, "tsBuildInfoFile": ".artifacts/tsgo-cache/test-root.tsbuildinfo"