Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
601
test/tarakan_web/controllers/api/work_controller_test.exs
Normal file
601
test/tarakan_web/controllers/api/work_controller_test.exs
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
defmodule TarakanWeb.API.WorkControllerTest do
|
||||
use TarakanWeb.ConnCase, async: true
|
||||
|
||||
alias Tarakan.Accounts
|
||||
alias Tarakan.Accounts.ApiCredentials
|
||||
alias Tarakan.Repositories.Repository
|
||||
alias Tarakan.Repo
|
||||
alias Tarakan.Work
|
||||
|
||||
setup %{conn: conn} do
|
||||
creator = github_account_fixture()
|
||||
repository = listed_github_repository_fixture(creator)
|
||||
worker = account_fixture()
|
||||
worker_token = client_token(worker)
|
||||
creator_token = client_token(creator)
|
||||
|
||||
%{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
creator_token: creator_token,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: worker_token
|
||||
}
|
||||
end
|
||||
|
||||
defp authed(conn, token), do: put_req_header(conn, "authorization", "Bearer #{token}")
|
||||
|
||||
defp client_token(account) do
|
||||
{:ok, token, _credential} =
|
||||
ApiCredentials.create(account, %{
|
||||
name: "work controller test",
|
||||
scopes: ["tasks:read", "tasks:claim", "contributions:write"]
|
||||
})
|
||||
|
||||
token
|
||||
end
|
||||
|
||||
test "all work queue endpoints require an API token", %{conn: conn} do
|
||||
conn = get(conn, ~p"/api/github.com/openai/codex/jobs")
|
||||
|
||||
assert json_response(conn, 401)["error"] =~ "API token"
|
||||
end
|
||||
|
||||
test "lists the global open jobs queue", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn = conn |> authed(token) |> get(~p"/api/jobs")
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
assert Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
match = Enum.find(jobs, &(&1["id"] == task.id))
|
||||
assert match["status"] == "open"
|
||||
assert match["repository"]["owner"] == repository.owner
|
||||
assert match["repository"]["name"] == repository.name
|
||||
assert match["repository"]["primary_language"] == repository.primary_language
|
||||
assert match["repository"]["stars_count"] == repository.stars_count
|
||||
end
|
||||
|
||||
test "global jobs queue filters by language and min stars", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
# Fixture repo is Rust with 42000 stars (GitHub stub).
|
||||
conn =
|
||||
conn
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"language" => "Rust", "min_stars" => "1000"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
assert Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"language" => "Elixir"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
refute Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"min_stars" => "999999"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
refute Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
end
|
||||
|
||||
# A worker declares what it can run so it never claims a kind it does not
|
||||
# implement and then holds the lease until it expires.
|
||||
test "global jobs queue filters by the kinds a worker can run", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"kinds" => "#{task.kind},refute_finding"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
assert Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"kinds" => "reproduce_finding"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
refute Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
end
|
||||
|
||||
# An old client and a new server must still be able to talk, so a name the
|
||||
# server does not know is dropped rather than erroring or filtering to empty.
|
||||
test "unknown kind names are ignored rather than rejected", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authed(token)
|
||||
|> get(~p"/api/jobs", %{"kinds" => "not_a_real_kind"})
|
||||
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
assert Enum.any?(jobs, &(&1["id"] == task.id))
|
||||
end
|
||||
|
||||
test "global jobs queue includes the caller's active claims", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: token
|
||||
} do
|
||||
task =
|
||||
review_task_fixture(repository, creator, %{"kind" => "code_review", "capability" => "agent"})
|
||||
|
||||
{:ok, claimed} = Work.claim_task(task, worker)
|
||||
|
||||
assert claimed.status == "claimed"
|
||||
|
||||
conn = conn |> authed(token) |> get(~p"/api/jobs")
|
||||
assert %{"jobs" => jobs} = json_response(conn, 200)
|
||||
match = Enum.find(jobs, &(&1["id"] == claimed.id))
|
||||
assert match
|
||||
assert match["status"] == "claimed"
|
||||
assert match["lease"]["active"] == true
|
||||
end
|
||||
|
||||
test "lists a repository's tasks with client-ready relationships", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn = conn |> authed(token) |> get(~p"/api/github.com/openai/codex/jobs")
|
||||
|
||||
assert %{"jobs" => [response]} = json_response(conn, 200)
|
||||
assert response["id"] == task.id
|
||||
assert response["kind"] == "threat_model"
|
||||
assert response["capability"] == "human"
|
||||
assert response["status"] == "open"
|
||||
assert response["visibility"] == "public"
|
||||
assert response["commit_sha"] == task.commit_sha
|
||||
assert response["commit_committed_at"] == "2026-07-01T12:00:00.000000Z"
|
||||
|
||||
assert response["repository"]["canonical_url"] == "https://github.com/openai/codex"
|
||||
assert response["repository"]["host"] == "github.com"
|
||||
assert response["repository"]["id"] == repository.id
|
||||
assert response["repository"]["name"] == "codex"
|
||||
assert response["repository"]["owner"] == "openai"
|
||||
assert response["repository"]["participation_mode"] == repository.participation_mode
|
||||
assert response["repository"]["record_url"] =~ "/github.com/openai/codex"
|
||||
|
||||
assert response["creator"]["id"] == creator.id
|
||||
assert response["creator"]["handle"] == creator.handle
|
||||
assert response["creator"] |> Map.keys() |> Enum.sort() == ["handle", "id"]
|
||||
assert response["claimant"] == nil
|
||||
assert response["lease"] == nil
|
||||
assert response["contribution"] == nil
|
||||
assert response["job_url"] =~ "/jobs/#{task.id}"
|
||||
end
|
||||
|
||||
test "returns an empty list and 404s an unregistered repository", %{
|
||||
conn: conn,
|
||||
worker_token: token
|
||||
} do
|
||||
conn1 = conn |> authed(token) |> get(~p"/api/github.com/openai/codex/jobs")
|
||||
assert json_response(conn1, 200) == %{"jobs" => []}
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(token)
|
||||
|> get(~p"/api/github.com/unknown/repository/jobs")
|
||||
|
||||
assert json_response(conn2, 404)["error"] =~ "not registered"
|
||||
end
|
||||
|
||||
test "does not disclose a quarantined repository to an unrelated credential", %{
|
||||
conn: conn,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
_contained =
|
||||
repository
|
||||
|> Repository.listing_changeset(%{listing_status: "quarantined"})
|
||||
|> Repo.update!()
|
||||
|
||||
conn = conn |> authed(token) |> get(~p"/api/github.com/openai/codex/jobs")
|
||||
|
||||
assert json_response(conn, 404)["error"] =~ "not registered"
|
||||
end
|
||||
|
||||
test "shows a task and returns 404 for missing or malformed ids", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn1 = conn |> authed(token) |> get(~p"/api/jobs/#{task.id}")
|
||||
assert json_response(conn1, 200)["id"] == task.id
|
||||
|
||||
conn2 = conn |> recycle() |> authed(token) |> get("/api/jobs/not-an-id")
|
||||
assert json_response(conn2, 404)["error"] == "job not found"
|
||||
|
||||
conn3 = conn |> recycle() |> authed(token) |> get("/api/jobs/999999999")
|
||||
assert json_response(conn3, 404)["error"] == "job not found"
|
||||
end
|
||||
|
||||
test "claims a task and exposes the active lease", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn = conn |> authed(token) |> post(~p"/api/jobs/#{task.id}/claim")
|
||||
|
||||
response = json_response(conn, 200)
|
||||
assert response["status"] == "claimed"
|
||||
assert response["claimant"]["id"] == worker.id
|
||||
assert response["lease"]["active"] == true
|
||||
assert response["lease"]["claimed_at"]
|
||||
assert response["lease"]["expires_at"]
|
||||
|
||||
original_expiry = response["lease"]["expires_at"]
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(token)
|
||||
|> post(~p"/api/jobs/#{task.id}/claim")
|
||||
|
||||
repeated = json_response(conn, 200)
|
||||
assert repeated["claimant"]["id"] == worker.id
|
||||
assert repeated["lease"]["expires_at"] == original_expiry
|
||||
end
|
||||
|
||||
test "creators may claim their own jobs; active claims conflict for others", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
creator_token: creator_token,
|
||||
repository: repository,
|
||||
worker_token: worker_token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
# Solo/hosted workflow: job creators may claim and perform their own Jobs.
|
||||
conn1 = conn |> authed(creator_token) |> post(~p"/api/jobs/#{task.id}/claim")
|
||||
assert json_response(conn1, 200)["claimant"]["id"] == creator.id
|
||||
|
||||
conn_release =
|
||||
conn |> recycle() |> authed(creator_token) |> delete(~p"/api/jobs/#{task.id}/claim")
|
||||
|
||||
assert json_response(conn_release, 200)["status"] == "open"
|
||||
|
||||
conn2 = conn |> recycle() |> authed(worker_token) |> post(~p"/api/jobs/#{task.id}/claim")
|
||||
assert json_response(conn2, 200)
|
||||
|
||||
outsider = account_fixture()
|
||||
outsider_token = client_token(outsider)
|
||||
|
||||
conn3 = conn |> recycle() |> authed(outsider_token) |> post(~p"/api/jobs/#{task.id}/claim")
|
||||
assert json_response(conn3, 409)["error"] =~ "active claim"
|
||||
end
|
||||
|
||||
test "only the claimant can release a task", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: worker_token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
{:ok, task} = Work.claim_task(task, worker)
|
||||
outsider_token = client_token(account_fixture())
|
||||
|
||||
conn1 = conn |> authed(outsider_token) |> delete(~p"/api/jobs/#{task.id}/claim")
|
||||
assert json_response(conn1, 403)["error"] =~ "current claimant"
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(worker_token)
|
||||
|> delete(~p"/api/jobs/#{task.id}/claim")
|
||||
|
||||
response = json_response(conn2, 200)
|
||||
assert response["status"] == "open"
|
||||
assert response["claimant"] == nil
|
||||
assert response["lease"] == nil
|
||||
end
|
||||
|
||||
test "claimant can renew an active lease", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn = conn |> authed(token) |> post(~p"/api/jobs/#{task.id}/claim")
|
||||
first = json_response(conn, 200)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(token)
|
||||
|> post(~p"/api/jobs/#{task.id}/claim/renew")
|
||||
|
||||
renewed = json_response(conn, 200)
|
||||
assert renewed["lease"]["active"]
|
||||
assert renewed["lease"]["expires_at"] >= first["lease"]["expires_at"]
|
||||
end
|
||||
|
||||
test "completion path submits a claimed task for independent review", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
{:ok, task} = Work.claim_task(task, worker)
|
||||
|
||||
body = %{
|
||||
"provenance" => "hybrid",
|
||||
"summary" => "The authorization boundary is enforced.",
|
||||
"evidence" => "Reviewed both entry points and ran the focused regression test."
|
||||
}
|
||||
|
||||
conn = conn |> authed(token) |> post(~p"/api/jobs/#{task.id}/complete", body)
|
||||
|
||||
response = json_response(conn, 200)
|
||||
assert response["status"] == "submitted"
|
||||
assert response["visibility"] == "public"
|
||||
assert response["submitted_at"]
|
||||
assert response["completed_at"] == nil
|
||||
assert response["lease"]["active"] == false
|
||||
|
||||
assert response["contribution"] == %{
|
||||
"contributor" => %{
|
||||
"handle" => worker.handle,
|
||||
"id" => worker.id
|
||||
},
|
||||
"evidence" => body["evidence"],
|
||||
"id" => response["contribution"]["id"],
|
||||
"version" => 1,
|
||||
"provenance" => "hybrid",
|
||||
"submitted_at" => response["contribution"]["submitted_at"],
|
||||
"summary" => body["summary"]
|
||||
}
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(token)
|
||||
|> delete(~p"/api/jobs/#{task.id}/claim")
|
||||
|
||||
assert json_response(conn2, 403)["error"] =~ "current claimant"
|
||||
end
|
||||
|
||||
test "completion requires the active claimant and valid evidence metadata", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
|
||||
conn1 =
|
||||
conn
|
||||
|> authed(token)
|
||||
|> post(~p"/api/jobs/#{task.id}/complete", %{
|
||||
"provenance" => "human",
|
||||
"summary" => "Completed",
|
||||
"evidence" => "Attempted submission without first holding the task's active claim."
|
||||
})
|
||||
|
||||
assert json_response(conn1, 403)["error"] =~ "current claimant"
|
||||
|
||||
{:ok, _task} = Work.claim_task(task, worker)
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(token)
|
||||
|> post(~p"/api/jobs/#{task.id}/complete", %{
|
||||
"provenance" => "unverifiable",
|
||||
"summary" => ""
|
||||
})
|
||||
|
||||
errors = json_response(conn2, 422)["errors"]
|
||||
assert errors["provenance"] == ["is invalid"]
|
||||
assert errors["summary"] == ["can't be blank"]
|
||||
end
|
||||
|
||||
test "completion provenance must satisfy the requested capability", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator, %{"capability" => "hybrid"})
|
||||
{:ok, _task} = Work.claim_task(task, worker)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authed(token)
|
||||
|> post(~p"/api/jobs/#{task.id}/complete", %{
|
||||
"provenance" => "human",
|
||||
"summary" => "Manual review completed.",
|
||||
"evidence" => "Manually traced the requested paths and recorded reproducible notes."
|
||||
})
|
||||
|
||||
assert json_response(conn, 422)["errors"]["provenance"] == [
|
||||
"does not satisfy this task's required capability"
|
||||
]
|
||||
end
|
||||
|
||||
test "proposals are publicly listed and publishing is not exposed to client credentials", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
creator_token: creator_token,
|
||||
worker_token: worker_token
|
||||
} do
|
||||
proposal = proposed_review_task_fixture(repository, creator)
|
||||
|
||||
conn1 =
|
||||
conn
|
||||
|> authed(worker_token)
|
||||
|> get(~p"/api/github.com/openai/codex/jobs")
|
||||
|
||||
assert [listed] = json_response(conn1, 200)["jobs"]
|
||||
assert listed["id"] == proposal.id
|
||||
assert listed["status"] == "proposed"
|
||||
assert listed["visibility"] == "public"
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(creator_token)
|
||||
|> get(~p"/api/jobs/#{proposal.id}")
|
||||
|
||||
assert json_response(conn2, 200)["status"] == "proposed"
|
||||
|
||||
moderator = moderator_account_fixture()
|
||||
moderator_token = Accounts.create_account_api_token(moderator)
|
||||
|
||||
conn3 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(moderator_token)
|
||||
|> post("/api/jobs/#{proposal.id}/publish", %{
|
||||
"reason" => "The task scope is safe, bounded, and useful to contributors."
|
||||
})
|
||||
|
||||
assert response(conn3, 404) == "Not Found"
|
||||
|
||||
assert {:ok, open} =
|
||||
Work.publish_task(proposal, moderator, %{
|
||||
"reason" => "The task scope is safe, bounded, and useful to contributors."
|
||||
})
|
||||
|
||||
assert open.status == "open"
|
||||
end
|
||||
|
||||
test "acceptance is not exposed to client credentials", %{
|
||||
conn: conn,
|
||||
creator: creator,
|
||||
repository: repository,
|
||||
worker: worker,
|
||||
worker_token: worker_token
|
||||
} do
|
||||
task = review_task_fixture(repository, creator)
|
||||
{:ok, task} = Work.claim_task(task, worker)
|
||||
|
||||
{:ok, submitted} =
|
||||
Work.submit_task(task, worker, %{
|
||||
"provenance" => "human",
|
||||
"summary" => "The requested boundary was independently traced.",
|
||||
"evidence" => "Ran the negative authorization suite against both repository entry points."
|
||||
})
|
||||
|
||||
conn1 =
|
||||
conn
|
||||
|> authed(worker_token)
|
||||
|> post("/api/jobs/#{submitted.id}/accept", %{
|
||||
"reason" => "I should not be allowed to approve my own contribution.",
|
||||
"evidence" => "This evidence comes from the original contributor and is not independent."
|
||||
})
|
||||
|
||||
assert response(conn1, 404) == "Not Found"
|
||||
|
||||
reviewer = moderator_account_fixture()
|
||||
reviewer_token = Accounts.create_account_api_token(reviewer)
|
||||
assert Work.get_visible_task(submitted.id, Tarakan.Accounts.Scope.for_account(reviewer))
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(reviewer_token)
|
||||
|> post("/api/jobs/#{submitted.id}/accept", %{
|
||||
"reason" => "The result was independently reproduced at the pinned commit.",
|
||||
"evidence" => "Checked out the pinned SHA and ran the documented negative-path tests."
|
||||
})
|
||||
|
||||
assert response(conn2, 404) == "Not Found"
|
||||
|
||||
assert {:ok, accepted} =
|
||||
Work.accept_task(submitted, reviewer, %{
|
||||
"reason" => "The result was independently reproduced at the pinned commit.",
|
||||
"evidence" =>
|
||||
"Checked out the pinned SHA and ran the documented negative-path tests."
|
||||
})
|
||||
|
||||
assert accepted.status == "accepted"
|
||||
assert accepted.visibility == "public"
|
||||
|
||||
outsider = account_fixture()
|
||||
outsider_token = Accounts.create_account_api_token(outsider)
|
||||
|
||||
conn3 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(outsider_token)
|
||||
|> get(~p"/api/jobs/#{accepted.id}")
|
||||
|
||||
response = json_response(conn3, 200)
|
||||
assert response["status"] == "accepted"
|
||||
assert response["visibility"] == "public"
|
||||
assert response["contribution"]["summary"] =~ "boundary"
|
||||
assert response["contribution"]["evidence"] =~ "negative authorization suite"
|
||||
|
||||
assert {:ok, redacted} =
|
||||
Work.disclose_task(accepted, reviewer, "public_summary", %{
|
||||
"reason" =>
|
||||
"The redacted result is safe and useful without publishing raw evidence."
|
||||
})
|
||||
|
||||
conn4 =
|
||||
conn
|
||||
|> recycle()
|
||||
|> authed(outsider_token)
|
||||
|> get(~p"/api/jobs/#{redacted.id}")
|
||||
|
||||
response = json_response(conn4, 200)
|
||||
assert response["visibility"] == "public_summary"
|
||||
assert response["contribution"]["summary"] =~ "boundary"
|
||||
assert response["contribution"]["evidence"] == nil
|
||||
assert response["decisions"] == []
|
||||
assert response["disclosed_at"]
|
||||
assert response["sensitive_data_reviewed"] == false
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue