// cli/api.classify.test.js
// Regression tests for backend/external-resource error classification.
//
// WHY THIS EXISTS: on 2026-06-19 a saturated Postgres database made every
// auth-vault verify call time out. clauth counted each timeout as an
// authentication failure ([AUTH FAIL n/10]), hit the strike ceiling, and
// hard-locked the vault — turning a transient DB blip into a full lockout that
// blocked Claude/Codex from starting. A database timeout, a 5xx, a network
// drop, or a rate-limit is NOT a wrong password and must never take a strike.
// These tests pin that classification so the regression can't return.

import { test } from "node:test";
import assert from "node:assert/strict";
import {
  VaultBackendError,
  classifyBackendError,
  classifyServerReason,
} from "./api.js";

test("VaultBackendError marks failures as non-strike + retriable", () => {
  const e = new VaultBackendError("timeout", "no response", { status: 504, detail: "abort" });
  assert.equal(e.kind, "timeout");
  assert.equal(e.isBackend, true);
  assert.equal(e.retriable, true);
  assert.equal(e.status, 504);
  assert.ok(e instanceof Error);
});

test("classifyBackendError: AbortSignal.timeout shapes => 'timeout'", () => {
  // This is the exact error that slipped through the old substring check.
  assert.equal(classifyBackendError({ name: "TimeoutError", message: "The operation was aborted due to timeout" }), "timeout");
  assert.equal(classifyBackendError({ name: "AbortError", message: "This operation was aborted" }), "timeout");
  assert.equal(classifyBackendError(new Error("request timed out")), "timeout");
});

test("classifyBackendError: network/transport shapes => 'network'", () => {
  assert.equal(classifyBackendError(new Error("fetch failed")), "network");
  assert.equal(classifyBackendError(new Error("connect ECONNREFUSED 127.0.0.1:443")), "network");
  assert.equal(classifyBackendError(new Error("getaddrinfo ENOTFOUND db.supabase.co")), "network");
  assert.equal(classifyBackendError(new Error("read ECONNRESET")), "network");
});

test("classifyBackendError: a typed VaultBackendError passes its kind through", () => {
  assert.equal(classifyBackendError(new VaultBackendError("rate_limited", "slow down")), "rate_limited");
  assert.equal(classifyBackendError(new VaultBackendError("db_error", "pool exhausted")), "db_error");
});

test("classifyBackendError: a genuine credential error is NOT a backend error", () => {
  assert.equal(classifyBackendError(new Error("Wrong password")), null);
  assert.equal(classifyBackendError(new Error("invalid_token")), null);
  assert.equal(classifyBackendError(null), null);
  assert.equal(classifyBackendError(undefined), null);
});

test("classifyServerReason: rate-limit reasons => 'rate_limited'", () => {
  assert.equal(classifyServerReason("rate_limited"), "rate_limited");
  assert.equal(classifyServerReason("Rate limit: 38/30 per 60s"), "rate_limited");
});

test("classifyServerReason: database/backend reasons => 'db_error'", () => {
  assert.equal(classifyServerReason("canceling statement due to statement timeout"), "db_error");
  assert.equal(classifyServerReason("database connection pool exhausted"), "db_error");
  assert.equal(classifyServerReason("internal server error"), "db_error");
  assert.equal(classifyServerReason("service unavailable"), "db_error");
});

test("classifyServerReason: genuine auth verdicts are NOT backend errors (they strike)", () => {
  assert.equal(classifyServerReason("invalid_token (3/5)"), null);
  assert.equal(classifyServerReason("machine_locked"), null);
  assert.equal(classifyServerReason("machine_disabled"), null);
  assert.equal(classifyServerReason("machine_not_found"), null);
  assert.equal(classifyServerReason("wrong_password"), null);
  assert.equal(classifyServerReason(null), null);
  assert.equal(classifyServerReason(""), null);
});
