defmodule Tarakan.SuppressionsTest do
  use Tarakan.DataCase, async: true

  alias Tarakan.FindingMemory
  alias Tarakan.Repo
  alias Tarakan.Scans.CanonicalFinding
  alias Tarakan.Suppressions

  defp finding_fixture(repository, title, status) do
    now = DateTime.utc_now()

    %CanonicalFinding{}
    |> Ecto.Changeset.change(%{
      repository_id: repository.id,
      fingerprint: "fp-#{System.unique_integer([:positive])}",
      pattern_key: FindingMemory.pattern_key(title),
      file_path: "lib/app.ex",
      line_start: 12,
      severity: "medium",
      title: title,
      description: "Body.",
      status: status,
      disputes_count: if(status == "disputed", do: 2, else: 0),
      first_seen_commit_sha: random_commit_sha(),
      last_seen_commit_sha: random_commit_sha(),
      inserted_at: now,
      updated_at: now
    })
    |> Repo.insert!()
  end

  describe "repository_suppressions/2" do
    test "returns disputed findings and nothing else" do
      repository = listed_github_repository_fixture()

      disputed =
        finding_fixture(repository, "Hardcoded test fixture flagged as secret", "disputed")

      finding_fixture(repository, "Real SQL injection", "verified")
      finding_fixture(repository, "Still being argued about", "open")

      assert [entry] = Suppressions.repository_suppressions(repository)
      assert entry.public_id == disputed.public_id
      assert entry.fingerprint == disputed.fingerprint
      assert entry.file_path == "lib/app.ex"
      assert entry.scope == "repository"
    end

    test "does not leak another repository's disputes" do
      repository = listed_github_repository_fixture()
      other = listed_hosted_repository_fixture()

      finding_fixture(other, "Disputed elsewhere", "disputed")

      assert Suppressions.repository_suppressions(repository) == []
    end
  end

  describe "suppressed?/2" do
    test "matches a disputed fingerprint on the same repository" do
      repository = listed_github_repository_fixture()
      disputed = finding_fixture(repository, "Noisy detector output", "disputed")
      verified = finding_fixture(repository, "Genuine bug", "verified")

      assert Suppressions.suppressed?(repository, disputed.fingerprint)
      refute Suppressions.suppressed?(repository, verified.fingerprint)
      refute Suppressions.suppressed?(repository, "unknown")
    end
  end

  describe "pattern_suppressions/1" do
    test "flags a class disputed on several repositories and confirmed nowhere" do
      title = "Missing rate limit on internal health endpoint"

      for _index <- 1..2 do
        finding_fixture(listed_hosted_repository_fixture(), title, "disputed")
      end

      assert [pattern] = Suppressions.pattern_suppressions()
      assert pattern.pattern_key == FindingMemory.pattern_key(title)
      assert pattern.disputed_repositories == 2
      assert pattern.scope == "pattern"
    end

    test "one repository's disagreement is not a detector artefact" do
      finding_fixture(
        listed_hosted_repository_fixture(),
        "Only disputed in one place",
        "disputed"
      )

      assert Suppressions.pattern_suppressions() == []
    end

    test "a single confirmation anywhere removes the pattern from the corpus" do
      title = "Contested but sometimes real"

      for _index <- 1..2 do
        finding_fixture(listed_hosted_repository_fixture(), title, "disputed")
      end

      assert [_pattern] = Suppressions.pattern_suppressions()

      finding_fixture(listed_hosted_repository_fixture(), title, "verified")

      assert Suppressions.pattern_suppressions() == []
    end
  end
end
