// no-secret-leak contract test.
//
// Verifies that `redactSecrets` strips every key declared as a secret in the
// form schema, and is meaningful (not a no-op) — removing the `secret` tag
// from a field MUST cause a deliberately-secret value to leak into the result,
// proving the test would catch a regression.

import { describe, expect, it } from 'vitest';
import { redactSecrets, type FormSchema } from '../index.js';

const SECRET_VALUE = 'hunter2';
const SECRET_REGEX = /password|token|api[_-]?key|secret|credential|bearer/i;

describe('redactSecrets', () => {
  it('drops every key listed in secretFields', () => {
    const schema: FormSchema = { secretFields: ['password', 'apiKey'] };
    const payload = {
      adminLabel: 'maxy-test',
      adminDomain: 'maxy.bot',
      password: SECRET_VALUE,
      apiKey: 'sk-abc',
    };
    const { redacted, droppedFields, droppedFieldNames } = redactSecrets(payload, schema);

    expect(redacted).toEqual({ adminLabel: 'maxy-test', adminDomain: 'maxy.bot' });
    expect(droppedFields).toBe(2);
    expect(droppedFieldNames).toEqual(['apiKey', 'password']);
  });

  it('passes every key through when schema has no secretFields', () => {
    const payload = { mode: 'personal', emailProvided: true };
    const { redacted, droppedFields } = redactSecrets(payload, {});
    expect(redacted).toEqual(payload);
    expect(droppedFields).toBe(0);
  });

  it('passes every key through when schema is undefined', () => {
    const payload = { mode: 'personal' };
    const { redacted, droppedFields } = redactSecrets(payload, undefined);
    expect(redacted).toEqual(payload);
    expect(droppedFields).toBe(0);
  });

  it('returns empty result for null/undefined payload', () => {
    expect(redactSecrets(null, { secretFields: ['x'] })).toEqual({
      redacted: {},
      droppedFields: 0,
      droppedFieldNames: [],
    });
    expect(redactSecrets(undefined, undefined)).toEqual({
      redacted: {},
      droppedFields: 0,
      droppedFieldNames: [],
    });
  });

  it('preserves falsy non-secret values (false, 0, empty string, null)', () => {
    const payload = { a: false, b: 0, c: '', d: null, secret: 's' };
    const { redacted } = redactSecrets(payload, { secretFields: ['secret'] });
    expect(redacted).toEqual({ a: false, b: 0, c: '', d: null });
  });

  it('does not count secret keys absent from payload', () => {
    const schema: FormSchema = { secretFields: ['password', 'missingField'] };
    const { droppedFields, droppedFieldNames } = redactSecrets({ password: SECRET_VALUE }, schema);
    expect(droppedFields).toBe(1);
    expect(droppedFieldNames).toEqual(['password']);
  });

  it('drops a secret key regardless of its value shape', () => {
    const schema: FormSchema = { secretFields: ['password'] };
    const cases: Array<{ password: unknown }> = [
      { password: 'string-value' },
      { password: 42 },
      { password: null },
      { password: { nested: 'object' } },
      { password: ['array', 'of', 'strings'] },
    ];
    for (const c of cases) {
      const { redacted } = redactSecrets(c as Record<string, never>, schema);
      expect(redacted).not.toHaveProperty('password');
    }
  });

  it('CONTRACT: no recorded property value contains a secret regex match (cloudflare-setup-form)', () => {
    // Synthetic cloudflare submission shape — mirrors CloudflareSetupRequest
    // including both the operator-supplied secret (password) and the
    // wire-protocol envelope (session_key, messageId) the schema also drops.
    const cloudflareSchema: FormSchema = { secretFields: ['password', 'session_key', 'messageId'] };
    const payload = {
      adminLabel: 'maxy-test',
      adminDomain: 'maxy.bot',
      publicLabel: 'public',
      publicDomain: 'maxy.bot',
      apex: 'maxy.bot',
      password: SECRET_VALUE,
      tunnelName: 'maxy-test',
      session_key: 'sk-conversation-correlation-12345678',
      messageId: '11111111-2222-3333-4444-555555555555',
    };
    const { redacted } = redactSecrets(payload, cloudflareSchema);

    // Scan every recorded value for any known-secret regex match.
    for (const [key, value] of Object.entries(redacted)) {
      const stringified = JSON.stringify(value);
      expect(stringified, `field ${key} carried a secret-shaped value`).not.toMatch(SECRET_REGEX);
      expect(stringified, `field ${key} carried the secret literal`).not.toContain(SECRET_VALUE);
    }
    // Wire-envelope fields must also be absent from recorded props.
    expect(redacted).not.toHaveProperty('session_key');
    expect(redacted).not.toHaveProperty('messageId');
  });

  it('CONTRACT (falsifiability): removing the secret tag causes the password to leak — proves the test is meaningful', () => {
    // This test is the canary that proves the no-secret-leak guarantee comes
    // from the schema, not from coincidence. With `secretFields` empty (the
    // schema-author forgot to tag `password`), the value MUST leak into the
    // recorded payload — otherwise the contract test above would still pass
    // even with a broken schema, and would catch nothing.
    const brokenSchema: FormSchema = { secretFields: [] };
    const payload = { adminLabel: 'maxy-test', password: SECRET_VALUE };
    const { redacted } = redactSecrets(payload, brokenSchema);

    expect(redacted.password).toBe(SECRET_VALUE);
    expect(JSON.stringify(redacted)).toContain(SECRET_VALUE);
  });
});
