// src/lib/backlink/assert-link-source.test.ts — integration, gated on DB_TESTS=1.
// Tenancy guard for linkSourceIds (IDOR cross-org, audit sécu finding B1/B2).
import { beforeEach, describe, expect, mock, test } from "bun:test";

// server-only throws outside Next.js; stub it for the test runner.
mock.module("server-only", () => ({}));

const RUN_DB = process.env.DB_TESTS === "1";

describe.skipIf(!RUN_DB)("linkSourceIdsInOrg (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let authSchema: typeof import("@/lib/db/auth-schema");
  const ORG = "demo-org"; // guaranteed by db:seed / test DB setup
  const OTHER_ORG = "als-other-org";

  async function insertSource(
    id: string,
    orgId: string,
    overrides: Record<string, unknown> = {},
  ) {
    await db.insert(schema.linkSources).values({
      id,
      orgId,
      platform: id,
      url: `https://${id.toLowerCase()}.com`,
      domainNorm: `${id.toLowerCase()}.com`,
      kind: "blog",
      da: 40,
      effort: "low",
      linkType: "dofollow",
      language: "en",
      ...overrides,
    } as never);
  }

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    authSchema = await import("@/lib/db/auth-schema");
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    const { eq, inArray } = await import("drizzle-orm");
    await assertTestDb();
    await db.delete(schema.backlinks);
    await db
      .delete(schema.linkSources)
      .where(inArray(schema.linkSources.id, ["S1", "S-GLOBAL", "S-OTHER"]));
    // Foreign org for the cross-org rows (FK target of link_sources.org_id).
    await db
      .delete(authSchema.organization)
      .where(eq(authSchema.organization.id, OTHER_ORG));
    await db.insert(authSchema.organization).values({
      id: OTHER_ORG,
      name: "ALS Other Org",
      slug: OTHER_ORG,
      createdAt: new Date(),
    });
  });

  test("garde l'id d'une source de l'org active", async () => {
    await insertSource("S1", ORG);
    const { linkSourceIdsInOrg } = await import("./assert-link-source");
    expect(await linkSourceIdsInOrg(["S1"], ORG)).toEqual(["S1"]);
  });

  test("filtre l'id d'une source d'une autre org (IDOR cross-org)", async () => {
    await insertSource("S-OTHER", OTHER_ORG);
    const { linkSourceIdsInOrg } = await import("./assert-link-source");
    expect(await linkSourceIdsInOrg(["S-OTHER"], ORG)).toEqual([]);
  });

  test("filtre les ids inexistants", async () => {
    const { linkSourceIdsInOrg } = await import("./assert-link-source");
    expect(await linkSourceIdsInOrg(["ghost"], ORG)).toEqual([]);
  });

  test("garde une source copiée du catalogue global (global=true, org_id = org active)", async () => {
    // org-bootstrap.ts copie chaque source du catalogue curé avec l'org_id de
    // la nouvelle org : aucune ligne n'est jamais partagée entre orgs, même
    // les entrées flag global=true.
    await insertSource("S-GLOBAL", ORG, { global: true });
    const { linkSourceIdsInOrg } = await import("./assert-link-source");
    expect(await linkSourceIdsInOrg(["S-GLOBAL"], ORG)).toEqual(["S-GLOBAL"]);
  });

  test("mix : ne garde que les ids de l'org active, silencieusement", async () => {
    await insertSource("S1", ORG);
    await insertSource("S-OTHER", OTHER_ORG);
    const { linkSourceIdsInOrg } = await import("./assert-link-source");
    const kept = await linkSourceIdsInOrg(["S1", "S-OTHER", "ghost"], ORG);
    expect(kept).toEqual(["S1"]);
  });
});
