mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(tooling): enforce indexed access checks in root tests (#105223)
* feat(tooling): enforce indexed access checks in root tests * style(tooling): clarify scoped package guard
This commit is contained in:
committed by
GitHub
parent
251f54baec
commit
e0f45bfbf0
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<number>(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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<string>();
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<void> {
|
||||
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<string, ReturnType<typeof legacyEntry>> = {
|
||||
[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",
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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:/);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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<string, string>;
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -50,7 +50,9 @@ function expectDeclaredDispatchInputs(command: string): void {
|
||||
on?: { workflow_dispatch?: { inputs?: Record<string, unknown> } };
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -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<string, string>, hostArch: string): string {
|
||||
|
||||
@@ -58,7 +58,9 @@ function expectDeclaredDispatchInputs(command: string): void {
|
||||
on?: { workflow_dispatch?: { inputs?: Record<string, unknown> } };
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, string>).authorization).toContain(
|
||||
expect((requests[0]?.headers as Record<string, string> | undefined)?.authorization).toContain(
|
||||
"Credential=access/",
|
||||
);
|
||||
expect(String(requests[4]?.body)).toContain(
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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.`,
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -710,7 +710,9 @@ describe("release candidate checklist", () => {
|
||||
) as {
|
||||
on: { workflow_dispatch: { inputs: Record<string, unknown> } };
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>) {
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
+7
-2
@@ -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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.test.json",
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"tsBuildInfoFile": ".artifacts/tsgo-cache/test-root.tsbuildinfo"
|
||||
|
||||
Reference in New Issue
Block a user