






















11import { describe, expect, it } from "vitest";
22import { classifyFailoverSignal } from "./embedded-agent-helpers/errors.js";
33import {
4+buildFailoverRemediationHint,
5+buildProviderReauthCommand,
46coerceToFailoverError,
57describeFailoverError,
68FailoverError,
@@ -1240,3 +1242,72 @@ describe("failover-error", () => {
12401242});
12411243});
12421244});
1245+1246+describe("buildFailoverRemediationHint", () => {
1247+it("returns a copy-pasteable login command for auth failures", () => {
1248+const err = new FailoverError("missing token", {
1249+reason: "auth",
1250+provider: "anthropic",
1251+model: "claude-opus-4-7",
1252+});
1253+expect(buildFailoverRemediationHint(err)).toBe(
1254+"Re-authenticate with: openclaw models auth login --provider 'anthropic' --force",
1255+);
1256+});
1257+1258+it("returns a hint for auth_permanent as well", () => {
1259+const err = new FailoverError("revoked", {
1260+reason: "auth_permanent",
1261+provider: "google-gemini-cli",
1262+model: "gemini-3.1-pro-preview",
1263+});
1264+expect(buildFailoverRemediationHint(err)).toBe(
1265+"Re-authenticate with: openclaw models auth login --provider 'google-gemini-cli' --force",
1266+);
1267+});
1268+1269+it("quotes provider ids that contain shell metacharacters", () => {
1270+expect(buildProviderReauthCommand("custom;touch /tmp/pwned")).toBe(
1271+"openclaw models auth login --provider 'custom;touch /tmp/pwned' --force",
1272+);
1273+expect(buildProviderReauthCommand("custom'provider")).toBe(
1274+"openclaw models auth login --provider 'custom'\\''provider' --force",
1275+);
1276+});
1277+1278+it("refuses control characters in rendered provider commands", () => {
1279+expect(buildProviderReauthCommand("custom\nprovider")).toBeUndefined();
1280+});
1281+1282+it("wraps rendered provider commands in the standard CLI formatter", () => {
1283+expect(buildProviderReauthCommand("anthropic", { OPENCLAW_PROFILE: "work" })).toBe(
1284+"openclaw --profile work models auth login --provider 'anthropic' --force",
1285+);
1286+expect(buildProviderReauthCommand("anthropic", { OPENCLAW_CONTAINER_HINT: "dev" })).toBe(
1287+"openclaw --container dev models auth login --provider 'anthropic' --force",
1288+);
1289+});
1290+1291+it("returns undefined for non-auth reasons", () => {
1292+const err = new FailoverError("429", {
1293+reason: "rate_limit",
1294+provider: "openai",
1295+model: "gpt-5",
1296+});
1297+expect(buildFailoverRemediationHint(err)).toBeUndefined();
1298+});
1299+1300+it("returns undefined when provider is not attributed", () => {
1301+const err = new FailoverError("no token", {
1302+reason: "auth",
1303+model: "claude-opus-4-7",
1304+});
1305+expect(buildFailoverRemediationHint(err)).toBeUndefined();
1306+});
1307+1308+it("returns undefined for non-FailoverError inputs", () => {
1309+expect(buildFailoverRemediationHint(new Error("oops"))).toBeUndefined();
1310+expect(buildFailoverRemediationHint(undefined)).toBeUndefined();
1311+expect(buildFailoverRemediationHint("just a string")).toBeUndefined();
1312+});
1313+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。