/**
 * `isModelAllowed` — virtual-key model allowlist, made role-aware for
 * role-based routing (2026-06-09).
 *
 * The allowlist middleware runs BEFORE routing on the raw `request.model`.
 * With role-based routing a worker sends a tier (auto/fast/quality/...) as
 * `request.model`, so the check must understand roles or it 403s every
 * role request from a key that has a model-name allowlist.
 *
 * Soundness rule for a role request: the gateway will route to SOME backend
 * serving that role (least-busy, chosen post-check). Allow only when EVERY
 * backend serving the role is within the allowlist (by id or model), so
 * whichever the router picks is permitted.
 */
import { describe, it, expect } from 'vitest';
import { isModelAllowed } from './auth.js';
import type { GatewayBackendConfig } from './config.js';

function bk(id: string, model: string, roles?: string[]): GatewayBackendConfig {
  return { id, endpoint: `http://${id}/v1`, model, maxSlots: 4, ...(roles ? { roles } : {}) };
}

describe('isModelAllowed (no allowlist / literal-model behaviour)', () => {
  it('allows anything when allowlist is undefined', () => {
    expect(isModelAllowed('qwen3:32b', undefined)).toBe(true);
    expect(isModelAllowed('quality', undefined)).toBe(true);
  });

  it('denies an empty-string / missing requested model when an allowlist exists', () => {
    expect(isModelAllowed(undefined, ['qwen3:32b'])).toBe(false);
    expect(isModelAllowed('', ['qwen3:32b'])).toBe(false);
  });

  it('allows a literal model that is in the allowlist', () => {
    expect(isModelAllowed('qwen3:32b', ['qwen3:32b'])).toBe(true);
  });

  it('denies a literal model not in the allowlist', () => {
    expect(isModelAllowed('qwen3:70b', ['qwen3:32b'])).toBe(false);
  });

  it('empty allowlist array denies everything (intentional lockdown)', () => {
    expect(isModelAllowed('qwen3:32b', [])).toBe(false);
    expect(isModelAllowed('quality', [])).toBe(false);
  });
});

describe('isModelAllowed (role-aware)', () => {
  it('allows a role that is itself listed (role-name allowlist)', () => {
    expect(isModelAllowed('quality', ['quality', 'fast'])).toBe(true);
  });

  it('allows a role request when EVERY backend serving the role is in the model allowlist', () => {
    const backends = [bk('gpu-a', 'qwen3:32b', ['quality'])];
    // Legacy model-name allowlist keeps working: the only quality backend
    // serves an allowed model.
    expect(isModelAllowed('quality', ['qwen3:32b'], backends)).toBe(true);
  });

  it('denies a role request when SOME backend serving the role is not allowed (sound)', () => {
    const backends = [
      bk('gpu-a', 'qwen3:32b', ['quality']),
      bk('gpu-b', 'qwen3:70b', ['quality']), // not in allowlist — router could pick it
    ];
    expect(isModelAllowed('quality', ['qwen3:32b'], backends)).toBe(false);
  });

  it('matches a serving backend by id as well as model', () => {
    const backends = [bk('gpu-a', 'qwen3:32b', ['fast'])];
    expect(isModelAllowed('fast', ['gpu-a'], backends)).toBe(true);
  });

  it('denies a role with no serving backend that is not literally allowlisted', () => {
    const backends = [bk('gpu-a', 'qwen3:32b', ['quality'])];
    expect(isModelAllowed('reflection', ['qwen3:32b'], backends)).toBe(false);
  });

  it('treats an unspecified-roles backend as serving the role (must be allowlisted too)', () => {
    const backends = [bk('generalist', 'qwen3:8b')]; // serves all roles
    expect(isModelAllowed('quality', ['qwen3:8b'], backends)).toBe(true);
    expect(isModelAllowed('quality', ['other'], backends)).toBe(false);
  });
});
