mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
test: speed up changed test paths
This commit is contained in:
@@ -6,6 +6,10 @@ import type { FailoverReason } from "./pi-embedded-helpers.js";
|
||||
|
||||
const decisionLog = createSubsystemLogger("model-fallback").child("decision");
|
||||
|
||||
export function isModelFallbackDecisionLogEnabled(): boolean {
|
||||
return decisionLog.isEnabled("warn");
|
||||
}
|
||||
|
||||
function buildErrorObservationFields(error?: string): {
|
||||
errorPreview?: string;
|
||||
errorHash?: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -280,7 +281,7 @@ describe("runWithModelFallback – probe logic", () => {
|
||||
setLoggerOverride({
|
||||
level: "trace",
|
||||
consoleLevel: "silent",
|
||||
file: path.join(os.tmpdir(), `openclaw-model-fallback-probe-${Date.now()}.log`),
|
||||
file: path.join(os.tmpdir(), `openclaw-model-fallback-probe-${randomUUID()}.log`),
|
||||
});
|
||||
|
||||
const run = vi.fn().mockResolvedValue("probed-ok");
|
||||
@@ -410,19 +411,26 @@ describe("runWithModelFallback – probe logic", () => {
|
||||
expectPrimaryProbeSuccess(result, run, "recovered");
|
||||
});
|
||||
|
||||
it("attempts non-primary fallbacks during rate-limit cooldown after primary probe failure", async () => {
|
||||
await expectProbeFailureFallsBack({
|
||||
reason: "rate_limit",
|
||||
it.each([
|
||||
{
|
||||
label: "rate-limit",
|
||||
reason: "rate_limit" as const,
|
||||
probeError: Object.assign(new Error("rate limited"), { status: 429 }),
|
||||
});
|
||||
});
|
||||
|
||||
it("attempts non-primary fallbacks during overloaded cooldown after primary probe failure", async () => {
|
||||
await expectProbeFailureFallsBack({
|
||||
reason: "overloaded",
|
||||
},
|
||||
{
|
||||
label: "overloaded",
|
||||
reason: "overloaded" as const,
|
||||
probeError: Object.assign(new Error("service overloaded"), { status: 503 }),
|
||||
});
|
||||
});
|
||||
},
|
||||
])(
|
||||
"attempts non-primary fallbacks during $label cooldown after primary probe failure",
|
||||
async ({ reason, probeError }) => {
|
||||
await expectProbeFailureFallsBack({
|
||||
reason,
|
||||
probeError,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps walking remaining fallbacks after an abort-wrapped RESOURCE_EXHAUSTED probe failure", async () => {
|
||||
const cfg = makeCfg({
|
||||
@@ -556,38 +564,22 @@ describe("runWithModelFallback – probe logic", () => {
|
||||
expect(_probeThrottleInternals.lastProbeAttempt.has("key-0")).toBe(true);
|
||||
});
|
||||
|
||||
it("handles non-finite soonest safely (treats as probe-worthy)", async () => {
|
||||
it("handles missing or non-finite soonest safely (treats as probe-worthy)", async () => {
|
||||
const cfg = makeCfg();
|
||||
|
||||
// Return Infinity — should be treated as "probe" per the guard
|
||||
mockedGetSoonestCooldownExpiry.mockReturnValue(Infinity);
|
||||
for (const [label, soonest] of [
|
||||
["infinity", Infinity],
|
||||
["nan", Number.NaN],
|
||||
["null", null],
|
||||
] as const) {
|
||||
_probeThrottleInternals.lastProbeAttempt.clear();
|
||||
mockedGetSoonestCooldownExpiry.mockReturnValue(soonest);
|
||||
|
||||
const run = vi.fn().mockResolvedValue("ok-infinity");
|
||||
const run = vi.fn().mockResolvedValue(`ok-${label}`);
|
||||
|
||||
const result = await runPrimaryCandidate(cfg, run);
|
||||
expectPrimaryProbeSuccess(result, run, "ok-infinity");
|
||||
});
|
||||
|
||||
it("handles NaN soonest safely (treats as probe-worthy)", async () => {
|
||||
const cfg = makeCfg();
|
||||
|
||||
mockedGetSoonestCooldownExpiry.mockReturnValue(Number.NaN);
|
||||
|
||||
const run = vi.fn().mockResolvedValue("ok-nan");
|
||||
|
||||
const result = await runPrimaryCandidate(cfg, run);
|
||||
expectPrimaryProbeSuccess(result, run, "ok-nan");
|
||||
});
|
||||
|
||||
it("handles null soonest safely (treats as probe-worthy)", async () => {
|
||||
const cfg = makeCfg();
|
||||
|
||||
mockedGetSoonestCooldownExpiry.mockReturnValue(null);
|
||||
|
||||
const run = vi.fn().mockResolvedValue("ok-null");
|
||||
|
||||
const result = await runPrimaryCandidate(cfg, run);
|
||||
expectPrimaryProbeSuccess(result, run, "ok-null");
|
||||
const result = await runPrimaryCandidate(cfg, run);
|
||||
expectPrimaryProbeSuccess(result, run, `ok-${label}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("single candidate skips with rate_limit and exhausts candidates", async () => {
|
||||
@@ -700,8 +692,4 @@ describe("runWithModelFallback – probe logic", () => {
|
||||
|
||||
expectPrimaryProbeSuccess(result, run, "billing-probe-ok");
|
||||
});
|
||||
|
||||
it("skips billing-cooldowned primary with fallbacks when far from cooldown expiry", async () => {
|
||||
await expectPrimarySkippedAfterLongCooldown("billing");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "./failover-policy.js";
|
||||
import { LiveSessionModelSwitchError } from "./live-model-switch-error.js";
|
||||
import {
|
||||
isModelFallbackDecisionLogEnabled,
|
||||
logModelFallbackDecision,
|
||||
type ModelFallbackDecisionParams,
|
||||
type ModelFallbackStepFields,
|
||||
@@ -815,6 +816,9 @@ export async function runWithModelFallback<T>(params: {
|
||||
let lastError: unknown;
|
||||
const cooldownProbeUsedProviders = new Set<string>();
|
||||
const observeDecision = async (decision: ModelFallbackDecisionParams) => {
|
||||
if (!params.onFallbackStep && !isModelFallbackDecisionLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
const fallbackStep = logModelFallbackDecision(decision);
|
||||
if (fallbackStep) {
|
||||
await params.onFallbackStep?.(fallbackStep);
|
||||
|
||||
@@ -190,6 +190,8 @@ async function loadSourceConfigSnapshotForTest(fallback: unknown): Promise<unkno
|
||||
beforeEach(() => {
|
||||
previousExitCode = process.exitCode;
|
||||
process.exitCode = undefined;
|
||||
modelRegistryState.models = [];
|
||||
modelRegistryState.available = [];
|
||||
modelRegistryState.getAllError = undefined;
|
||||
modelRegistryState.getAvailableError = undefined;
|
||||
modelRegistryState.findError = undefined;
|
||||
@@ -510,7 +512,9 @@ describe("models list/status", () => {
|
||||
loadProviderCatalogModelsForList.mockResolvedValueOnce([MOONSHOT_MODEL]);
|
||||
const runtime = makeRuntime();
|
||||
|
||||
await modelsListCommand({ all: true, provider: "moonshot", json: true }, runtime);
|
||||
await withEnvAsync({ KIMI_API_KEY: undefined, MOONSHOT_API_KEY: undefined }, () =>
|
||||
modelsListCommand({ all: true, provider: "moonshot", json: true }, runtime),
|
||||
);
|
||||
|
||||
const payload = parseJsonLog(runtime);
|
||||
expect(loadModelCatalog).not.toHaveBeenCalled();
|
||||
|
||||
@@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { StringDecoder } from "node:string_decoder";
|
||||
import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
|
||||
import {
|
||||
acquireSessionWriteLock,
|
||||
type SessionWriteLockAcquireTimeoutConfig,
|
||||
@@ -12,6 +11,14 @@ import {
|
||||
const TRANSCRIPT_APPEND_SCAN_CHUNK_BYTES = 64 * 1024;
|
||||
const SESSION_MANAGER_APPEND_MAX_BYTES = 8 * 1024 * 1024;
|
||||
|
||||
let piCodingAgentModulePromise: Promise<typeof import("@mariozechner/pi-coding-agent")> | null =
|
||||
null;
|
||||
|
||||
async function loadCurrentSessionVersion(): Promise<number> {
|
||||
piCodingAgentModulePromise ??= import("@mariozechner/pi-coding-agent");
|
||||
return (await piCodingAgentModulePromise).CURRENT_SESSION_VERSION;
|
||||
}
|
||||
|
||||
type TranscriptLeafInfo = {
|
||||
leafId?: string;
|
||||
hasParentLinkedEntries: boolean;
|
||||
@@ -117,6 +124,7 @@ async function migrateLinearTranscriptToParentLinked(transcriptPath: string): Pr
|
||||
leafId?: string;
|
||||
}> {
|
||||
const raw = await fs.readFile(transcriptPath, "utf-8");
|
||||
const currentSessionVersion = await loadCurrentSessionVersion();
|
||||
const existingIds = new Set<string>();
|
||||
const output: string[] = [];
|
||||
let previousId: string | null = null;
|
||||
@@ -138,7 +146,7 @@ async function migrateLinearTranscriptToParentLinked(transcriptPath: string): Pr
|
||||
}
|
||||
const record = parsed as Record<string, unknown>;
|
||||
if (record.type === "session") {
|
||||
output.push(JSON.stringify({ ...record, version: CURRENT_SESSION_VERSION }));
|
||||
output.push(JSON.stringify({ ...record, version: currentSessionVersion }));
|
||||
continue;
|
||||
}
|
||||
const id = normalizeEntryId(record.id) ?? generateEntryId(existingIds);
|
||||
@@ -170,10 +178,11 @@ async function ensureTranscriptHeader(
|
||||
if (stat?.isFile() && stat.size > 0) {
|
||||
return;
|
||||
}
|
||||
const currentSessionVersion = await loadCurrentSessionVersion();
|
||||
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
||||
const header = {
|
||||
type: "session",
|
||||
version: CURRENT_SESSION_VERSION,
|
||||
version: currentSessionVersion,
|
||||
id: params.sessionId ?? randomUUID(),
|
||||
timestamp: new Date().toISOString(),
|
||||
cwd: params.cwd ?? process.cwd(),
|
||||
|
||||
@@ -80,6 +80,11 @@ const facadeMockHelpers = vi.hoisted(() => {
|
||||
|
||||
vi.mock("./plugins/plugin-registry.js", () => ({
|
||||
loadPluginManifestRegistryForPluginRegistry,
|
||||
loadPluginRegistrySnapshotWithMetadata: () => ({
|
||||
source: "derived",
|
||||
snapshot: { plugins: [] },
|
||||
diagnostics: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./secrets/channel-env-vars.js", () => ({
|
||||
|
||||
@@ -51,7 +51,6 @@ const EXPECTED_EMPTY_CONFIG_GATEWAY_STARTUP_PLUGIN_IDS = [
|
||||
"acpx",
|
||||
"browser",
|
||||
"device-pair",
|
||||
"discord",
|
||||
"file-transfer",
|
||||
"memory-core",
|
||||
"phone-control",
|
||||
@@ -470,7 +469,7 @@ describe("bundled plugin metadata", () => {
|
||||
expect(
|
||||
resolveGatewayStartupPluginIdsFromRegistry({
|
||||
config: {},
|
||||
env: process.env,
|
||||
env: {},
|
||||
index,
|
||||
manifestRegistry,
|
||||
platform: "linux",
|
||||
|
||||
Reference in New Issue
Block a user