mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
test: align release validation expectations
This commit is contained in:
@@ -25,6 +25,7 @@ const loadStaticManifestCatalogRowsForList = vi.fn<() => Array<Record<string, un
|
||||
const loadProviderIndexCatalogRowsForList = vi.fn<() => Array<Record<string, unknown>>>(() => []);
|
||||
const hasProviderStaticCatalogForFilter = vi.fn().mockResolvedValue(false);
|
||||
const shouldSuppressBuiltInModel = vi.fn().mockReturnValue(false);
|
||||
const shouldSuppressBuiltInModelFromManifest = vi.fn().mockReturnValue(false);
|
||||
const modelRegistryState = {
|
||||
models: [] as Array<Record<string, unknown>>,
|
||||
available: [] as Array<Record<string, unknown>>,
|
||||
@@ -203,6 +204,7 @@ vi.mock("./models/list.provider-index-catalog.js", () => ({
|
||||
|
||||
vi.mock("../agents/model-suppression.js", () => ({
|
||||
shouldSuppressBuiltInModel,
|
||||
shouldSuppressBuiltInModelFromManifest,
|
||||
}));
|
||||
|
||||
function makeRuntime() {
|
||||
|
||||
@@ -37,6 +37,23 @@ describe("gateway codex harness live helpers", () => {
|
||||
expect(isExpectedCodexStatusCommandText(text)).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts the current status card emitted by OpenAI Codex", () => {
|
||||
const text = [
|
||||
"Current session status:",
|
||||
"",
|
||||
"- Model: `openai/gpt-5.5`",
|
||||
"- Context: `22k/272k` tokens, `8%`",
|
||||
"- Cache hit: `52%`",
|
||||
"- Compactions: `0`",
|
||||
"- Execution: `direct`",
|
||||
"- Runtime: `OpenAI Codex`",
|
||||
"- Think: `low`",
|
||||
"- Active tasks: `1`",
|
||||
].join("\n");
|
||||
|
||||
expect(isExpectedCodexStatusCommandText(text)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects status prose for a different codex session", () => {
|
||||
const text =
|
||||
"OpenClaw is running on `openai/gpt-5.5` with low reasoning/text settings. Context is at `22k/272k` tokens, no compactions, and the current session is `agent:dev:other`.";
|
||||
|
||||
@@ -104,8 +104,14 @@ export function isExpectedCodexStatusCommandText(text: string): boolean {
|
||||
normalized.includes(" openai/") ||
|
||||
normalized.includes("`codex/") ||
|
||||
normalized.includes(" codex/");
|
||||
const isCurrentSessionStatus =
|
||||
normalized.includes("current session status:") &&
|
||||
normalized.includes("runtime: `openai codex`") &&
|
||||
mentionsModel;
|
||||
|
||||
return mentionsOpenClawStatus && mentionsHarnessSession && mentionsModel;
|
||||
return (
|
||||
isCurrentSessionStatus || (mentionsOpenClawStatus && mentionsHarnessSession && mentionsModel)
|
||||
);
|
||||
}
|
||||
|
||||
export function isExpectedCodexModelsCommandText(text: string): boolean {
|
||||
|
||||
@@ -260,6 +260,53 @@ describe("gateway startup config recovery", () => {
|
||||
expect(recoveryNotice.enqueueConfigRecoveryNotice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects legacy config entries in Nix mode before recovery", async () => {
|
||||
const legacySnapshot = buildTestConfigSnapshot({
|
||||
path: configPath,
|
||||
exists: true,
|
||||
raw: `${JSON.stringify({
|
||||
heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" },
|
||||
})}\n`,
|
||||
parsed: {
|
||||
heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" },
|
||||
},
|
||||
valid: false,
|
||||
config: {} as OpenClawConfig,
|
||||
issues: [
|
||||
{
|
||||
path: "heartbeat",
|
||||
message:
|
||||
"top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator).",
|
||||
},
|
||||
],
|
||||
legacyIssues: [
|
||||
{
|
||||
path: "heartbeat",
|
||||
message:
|
||||
"top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator).",
|
||||
},
|
||||
],
|
||||
});
|
||||
vi.mocked(configIo.readConfigFileSnapshotWithPluginMetadata).mockResolvedValueOnce({
|
||||
snapshot: legacySnapshot,
|
||||
pluginMetadataSnapshot,
|
||||
});
|
||||
vi.mocked(configIo, true).isNixMode = true;
|
||||
|
||||
await expect(
|
||||
loadGatewayStartupConfigSnapshot({
|
||||
minimalTestGateway: true,
|
||||
log: { info: vi.fn(), warn: vi.fn() },
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"Legacy config entries detected while running in Nix mode. Update your Nix config to the latest schema and restart.",
|
||||
);
|
||||
|
||||
expect(configIo.recoverConfigFromLastKnownGood).not.toHaveBeenCalled();
|
||||
expect(configIo.recoverConfigFromJsonRootSuffix).not.toHaveBeenCalled();
|
||||
expect(recoveryNotice.enqueueConfigRecoveryNotice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues startup in degraded mode for plugin-local startup invalidity", async () => {
|
||||
const invalidSnapshot = buildTestConfigSnapshot({
|
||||
path: configPath,
|
||||
|
||||
@@ -1402,7 +1402,7 @@ describe("gateway server cron", () => {
|
||||
}
|
||||
}, 45_000);
|
||||
|
||||
test("ignores non-string cron.webhookToken values without crashing webhook delivery", async () => {
|
||||
test("rejects malformed cron.webhookToken objects at startup", async () => {
|
||||
const { prevSkipCron } = await setupCronTestRun({
|
||||
tempPrefix: "openclaw-gw-cron-webhook-secretinput-",
|
||||
cronEnabled: false,
|
||||
@@ -1416,33 +1416,7 @@ describe("gateway server cron", () => {
|
||||
},
|
||||
});
|
||||
|
||||
fetchWithSsrFGuardMock.mockClear();
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
try {
|
||||
const notifyJobId = await addWebhookCronJob({
|
||||
ws,
|
||||
name: "webhook secretinput object",
|
||||
delivery: { mode: "webhook", to: "https://example.invalid/cron-finished" },
|
||||
});
|
||||
await runCronJobAndWaitForFinished(ws, notifyJobId);
|
||||
const [notifyArgs] = fetchWithSsrFGuardMock.mock.calls[0] as unknown as [
|
||||
{
|
||||
url?: string;
|
||||
init?: {
|
||||
method?: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
},
|
||||
];
|
||||
expect(notifyArgs.url).toBe("https://example.invalid/cron-finished");
|
||||
expect(notifyArgs.init?.method).toBe("POST");
|
||||
expect(notifyArgs.init?.headers?.Authorization).toBeUndefined();
|
||||
expect(notifyArgs.init?.headers?.["Content-Type"]).toBe("application/json");
|
||||
} finally {
|
||||
await cleanupCronTestRun({ ws, server, prevSkipCron });
|
||||
}
|
||||
await expect(startServerWithClient()).rejects.toThrow("cron.webhookToken: Invalid input");
|
||||
await cleanupCronTestRun({ prevSkipCron });
|
||||
}, 45_000);
|
||||
});
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
getFreePort,
|
||||
installGatewayTestHooks,
|
||||
startGatewayServer,
|
||||
testState,
|
||||
} from "./test-helpers.js";
|
||||
|
||||
installGatewayTestHooks({ scope: "suite" });
|
||||
|
||||
async function expectHeartbeatValidationError(legacyParsed: Record<string, unknown>) {
|
||||
testState.legacyIssues = [
|
||||
{
|
||||
path: "heartbeat",
|
||||
message:
|
||||
"top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator).",
|
||||
},
|
||||
];
|
||||
testState.legacyParsed = legacyParsed;
|
||||
testState.migrationConfig = null;
|
||||
testState.migrationChanges = [];
|
||||
|
||||
let server: Awaited<ReturnType<typeof startGatewayServer>> | undefined;
|
||||
let thrown: unknown;
|
||||
try {
|
||||
server = await startGatewayServer(await getFreePort());
|
||||
} catch (err) {
|
||||
thrown = err;
|
||||
}
|
||||
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
expect(thrown).toBeInstanceOf(Error);
|
||||
const message = (thrown as Error).message;
|
||||
expect(message).toContain("Invalid config at");
|
||||
expect(message).toContain(
|
||||
"heartbeat: top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator).",
|
||||
);
|
||||
expect(message).not.toContain("Legacy config entries detected but auto-migration failed.");
|
||||
}
|
||||
|
||||
describe("gateway startup legacy migration fallback", () => {
|
||||
test("surfaces detailed validation errors when legacy entries have no migration output", async () => {
|
||||
await expectHeartbeatValidationError({
|
||||
heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" },
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps detailed validation errors when heartbeat comes from include-resolved config", async () => {
|
||||
// Simulate a parsed source that only contains include directives, while
|
||||
// legacy heartbeat is surfaced from the resolved config.
|
||||
await expectHeartbeatValidationError({
|
||||
$include: ["heartbeat.defaults.json"],
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user