import { describe, expect, test } from "bun:test";
import { redact, redactRun } from "./redact";

describe("redact", () => {
  test("AWS access keys", () => {
    expect(redact("key AKIAAAAABBBBCCCCDDDD in output")).toBe("key [REDACTED:aws-access-key] in output");
  });

  test("GitHub tokens, all prefixes", () => {
    for (const prefix of ["ghp", "gho", "ghu", "ghs", "ghr"]) {
      expect(redact(`${prefix}_abcdefghijklmnopqrst123456`)).toBe("[REDACTED:github-token]");
    }
    expect(redact("github_pat_11ABCDEFG_abcdefghijklmnop")).toBe("[REDACTED:github-token]");
  });

  test("Slack tokens", () => {
    expect(redact("xoxb-1234567890-abcdefgh")).toBe("[REDACTED:slack-token]");
    expect(redact("xoxp-1234567890-abcdefgh")).toBe("[REDACTED:slack-token]");
  });

  test("sk- API keys", () => {
    expect(redact("using sk-proj-AbCdEfGhIjKlMnOpQrStUv")).toBe("using [REDACTED:api-key]");
  });

  test("JWTs", () => {
    const jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJVadQssw5c";
    expect(redact(`Authorization: ${jwt}`)).toBe("Authorization: [REDACTED:jwt]");
  });

  test("Bearer tokens in headers", () => {
    expect(redact("authorization: Bearer tsk_live_0123456789abcdef")).toBe(
      "authorization: Bearer [REDACTED:bearer-token]",
    );
  });

  test("PEM private key blocks, including multi-line bodies", () => {
    const pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIEsynthetic\nlines\n-----END RSA PRIVATE KEY-----";
    expect(redact(`before\n${pem}\nafter`)).toBe("before\n[REDACTED:private-key]\nafter");
  });

  test("generic credential assignments keep the key, lose the value", () => {
    expect(redact("password=hunter2")).toBe("password=[REDACTED:credential]");
    expect(redact("API_KEY: abc123def")).toBe("API_KEY: [REDACTED:credential]");
    expect(redact("secret = topsecret")).toBe("secret = [REDACTED:credential]");
    expect(redact("token=deadbeef")).toBe("token=[REDACTED:credential]");
  });

  test("does not double-wrap a value another rule already redacted", () => {
    expect(redact("api_key=sk-AbCdEfGhIjKlMnOpQrStUv")).toBe("api_key=[REDACTED:api-key]");
  });

  // Both of these escaped the first redactor and were caught by the publish gate
  // when sealing a real session — the underscore in a prefixed name is a word char,
  // so a bare \b before the keyword never fired.
  test("prefixed env-var names, not just bare keywords", () => {
    expect(redact("TALESEAL_API_KEY=abc123def456")).toBe("TALESEAL_API_KEY=[REDACTED:credential]");
    expect(redact("MY_DB_PASSWORD=hunter2")).toBe("MY_DB_PASSWORD=[REDACTED:credential]");
    expect(redact("BETTER_AUTH_SECRET: s3cr3tvalue")).toBe("BETTER_AUTH_SECRET: [REDACTED:credential]");
  });

  test("taleseal publish keys, bare or assigned", () => {
    expect(redact("seal --key tk_SAgfQqBrlFlbPf5AsEK3zwEPccV8AI61")).toBe("seal --key [REDACTED:taleseal-key]");
  });

  test("passwords inside connection strings", () => {
    expect(redact("postgresql://neon:hunter2@ep-x.aws.neon.tech/db")).toBe(
      "postgresql://neon:[REDACTED:connection-password]@ep-x.aws.neon.tech/db",
    );
    // DATABASE_URL is not a credential-named var, so the connection rule carries it:
    // the password dies, the host stays — which is what a reader needs to see anyway.
    expect(redact("DATABASE_URL=postgres://u:p4ss@host:5432/x")).toBe(
      "DATABASE_URL=postgres://u:[REDACTED:connection-password]@host:5432/x",
    );
  });

  test("no false positives on prose", () => {
    const prose =
      "The token budget was fine; we discussed the secret to good tests and the password policy. Skip the risky bits.";
    expect(redact(prose)).toBe(prose);
    expect(redact("The AKIA prefix marks AWS keys")).toBe("The AKIA prefix marks AWS keys");
    expect(redact("Bearer of good news")).toBe("Bearer of good news");
  });
});

describe("redactRun", () => {
  test("scrubs every string field of the run", () => {
    const run = redactRun({
      agent: "claude-code",
      runId: "run-1",
      userMessages: ["use AKIAAAAABBBBCCCCDDDD please"],
      steps: [
        {
          assistantText: "found ghp_abcdefghijklmnopqrst123456",
          toolCalls: [
            {
              name: "Bash",
              inputSummary: "password=hunter2",
              outputSummary: "xoxb-1234567890-abcdefgh",
              command: "curl -H 'authorization: Bearer tsk_live_0123456789abcdef' https://api.example.com",
              files: ["/home/dev/secret=hunter2.txt"],
              url: "https://example.com/?token=deadbeef",
            },
          ],
        },
      ],
    });
    expect(JSON.stringify(run)).not.toMatch(/AKIAAAAABBBBCCCCDDDD|ghp_|hunter2|xoxb-|tsk_live_|deadbeef/);
  });
});
