/**
 * G-12: Smoke security tests for /api/services/ and /dashboard/providers/services/ route guard.
 *
 * Tests that `isLocalOnlyPath` and `isSpawnCapablePath` return the expected
 * values for all embedded-service prefixes — no running server required.
 *
 * Run as part of the integration/services suite:
 *   node --import tsx/esm --test tests/integration/services/*.test.ts
 */

import { describe, it } from "node:test";
import assert from "node:assert/strict";

// Import the actual module under test — no mocks needed.
import {
  isLocalOnlyPath,
  isLocalOnlyBypassableByManageScope,
  LOCAL_ONLY_API_PREFIXES,
  SPAWN_CAPABLE_PREFIXES,
} from "../../../src/server/authz/routeGuard.ts";

// ---------------------------------------------------------------------------
// isLocalOnlyPath — /api/services/* coverage
// ---------------------------------------------------------------------------

describe("isLocalOnlyPath — /api/services/* and /dashboard/providers/services/* are LOCAL_ONLY", () => {
  it("returns true for /api/services/9router/start", () => {
    assert.equal(isLocalOnlyPath("/api/services/9router/start"), true);
  });

  it("returns true for /api/services/cliproxy/install", () => {
    assert.equal(isLocalOnlyPath("/api/services/cliproxy/install"), true);
  });

  it("returns true for /api/services/9router/models", () => {
    assert.equal(isLocalOnlyPath("/api/services/9router/models"), true);
  });

  it("returns true for /api/services/9router/status", () => {
    assert.equal(isLocalOnlyPath("/api/services/9router/status"), true);
  });

  it("returns true for /api/services/9router/stop", () => {
    assert.equal(isLocalOnlyPath("/api/services/9router/stop"), true);
  });

  it("returns true for /api/services/cliproxy/start", () => {
    assert.equal(isLocalOnlyPath("/api/services/cliproxy/start"), true);
  });

  it("returns true for /api/services/cliproxy/status", () => {
    assert.equal(isLocalOnlyPath("/api/services/cliproxy/status"), true);
  });

  it("returns true for /api/services/ (root prefix)", () => {
    assert.equal(isLocalOnlyPath("/api/services/"), true);
  });

  it("returns true for /dashboard/providers/services/9router/embed/foo", () => {
    assert.equal(isLocalOnlyPath("/dashboard/providers/services/9router/embed/foo"), true);
  });

  it("returns true for /dashboard/providers/services/ (root prefix)", () => {
    assert.equal(isLocalOnlyPath("/dashboard/providers/services/"), true);
  });

  it("returns true for path with query string (raw path matching)", () => {
    // The path portion only (no query string) is what routeGuard receives.
    // This test validates that a path equal to the prefix works.
    assert.equal(isLocalOnlyPath("/api/services/9router/status"), true);
    // Confirm that if caller strips query string themselves before calling,
    // the behavior is correct.
    const rawWithQuery = "/api/services/9router/models?refresh=true";
    const pathOnly = rawWithQuery.split("?")[0];
    assert.equal(isLocalOnlyPath(pathOnly), true);
  });

  it("returns false for /api/services (no trailing slash — does NOT match prefix)", () => {
    // "/api/services" does not start with "/api/services/" — intentional behavior.
    assert.equal(isLocalOnlyPath("/api/services"), false);
  });

  it("returns false for /api/settings (unrelated route)", () => {
    assert.equal(isLocalOnlyPath("/api/settings"), false);
  });

  it("returns false for /api/providers (unrelated route)", () => {
    assert.equal(isLocalOnlyPath("/api/providers"), false);
  });
});

// ---------------------------------------------------------------------------
// isSpawnCapablePath — /api/services/* must NOT be bypassable by manage-scope
// ---------------------------------------------------------------------------

describe("isLocalOnlyBypassableByManageScope — /api/services/* is NOT bypassable (spawn-capable)", () => {
  it("returns false for /api/services/9router/install (spawn-capable)", () => {
    // Spawn-capable paths must never be bypassable — even when the DB kill-switch is on.
    assert.equal(isLocalOnlyBypassableByManageScope("/api/services/9router/install"), false);
  });

  it("returns false for /api/services/9router/start", () => {
    assert.equal(isLocalOnlyBypassableByManageScope("/api/services/9router/start"), false);
  });

  it("returns false for /api/services/ (root prefix)", () => {
    assert.equal(isLocalOnlyBypassableByManageScope("/api/services/"), false);
  });

  it("returns false for /api/services/cliproxy/install", () => {
    assert.equal(isLocalOnlyBypassableByManageScope("/api/services/cliproxy/install"), false);
  });
});

// ---------------------------------------------------------------------------
// Constant integrity: LOCAL_ONLY_API_PREFIXES and SPAWN_CAPABLE_PREFIXES
// ---------------------------------------------------------------------------

describe("LOCAL_ONLY_API_PREFIXES constant integrity", () => {
  it("includes /api/services/ (T-10 hard rule)", () => {
    assert.ok(
      LOCAL_ONLY_API_PREFIXES.includes("/api/services/"),
      `Expected /api/services/ in LOCAL_ONLY_API_PREFIXES, got: ${JSON.stringify(LOCAL_ONLY_API_PREFIXES)}`
    );
  });

  it("includes /dashboard/providers/services/ (T-07 reverse proxy hard rule)", () => {
    assert.ok(
      LOCAL_ONLY_API_PREFIXES.includes("/dashboard/providers/services/"),
      `Expected /dashboard/providers/services/ in LOCAL_ONLY_API_PREFIXES, got: ${JSON.stringify(LOCAL_ONLY_API_PREFIXES)}`
    );
  });

  it("includes /api/mcp/ (pre-existing hard rule)", () => {
    assert.ok(
      LOCAL_ONLY_API_PREFIXES.includes("/api/mcp/"),
      "Expected /api/mcp/ in LOCAL_ONLY_API_PREFIXES"
    );
  });

  it("includes /api/cli-tools/runtime/ (pre-existing hard rule)", () => {
    assert.ok(
      LOCAL_ONLY_API_PREFIXES.includes("/api/cli-tools/runtime/"),
      "Expected /api/cli-tools/runtime/ in LOCAL_ONLY_API_PREFIXES"
    );
  });

  it("has exactly 5 entries (no silent regressions adding or removing prefixes)", () => {
    // 4 baseline entries (/api/mcp/, /api/cli-tools/runtime/, /api/services/,
    // /dashboard/providers/services/) + /api/copilot/ added in the v3.8.4
    // semgrep MCP hardening pass (commit 21f8dc4b3).
    assert.equal(
      LOCAL_ONLY_API_PREFIXES.length,
      5,
      `Expected 5 LOCAL_ONLY_API_PREFIXES, got ${LOCAL_ONLY_API_PREFIXES.length}: ${JSON.stringify(LOCAL_ONLY_API_PREFIXES)}`
    );
  });
});

describe("SPAWN_CAPABLE_PREFIXES constant integrity", () => {
  it("includes /api/services/ (can run npm install + spawn node)", () => {
    assert.ok(
      SPAWN_CAPABLE_PREFIXES.includes("/api/services/"),
      `Expected /api/services/ in SPAWN_CAPABLE_PREFIXES, got: ${JSON.stringify(SPAWN_CAPABLE_PREFIXES)}`
    );
  });

  it("includes /api/cli-tools/runtime/ (pre-existing spawn-capable)", () => {
    assert.ok(
      SPAWN_CAPABLE_PREFIXES.includes("/api/cli-tools/runtime/"),
      "Expected /api/cli-tools/runtime/ in SPAWN_CAPABLE_PREFIXES"
    );
  });

  it("has exactly 2 entries (no silent regressions)", () => {
    assert.equal(
      SPAWN_CAPABLE_PREFIXES.length,
      2,
      `Expected 2 SPAWN_CAPABLE_PREFIXES, got ${SPAWN_CAPABLE_PREFIXES.length}: ${JSON.stringify(SPAWN_CAPABLE_PREFIXES)}`
    );
  });

  it("does NOT include /api/mcp/ (bypassable, not spawn-capable)", () => {
    assert.ok(
      !SPAWN_CAPABLE_PREFIXES.includes("/api/mcp/"),
      "/api/mcp/ should NOT be in SPAWN_CAPABLE_PREFIXES"
    );
  });

  it("does NOT include /dashboard/providers/services/ (local-only but separate concern)", () => {
    assert.ok(
      !SPAWN_CAPABLE_PREFIXES.includes("/dashboard/providers/services/"),
      "/dashboard/providers/services/ should NOT be in SPAWN_CAPABLE_PREFIXES"
    );
  });
});
