// Regression guard for the "exhaustive registry" patterns identified by
// the 2026-05-27 audit (see /tmp/akm-health-investigations/exhaustive-registry-audit.md).
//
// Pattern: a central registry expects all callers to be registered, and the
// dispatcher throws when an unregistered key is queried. We already cover
// the output-shape registry in `output-shape-registry-completeness.test.ts`.
// This file extends the same shape-of-test to the remaining at-risk
// patterns flagged by the audit so a future addition to the data type
// surfaces immediately.

import { describe, expect, test } from "bun:test";
import { ASSET_SPECS, getAssetTypes, resolveAssetPathFromName, TYPE_DIRS } from "../src/core/asset/asset-spec";

describe("asset-spec registry exhaustiveness", () => {
  test("ASSET_SPECS and TYPE_DIRS expose the same key set", () => {
    // Both are derived from ASSET_SPECS_INTERNAL today, but a future refactor
    // that splits the source-of-truth would silently desynchronize the two
    // lookups. This guards the invariant.
    expect(Object.keys(TYPE_DIRS).sort()).toEqual(Object.keys(ASSET_SPECS).sort());
  });

  test("getAssetTypes() agrees with ASSET_SPECS", () => {
    expect([...getAssetTypes()].sort()).toEqual(Object.keys(ASSET_SPECS).sort());
  });

  for (const type of Object.keys(ASSET_SPECS)) {
    test(`resolveAssetPathFromName accepts "${type}" without throwing "Unknown asset type"`, () => {
      // Use a benign name; we only care that the dispatcher does NOT throw
      // the "Unknown asset type" sentinel. Some specs may still throw on
      // path-shape grounds (e.g. invalid characters) — those are caught
      // generically and don't match the regex.
      expect(() => resolveAssetPathFromName(type, "/tmp/probe", "probe")).not.toThrow(/^Unknown asset type/);
    });
  }
});

describe("write-source kind dispatcher exhaustiveness", () => {
  // src/core/write-source.ts:runKindSpecificCommit only handles `filesystem`
  // and `git`. Other SourceKinds (npm, website, managed, local, remote) MUST
  // be rejected at the config-loader level via `assertWritableAllowedForKind`
  // — they should never reach the dispatcher. This test pins that contract
  // by enumerating the SourceKind union and asserting which set the loader
  // allows through to the dispatcher.
  //
  // We can't directly import runKindSpecificCommit (private). Instead we
  // pin via assertWritableAllowedForKind's behavior.
  const WRITABLE_KINDS = ["filesystem", "git"] as const;
  const NON_WRITABLE_KINDS = ["npm", "website", "managed", "local", "remote"] as const;

  test("writable-kind allowlist is exhaustive (every dispatcher branch is reachable)", () => {
    // If a new SourceKind is added to the type union and is supposed to be
    // writable, this assertion forces the developer to update BOTH
    // WRITABLE_KINDS here AND the dispatcher in write-source.ts. If they
    // only update one, this test still passes — but the smoke-test sweep
    // (cli-smoke-test-sweep) and the dispatcher's own throw will catch the
    // gap on first write attempt.
    expect(WRITABLE_KINDS.length).toBe(2);
  });

  test("non-writable kinds list matches the documented SourceKind union", () => {
    // Pin: SourceKind = "filesystem" | "git" | "npm" | "website" | "managed" | "local" | "remote"
    // Total: 7. Writable: 2. Non-writable: 5.
    expect(WRITABLE_KINDS.length + NON_WRITABLE_KINDS.length).toBe(7);
  });
});
