import os from "node:os"; import path from "node:path"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { createDiagnosticLogRecordCapture } from "../logging/test-helpers/diagnostic-log-capture.js"; import type { AuthProfileStore } from "./auth-profiles.js"; import { makeModelFallbackCfg } from "./test-helpers/model-fallback-config-fixture.js";
// Mock auth-profile submodules — must be before importing model-fallback
vi.mock("./auth-profiles/store.js", () => ({
ensureAuthProfileStore: vi.fn(),
loadAuthProfileStoreForRuntime: vi.fn(),
}));
type AuthProfilesStoreModule = typeofimport("./auth-profiles/store.js");
type AuthProfilesSourceCheckModule = typeofimport("./auth-profiles/source-check.js");
type AuthProfilesUsageModule = typeofimport("./auth-profiles/usage.js");
type AuthProfilesOrderModule = typeofimport("./auth-profiles/order.js");
type ModelFallbackModule = typeofimport("./model-fallback.js");
type LoggerModule = typeofimport("../logging/logger.js");
let mockedEnsureAuthProfileStore: ReturnType< typeof vi.mocked<AuthProfilesStoreModule["ensureAuthProfileStore"]>
>;
let mockedHasAnyAuthProfileStoreSource: ReturnType< typeof vi.mocked<AuthProfilesSourceCheckModule["hasAnyAuthProfileStoreSource"]>
>;
let mockedGetSoonestCooldownExpiry: ReturnType< typeof vi.mocked<AuthProfilesUsageModule["getSoonestCooldownExpiry"]>
>;
let mockedIsProfileInCooldown: ReturnType< typeof vi.mocked<AuthProfilesUsageModule["isProfileInCooldown"]>
>;
let mockedResolveProfilesUnavailableReason: ReturnType< typeof vi.mocked<AuthProfilesUsageModule["resolveProfilesUnavailableReason"]>
>;
let mockedResolveAuthProfileOrder: ReturnType< typeof vi.mocked<AuthProfilesOrderModule["resolveAuthProfileOrder"]>
>;
let runWithModelFallback: ModelFallbackModule["runWithModelFallback"];
let _probeThrottleInternals: ModelFallbackModule["_probeThrottleInternals"];
let resetLogger: LoggerModule["resetLogger"];
let setLoggerOverride: LoggerModule["setLoggerOverride"];
it("skips primary model when far from cooldown expiry (30 min remaining)", async () => { const cfg = makeCfg(); // Cooldown expires in 30 min — well beyond the 2-min margin const expiresIn30Min = NOW + 30 * 60 * 1000;
mockedGetSoonestCooldownExpiry.mockReturnValue(expiresIn30Min);
const run = vi.fn().mockResolvedValue("ok");
const result = await runPrimaryCandidate(cfg, run);
// Should skip primary and use fallback
expectFallbackUsed(result, run);
});
it("uses inferred unavailable reason when skipping a cooldowned primary model", async () => {
await expectPrimarySkippedAfterLongCooldown("billing");
});
it("probes primary model when within 2-min margin of cooldown expiry", async () => { const cfg = makeCfg(); // Cooldown expires in 1 minute — within 2-min probe margin const expiresIn1Min = NOW + 60 * 1000;
mockedGetSoonestCooldownExpiry.mockReturnValue(expiresIn1Min);
const run = vi.fn().mockResolvedValue("probed-ok");
const result = await runPrimaryCandidate(cfg, run);
expectPrimaryProbeSuccess(result, run, "probed-ok");
});
it("throttles probe when called within 30s interval", async () => { const cfg = makeCfg(); // Cooldown just about to expire (within probe margin) const almostExpired = NOW + 30 * 1000;
mockedGetSoonestCooldownExpiry.mockReturnValue(almostExpired);
// Simulate a recent probe 10s ago
_probeThrottleInternals.lastProbeAttempt.set("openai", NOW - 10_000);
const run = vi.fn().mockResolvedValue("ok");
const result = await runPrimaryCandidate(cfg, run);
// Should be throttled → skip primary, use fallback
expectFallbackUsed(result, run);
});
it("allows probe when 30s have passed since last probe", async () => { const cfg = makeCfg(); const almostExpired = NOW + 30 * 1000;
mockedGetSoonestCooldownExpiry.mockReturnValue(almostExpired);
// Last probe was 31s ago — should NOT be throttled
_probeThrottleInternals.lastProbeAttempt.set("openai", NOW - 31_000);
const run = vi.fn().mockResolvedValue("probed-ok");
const result = await runPrimaryCandidate(cfg, run);
expectPrimaryProbeSuccess(result, run, "probed-ok");
});
it("prunes stale probe throttle entries before checking eligibility", () => {
_probeThrottleInternals.lastProbeAttempt.set( "stale",
NOW - _probeThrottleInternals.PROBE_STATE_TTL_MS - 1,
);
_probeThrottleInternals.lastProbeAttempt.set("fresh", NOW - 5_000);
it("caps probe throttle state by evicting the oldest entries", () => { for (let i = 0; i < _probeThrottleInternals.MAX_PROBE_KEYS; i += 1) {
_probeThrottleInternals.lastProbeAttempt.set(`key-${i}`, NOW - (i + 1));
}
// Single-provider setups need periodic probes even when the billing // cooldown is far from expiry, otherwise topping up credits never recovers // without a restart. const expiresIn30Min = NOW + 30 * 60 * 1000;
mockedGetSoonestCooldownExpiry.mockReturnValue(expiresIn30Min);
mockedResolveProfilesUnavailableReason.mockReturnValue("billing");
const run = vi.fn().mockResolvedValue("billing-recovered");
it("skips billing-cooldowned primary with fallbacks when far from cooldown expiry", async () => {
await expectPrimarySkippedAfterLongCooldown("billing");
});
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-05-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.