defmodule Tarakan.AdversarialAndReproTest do
  @moduledoc """
  The two evidence signals that are meant to be stronger than agreement:
  an attack that failed, and a reproduction that ran.
  """
  use Tarakan.DataCase, async: true

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

  @sha String.duplicate("b", 40)

  defp setup_finding do
    submitter = github_account_fixture()
    repository = listed_github_repository_fixture(submitter)

    findings =
      Jason.encode!(%{
        "tarakan_scan_format" => 1,
        "findings" => [
          %{
            "file" => "lib/vulnerable.ex",
            "severity" => "high",
            "title" => "Command injection in exec path",
            "description" => "User input reaches System.cmd without escaping."
          }
        ]
      })

    scan = scan_fixture(repository, submitter, %{"findings_json" => findings})

    canonical =
      Repo.one!(from f in CanonicalFinding, where: f.repository_id == ^repository.id)

    %{repository: repository, canonical: canonical, scan: scan, submitter: submitter}
  end

  defp check(ctx, account, attrs) do
    base = %{
      "commit_sha" => ctx.scan.commit_sha,
      "provenance" => "human",
      "notes" => "Independent evidence recorded for this finding, at length."
    }

    FindingMemory.record_check(
      Tarakan.Accounts.Scope.for_account(account),
      ctx.repository,
      ctx.canonical.public_id,
      Map.merge(base, attrs)
    )
  end

  describe "adversarial checks" do
    test "an attack that fails counts as survived, not merely confirmed" do
      ctx = setup_finding()

      assert {:ok, _check, updated} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "confirmed",
                 "adversarial" => true
               })

      assert updated.refutations_count == 1
      assert updated.survived_refutations_count == 1
    end

    test "an attack that lands counts as attempted but not survived" do
      ctx = setup_finding()

      assert {:ok, _check, updated} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "disputed",
                 "adversarial" => true
               })

      assert updated.refutations_count == 1
      assert updated.survived_refutations_count == 0
    end

    test "an ordinary agreement is not counted as a survived attack" do
      ctx = setup_finding()

      assert {:ok, _check, updated} =
               check(ctx, reviewer_account_fixture(), %{"verdict" => "confirmed"})

      assert updated.confirmations_count == 1
      assert updated.refutations_count == 0
      assert updated.survived_refutations_count == 0
    end
  end

  describe "reproductions" do
    test "a passing reproduction is counted and stamped" do
      ctx = setup_finding()

      assert {:ok, check, updated} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "confirmed",
                 "repro_status" => "pass",
                 "repro_runtime" => "bash",
                 "repro_artifact" => "curl -s localhost:4000/?q=%27%20OR%201=1",
                 "repro_transcript" => "HTTP/1.1 500\\nstacktrace..."
               })

      assert check.repro_status == "pass"
      assert updated.reproductions_count == 1
      assert updated.reproduced_at
    end

    test "a failed reproduction is stored but does not count as one" do
      ctx = setup_finding()

      assert {:ok, _check, updated} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "disputed",
                 "repro_status" => "fail",
                 "repro_artifact" => "curl -s localhost:4000/"
               })

      assert updated.reproductions_count == 0
      refute updated.reproduced_at
    end

    test "a transcript carrying a credential is refused" do
      ctx = setup_finding()

      assert {:error, :secrets_detected} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "confirmed",
                 "repro_status" => "pass",
                 "repro_artifact" => "env | grep KEY",
                 "repro_transcript" => "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE"
               })
    end

    test "an artifact carrying a credential is refused" do
      ctx = setup_finding()

      assert {:error, :secrets_detected} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "confirmed",
                 "repro_status" => "pass",
                 "repro_artifact" =>
                   "curl -H 'Authorization: Bearer ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaa' x"
               })
    end

    test "an outcome without an artifact is rejected as unfalsifiable" do
      ctx = setup_finding()

      assert {:error, changeset} =
               check(ctx, reviewer_account_fixture(), %{
                 "verdict" => "confirmed",
                 "repro_status" => "pass"
               })

      assert %{repro_artifact: _} = errors_on(changeset)
    end
  end
end
