import { describe, expect, test } from "bun:test";
import {
  RPC_ERROR_CODES,
  ServiceError,
  failure,
  isRetryableStatus,
  success,
} from "../src/errors.ts";

describe("rpc errors", () => {
  test("success and failure build the result shape", () => {
    expect(success(42)).toEqual({ ok: true, value: 42 });
    const f = failure(RPC_ERROR_CODES.denied, "Forbidden");
    expect(f.ok).toBe(false);
    if (!f.ok) {
      expect(f.code).toBe("RPC_DENIED");
      expect(f.retryable).toBe(false);
    }
  });

  test("only transport failures are retryable", () => {
    // 5xx and 429 are the callee saying "try again"; everything else is final.
    expect(isRetryableStatus(500)).toBe(true);
    expect(isRetryableStatus(503)).toBe(true);
    expect(isRetryableStatus(429)).toBe(true);
    expect(isRetryableStatus(599)).toBe(true);
    expect(isRetryableStatus(408)).toBe(true);
    expect(isRetryableStatus(400)).toBe(false);
    expect(isRetryableStatus(403)).toBe(false);
    expect(isRetryableStatus(404)).toBe(false);
    expect(isRetryableStatus(409)).toBe(false);
    expect(isRetryableStatus(200)).toBe(false);
  });

  test("an out-of-range status fails closed", () => {
    for (const status of [600, 1000, 0, -1, Number.NaN]) {
      expect(isRetryableStatus(status)).toBe(false);
    }
  });

  test("a malformed response is never retryable", () => {
    const f = failure(RPC_ERROR_CODES.malformed, "Unexpected response shape");
    expect(f.ok).toBe(false);
    if (!f.ok) {
      expect(f.code).toBe("RPC_MALFORMED");
      expect(f.retryable).toBe(false);
    }
  });

  test("a denial is never retryable", () => {
    const f = failure(RPC_ERROR_CODES.denied, "Forbidden");
    if (!f.ok) expect(f.retryable).toBe(false);
  });

  test("ServiceError carries a code and does not leak a cause into its message", () => {
    const error = new ServiceError(RPC_ERROR_CODES.handler, "Something failed");
    expect(error.name).toBe("ServiceError");
    expect(error.code).toBe("RPC_HANDLER");
    expect(error.message).toBe("Something failed");
    expect(error.retryable).toBe(false);
  });

  test("toResult produces an opaque failure", () => {
    const error = new ServiceError(RPC_ERROR_CODES.handler, "db password is hunter2");
    const result = error.toResult({ exposeMessage: false });
    if (!result.ok) {
      expect(result.message).toBe("Internal error");
      expect(result.message).not.toContain("hunter2");
    }
  });
});
