tarakan/test/tarakan_web/controllers/api/finding_signals_api_test.exs
Maxfield Luke af6077b9c3
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s
Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
2026-07-29 04:43:40 -04:00

153 lines
4.7 KiB
Elixir

defmodule TarakanWeb.API.FindingSignalsApiTest do
@moduledoc """
The three worker submission endpoints that had no HTTP surface until now:
calibrated severity, code embeddings, and cluster detectors.
"""
use TarakanWeb.ConnCase, async: true
import Ecto.Query, only: [from: 2]
alias Tarakan.Accounts.ApiCredentials
alias Tarakan.Repo
alias Tarakan.Scans.CanonicalFinding
setup %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
findings =
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/vulnerable.ex",
"severity" => "critical",
"title" => "Command injection #{System.unique_integer([:positive])}",
"description" => "User input reaches System.cmd without escaping."
}
]
})
scan_fixture(repository, submitter, %{"findings_json" => findings})
finding = Repo.one!(from f in CanonicalFinding, where: f.repository_id == ^repository.id)
reviewer = reviewer_account_fixture()
{:ok, token, _cred} =
ApiCredentials.create(reviewer, %{
name: "signals",
scopes: ["reviews:read", "reviews:verify"]
})
%{
conn: put_req_header(conn, "authorization", "Bearer #{token}"),
anon: build_conn(),
repository: repository,
finding: finding
}
end
defp signal_path(repository, finding, suffix) do
"/api/github.com/#{repository.owner}/#{repository.name}/findings/#{finding.public_id}/#{suffix}"
end
describe "POST .../severity" do
test "stores a rescore beside the submitter's claim", ctx do
conn =
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "severity"), %{
"severity" => "low",
"rubric" => "rubric-v1"
})
assert %{"calibrated_severity" => "low"} = json_response(conn, 200)
assert Repo.get!(CanonicalFinding, ctx.finding.id).severity == "critical"
end
test "rejects a severity outside the scale", ctx do
conn =
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "severity"), %{
"severity" => "catastrophic",
"rubric" => "rubric-v1"
})
assert %{"errors" => %{"calibrated_severity" => _}} = json_response(conn, 422)
end
end
describe "POST .../embedding" do
test "stores the vector and assigns a cluster", ctx do
conn =
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "embedding"), %{
"embedding" => [0.1, 0.9, 0.2],
"embedding_model" => "test-embed-v1"
})
assert %{"code_pattern_key" => key} = json_response(conn, 200)
assert key =~ ~r/^code:/
end
test "rejects an all-zero vector, which would match everything", ctx do
conn =
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "embedding"), %{
"embedding" => [0.0, 0.0, 0.0],
"embedding_model" => "test-embed-v1"
})
assert %{"errors" => %{"embedding" => _}} = json_response(conn, 422)
end
end
describe "POST /api/patterns/:key/rule" do
@rule """
rules:
- id: tarakan-cmd-injection
message: user input reaches System.cmd
severity: ERROR
languages: [elixir]
patterns:
- pattern: System.cmd($C, $A)
"""
test "accepts a validated detector and marks it servable", ctx do
conn =
post(ctx.conn, "/api/patterns/code:abc/rule", %{
"rule_yaml" => @rule,
"language" => "elixir",
"checked_count" => 3,
"matched_count" => 2,
"matched_finding_ids" => [Ecto.UUID.generate(), Ecto.UUID.generate()]
})
assert %{"servable" => true, "matched_count" => 2} = json_response(conn, 201)
end
test "a detector that matched nothing is stored but not servable", ctx do
conn =
post(ctx.conn, "/api/patterns/code:abc/rule", %{
"rule_yaml" => @rule,
"checked_count" => 3,
"matched_count" => 0,
"matched_finding_ids" => []
})
assert %{"servable" => false, "validated_at" => nil} = json_response(conn, 201)
end
test "rejects a match count with no receipts", ctx do
conn =
post(ctx.conn, "/api/patterns/code:abc/rule", %{
"rule_yaml" => @rule,
"checked_count" => 3,
"matched_count" => 2,
"matched_finding_ids" => []
})
assert %{"errors" => %{"matched_finding_ids" => _}} = json_response(conn, 422)
end
test "anonymous submissions are refused", ctx do
conn = post(ctx.anon, "/api/patterns/code:abc/rule", %{"rule_yaml" => @rule})
assert json_response(conn, 401)
end
end
end