Initial commit on Forgejo
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s

Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
Maxfield Luke 2026-07-29 04:43:40 -04:00
commit af6077b9c3
455 changed files with 78366 additions and 0 deletions

View file

@ -0,0 +1,489 @@
defmodule TarakanWeb.AccountAuthTest do
use TarakanWeb.ConnCase, async: true
alias Phoenix.LiveView
alias Tarakan.Accounts
alias Tarakan.Accounts.ApiCredentials
alias Tarakan.Accounts.Scope
alias TarakanWeb.AccountAuth
import Tarakan.AccountsFixtures
@remember_me_cookie "_tarakan_web_account_remember_me"
@remember_me_cookie_max_age 60 * 60 * 24 * 60
setup %{conn: conn} do
conn =
conn
|> Map.replace!(:secret_key_base, TarakanWeb.Endpoint.config(:secret_key_base))
|> init_test_session(%{})
%{account: %{account_fixture() | authenticated_at: DateTime.utc_now(:second)}, conn: conn}
end
describe "log_in_account/3" do
test "stores the account token in the session", %{conn: conn, account: account} do
conn = AccountAuth.log_in_account(conn, account)
assert token = get_session(conn, :account_token)
assert get_session(conn, :live_socket_id) == Accounts.account_sessions_topic(account.id)
assert redirected_to(conn) == ~p"/"
assert Accounts.get_account_by_session_token(token)
end
test "clears everything previously stored in the session", %{conn: conn, account: account} do
conn = conn |> put_session(:to_be_removed, "value") |> AccountAuth.log_in_account(account)
refute get_session(conn, :to_be_removed)
end
test "keeps session when re-authenticating", %{conn: conn, account: account} do
conn =
conn
|> assign(:current_scope, Scope.for_account(account))
|> put_session(:to_be_removed, "value")
|> AccountAuth.log_in_account(account)
assert get_session(conn, :to_be_removed)
end
test "clears session when account does not match when re-authenticating", %{
conn: conn,
account: account
} do
other_account = account_fixture()
conn =
conn
|> assign(:current_scope, Scope.for_account(other_account))
|> put_session(:to_be_removed, "value")
|> AccountAuth.log_in_account(account)
refute get_session(conn, :to_be_removed)
end
test "redirects to the configured path", %{conn: conn, account: account} do
conn =
conn |> put_session(:account_return_to, "/hello") |> AccountAuth.log_in_account(account)
assert redirected_to(conn) == "/hello"
end
test "writes a cookie if remember_me is configured", %{conn: conn, account: account} do
conn =
conn |> fetch_cookies() |> AccountAuth.log_in_account(account, %{"remember_me" => "true"})
assert get_session(conn, :account_token) == conn.cookies[@remember_me_cookie]
assert get_session(conn, :account_remember_me) == true
assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
assert signed_token != get_session(conn, :account_token)
assert max_age == @remember_me_cookie_max_age
end
test "writes the persistent cookie without remember_me params (OAuth logins)", %{
conn: conn,
account: account
} do
conn = conn |> fetch_cookies() |> AccountAuth.log_in_account(account)
assert get_session(conn, :account_token) == conn.cookies[@remember_me_cookie]
assert get_session(conn, :account_remember_me) == true
assert %{max_age: @remember_me_cookie_max_age} = conn.resp_cookies[@remember_me_cookie]
end
test "redirects home when account is already logged in and no return_to is set", %{
conn: conn,
account: account
} do
conn =
conn
|> assign(:current_scope, Scope.for_account(account))
|> AccountAuth.log_in_account(account)
assert redirected_to(conn) == ~p"/"
end
test "writes a cookie if remember_me was set in previous session", %{
conn: conn,
account: account
} do
conn =
conn |> fetch_cookies() |> AccountAuth.log_in_account(account, %{"remember_me" => "true"})
assert get_session(conn, :account_token) == conn.cookies[@remember_me_cookie]
assert get_session(conn, :account_remember_me) == true
conn =
conn
|> recycle()
|> Map.replace!(:secret_key_base, TarakanWeb.Endpoint.config(:secret_key_base))
|> fetch_cookies()
|> init_test_session(%{account_remember_me: true})
# the conn is already logged in and has the remember_me cookie set,
# now we log in again and even without explicitly setting remember_me,
# the cookie should be set again
conn = conn |> AccountAuth.log_in_account(account, %{})
assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
assert signed_token != get_session(conn, :account_token)
assert max_age == @remember_me_cookie_max_age
assert get_session(conn, :account_remember_me) == true
end
end
describe "logout_account/1" do
test "erases session and cookies", %{conn: conn, account: account} do
account_token = Accounts.generate_account_session_token(account)
conn =
conn
|> put_session(:account_token, account_token)
|> put_req_cookie(@remember_me_cookie, account_token)
|> fetch_cookies()
|> AccountAuth.log_out_account()
refute get_session(conn, :account_token)
refute conn.cookies[@remember_me_cookie]
assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
assert redirected_to(conn) == ~p"/"
refute Accounts.get_account_by_session_token(account_token)
end
test "broadcasts to the given live_socket_id", %{conn: conn} do
live_socket_id = "accounts_sessions:abcdef-token"
TarakanWeb.Endpoint.subscribe(live_socket_id)
conn
|> put_session(:live_socket_id, live_socket_id)
|> AccountAuth.log_out_account()
assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id}
end
test "works even if account is already logged out", %{conn: conn} do
conn = conn |> fetch_cookies() |> AccountAuth.log_out_account()
refute get_session(conn, :account_token)
assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
assert redirected_to(conn) == ~p"/"
end
end
describe "fetch_current_scope_for_account/2" do
test "authenticates account from session", %{conn: conn, account: account} do
account_token = Accounts.generate_account_session_token(account)
conn =
conn
|> put_session(:account_token, account_token)
|> AccountAuth.fetch_current_scope_for_account([])
assert conn.assigns.current_scope.account.id == account.id
assert conn.assigns.current_scope.account.authenticated_at == account.authenticated_at
assert get_session(conn, :account_token) == account_token
end
test "authenticates account from cookies", %{conn: conn, account: account} do
logged_in_conn =
conn |> fetch_cookies() |> AccountAuth.log_in_account(account, %{"remember_me" => "true"})
account_token = logged_in_conn.cookies[@remember_me_cookie]
%{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
conn =
conn
|> put_req_cookie(@remember_me_cookie, signed_token)
|> AccountAuth.fetch_current_scope_for_account([])
assert conn.assigns.current_scope.account.id == account.id
assert conn.assigns.current_scope.account.authenticated_at == account.authenticated_at
assert get_session(conn, :account_token) == account_token
assert get_session(conn, :account_remember_me)
assert get_session(conn, :live_socket_id) == Accounts.account_sessions_topic(account.id)
end
test "does not authenticate if data is missing", %{conn: conn, account: account} do
_ = Accounts.generate_account_session_token(account)
conn = AccountAuth.fetch_current_scope_for_account(conn, [])
refute get_session(conn, :account_token)
refute conn.assigns.current_scope
end
test "reissues a new token after a few days and refreshes cookie", %{
conn: conn,
account: account
} do
logged_in_conn =
conn |> fetch_cookies() |> AccountAuth.log_in_account(account, %{"remember_me" => "true"})
token = logged_in_conn.cookies[@remember_me_cookie]
%{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
offset_account_token(token, -10, :day)
{account, _} = Accounts.get_account_by_session_token(token)
conn =
conn
|> put_session(:account_token, token)
|> put_session(:account_remember_me, true)
|> put_req_cookie(@remember_me_cookie, signed_token)
|> AccountAuth.fetch_current_scope_for_account([])
assert conn.assigns.current_scope.account.id == account.id
assert conn.assigns.current_scope.account.authenticated_at == account.authenticated_at
assert new_token = get_session(conn, :account_token)
assert new_token != token
assert %{value: new_signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
assert new_signed_token != signed_token
assert max_age == @remember_me_cookie_max_age
# The predecessor token is deleted so a stolen cookie dies at rotation.
refute Accounts.get_account_by_session_token(token)
assert Accounts.get_account_by_session_token(new_token)
end
end
describe "fetch_api_account/2" do
test "assigns credential scopes, repository boundary, and authentication method", %{
conn: conn,
account: account
} do
repository = github_repository_fixture(account)
assert {:ok, token, credential} =
ApiCredentials.create(account, %{
name: "repository worker",
scopes: ["tasks:read", "tasks:claim"],
repository_id: repository.id
})
conn =
conn
|> put_req_header("authorization", "Bearer #{token}")
|> AccountAuth.fetch_api_account([])
scope = conn.assigns.current_scope
assert scope.account_id == account.id
assert scope.token_id == credential.id
assert scope.token_scopes == MapSet.new(["tasks:read", "tasks:claim"])
assert scope.token_repository_id == repository.id
assert scope.authentication_method == :api_credential
end
test "rejects malformed and revoked credentials", %{conn: conn, account: account} do
invalid =
conn
|> put_req_header("authorization", "Bearer not-a-token")
|> AccountAuth.fetch_api_account([])
assert invalid.halted
assert invalid.status == 401
assert {:ok, token, credential} = ApiCredentials.create(account, %{name: "revoked"})
assert {:ok, _credential} = ApiCredentials.revoke(account, credential.id)
revoked =
conn
|> recycle()
|> put_req_header("authorization", "Bearer #{token}")
|> AccountAuth.fetch_api_account([])
assert revoked.halted
assert revoked.status == 401
end
end
describe "on_mount :mount_current_scope" do
setup %{conn: conn} do
%{conn: AccountAuth.fetch_current_scope_for_account(conn, [])}
end
test "assigns current_scope based on a valid account_token", %{conn: conn, account: account} do
account_token = Accounts.generate_account_session_token(account)
session = conn |> put_session(:account_token, account_token) |> get_session()
{:cont, updated_socket} =
AccountAuth.on_mount(:mount_current_scope, %{}, session, %LiveView.Socket{})
assert updated_socket.assigns.current_scope.account.id == account.id
end
test "assigns nil to current_scope assign if there isn't a valid account_token", %{conn: conn} do
account_token = "invalid_token"
session = conn |> put_session(:account_token, account_token) |> get_session()
{:cont, updated_socket} =
AccountAuth.on_mount(:mount_current_scope, %{}, session, %LiveView.Socket{})
assert updated_socket.assigns.current_scope == nil
end
test "assigns nil to current_scope assign if there isn't a account_token", %{conn: conn} do
session = conn |> get_session()
{:cont, updated_socket} =
AccountAuth.on_mount(:mount_current_scope, %{}, session, %LiveView.Socket{})
assert updated_socket.assigns.current_scope == nil
end
end
describe "on_mount :require_authenticated" do
test "authenticates current_scope based on a valid account_token", %{
conn: conn,
account: account
} do
account_token = Accounts.generate_account_session_token(account)
session = conn |> put_session(:account_token, account_token) |> get_session()
{:cont, updated_socket} =
AccountAuth.on_mount(:require_authenticated, %{}, session, %LiveView.Socket{})
assert updated_socket.assigns.current_scope.account.id == account.id
end
test "redirects to login page if there isn't a valid account_token", %{conn: conn} do
account_token = "invalid_token"
session = conn |> put_session(:account_token, account_token) |> get_session()
socket = %LiveView.Socket{
endpoint: TarakanWeb.Endpoint,
assigns: %{__changed__: %{}, flash: %{}}
}
{:halt, updated_socket} = AccountAuth.on_mount(:require_authenticated, %{}, session, socket)
assert updated_socket.assigns.current_scope == nil
end
test "redirects to login page if there isn't a account_token", %{conn: conn} do
session = conn |> get_session()
socket = %LiveView.Socket{
endpoint: TarakanWeb.Endpoint,
assigns: %{__changed__: %{}, flash: %{}}
}
{:halt, updated_socket} = AccountAuth.on_mount(:require_authenticated, %{}, session, socket)
assert updated_socket.assigns.current_scope == nil
end
end
describe "on_mount :require_sudo_mode" do
test "allows accounts that have authenticated in the last 10 minutes", %{
conn: conn,
account: account
} do
account_token = Accounts.generate_account_session_token(account)
session = conn |> put_session(:account_token, account_token) |> get_session()
socket = %LiveView.Socket{
endpoint: TarakanWeb.Endpoint,
assigns: %{__changed__: %{}, flash: %{}}
}
assert {:cont, _updated_socket} =
AccountAuth.on_mount(:require_sudo_mode, %{}, session, socket)
end
test "redirects when authentication is too old", %{conn: conn, account: account} do
nine_hours_ago = DateTime.utc_now(:second) |> DateTime.add(-9 * 60, :minute)
account = %{account | authenticated_at: nine_hours_ago}
account_token = Accounts.generate_account_session_token(account)
{account, token_inserted_at} = Accounts.get_account_by_session_token(account_token)
assert DateTime.compare(token_inserted_at, account.authenticated_at) == :gt
session = conn |> put_session(:account_token, account_token) |> get_session()
socket = %LiveView.Socket{
endpoint: TarakanWeb.Endpoint,
assigns: %{__changed__: %{}, flash: %{}}
}
assert {:halt, _updated_socket} =
AccountAuth.on_mount(:require_sudo_mode, %{}, session, socket)
end
test "redirects when there is no authenticated account", %{conn: conn} do
session = conn |> get_session()
socket = %LiveView.Socket{
endpoint: TarakanWeb.Endpoint,
assigns: %{__changed__: %{}, flash: %{}}
}
assert {:halt, updated_socket} =
AccountAuth.on_mount(:require_sudo_mode, %{}, session, socket)
assert updated_socket.assigns.current_scope == nil
end
end
describe "require_authenticated_account/2" do
setup %{conn: conn} do
%{conn: AccountAuth.fetch_current_scope_for_account(conn, [])}
end
test "redirects if account is not authenticated", %{conn: conn} do
conn = conn |> fetch_flash() |> AccountAuth.require_authenticated_account([])
assert conn.halted
assert redirected_to(conn) == ~p"/accounts/log-in"
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"You must log in to access this page."
end
test "stores the path to redirect to on GET", %{conn: conn} do
halted_conn =
%{conn | path_info: ["foo"], query_string: ""}
|> fetch_flash()
|> AccountAuth.require_authenticated_account([])
assert halted_conn.halted
assert get_session(halted_conn, :account_return_to) == "/foo"
halted_conn =
%{conn | path_info: ["foo"], query_string: "bar=baz"}
|> fetch_flash()
|> AccountAuth.require_authenticated_account([])
assert halted_conn.halted
assert get_session(halted_conn, :account_return_to) == "/foo?bar=baz"
halted_conn =
%{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
|> fetch_flash()
|> AccountAuth.require_authenticated_account([])
assert halted_conn.halted
refute get_session(halted_conn, :account_return_to)
end
test "does not redirect if account is authenticated", %{conn: conn, account: account} do
conn =
conn
|> assign(:current_scope, Scope.for_account(account))
|> AccountAuth.require_authenticated_account([])
refute conn.halted
refute conn.status
end
end
describe "disconnect_sessions/1" do
test "broadcasts disconnect messages for each account id" do
TarakanWeb.Endpoint.subscribe(Accounts.account_sessions_topic(11))
TarakanWeb.Endpoint.subscribe(Accounts.account_sessions_topic(22))
AccountAuth.disconnect_sessions([11, %{account_id: 22}])
assert_receive %Phoenix.Socket.Broadcast{
event: "disconnect",
topic: "accounts_sessions:11"
}
assert_receive %Phoenix.Socket.Broadcast{
event: "disconnect",
topic: "accounts_sessions:22"
}
end
end
end

View file

@ -0,0 +1,38 @@
defmodule TarakanWeb.CoreComponentsTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias TarakanWeb.CoreComponents
test "ordinary flash toasts auto-dismiss with kind-specific delays" do
info =
render_component(&CoreComponents.flash/1, %{
kind: :info,
flash: %{"info" => "Saved"}
})
error =
render_component(&CoreComponents.flash/1, %{
kind: :error,
flash: %{"error" => "Failed"}
})
assert info =~ ~s(phx-hook="AutoDismiss")
assert info =~ ~s(data-auto-dismiss-ms="5000")
assert error =~ ~s(data-auto-dismiss-ms="8000")
end
test "connection status alerts can opt out of auto-dismiss" do
html =
render_component(&CoreComponents.flash/1, %{
id: "connection-error",
kind: :error,
flash: %{"error" => "Disconnected"},
auto_dismiss: false
})
refute html =~ ~s(phx-hook="AutoDismiss")
refute html =~ "data-auto-dismiss-ms"
end
end

View file

@ -0,0 +1,219 @@
defmodule TarakanWeb.AccountSessionControllerTest do
use TarakanWeb.ConnCase, async: true
import Tarakan.AccountsFixtures
alias Tarakan.Accounts
setup do
%{unconfirmed_account: unconfirmed_account_fixture(), account: account_fixture()}
end
describe "POST /accounts/log-in - handle/email and password" do
test "logs the account in", %{conn: conn, account: account} do
account = set_password(account)
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{"identifier" => account.handle, "password" => valid_account_password()}
})
assert get_session(conn, :account_token)
assert redirected_to(conn) == ~p"/"
# Now do a logged in request and assert on the menu
conn = get(conn, ~p"/")
response = html_response(conn, 200)
assert response =~ "@#{account.handle}"
assert response =~ ~p"/accounts/settings"
assert response =~ ~p"/accounts/log-out"
end
test "logs the account in with remember me", %{conn: conn, account: account} do
account = set_password(account)
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{
"identifier" => account.email,
"password" => valid_account_password(),
"remember_me" => "true"
}
})
assert conn.resp_cookies["_tarakan_web_account_remember_me"]
assert redirected_to(conn) == ~p"/"
end
test "logs the account in with return to", %{conn: conn, account: account} do
account = set_password(account)
conn =
conn
|> init_test_session(account_return_to: "/foo/bar")
|> post(~p"/accounts/log-in", %{
"account" => %{
"identifier" => account.handle,
"password" => valid_account_password()
}
})
assert redirected_to(conn) == "/foo/bar"
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!"
end
test "redirects to login page with invalid credentials", %{conn: conn, account: account} do
conn =
post(conn, ~p"/accounts/log-in?mode=password", %{
"account" => %{"identifier" => account.handle, "password" => "invalid_password"}
})
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Invalid handle, email, or password"
assert redirected_to(conn) == ~p"/accounts/log-in"
end
end
describe "POST /accounts/log-in - magic link" do
test "logs the account in", %{conn: conn, account: account} do
{token, _hashed_token} = generate_account_magic_link_token(account)
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{"token" => token}
})
assert get_session(conn, :account_token)
assert redirected_to(conn) == ~p"/"
# Now do a logged in request and assert on the menu
conn = get(conn, ~p"/")
response = html_response(conn, 200)
assert response =~ "@#{account.handle}"
assert response =~ ~p"/accounts/settings"
assert response =~ ~p"/accounts/log-out"
end
test "confirms unconfirmed account", %{conn: conn, unconfirmed_account: account} do
{token, _hashed_token} = generate_account_magic_link_token(account)
refute account.confirmed_at
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{"token" => token},
"_action" => "confirmed"
})
assert get_session(conn, :account_token)
assert redirected_to(conn) == ~p"/"
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account confirmed successfully."
assert Accounts.get_account!(account.id).confirmed_at
# Now do a logged in request and assert on the menu
conn = get(conn, ~p"/")
response = html_response(conn, 200)
assert response =~ "@#{account.handle}"
assert response =~ ~p"/accounts/settings"
assert response =~ ~p"/accounts/log-out"
end
test "redirects to login page when magic link is invalid", %{conn: conn} do
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{"token" => "invalid"}
})
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"The link is invalid or it has expired."
assert redirected_to(conn) == ~p"/accounts/log-in"
end
test "redirects to login page for a crafted non-base64 magic link", %{conn: conn} do
conn =
post(conn, ~p"/accounts/log-in", %{
"account" => %{"token" => "not base64!"}
})
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"The link is invalid or it has expired."
assert redirected_to(conn) == ~p"/accounts/log-in"
end
end
describe "POST /accounts/update-password" do
test "updates the password for a confirmed account in sudo mode", %{
conn: conn,
account: account
} do
conn =
conn
|> log_in_account(account)
|> post(~p"/accounts/update-password", %{
"account" => %{
"password" => valid_account_password(),
"password_confirmation" => valid_account_password()
}
})
assert redirected_to(conn) == ~p"/accounts/settings"
assert Accounts.get_account_by_email_and_password(account.email, valid_account_password())
end
test "redirects to re-authentication when the sudo window expired", %{
conn: conn,
account: account
} do
stale_at = DateTime.add(DateTime.utc_now(:second), -3, :hour)
conn =
conn
|> log_in_account(account, token_authenticated_at: stale_at)
|> post(~p"/accounts/update-password", %{
"account" => %{
"password" => valid_account_password(),
"password_confirmation" => valid_account_password()
}
})
assert redirected_to(conn) == ~p"/accounts/log-in?return_to=%2Faccounts%2Fsettings"
refute Accounts.get_account_by_email_and_password(account.email, valid_account_password())
end
test "refuses to establish a password before the email is confirmed", %{
conn: conn,
unconfirmed_account: account
} do
conn =
conn
|> log_in_account(account)
|> post(~p"/accounts/update-password", %{
"account" => %{
"password" => valid_account_password(),
"password_confirmation" => valid_account_password()
}
})
assert redirected_to(conn) == ~p"/accounts/settings"
refute Accounts.get_account!(account.id).hashed_password
end
end
describe "DELETE /accounts/log-out" do
test "logs the account out", %{conn: conn, account: account} do
conn = conn |> log_in_account(account) |> delete(~p"/accounts/log-out")
assert redirected_to(conn) == ~p"/"
refute get_session(conn, :account_token)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
end
test "succeeds even if the account is not logged in", %{conn: conn} do
conn = delete(conn, ~p"/accounts/log-out")
assert redirected_to(conn) == ~p"/"
refute get_session(conn, :account_token)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
end
end
end

View file

@ -0,0 +1,148 @@
defmodule TarakanWeb.API.ClientAuthControllerTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Accounts.ApiCredentials
test "browser approval exchanges a device code for one scoped credential", %{conn: conn} do
start_response =
conn
|> post(~p"/api/client-auth/start", %{client_name: "Tarakan CLI on laptop"})
|> json_response(201)
assert start_response["device_code"] =~ "trkd_"
assert start_response["user_code"] =~ ~r/^[A-Z2-7]{4}-[A-Z2-7]{4}$/
assert start_response["verification_uri_complete"] =~
"/client/authorize/#{start_response["user_code"]}"
pending =
build_conn()
|> post(~p"/api/client-auth/exchange", %{device_code: start_response["device_code"]})
|> json_response(400)
assert pending == %{"error" => "authorization_pending"}
account = account_fixture()
{:ok, view, _html} =
build_conn()
|> log_in_account(account)
|> live(~p"/client/authorize/#{start_response["user_code"]}")
assert has_element?(view, "#client-authorization-code")
assert has_element?(view, "#client-authorization-approve-button")
view
|> element("#client-authorization-approve-button")
|> render_click()
assert has_element?(view, "#client-authorization-approved")
exchange_response =
build_conn()
|> post(~p"/api/client-auth/exchange", %{device_code: start_response["device_code"]})
|> json_response(200)
assert exchange_response["token"] =~ "trkn_"
assert exchange_response["token_type"] == "Bearer"
assert "tasks:read" in exchange_response["scopes"]
assert "tasks:claim" in exchange_response["scopes"]
assert "contributions:write" in exchange_response["scopes"]
assert "reviews:submit" in exchange_response["scopes"]
refute "reviews:verify" in exchange_response["scopes"]
refute "reviews:read" in exchange_response["scopes"]
assert {:ok, ^account, credential} = ApiCredentials.authenticate(exchange_response["token"])
assert credential.name == "Tarakan CLI on laptop"
# Device-minted credentials are short-lived (agent blast-radius control).
expires_in_days = DateTime.diff(credential.expires_at, DateTime.utc_now(), :day)
assert expires_in_days in 6..7
consumed =
build_conn()
|> post(~p"/api/client-auth/exchange", %{device_code: start_response["device_code"]})
|> json_response(400)
assert consumed == %{"error" => "invalid_device_code"}
revoke_conn =
build_conn()
|> put_req_header("authorization", "Bearer #{exchange_response["token"]}")
|> delete(~p"/api/client-auth/session")
assert response(revoke_conn, 204) == ""
assert :error = ApiCredentials.authenticate(exchange_response["token"])
end
test "the browser can deny a login without issuing a credential", %{conn: conn} do
start_response =
conn
|> post(~p"/api/client-auth/start", %{})
|> json_response(201)
{:ok, view, _html} =
build_conn()
|> log_in_account(account_fixture())
|> live(~p"/client/authorize/#{start_response["user_code"]}")
view
|> element("#client-authorization-deny-button")
|> render_click()
assert has_element?(view, "#client-authorization-denied")
denied =
build_conn()
|> post(~p"/api/client-auth/exchange", %{device_code: start_response["device_code"]})
|> json_response(403)
assert denied == %{"error" => "access_denied"}
end
test "authorization page requires a signed-in web account", %{conn: conn} do
start_response =
conn
|> post(~p"/api/client-auth/start", %{})
|> json_response(201)
assert {:error, {:redirect, %{to: path}}} =
live(build_conn(), ~p"/client/authorize/#{start_response["user_code"]}")
assert path == ~p"/accounts/log-in"
end
test "a signed-in session can approve without recent reauth", %{conn: conn} do
start_response =
conn
|> post(~p"/api/client-auth/start", %{})
|> json_response(201)
return_to = ~p"/client/authorize/#{start_response["user_code"]}"
stale_at = DateTime.add(DateTime.utc_now(:second), -9 * 60, :minute)
{:ok, view, _html} =
build_conn()
|> log_in_account(account_fixture(), token_authenticated_at: stale_at)
|> live(return_to)
assert has_element?(view, "#client-authorization-approve-button")
view
|> element("#client-authorization-approve-button")
|> render_click()
assert has_element?(view, "#client-authorization-approved")
end
test "invalid device codes fail without revealing authorization state", %{conn: conn} do
response =
conn
|> post(~p"/api/client-auth/exchange", %{device_code: "not-a-device-code"})
|> json_response(400)
assert response == %{"error" => "invalid_device_code"}
end
end

View file

@ -0,0 +1,142 @@
defmodule TarakanWeb.API.FindingControllerTest do
use TarakanWeb.ConnCase
setup do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
%{repository: repository, submitter: submitter}
end
test "returns a serialized public finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_with_disclosure_fixture()})
|> publish_scan("public")
[finding] = scan.findings
conn = get(conn, ~p"/api/findings/#{finding.public_id}")
assert %{"finding" => data} = json_response(conn, 200)
assert data["public_id"] == finding.public_id
assert data["title"] == finding.title
assert data["severity"] == "high"
assert data["status"] == "verified"
assert data["description"] =~ "String-built SQL"
assert data["reproduction_steps"] =~ "Run the payload"
assert data["affected_versions"] == ">= 1.0, < 2.0"
assert data["cwe_id"] == "CWE-89"
assert data["cve_id"] == "CVE-2026-12345"
assert data["file_path"] == "lib/example/module_1.ex"
assert data["line_start"] == 10
assert data["line_end"] == 15
assert data["first_seen_commit_sha"] == scan.commit_sha
assert data["last_seen_commit_sha"] == scan.commit_sha
assert data["verified_at"]
assert is_map(data["counters"])
assert data["repository"] == %{"host" => "github.com", "owner" => "openai", "name" => "codex"}
assert data["record_url"] == TarakanWeb.Endpoint.url() <> "/findings/#{finding.public_id}"
assert data["inserted_at"]
end
test "404s for a restricted finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Tarakan.Accounts.Scope.for_account(moderator_account_fixture()),
scan,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" => "Restricted in this test to keep it off the public API."
}
)
[finding] = scan.findings
conn = get(conn, ~p"/api/findings/#{finding.public_id}")
assert json_response(conn, 404)["error"] =~ "not found"
end
test "404s for a summary-only finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public_summary")
[finding] = scan.findings
conn = get(conn, ~p"/api/findings/#{finding.public_id}")
assert json_response(conn, 404)["error"] =~ "not found"
end
test "404s for an unknown public id", %{conn: conn} do
conn = get(conn, ~p"/api/findings/#{Ecto.UUID.generate()}")
assert json_response(conn, 404)["error"] =~ "not found"
end
defp findings_json_with_disclosure_fixture do
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/example/module_1.ex",
"line_start" => 10,
"line_end" => 15,
"severity" => "high",
"title" => "Unsanitized input reaches interpolated query (1)",
"description" => "String-built SQL executed with request parameters.",
"reproduction" => "1. Check out the pinned commit\n2. Run the payload",
"affected_versions" => ">= 1.0, < 2.0",
"cwe" => "CWE-89",
"cve" => "CVE-2026-12345"
}
]
})
end
defp publish_scan(scan, visibility) do
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
scope = Tarakan.Accounts.Scope.for_account(moderator_account_fixture())
if visibility == "public" do
scan.repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
end
{:ok, scan} =
Tarakan.Scans.accept_scan(scope, scan, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Two independent reviewers supplied reproducible evidence for the pinned commit."
})
{:ok, scan} =
Tarakan.Scans.update_visibility(scope, scan, visibility, %{
"moderation_reason" => "disclosure_reviewed",
"moderation_notes" =>
"Disclosure was separately reviewed for scope, secrets, and personal data.",
"sensitive_data_reviewed" => "true"
})
scan
end
end

View file

@ -0,0 +1,153 @@
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

View file

@ -0,0 +1,137 @@
defmodule TarakanWeb.API.InfestationControllerTest do
use TarakanWeb.ConnCase, async: true
import Ecto.Query, only: [from: 2]
alias Tarakan.FindingMemory
setup do
submitter = github_account_fixture()
other = github_account_fixture()
repo_a = listed_github_repository_fixture(submitter)
{:ok, repo_b} = Tarakan.Repositories.register_github_repository("acme/widget", other)
repo_b = listed_repository_fixture(repo_b)
title = "API infestation sample #{System.unique_integer([:positive])}"
findings = fn path ->
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => path,
"line_start" => 1,
"line_end" => 2,
"severity" => "high",
"title" => title,
"description" => "Shared issue class for the patterns API test."
}
]
})
end
scan_fixture(repo_a, submitter, %{"findings_json" => findings.("lib/a.ex")})
scan_fixture(repo_b, other, %{"findings_json" => findings.("src/b.rs")})
%{pattern_key: FindingMemory.pattern_key(title), title: title}
end
test "lists patterns", %{conn: conn, pattern_key: pattern_key} do
conn = get(conn, ~p"/api/infestations")
assert %{"patterns" => patterns} = json_response(conn, 200)
assert pattern = Enum.find(patterns, &(&1["pattern_key"] == pattern_key))
assert pattern["repo_count"] >= 2
assert pattern["instance_count"] >= 2
assert pattern["record_url"] =~ "/infestations/#{pattern_key}"
end
test "whitelists query params", %{conn: conn, pattern_key: pattern_key} do
conn = get(conn, ~p"/api/infestations?days=13&min_repos=0&limit=5000")
assert %{"patterns" => _} = json_response(conn, 200)
conn = get(conn, ~p"/api/infestations?days=7&min_repos=2&limit=10")
assert %{"patterns" => patterns} = json_response(conn, 200)
assert Enum.any?(patterns, &(&1["pattern_key"] == pattern_key))
end
test "shows a pattern with its first page of instances", %{
conn: conn,
pattern_key: pattern_key,
title: title
} do
conn = get(conn, ~p"/api/infestations/#{pattern_key}")
assert %{"pattern" => pattern, "instances" => instances} = json_response(conn, 200)
assert pattern["pattern_key"] == pattern_key
assert pattern["title"] =~ title
assert length(instances) >= 2
assert Enum.any?(instances, &(&1["repository"]["owner"] == "openai"))
assert Enum.any?(instances, &(&1["repository"]["owner"] == "acme"))
end
test "404s for an unknown pattern", %{conn: conn} do
conn = get(conn, ~p"/api/infestations/no-such-pattern")
assert json_response(conn, 404)["error"] =~ "not found"
end
describe "vaccine packs" do
alias Tarakan.Accounts.Scope
alias Tarakan.FindingSignals
alias Tarakan.Repo
alias Tarakan.Scans.CanonicalFinding
# The endpoint must refuse to invent a detector. It used to always return
# one, and the one it returned matched nothing.
test "404s while nobody has written a validated detector", %{
conn: conn,
pattern_key: pattern_key
} do
conn = get(conn, ~p"/api/infestations/#{pattern_key}/vaccine.yaml")
assert %{"error" => error} = json_response(conn, 404)
assert error =~ "no validated detector"
end
test "serves the validated rule once one exists", %{conn: conn, pattern_key: pattern_key} do
# Put this infestation's findings into one code cluster.
cluster = "code:vaccinetest"
Repo.all(from f in CanonicalFinding, where: f.pattern_key == ^pattern_key)
|> Enum.each(fn finding ->
finding
|> CanonicalFinding.embedding_changeset(%{code_pattern_key: cluster})
|> Repo.update!()
end)
scope = Scope.for_account(reviewer_account_fixture())
{:ok, _rule} =
FindingSignals.put_rule(scope, cluster, %{
engine: "semgrep",
language: "elixir",
rule_yaml: """
rules:
- id: tarakan-real-detector
message: Shared issue class
severity: ERROR
languages: [elixir]
patterns:
- pattern: System.cmd($C, $A)
""",
checked_count: 2,
matched_count: 2,
matched_finding_ids: [Ecto.UUID.generate(), Ecto.UUID.generate()]
})
conn = get(conn, ~p"/api/infestations/#{pattern_key}/vaccine.yaml")
assert body = response(conn, 200)
assert get_resp_header(conn, "content-type") |> hd() =~ "application/x-yaml"
assert body =~ "tarakan-real-detector"
assert body =~ "matched 2 of 2 known instances"
refute body =~ "tarakan-vaccine-placeholder"
end
end
end

View file

@ -0,0 +1,66 @@
defmodule TarakanWeb.API.LeaderboardControllerTest do
use TarakanWeb.ConnCase
setup do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public")
%{submitter: submitter}
end
test "returns ranked rows", %{conn: conn, submitter: submitter} do
conn = get(conn, ~p"/api/leaderboard")
assert %{"leaderboard" => rows} = json_response(conn, 200)
assert row = Enum.find(rows, &(&1["handle"] == submitter.handle))
assert row["rank"] == 1
assert row["reputation"]["total"] > 0
assert is_map(row["stats"])
assert row["slashed_stakes"] == 0
assert row["profile_url"] == TarakanWeb.Endpoint.url() <> "/" <> submitter.handle
end
test "whitelists sort, severity, and window params", %{conn: conn} do
conn = get(conn, ~p"/api/leaderboard?sort=reviews&severity=high&window=7")
assert %{"leaderboard" => _} = json_response(conn, 200)
conn = get(conn, ~p"/api/leaderboard?sort=bogus&severity=bogus&window=bogus")
assert %{"leaderboard" => _} = json_response(conn, 200)
end
defp publish_scan(scan, visibility) do
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
scope = Tarakan.Accounts.Scope.for_account(moderator_account_fixture())
if visibility == "public" do
scan.repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
end
{:ok, scan} =
Tarakan.Scans.accept_scan(scope, scan, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Two independent reviewers supplied reproducible evidence for the pinned commit."
})
{:ok, scan} =
Tarakan.Scans.update_visibility(scope, scan, visibility, %{
"moderation_reason" => "disclosure_reviewed",
"moderation_notes" =>
"Disclosure was separately reviewed for scope, secrets, and personal data.",
"sensitive_data_reviewed" => "true"
})
scan
end
end

View file

@ -0,0 +1,88 @@
defmodule TarakanWeb.API.RepositoryControllerTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Accounts.ApiCredentials
alias Tarakan.Repositories
setup %{conn: conn} do
account = github_account_fixture()
token = api_token(account)
%{conn: conn, account: account, token: token}
end
defp authed(conn, token), do: put_req_header(conn, "authorization", "Bearer #{token}")
defp api_token(account) do
{:ok, token, _credential} =
ApiCredentials.create(account, %{
name: "registry importer",
scopes: ["findings:submit"]
})
token
end
test "rejects registration without a token", %{conn: conn} do
conn = post(conn, ~p"/api/repositories", %{"url" => "openai/codex"})
assert json_response(conn, 401)["error"] =~ "API token"
end
test "registers a public repository by owner/name", %{conn: conn, token: token} do
conn = conn |> authed(token) |> post(~p"/api/repositories", %{"url" => "openai/codex"})
assert %{"repository" => repo} = json_response(conn, 200)
assert repo["owner"] == "openai"
assert repo["name"] == "codex"
assert repo["host"] == "github.com"
assert repo["record_url"] =~ "/github.com/openai/codex"
end
test "is idempotent on re-register", %{conn: conn, token: token, account: account} do
{:ok, first} = Repositories.register_github_repository("openai/codex", account)
conn = conn |> authed(token) |> post(~p"/api/repositories", %{"url" => "openai/codex"})
assert %{"repository" => repo} = json_response(conn, 200)
assert repo["owner"] == first.owner
assert repo["name"] == first.name
end
test "requires a url", %{conn: conn, token: token} do
conn = conn |> authed(token) |> post(~p"/api/repositories", %{})
assert json_response(conn, 422)["errors"]["url"] == ["is required"]
end
test "lists reviewable repositories", %{conn: conn, token: token, account: account} do
{:ok, _repo} = Repositories.register_github_repository("openai/codex", account)
conn = conn |> authed(token) |> get(~p"/api/repositories?status=unscanned")
assert %{"repositories" => repos} = json_response(conn, 200)
assert Enum.any?(repos, &(&1["owner"] == "openai" and &1["name"] == "codex"))
end
test "admin credentials are not registration rate limited", %{conn: conn} do
admin =
github_account_fixture()
|> then(fn account ->
account
|> Tarakan.Accounts.Account.authorization_changeset(%{
state: "active",
platform_role: "admin",
trust_tier: "reviewer"
})
|> Tarakan.Repo.update!()
end)
token = api_token(admin)
# Well above the normal mutation (20) and repository_fetch (10) windows.
for _ <- 1..35 do
conn =
conn
|> recycle()
|> authed(token)
|> post(~p"/api/repositories", %{"url" => "openai/codex"})
assert json_response(conn, 200)["repository"]["name"] == "codex"
end
end
end

View file

@ -0,0 +1,548 @@
defmodule TarakanWeb.API.ScanControllerTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Accounts.ApiCredentials
alias Tarakan.Accounts.Scope
alias Tarakan.Repositories
alias Tarakan.Repositories.Repository
alias Tarakan.Repo
alias Tarakan.Scans
setup %{conn: conn} do
account = github_account_fixture()
repository = github_repository_fixture(account)
token = api_token(account)
%{conn: conn, account: account, repository: repository, token: token}
end
defp authed(conn, token), do: put_req_header(conn, "authorization", "Bearer #{token}")
defp api_token(account) do
{:ok, token, _credential} =
ApiCredentials.create(account, %{
name: "Scan submitter",
scopes: ["findings:submit"]
})
token
end
defp scan_body(overrides \\ %{}) do
Map.merge(
%{
"commit_sha" => random_commit_sha(),
"model" => "claude-sonnet-5",
"prompt_version" => "tarakan-baseline/v1",
"run_id" => "api-run-#{System.unique_integer([:positive, :monotonic])}",
"document" => %{"tarakan_scan_format" => 1, "findings" => []}
},
overrides
)
end
test "rejects requests without a token", %{conn: conn} do
conn = post(conn, ~p"/api/github.com/openai/codex/reports", scan_body())
assert json_response(conn, 401)["error"] =~ "API token"
end
test "rejects requests with an invalid or revoked token", %{conn: conn, account: account} do
conn1 =
conn |> authed("garbage") |> post(~p"/api/github.com/openai/codex/reports", scan_body())
assert json_response(conn1, 401)
old_token = api_token(account)
[credential | _rest] = ApiCredentials.list(account)
{:ok, _credential} = ApiCredentials.revoke(account, credential.id)
conn2 =
conn |> authed(old_token) |> post(~p"/api/github.com/openai/codex/reports", scan_body())
assert json_response(conn2, 401)
end
test "404s for a repository not in the registry", %{conn: conn, token: token} do
conn = conn |> authed(token) |> post(~p"/api/github.com/unknown/repo/reports", scan_body())
assert json_response(conn, 404)["error"] =~ "not registered"
end
test "does not disclose a quarantined repository to an unrelated credential", %{
conn: conn,
repository: repository
} do
_contained =
repository
|> Repository.listing_changeset(%{listing_status: "quarantined"})
|> Repo.update!()
outsider_token = api_token(account_fixture())
conn =
conn
|> authed(outsider_token)
|> post(~p"/api/github.com/openai/codex/reports", scan_body())
assert json_response(conn, 404)["error"] =~ "not registered"
end
test "records a clean scan", %{conn: conn, token: token} do
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", scan_body())
response = json_response(conn, 201)
assert response["findings_count"] == 0
assert response["verified"] == false
assert response["review_status"] == "quarantined"
assert response["visibility"] == "public"
assert response["provenance_attestation"] == "self_reported"
assert response["repository"] == "openai/codex"
assert response["record_url"] =~ "/github.com/openai/codex"
repository = Repositories.get_github_repository("openai", "codex")
assert repository.status == "reviewed"
assert repository.scan_count == 1
end
test "records a scan with findings", %{conn: conn, token: token} do
document = Jason.decode!(findings_json_fixture(2))
body = scan_body(%{"document" => document, "notes" => "run #7 of the nightly sweep"})
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
response = json_response(conn, 201)
assert response["findings_count"] == 2
assert response["kind"] == "report"
assert response["disclosed"] == true
assert is_list(response["findings"])
assert length(response["findings"]) == 2
assert hd(response["findings"])["url"] =~ "/findings/"
repository = Repositories.get_github_repository("openai", "codex")
assert repository.status == "findings"
assert repository.open_findings_count == 2
end
test "mass report path works without a job claim", %{conn: conn, token: token} do
document = Jason.decode!(findings_json_fixture(1))
body = scan_body(%{"document" => document, "provenance" => "agent"})
conn =
conn
|> authed(token)
|> post(~p"/api/github.com/openai/codex/reports", body)
response = json_response(conn, 201)
assert response["kind"] == "report"
assert response["visibility"] == "public"
assert response["findings_count"] == 1
assert hd(response["findings"])["public_id"]
end
test "cannot self-accept or self-publish through submission attributes", %{
conn: conn,
token: token
} do
body =
scan_body(%{
"review_status" => "accepted",
"visibility" => "public",
"verified_at" => DateTime.utc_now()
})
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
response = json_response(conn, 201)
assert response["review_status"] == "quarantined"
assert response["visibility"] == "public"
assert response["verified"] == false
end
test "records a human-authored review without model metadata", %{conn: conn, token: token} do
body = %{
"commit_sha" => random_commit_sha(),
"provenance" => "human",
"review_kind" => "business_logic",
"notes" => "Manually traced organization ownership transfer.",
"document" => Jason.decode!(findings_json_fixture(1))
}
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
response = json_response(conn, 201)
assert response["provenance"] == "human"
assert response["review_kind"] == "business_logic"
assert response["model"] == nil
assert response["prompt_version"] == nil
assert response["findings_count"] == 1
end
test "requires the scan document", %{conn: conn, token: token} do
body = Map.delete(scan_body(), "document")
conn1 = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
assert %{"document" => [message]} = json_response(conn1, 422)["errors"]
assert message =~ "required"
conn2 =
conn
|> authed(token)
|> post(~p"/api/github.com/openai/codex/reports", scan_body(%{"document" => "[]"}))
assert json_response(conn2, 422)["errors"]["document"]
end
test "rejects an invalid document with the parser's message", %{conn: conn, token: token} do
body = scan_body(%{"document" => %{"tarakan_scan_format" => 2, "findings" => []}})
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
assert %{"findings_json" => [message]} = json_response(conn, 422)["errors"]
assert message == "tarakan_scan_format must be 1"
end
test "rejects envelope validation errors", %{conn: conn, token: token} do
body = scan_body(%{"commit_sha" => "abc123", "model" => nil})
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
errors = json_response(conn, 422)["errors"]
assert errors["commit_sha"] == ["must be a full 40-character commit SHA"]
assert errors["model"] == ["can't be blank"]
end
test "rejects a commit GitHub does not know", %{conn: conn, token: token} do
body = scan_body(%{"commit_sha" => "dead" <> String.duplicate("0", 36)})
conn = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
assert json_response(conn, 422)["errors"]["commit_sha"] == [
"commit not found in this repository on GitHub"
]
end
test "blocks retrying the same run id", %{conn: conn, token: token} do
body = scan_body()
conn1 = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
assert json_response(conn1, 201)
conn2 = conn |> authed(token) |> post(~p"/api/github.com/openai/codex/reports", body)
assert json_response(conn2, 422)["errors"]["run_id"] == [
"this agent run was already submitted"
]
end
describe "GET /reports and verdict" do
setup %{repository: repository, account: submitter} do
scan =
scan_fixture(repository, submitter, %{
"findings_json" => findings_json_fixture(1)
})
reviewer = reviewer_tier_account_fixture()
%{scan: scan, reviewer: reviewer, reviewer_token: reviews_token(reviewer)}
end
test "reviewer-tier reviews:read token sees restricted findings", %{
conn: conn,
reviewer_token: token,
scan: scan
} do
scan = restrict_scan(scan)
body =
conn
|> authed(token)
|> get(~p"/api/github.com/openai/codex/reports")
|> json_response(200)
entry = Enum.find(body["reports"], &(&1["id"] == scan.id))
assert entry["details_visible"]
assert length(entry["findings"]) == 1
assert hd(entry["findings"])["severity"]
end
test "returns compact canonical memory for reconciliation", %{
conn: conn,
token: token,
scan: scan
} do
body =
conn
|> authed(token)
|> get(~p"/api/github.com/openai/codex/memory?commit_sha=#{scan.commit_sha}")
|> json_response(200)
assert body["target_commit_sha"] == scan.commit_sha
assert [finding] = body["findings"]
assert finding["same_commit"]
assert finding["status"] == "open"
assert finding["detections_count"] == 1
assert finding["public_id"]
# Nothing disputed yet, but the corpus ships on every response so clients
# can rely on the key existing.
assert body["suppressions"]["repository"] == []
assert body["suppressions"]["patterns"] == []
assert body["suppressions"]["note"] =~ "non-bugs"
end
test "memory carries disputed findings as suppressions", %{
conn: conn,
token: token,
scan: scan
} do
[occurrence] = scan.findings
Tarakan.Repo.get!(Tarakan.Scans.CanonicalFinding, occurrence.canonical_finding_id)
|> Ecto.Changeset.change(status: "disputed", disputes_count: 2)
|> Tarakan.Repo.update!()
body =
conn
|> authed(token)
|> get(~p"/api/github.com/openai/codex/memory?commit_sha=#{scan.commit_sha}")
|> json_response(200)
assert [suppression] = body["suppressions"]["repository"]
assert suppression["scope"] == "repository"
assert suppression["fingerprint"]
assert suppression["file_path"]
end
test "records a check on one canonical finding", %{
conn: conn,
scan: scan
} do
token = reviews_token(moderator_account_fixture())
memory =
conn
|> authed(token)
|> get(~p"/api/github.com/openai/codex/memory?commit_sha=#{scan.commit_sha}")
|> json_response(200)
[finding] = memory["findings"]
body =
conn
|> recycle()
|> authed(token)
|> post(~p"/api/github.com/openai/codex/findings/#{finding["public_id"]}/check", %{
"commit_sha" => scan.commit_sha,
"verdict" => "confirmed",
"provenance" => "human",
"notes" => "Independently reproduced this individual finding at the pinned commit."
})
|> json_response(201)
assert body["status"] == "open"
assert body["confirmations_count"] == 1
end
test "a plain findings:submit token cannot see restricted findings", %{
conn: conn,
token: token,
scan: scan
} do
scan = restrict_scan(scan)
body =
conn
|> authed(token)
|> get(~p"/api/github.com/openai/codex/reports")
|> json_response(200)
# After a moderator takedown, the submitter's own restricted scan is not
# exposed to a non-reviewer token.
refute Enum.any?(body["reports"], &(&1["id"] == scan.id and &1["findings"] != []))
end
test "records a verdict with a proof-of-concept", %{
conn: conn,
reviewer_token: token,
scan: scan
} do
body =
conn
|> authed(token)
|> post(~p"/api/github.com/openai/codex/reports/#{scan.id}/check", %{
"verdict" => "confirmed",
"provenance" => "agent",
"notes" => "Reproduced the reported issue against the pinned commit source.",
"evidence" => "import test; test('repro', t => t.throws(() => vulnerable()))"
})
|> json_response(201)
assert length(body["confirmations"]) == 1
assert hd(body["confirmations"])["verdict"] == "confirmed"
end
test "the submitter cannot verify their own review", %{
conn: conn,
account: submitter,
scan: scan
} do
token = reviews_token(reviewer_tier(submitter))
conn =
conn
|> authed(token)
|> post(~p"/api/github.com/openai/codex/reports/#{scan.id}/check", %{
"verdict" => "confirmed",
"notes" => "Trying to verify my own submission, which must be refused."
})
assert json_response(conn, 409)["error"] =~ "submitter"
end
test "a read-only reviewer credential cannot record a verdict", %{conn: conn, scan: scan} do
# Reviewer-tier account (can see the scan) but the credential lacks
# reviews:verify, so recording a verdict is forbidden.
{:ok, read_only, _cred} =
ApiCredentials.create(reviewer_tier_account_fixture(), %{
name: "Read-only reviewer",
scopes: ["reviews:read"]
})
conn =
conn
|> authed(read_only)
|> post(~p"/api/github.com/openai/codex/reports/#{scan.id}/check", %{
"verdict" => "confirmed",
"notes" => "Has read access but no verify scope, so this must be denied."
})
assert json_response(conn, 403)["error"] =~ "not authorized"
end
test "a token that cannot see the review gets 404, not a leak", %{
conn: conn,
token: token,
scan: scan
} do
scan = restrict_scan(scan)
# A plain findings:submit token lacks read scope, so the restricted scan
# is invisible - the endpoint must 404 rather than reveal it exists.
conn =
conn
|> authed(token)
|> post(~p"/api/github.com/openai/codex/reports/#{scan.id}/check", %{
"verdict" => "confirmed",
"notes" => "This token cannot see the scan, so it must not verify it."
})
assert json_response(conn, 404)["error"] =~ "not found"
end
test "an unrelated credential cannot distinguish a quarantined repository", %{
conn: conn,
repository: repository,
scan: scan
} do
_contained =
repository
|> Repository.listing_changeset(%{listing_status: "quarantined"})
|> Repo.update!()
outsider_token = reviews_token(account_fixture())
conn1 =
conn
|> authed(outsider_token)
|> get(~p"/api/github.com/openai/codex/reports")
assert json_response(conn1, 404)["error"] =~ "not registered"
conn2 =
conn
|> recycle()
|> authed(outsider_token)
|> post(~p"/api/github.com/openai/codex/reports/#{scan.id}/check", %{
"verdict" => "confirmed",
"notes" => "Quarantined repositories must look exactly like unregistered ones."
})
assert json_response(conn2, 404)["error"] =~ "not registered"
end
test "the submitter keeps access to their quarantined repository", %{
conn: conn,
repository: repository,
account: submitter,
scan: scan
} do
_contained =
repository
|> Repository.listing_changeset(%{listing_status: "quarantined"})
|> Repo.update!()
{:ok, read_token, _credential} =
ApiCredentials.create(submitter, %{name: "Reader", scopes: ["reviews:read"]})
body =
conn
|> authed(read_token)
|> get(~p"/api/github.com/openai/codex/reports")
|> json_response(200)
assert Enum.any?(body["reports"], &(&1["id"] == scan.id))
end
test "an unrelated credential cannot list a pending repository's reviews", %{
conn: conn,
repository: repository
} do
_pending =
repository
|> Repository.listing_changeset(%{listing_status: "pending"})
|> Repo.update!()
outsider_token = reviews_token(account_fixture())
conn =
conn
|> authed(outsider_token)
|> get(~p"/api/github.com/openai/codex/reports")
assert json_response(conn, 404)["error"] =~ "not registered"
end
end
defp restrict_scan(scan) do
moderator_scope = Scope.for_account(moderator_account_fixture())
{:ok, scan} =
Scans.update_visibility(moderator_scope, scan, "restricted", %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" => "Moderator takedown recorded for this visibility boundary test."
})
scan
end
defp reviewer_tier_account_fixture do
account_fixture() |> reviewer_tier()
end
defp reviewer_tier(account) do
account
|> Tarakan.Accounts.Account.authorization_changeset(%{
state: "active",
platform_role: "member",
trust_tier: "reviewer"
})
|> Repo.update!()
end
defp reviews_token(account) do
{:ok, token, _credential} =
ApiCredentials.create(account, %{
name: "Verifier",
scopes: ["reviews:read", "reviews:verify"]
})
token
end
end

View 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

View file

@ -0,0 +1,71 @@
defmodule TarakanWeb.BadgeControllerTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Repo
alias Tarakan.Scans.CanonicalFinding
defp fixed_finding_fixture(repository, days_to_fix) do
now = DateTime.utc_now()
%CanonicalFinding{}
|> Ecto.Changeset.change(%{
repository_id: repository.id,
fingerprint: "fp-#{System.unique_integer([:positive])}",
file_path: "lib/app.ex",
line_start: 1,
severity: "high",
title: "Finding",
description: "Body.",
status: "fixed",
first_seen_commit_sha: random_commit_sha(),
last_seen_commit_sha: random_commit_sha(),
fixed_commit_sha: random_commit_sha(),
verified_at: DateTime.add(now, -days_to_fix * 86_400, :second),
fixed_at: now,
inserted_at: now,
updated_at: now
})
|> Repo.insert!()
end
test "serves an SVG carrying the median fix time", %{conn: conn} do
repository = listed_github_repository_fixture()
fixed_finding_fixture(repository, 3)
conn = get(conn, ~p"/badges/github.com/#{repository.owner}/#{repository.name}")
assert response_content_type(conn, :svg) =~ "image/svg+xml"
body = response(conn, 200)
assert body =~ "<svg"
assert body =~ "security"
assert body =~ "3d median fix"
assert get_resp_header(conn, "cache-control") == ["public, max-age=900"]
end
test "resolves a hosted repository from its bare slug", %{conn: conn} do
repository = listed_hosted_repository_fixture()
conn = get(conn, ~p"/badges/#{repository.owner}/#{repository.name}")
assert response(conn, 200) =~ "no findings"
end
test "does not leak whether an unlisted repository exists", %{conn: conn} do
repository =
github_repository_fixture()
|> Tarakan.Repositories.Repository.listing_changeset(%{listing_status: "quarantined"})
|> Repo.update!()
quarantined =
get(conn, ~p"/badges/github.com/#{repository.owner}/#{repository.name}")
|> response(200)
missing =
get(conn, ~p"/badges/github.com/nobody/nothing")
|> response(200)
assert quarantined =~ "not listed"
assert quarantined == missing
end
end

View file

@ -0,0 +1,14 @@
defmodule TarakanWeb.ErrorHTMLTest do
use TarakanWeb.ConnCase, async: true
# Bring render_to_string/4 for testing custom views
import Phoenix.Template, only: [render_to_string: 4]
test "renders 404.html" do
assert render_to_string(TarakanWeb.ErrorHTML, "404", "html", []) == "Not Found"
end
test "renders 500.html" do
assert render_to_string(TarakanWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
end
end

View file

@ -0,0 +1,12 @@
defmodule TarakanWeb.ErrorJSONTest do
use TarakanWeb.ConnCase, async: true
test "renders 404" do
assert TarakanWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
end
test "renders 500" do
assert TarakanWeb.ErrorJSON.render("500.json", %{}) ==
%{errors: %{detail: "Internal Server Error"}}
end
end

View file

@ -0,0 +1,143 @@
defmodule TarakanWeb.FeedControllerTest do
use TarakanWeb.ConnCase
alias Tarakan.FindingMemory
test "the findings feed is a well-formed Atom document with absolute entry links", %{
conn: conn
} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public")
[finding] = scan.findings
base = TarakanWeb.Endpoint.url()
conn = get(conn, ~p"/feeds/findings.xml")
assert response_content_type(conn, :xml) =~ "application/atom+xml"
assert get_resp_header(conn, "cache-control") == ["public, max-age=300"]
body = response(conn, 200)
assert_well_formed(body)
assert body =~ ~s(<feed xmlns="http://www.w3.org/2005/Atom">)
assert body =~ ~s(<link rel="self" href="#{base}/feeds/findings.xml"/>)
assert body =~ "<id>#{base}/findings/#{finding.public_id}</id>"
assert body =~ ~s(<link href="#{base}/findings/#{finding.public_id}"/>)
assert body =~ finding.title
end
test "the findings feed excludes open and restricted findings", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
open_scan =
scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
restricted =
scan_fixture(repository, account_fixture(), %{"findings_json" => findings_json_fixture(1)})
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Tarakan.Accounts.Scope.for_account(moderator_account_fixture()),
restricted,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" => "Restricted in this test to keep it out of the feed."
}
)
body = conn |> get(~p"/feeds/findings.xml") |> response(200)
for scan <- [open_scan, restricted], finding <- scan.findings do
refute body =~ to_string(finding.public_id)
end
end
test "the infestations feed is a well-formed Atom document with absolute entry links", %{
conn: conn
} do
submitter = github_account_fixture()
other = github_account_fixture()
repo_a = listed_github_repository_fixture(submitter)
{:ok, repo_b} = Tarakan.Repositories.register_github_repository("acme/widget", other)
repo_b = listed_repository_fixture(repo_b)
title = "Feed infestation sample #{System.unique_integer([:positive])}"
findings = fn path ->
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => path,
"severity" => "high",
"title" => title,
"description" => "Shared issue class for the patterns feed test."
}
]
})
end
scan_fixture(repo_a, submitter, %{"findings_json" => findings.("lib/a.ex")})
scan_fixture(repo_b, other, %{"findings_json" => findings.("src/b.rs")})
pattern_key = FindingMemory.pattern_key(title)
base = TarakanWeb.Endpoint.url()
conn = get(conn, ~p"/feeds/infestations.xml")
assert response_content_type(conn, :xml) =~ "application/atom+xml"
body = response(conn, 200)
assert_well_formed(body)
assert body =~ ~s(<link rel="self" href="#{base}/feeds/infestations.xml"/>)
assert body =~ "<id>#{base}/infestations/#{pattern_key}</id>"
assert body =~ title
end
defp assert_well_formed(xml) do
{document, rest} = :xmerl_scan.string(String.to_charlist(xml))
assert rest == []
assert {:xmlElement, :feed, :feed, _, _, _, _, _, _, _, _, _} = document
end
defp publish_scan(scan, visibility) do
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
scope = Tarakan.Accounts.Scope.for_account(moderator_account_fixture())
if visibility == "public" do
scan.repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
end
{:ok, scan} =
Tarakan.Scans.accept_scan(scope, scan, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Two independent reviewers supplied reproducible evidence for the pinned commit."
})
{:ok, scan} =
Tarakan.Scans.update_visibility(scope, scan, visibility, %{
"moderation_reason" => "disclosure_reviewed",
"moderation_notes" =>
"Disclosure was separately reviewed for scope, secrets, and personal data.",
"sensitive_data_reviewed" => "true"
})
scan
end
end

View file

@ -0,0 +1,121 @@
defmodule TarakanWeb.GitHubAuthControllerTest do
use TarakanWeb.ConnCase
alias Tarakan.Accounts
test "starts GitHub authorization with state and PKCE", %{conn: conn} do
conn = get(conn, ~p"/auth/github?return_to=/")
location = redirected_to(conn, 302)
query = location |> URI.parse() |> Map.fetch!(:query) |> URI.decode_query()
assert String.starts_with?(location, "https://github.com/login/oauth/authorize?")
assert query["client_id"] == "test-client-id"
assert query["code_challenge_method"] == "S256"
assert query["code_challenge"]
assert query["state"] == get_session(conn, :github_oauth_state)
assert get_session(conn, :github_oauth_verifier)
end
test "creates a session after GitHub authorizes the user", %{conn: conn} do
authorization_conn = get(conn, ~p"/auth/github?return_to=/")
state = get_session(authorization_conn, :github_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/github/callback?code=valid-code&state=#{state}")
assert redirected_to(callback_conn) == "/"
token = get_session(callback_conn, :account_token)
assert {account, _inserted_at} = Accounts.get_account_by_session_token(token)
assert account.handle == "tarakantester"
assert is_nil(account.display_name)
assert is_nil(account.email)
refute get_session(callback_conn, :github_oauth_state)
refute get_session(callback_conn, :github_oauth_verifier)
assert Tarakan.Repo.get_by!(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "github"
)
end
test "rejects a callback with an invalid state", %{conn: conn} do
authorization_conn = get(conn, ~p"/auth/github")
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/github/callback?code=valid-code&state=wrong-state")
assert redirected_to(callback_conn) == "/"
refute get_session(callback_conn, :account_token)
end
test "links GitHub to the signed-in Tarakan account", %{conn: conn} do
account = account_fixture()
authorization_conn =
conn
|> log_in_account(account)
|> get(~p"/auth/github?return_to=/accounts/settings")
state = get_session(authorization_conn, :github_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/github/callback?code=valid-code&state=#{state}")
assert redirected_to(callback_conn) == "/accounts/settings"
token = get_session(callback_conn, :account_token)
assert {linked_account, _inserted_at} = Accounts.get_account_by_session_token(token)
assert linked_account.id == account.id
assert Tarakan.Repo.get_by!(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "github"
)
end
test "refuses to link an identity from a stale signed-in session", %{conn: conn} do
account = account_fixture()
stale_at = DateTime.add(DateTime.utc_now(:second), -9 * 60, :minute)
authorization_conn =
conn
|> log_in_account(account, token_authenticated_at: stale_at)
|> get(~p"/auth/github?return_to=/accounts/settings")
state = get_session(authorization_conn, :github_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/github/callback?code=valid-code&state=#{state}")
assert redirected_to(callback_conn) == "/accounts/settings"
refute Tarakan.Repo.get_by(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "github"
)
end
test "does not retain a malicious return path in the OAuth session" do
for return_to <- ["//attacker.example", "/\\attacker.example", "/%255cattacker.example"] do
query = URI.encode_query(%{"return_to" => return_to})
authorization_conn = get(build_conn(), "/auth/github?#{query}")
assert get_session(authorization_conn, :github_oauth_return_to) == "/"
end
end
test "signs the current user out", %{conn: conn} do
conn = conn |> log_in_account(account_fixture()) |> delete(~p"/accounts/log-out")
assert redirected_to(conn) == "/"
refute get_session(conn, :account_token)
end
end

View file

@ -0,0 +1,115 @@
defmodule TarakanWeb.GitLabAuthControllerTest do
use TarakanWeb.ConnCase
alias Tarakan.Accounts
test "starts GitLab authorization with state, PKCE, and read_user", %{conn: conn} do
conn = get(conn, ~p"/auth/gitlab?return_to=/")
location = redirected_to(conn, 302)
query = location |> URI.parse() |> Map.fetch!(:query) |> URI.decode_query()
assert String.starts_with?(location, "https://gitlab.com/oauth/authorize?")
assert query["client_id"] == "test-gitlab-client-id"
assert query["response_type"] == "code"
assert query["scope"] == "read_user"
assert query["code_challenge_method"] == "S256"
assert query["code_challenge"]
assert query["state"] == get_session(conn, :gitlab_oauth_state)
assert get_session(conn, :gitlab_oauth_verifier)
end
test "creates an account and session without native registration", %{conn: conn} do
authorization_conn = get(conn, ~p"/auth/gitlab?return_to=/")
state = get_session(authorization_conn, :gitlab_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/gitlab/callback?code=valid-gitlab-code&state=#{state}")
assert redirected_to(callback_conn) == "/"
token = get_session(callback_conn, :account_token)
assert {account, _inserted_at} = Accounts.get_account_by_session_token(token)
assert account.handle == "gitlabsignal"
assert is_nil(account.display_name)
assert is_nil(account.email)
refute get_session(callback_conn, :gitlab_oauth_state)
refute get_session(callback_conn, :gitlab_oauth_verifier)
assert Tarakan.Repo.get_by!(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "gitlab",
provider_uid: "24680"
)
end
test "rejects a callback with an invalid state", %{conn: conn} do
authorization_conn = get(conn, ~p"/auth/gitlab")
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/gitlab/callback?code=valid-gitlab-code&state=wrong-state")
assert redirected_to(callback_conn) == "/"
refute get_session(callback_conn, :account_token)
end
test "links GitLab to the signed-in Tarakan account", %{conn: conn} do
account = account_fixture()
authorization_conn =
conn
|> log_in_account(account)
|> get(~p"/auth/gitlab?return_to=/accounts/settings")
state = get_session(authorization_conn, :gitlab_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/gitlab/callback?code=valid-gitlab-code&state=#{state}")
assert redirected_to(callback_conn) == "/accounts/settings"
token = get_session(callback_conn, :account_token)
assert {linked_account, _inserted_at} = Accounts.get_account_by_session_token(token)
assert linked_account.id == account.id
assert Tarakan.Repo.get_by!(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "gitlab"
)
end
test "refuses to link an identity from a stale signed-in session", %{conn: conn} do
account = account_fixture()
stale_at = DateTime.add(DateTime.utc_now(:second), -9 * 60, :minute)
authorization_conn =
conn
|> log_in_account(account, token_authenticated_at: stale_at)
|> get(~p"/auth/gitlab?return_to=/accounts/settings")
state = get_session(authorization_conn, :gitlab_oauth_state)
callback_conn =
authorization_conn
|> recycle()
|> get(~p"/auth/gitlab/callback?code=valid-gitlab-code&state=#{state}")
assert redirected_to(callback_conn) == "/accounts/settings"
refute Tarakan.Repo.get_by(Tarakan.Accounts.Identity,
account_id: account.id,
provider: "gitlab"
)
end
test "does not retain an encoded authority return path in the OAuth session" do
query = URI.encode_query(%{"return_to" => "/%252f%252fattacker.example"})
authorization_conn = get(build_conn(), "/auth/gitlab?#{query}")
assert get_session(authorization_conn, :gitlab_oauth_return_to) == "/"
end
end

View file

@ -0,0 +1,15 @@
defmodule TarakanWeb.InfestationRedirectControllerTest do
use TarakanWeb.ConnCase, async: true
test "GET /patterns permanently redirects to /infestations", %{conn: conn} do
conn = get(conn, ~p"/patterns")
assert redirected_to(conn, 301) == ~p"/infestations"
end
test "GET /patterns/:pattern_key permanently redirects to /infestations/:pattern_key", %{
conn: conn
} do
conn = get(conn, ~p"/patterns/some-pattern-key")
assert redirected_to(conn, 301) == ~p"/infestations/some-pattern-key"
end
end

View file

@ -0,0 +1,138 @@
defmodule TarakanWeb.SEOControllerTest do
use TarakanWeb.ConnCase
test "robots.txt allows crawling and points at the sitemap", %{conn: conn} do
response = conn |> get(~p"/robots.txt") |> text_response(200)
assert response =~ "User-agent: *"
assert response =~ "Allow: /"
assert response =~ "Disallow: /*/code/"
assert response =~ "Sitemap: " <> TarakanWeb.Endpoint.url() <> "/sitemap.xml"
end
test "security.txt follows RFC 9116 with a fresh expiry", %{conn: conn} do
conn = get(conn, "/.well-known/security.txt")
assert response_content_type(conn, :text) =~ "text/plain"
response = response(conn, 200)
base = TarakanWeb.Endpoint.url()
assert response =~ "Contact: mailto:security@example.com"
assert response =~ "Preferred-Languages: en"
assert response =~ "Canonical: #{base}/.well-known/security.txt"
assert response =~ "Policy: #{base}/policies/disclosure"
assert [_, expires] = Regex.run(~r/^Expires: (.+)$/m, response)
assert {:ok, expires_at, _} = DateTime.from_iso8601(expires)
assert DateTime.diff(expires_at, DateTime.utc_now(), :day) in 360..366
end
test "the sitemap lists hubs, listed repositories, public findings, and open jobs", %{
conn: conn
} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(2)})
|> publish_scan("public")
response = conn |> get(~p"/sitemap.xml") |> response(200)
base = TarakanWeb.Endpoint.url()
assert response =~ ~s(<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">)
assert response =~ "<loc>#{base}/</loc>"
assert response =~ "<loc>#{base}/explore</loc>"
assert response =~ "<loc>#{base}/leaderboard</loc>"
assert response =~ "<loc>#{base}/jobs</loc>"
assert response =~ "<loc>#{base}/agents</loc>"
assert response =~ "<loc>#{base}/github.com/openai/codex/security</loc>"
for finding <- scan.findings do
assert response =~ "<loc>#{base}/findings/#{finding.public_id}</loc>"
end
end
test "the sitemap indexes fresh findings but not restricted or summary ones", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
fresh = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
restricted =
scan_fixture(repository, account_fixture(), %{"findings_json" => findings_json_fixture(1)})
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Tarakan.Accounts.Scope.for_account(moderator_account_fixture()),
restricted,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" =>
"Deliberately restricted in this test to keep it out of search discovery."
}
)
summary =
scan_fixture(repository, account_fixture(), %{"findings_json" => findings_json_fixture(1)})
publish_scan(summary, "public_summary")
response = conn |> get(~p"/sitemap.xml") |> response(200)
for finding <- fresh.findings do
assert response =~ "/findings/#{finding.public_id}"
end
for scan <- [restricted, summary], finding <- scan.findings do
refute response =~ to_string(finding.public_id)
end
end
test "quarantined repositories stay out of the sitemap", %{conn: conn} do
repository = github_repository_fixture()
repository
|> Tarakan.Repositories.Repository.listing_changeset(%{listing_status: "quarantined"})
|> Tarakan.Repo.update!()
response = conn |> get(~p"/sitemap.xml") |> response(200)
refute response =~ "openai/codex"
end
defp publish_scan(scan, visibility) do
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
scope = Tarakan.Accounts.Scope.for_account(moderator_account_fixture())
if visibility == "public" do
scan.repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
end
{:ok, scan} =
Tarakan.Scans.accept_scan(scope, scan, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Two independent reviewers supplied reproducible evidence for the pinned commit."
})
{:ok, scan} =
Tarakan.Scans.update_visibility(scope, scan, visibility, %{
"moderation_reason" => "disclosure_reviewed",
"moderation_notes" =>
"Disclosure was separately reviewed for scope, secrets, and personal data.",
"sensitive_data_reviewed" => "true"
})
scan
end
end

View file

@ -0,0 +1,281 @@
defmodule TarakanWeb.Webhooks.StripeControllerTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Accounts.Scope
alias Tarakan.Market.Bounty
alias Tarakan.Repo
@secret "whsec_test_secret"
defp fiat_bounty do
repository = listed_github_repository_fixture()
sponsor = active_account_fixture() |> fund_credits(1_000)
{bounty, _checkout_url} = fiat_bounty_fixture(Scope.for_account(sponsor), repository)
bounty
end
defp signed_post(conn, payload, opts \\ []) do
timestamp = Keyword.get(opts, :timestamp, System.system_time(:second))
secret = Keyword.get(opts, :secret, @secret)
signature =
:crypto.mac(:hmac, :sha256, secret, "#{timestamp}.#{payload}")
|> Base.encode16(case: :lower)
conn
|> put_req_header("content-type", "application/json")
|> put_req_header("stripe-signature", "t=#{timestamp},v1=#{signature}")
|> post(~p"/webhooks/stripe", payload)
end
defp checkout_completed_payload(session_id) do
Jason.encode!(%{
"id" => "evt_1",
"type" => "checkout.session.completed",
"data" => %{"object" => %{"id" => session_id, "payment_intent" => "pi_1"}}
})
end
test "a validly signed checkout.session.completed funds the bounty", %{conn: conn} do
bounty = fiat_bounty()
conn = signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id))
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "open"
end
defp checkout_payload(session_id, type, extra) do
Jason.encode!(%{
"id" => "evt_1",
"type" => type,
"data" => %{"object" => Map.merge(%{"id" => session_id}, extra)}
})
end
test "a completed but unpaid session does not fund the bounty", %{conn: conn} do
bounty = fiat_bounty()
# Delayed-notification methods complete the session before the money
# arrives. Funding here would let work be claimed against nothing.
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.completed",
%{"payment_status" => "unpaid"}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "the later async payment confirmation funds it", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.completed",
%{"payment_status" => "unpaid"}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
conn =
conn
|> recycle()
|> signed_post(
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.async_payment_succeeded",
%{}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "open"
end
test "a failed async payment leaves the bounty unfunded", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(
conn,
checkout_payload(
bounty.stripe_checkout_session_id,
"checkout.session.async_payment_failed",
%{}
)
)
assert json_response(conn, 200) == %{"received" => true}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "an invalid signature is rejected", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id),
secret: "whsec_wrong"
)
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "a stale timestamp is rejected", %{conn: conn} do
bounty = fiat_bounty()
conn =
signed_post(conn, checkout_completed_payload(bounty.stripe_checkout_session_id),
timestamp: System.system_time(:second) - 600
)
assert json_response(conn, 400) == %{"error" => "timestamp_outside_tolerance"}
assert Repo.get!(Bounty, bounty.id).status == "pending_funding"
end
test "a missing signature header is rejected", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post(~p"/webhooks/stripe", checkout_completed_payload("cs_test_whatever"))
assert json_response(conn, 400) == %{"error" => "missing_signature"}
end
test "a tampered body is rejected", %{conn: conn} do
timestamp = System.system_time(:second)
signature =
:crypto.mac(
:hmac,
:sha256,
@secret,
"#{timestamp}.{\"type\":\"checkout.session.completed\"}"
)
|> Base.encode16(case: :lower)
conn =
conn
|> put_req_header("content-type", "application/json")
|> put_req_header("stripe-signature", "t=#{timestamp},v1=#{signature}")
|> post(~p"/webhooks/stripe", checkout_completed_payload("cs_test_tampered"))
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
end
test "unknown events are acknowledged and ignored", %{conn: conn} do
payload = Jason.encode!(%{"id" => "evt_2", "type" => "payment_intent.created", "data" => %{}})
conn = signed_post(conn, payload)
assert json_response(conn, 200) == %{"received" => true}
end
test "funding an unknown session still returns 200", %{conn: conn} do
conn = signed_post(conn, checkout_completed_payload("cs_test_unknown"))
assert json_response(conn, 200) == %{"received" => true}
end
describe "subscription events" do
defp subscription_event_payload(type, object) do
Jason.encode!(%{"id" => "evt_sub", "type" => type, "data" => %{"object" => object}})
end
defp checkout_subscription_payload(account) do
subscription_event_payload("checkout.session.completed", %{
"id" => "cs_test_sub",
"mode" => "subscription",
"client_reference_id" => Integer.to_string(account.id),
"customer" => "cus_webhook_1",
"subscription" => "sub_webhook_1",
"metadata" => %{"plan" => "team"}
})
end
test "a signed subscription checkout activates the account's subscription", %{conn: conn} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
subscription = Tarakan.Billing.subscription(account)
assert subscription.status == "active"
assert subscription.plan == "team"
assert subscription.stripe_subscription_id == "sub_webhook_1"
end
test "customer.subscription.updated syncs the subscription", %{conn: conn} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
payload =
subscription_event_payload("customer.subscription.updated", %{
"id" => "sub_webhook_1",
"status" => "active",
"current_period_end" => 1_900_000_000,
"items" => %{"data" => [%{"price" => %{"id" => "price_enterprise_test"}}]}
})
conn = signed_post(build_conn(), payload)
assert json_response(conn, 200) == %{"received" => true}
subscription = Tarakan.Billing.subscription(account)
assert subscription.plan == "enterprise"
assert DateTime.to_unix(subscription.current_period_end) == 1_900_000_000
end
test "invoice.payment_failed and customer.subscription.deleted move the status", %{
conn: conn
} do
account = account_fixture()
conn = signed_post(conn, checkout_subscription_payload(account))
assert json_response(conn, 200) == %{"received" => true}
failed =
subscription_event_payload("invoice.payment_failed", %{
"subscription" => "sub_webhook_1"
})
conn = signed_post(build_conn(), failed)
assert json_response(conn, 200) == %{"received" => true}
assert Tarakan.Billing.subscription(account).status == "past_due"
deleted =
subscription_event_payload("customer.subscription.deleted", %{
"id" => "sub_webhook_1"
})
conn = signed_post(build_conn(), deleted)
assert json_response(conn, 200) == %{"received" => true}
assert Tarakan.Billing.subscription(account).status == "canceled"
end
test "subscription events require a valid signature", %{conn: conn} do
account = account_fixture()
conn =
signed_post(conn, checkout_subscription_payload(account), secret: "whsec_wrong")
assert json_response(conn, 400) == %{"error" => "invalid_signature"}
assert Tarakan.Billing.subscription(account) == nil
end
end
end

View file

@ -0,0 +1,38 @@
defmodule TarakanWeb.FindingPresentationTest do
use ExUnit.Case, async: true
alias TarakanWeb.FindingPresentation
test "structure_description splits remediation and strips Verified prefix" do
text =
"Verified: static HTML is served with bad CSP. That is same-origin. Remediation: force subdomains."
%{lead: lead, sections: sections} = FindingPresentation.structure_description(text)
assert lead =~ "static HTML"
refute lead =~ ~r/^Verified/
assert {"Remediation", fix} = List.keyfind(sections, "Remediation", 0)
assert fix =~ "subdomains"
end
test "description_excerpt truncates lead" do
long = String.duplicate("word ", 100)
excerpt = FindingPresentation.description_excerpt(long, 40)
assert String.length(excerpt) <= 41
assert String.ends_with?(excerpt, "")
end
test "humanize_notes parses auto summary" do
notes =
"Review Format submission with 18 finding(s). Top issues: [high] Static HTML; [medium] DNS rebinding"
assert %{kind: :summary, count: 18, tops: tops} = FindingPresentation.humanize_notes(notes)
assert "Static HTML" in tops
assert Enum.any?(tops, &String.contains?(&1, "DNS"))
end
test "how_made_label" do
assert FindingPresentation.how_made_label("agent") == "Produced by an agent"
assert FindingPresentation.how_made_label("hybrid") == "Agent draft, human edited"
end
end

View file

@ -0,0 +1,513 @@
defmodule TarakanWeb.GitHTTPTest do
@moduledoc """
End-to-end smart-HTTP tests driven by a real git client against a real
Bandit listener serving the full endpoint.
"""
use Tarakan.DataCase, async: false
import Tarakan.AccountsFixtures
alias Tarakan.Accounts
alias Tarakan.HostedRepositories
@moduletag :git_client
setup do
on_exit(fn ->
File.rm_rf(Application.fetch_env!(:tarakan, Tarakan.HostedRepositories)[:root])
end)
Tarakan.RepositoryCode.Cache.clear()
port = free_port()
start_supervised!(
{Bandit, plug: TarakanWeb.Endpoint, scheme: :http, ip: {127, 0, 0, 1}, port: port}
)
account = account_fixture()
scope = Accounts.scope_for_account(account)
{:ok, repository} = HostedRepositories.create(scope, %{"name" => "hosted"})
{:ok, token, _credential} =
Accounts.ApiCredentials.create(account, %{
"name" => "git",
"scopes" => ["repo:read", "repo:write"]
})
%{
port: port,
account: account,
repository: repository,
token: token,
base_url: "http://127.0.0.1:#{port}"
}
end
defp free_port do
{:ok, socket} = :gen_tcp.listen(0, ip: {127, 0, 0, 1})
{:ok, port} = :inet.port(socket)
:gen_tcp.close(socket)
port
end
defp git_env do
[
{"GIT_TERMINAL_PROMPT", "0"},
{"GIT_CONFIG_GLOBAL", "/dev/null"},
{"GIT_CONFIG_SYSTEM", "/dev/null"},
{"GIT_AUTHOR_NAME", "t"},
{"GIT_AUTHOR_EMAIL", "t@example.com"},
{"GIT_COMMITTER_NAME", "t"},
{"GIT_COMMITTER_EMAIL", "t@example.com"}
]
end
defp git(args, opts \\ []) do
System.cmd("git", args, [stderr_to_stdout: true, env: git_env()] ++ opts)
end
defp clone_url(%{base_url: base_url, account: account}, name),
do: "#{base_url}/#{account.handle}/#{name}.git"
defp authed_url(%{token: token} = context, name) do
%URI{URI.parse(clone_url(context, name)) | userinfo: "git:#{token}"} |> URI.to_string()
end
defp seed_local_repository(tmp_dir) do
work = Path.join(tmp_dir, "seed")
File.mkdir_p!(work)
{_out, 0} = git(["init", "--quiet", "-b", "main", work])
File.write!(Path.join(work, "README.md"), "# Pushed\n")
{_out, 0} = git(["-C", work, "add", "."])
{_out, 0} = git(["-C", work, "commit", "--quiet", "-m", "seed"])
work
end
defp list_repository(repository) do
repository
|> Ecto.Changeset.change(listing_status: "listed")
|> Repo.update!()
end
describe "push over HTTP" do
@tag :tmp_dir
test "steward pushes with a repo:write credential", context do
%{repository: repository, tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{output, status} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
assert status == 0, output
repository = Repo.reload!(repository)
assert repository.default_branch == "main"
assert %DateTime{} = repository.pushed_at
assert repository.disk_size_bytes > 0
assert %{action: "repository_pushed"} =
Repo.get_by(Tarakan.Audit.Event, action: "repository_pushed")
end
@tag :tmp_dir
test "push is denied without credentials, with read-only scope, and for non-stewards",
context do
%{tmp_dir: tmp_dir, account: account} = context
work = seed_local_repository(tmp_dir)
# Anonymous: the server answers 401 so git asks for credentials, and
# with prompts disabled the push dies without touching the repository.
{:ok, {{_http, 401, _reason}, _headers, _body}} =
:httpc.request(
:get,
{~c"#{clone_url(context, "hosted")}/info/refs?service=git-receive-pack", []},
[autoredirect: false],
[]
)
{output, status} =
git(["-C", work, "push", "--quiet", clone_url(context, "hosted"), "main"])
assert status != 0
assert output =~ "Authentication" or output =~ "401" or output =~ "prompts disabled"
# Authenticated but read-only scope.
{:ok, read_token, _credential} =
Accounts.ApiCredentials.create(account, %{
"name" => "read-only",
"scopes" => ["repo:read"]
})
read_url =
%URI{URI.parse(clone_url(context, "hosted")) | userinfo: "git:#{read_token}"}
|> URI.to_string()
{output, status} = git(["-C", work, "push", "--quiet", read_url, "main"])
assert status != 0
assert output =~ "404" or output =~ "not found"
# A different account with a valid repo:write credential but no
# steward membership.
other = account_fixture()
{:ok, other_token, _credential} =
Accounts.ApiCredentials.create(other, %{
"name" => "other",
"scopes" => ["repo:write"]
})
other_url =
%URI{URI.parse(clone_url(context, "hosted")) | userinfo: "git:#{other_token}"}
|> URI.to_string()
{output, status} = git(["-C", work, "push", "--quiet", other_url, "main"])
assert status != 0
assert output =~ "404" or output =~ "not found"
assert Repo.reload!(context.repository).pushed_at == nil
end
end
describe "clone over HTTP" do
@tag :tmp_dir
test "anonymous clone of a listed repository round-trips content", context do
%{repository: repository, tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
list_repository(repository)
dest = Path.join(tmp_dir, "clone")
{output, status} = git(["clone", "--quiet", clone_url(context, "hosted"), dest])
assert status == 0, output
assert File.read!(Path.join(dest, "README.md")) == "# Pushed\n"
end
@tag :tmp_dir
test "anonymous clone succeeds at creation and is refused after quarantine", context do
%{repository: repository, tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
# Hosted repositories are listed at creation, so an anonymous clone
# works without any moderation step.
dest = Path.join(tmp_dir, "clone")
{output, status} = git(["clone", "--quiet", clone_url(context, "hosted"), dest])
assert status == 0, output
assert File.read!(Path.join(dest, "README.md")) == "# Pushed\n"
# A moderator quarantine is the only thing that hides the repository.
repository
|> Tarakan.Repositories.Repository.listing_changeset(%{listing_status: "quarantined"})
|> Repo.update!()
{:ok, {{_http, 401, _reason}, _headers, _body}} =
:httpc.request(
:get,
{~c"#{clone_url(context, "hosted")}/info/refs?service=git-upload-pack", []},
[autoredirect: false],
[]
)
quarantined_dest = Path.join(tmp_dir, "clone-quarantined")
{output, status} =
git(["clone", "--quiet", clone_url(context, "hosted"), quarantined_dest])
assert status != 0
assert output =~ "Authentication" or output =~ "401" or output =~ "prompts disabled"
end
@tag :tmp_dir
test "the owner clones a repository with a credential", context do
%{tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
dest = Path.join(tmp_dir, "clone")
{output, status} = git(["clone", "--quiet", authed_url(context, "hosted"), dest])
assert status == 0, output
assert File.read!(Path.join(dest, "README.md")) == "# Pushed\n"
end
@tag :tmp_dir
test "fetch after a new push exercises negotiation", context do
%{repository: repository, tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
list_repository(repository)
dest = Path.join(tmp_dir, "clone")
{_output, 0} = git(["clone", "--quiet", clone_url(context, "hosted"), dest])
File.write!(Path.join(work, "second.txt"), "more\n")
{_out, 0} = git(["-C", work, "add", "."])
{_out, 0} = git(["-C", work, "commit", "--quiet", "-m", "second"])
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
{output, status} = git(["-C", dest, "pull", "--quiet", "origin", "main"])
assert status == 0, output
assert File.read!(Path.join(dest, "second.txt")) == "more\n"
end
test "unknown repositories 404 without an existence oracle", context do
%{base_url: base_url} = context
{:ok, {{_http, 404, _reason}, _headers, _body}} =
:httpc.request(
:get,
{~c"#{base_url}/nobody/nothing.git/info/refs?service=git-upload-pack", []},
[],
[]
)
end
test "non-git routes still reach Phoenix", context do
{:ok, {{_http, 200, _reason}, _headers, body}} =
:httpc.request(:get, {~c"#{context.base_url}/", []}, [], [])
assert to_string(body) =~ "<html"
end
end
describe "protocol details" do
@tag :tmp_dir
test "info/refs advertisement uses the smart content type", context do
%{repository: repository} = context
list_repository(repository)
url =
~c"#{clone_url(context, "hosted")}/info/refs?service=git-upload-pack"
{:ok, {{_http, 200, _reason}, headers, body}} = :httpc.request(:get, {url, []}, [], [])
content_type =
headers
|> Enum.find_value(fn
{~c"content-type", value} -> to_string(value)
_other -> nil
end)
assert content_type == "application/x-git-upload-pack-advertisement"
assert to_string(body) =~ "# service=git-upload-pack"
assert String.starts_with?(to_string(body), "001e# service=git-upload-pack\n0000")
end
@tag :tmp_dir
test "a truncated RPC body cannot wedge the subprocess", context do
%{repository: repository, tmp_dir: tmp_dir} = context
work = seed_local_repository(tmp_dir)
{_output, 0} =
git(["-C", work, "push", "--quiet", authed_url(context, "hosted"), "main"])
list_repository(repository)
# A syntactically incomplete upload-pack request: git waits for the
# rest of the negotiation, and only the wall-clock deadline ends it.
original = Application.get_env(:tarakan, TarakanWeb.GitHTTP.Service, [])
Application.put_env(
:tarakan,
TarakanWeb.GitHTTP.Service,
Keyword.put(original, :upload_pack_timeout_ms, 300)
)
on_exit(fn -> Application.put_env(:tarakan, TarakanWeb.GitHTTP.Service, original) end)
truncated = "00a4want "
{time_us, {:ok, {{_http, 200, _reason}, _headers, _body}}} =
:timer.tc(fn ->
:httpc.request(
:post,
{~c"#{clone_url(context, "hosted")}/git-upload-pack",
[{~c"content-type", ~c"application/x-git-upload-pack-request"}],
~c"application/x-git-upload-pack-request", truncated},
[timeout: 5_000],
[]
)
end)
# Bounded by the configured deadline, not the httpc timeout.
assert div(time_us, 1000) < 4_000
end
test "a gzip bomb is refused without inflating it", context do
%{repository: repository} = context
list_repository(repository)
original = Application.get_env(:tarakan, TarakanWeb.GitHTTP.Service, [])
# A small cap makes the limit trip deterministically, before git has a
# chance to reject the garbage on its own and end the request first.
Application.put_env(
:tarakan,
TarakanWeb.GitHTTP.Service,
Keyword.put(original, :max_upload_pack_request_bytes, 64 * 1024)
)
on_exit(fn -> Application.put_env(:tarakan, TarakanWeb.GitHTTP.Service, original) end)
# ~64MB of compressible input in well under a megabyte on the wire.
zstream = :zlib.open()
:ok = :zlib.deflateInit(zstream, 9, :deflated, 31, 8, :default)
bomb =
zstream
|> :zlib.deflate(:binary.copy("A", 64 * 1024 * 1024), :finish)
|> IO.iodata_to_binary()
:ok = :zlib.deflateEnd(zstream)
:ok = :zlib.close(zstream)
assert byte_size(bomb) < 1_000_000, "the bomb must be small on the wire to be a bomb"
memory_before = :erlang.memory(:total)
{:ok, {{_http, status, _reason}, _headers, _body}} =
:httpc.request(
:post,
{~c"#{clone_url(context, "hosted")}/git-upload-pack",
[
{~c"content-type", ~c"application/x-git-upload-pack-request"},
{~c"content-encoding", ~c"gzip"}
], ~c"application/x-git-upload-pack-request", bomb},
[timeout: 30_000],
[]
)
assert status == 413
# The cap is enforced during expansion, so the whole 64MB is never
# materialized. Allow generous headroom for unrelated test allocation.
growth = :erlang.memory(:total) - memory_before
assert growth < 32 * 1024 * 1024, "inflated #{div(growth, 1024 * 1024)}MB before refusing"
end
test "a well-formed gzip request body is still accepted", context do
%{repository: repository} = context
list_repository(repository)
zstream = :zlib.open()
:ok = :zlib.deflateInit(zstream, 9, :deflated, 31, 8, :default)
# A truncated but legitimately compressed negotiation body: the point is
# that gzip decoding still works, not that git likes the contents.
body = zstream |> :zlib.deflate("0000", :finish) |> IO.iodata_to_binary()
:ok = :zlib.deflateEnd(zstream)
:ok = :zlib.close(zstream)
{:ok, {{_http, status, _reason}, _headers, _body}} =
:httpc.request(
:post,
{~c"#{clone_url(context, "hosted")}/git-upload-pack",
[
{~c"content-type", ~c"application/x-git-upload-pack-request"},
{~c"content-encoding", ~c"gzip"}
], ~c"application/x-git-upload-pack-request", body},
[timeout: 10_000],
[]
)
assert status == 200
end
test "a corrupt gzip body is rejected as a bad request", context do
%{repository: repository} = context
list_repository(repository)
{:ok, {{_http, status, _reason}, _headers, _body}} =
:httpc.request(
:post,
{~c"#{clone_url(context, "hosted")}/git-upload-pack",
[
{~c"content-type", ~c"application/x-git-upload-pack-request"},
{~c"content-encoding", ~c"gzip"}
], ~c"application/x-git-upload-pack-request", "not actually gzip at all"},
[timeout: 10_000],
[]
)
assert status == 400
end
test "dumb-protocol requests are refused", context do
%{repository: repository} = context
list_repository(repository)
{:ok, {{_http, 400, _reason}, _headers, _body}} =
:httpc.request(
:get,
{~c"#{clone_url(context, "hosted")}/info/refs", []},
[],
[]
)
end
end
describe "rate limits" do
test "failed basic authentications are throttled to 429 per client IP", context do
original = Application.get_env(:tarakan, TarakanWeb.GitHTTP, [])
Application.put_env(
:tarakan,
TarakanWeb.GitHTTP,
Keyword.put(original, :auth_failure_rate_limit, {2, 60})
)
on_exit(fn -> Application.put_env(:tarakan, TarakanWeb.GitHTTP, original) end)
authorization = ~c"Basic " ++ String.to_charlist(Base.encode64("git:bad-token"))
url = ~c"#{clone_url(context, "hosted")}/info/refs?service=git-upload-pack"
request = fn ->
:httpc.request(
:get,
{url, [{~c"authorization", authorization}]},
[autoredirect: false],
[]
)
end
{:ok, {{_http, 401, _reason}, _headers, _body}} = request.()
{:ok, {{_http, 401, _reason}, _headers, _body}} = request.()
{:ok, {{_http, 429, _reason}, _headers, _body}} = request.()
end
test "info/refs answers 503 when the git subprocess cap is exhausted", context do
%{repository: repository} = context
list_repository(repository)
checked_out =
Stream.repeatedly(fn -> Tarakan.Git.Concurrency.checkout() end)
|> Enum.take_while(&(&1 == :ok))
on_exit(fn -> for _ <- checked_out, do: Tarakan.Git.Concurrency.checkin() end)
{:ok, {{_http, 503, _reason}, headers, body}} =
:httpc.request(
:get,
{~c"#{clone_url(context, "hosted")}/info/refs?service=git-upload-pack", []},
[autoredirect: false],
[]
)
assert Enum.find(headers, fn {name, _value} -> name == ~c"retry-after" end)
assert to_string(body) =~ "busy"
end
end
end

View file

@ -0,0 +1,125 @@
defmodule TarakanWeb.AccountLive.ConfirmationTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Tarakan.AccountsFixtures
alias Tarakan.Accounts
setup do
%{unconfirmed_account: unconfirmed_account_fixture(), confirmed_account: account_fixture()}
end
describe "Magic-link login" do
test "renders login page for a legacy unconfirmed account", %{
conn: conn,
unconfirmed_account: account
} do
token =
extract_account_token(fn url ->
Accounts.deliver_login_instructions(account, url)
end)
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in/#{token}")
refute html =~ "Confirm"
assert html =~ "Log in"
end
test "renders login page for confirmed account", %{conn: conn, confirmed_account: account} do
token =
extract_account_token(fn url ->
Accounts.deliver_login_instructions(account, url)
end)
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in/#{token}")
assert html =~ "Stay logged in on this device"
end
test "renders login page for already logged in account", %{
conn: conn,
confirmed_account: account
} do
conn = log_in_account(conn, account)
token =
extract_account_token(fn url ->
Accounts.deliver_login_instructions(account, url)
end)
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in/#{token}")
assert html =~ "Log in"
end
test "logs in a legacy unconfirmed account with the given token once", %{
conn: conn,
unconfirmed_account: account
} do
token =
extract_account_token(fn url ->
Accounts.deliver_login_instructions(account, url)
end)
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in/#{token}")
form = form(lv, "#login_form", %{"account" => %{"token" => token}})
render_submit(form)
conn = follow_trigger_action(form, conn)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!"
assert Accounts.get_account!(account.id).confirmed_at
# we are logged in now
assert get_session(conn, :account_token)
assert redirected_to(conn) == ~p"/"
# log out, new conn
conn = build_conn()
{:ok, _lv, html} =
live(conn, ~p"/accounts/log-in/#{token}")
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "Magic link is invalid or it has expired"
end
test "logs confirmed account in without changing confirmed_at", %{
conn: conn,
confirmed_account: account
} do
token =
extract_account_token(fn url ->
Accounts.deliver_login_instructions(account, url)
end)
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in/#{token}")
form = form(lv, "#login_form", %{"account" => %{"token" => token}})
render_submit(form)
conn = follow_trigger_action(form, conn)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~
"Welcome back!"
assert Accounts.get_account!(account.id).confirmed_at == account.confirmed_at
# log out, new conn
conn = build_conn()
{:ok, _lv, html} =
live(conn, ~p"/accounts/log-in/#{token}")
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "Magic link is invalid or it has expired"
end
test "raises error for invalid token", %{conn: conn} do
{:ok, _lv, html} =
live(conn, ~p"/accounts/log-in/invalid-token")
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "Magic link is invalid or it has expired"
end
end
end

View file

@ -0,0 +1,134 @@
defmodule TarakanWeb.AccountLive.LoginTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Tarakan.AccountsFixtures
describe "login page" do
test "renders login page", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in")
assert html =~ "Log in"
assert html =~ "Continue with GitHub"
assert html =~ "Continue with GitLab"
assert html =~ "Email me a login link"
end
test "preserves a protected route stored in the session", %{conn: conn} do
return_to = "/client/authorize/ABCD-EFGH"
{:ok, view, _html} =
conn
|> init_test_session(%{account_return_to: return_to})
|> live(~p"/accounts/log-in")
assert has_element?(
view,
"#login_form_password input[name='account[return_to]'][value='#{return_to}']"
)
assert has_element?(
view,
"#github-login-button[href='/auth/github?return_to=%2Fclient%2Fauthorize%2FABCD-EFGH']"
)
end
end
describe "account login - magic link" do
test "sends magic link email when account exists", %{conn: conn} do
account = account_fixture()
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in")
{:ok, _lv, html} =
form(lv, "#login_form_magic", account: %{email: account.email})
|> render_submit()
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "If your email is in our system"
assert Tarakan.Repo.get_by!(Tarakan.Accounts.AccountToken, account_id: account.id).context ==
"login"
end
test "does not disclose if account is registered", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in")
{:ok, _lv, html} =
form(lv, "#login_form_magic", account: %{email: "idonotexist@example.com"})
|> render_submit()
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "If your email is in our system"
end
end
describe "account login - password" do
test "redirects if account logs in with valid credentials", %{conn: conn} do
account = account_fixture() |> set_password()
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in")
form =
form(lv, "#login_form_password",
account: %{
identifier: account.handle,
password: valid_account_password(),
remember_me: true
}
)
conn = submit_form(form, conn)
assert redirected_to(conn) == ~p"/"
end
test "redirects to login page with a flash error if credentials are invalid", %{
conn: conn
} do
{:ok, lv, _html} = live(conn, ~p"/accounts/log-in")
form =
form(lv, "#login_form_password",
account: %{identifier: "test@email.com", password: "123456"}
)
render_submit(form)
conn = follow_trigger_action(form, conn)
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Invalid handle, email, or password"
assert redirected_to(conn) == ~p"/accounts/log-in"
end
end
describe "login navigation" do
test "github is the primary path for new users", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in")
assert html =~ "One click. No password required."
assert html =~ ~s(id="github-login-button")
assert html =~ "Continue with GitHub"
end
end
describe "re-authentication (sudo mode)" do
setup %{conn: conn} do
account = account_fixture()
%{account: account, conn: log_in_account(conn, account)}
end
test "shows login page with email filled in", %{conn: conn, account: account} do
{:ok, _lv, html} = live(conn, ~p"/accounts/log-in")
assert html =~ "Confirm it"
assert html =~ "connect another host"
assert html =~ "Email me a login link"
assert html =~
~s(<input type="email" name="account[email]" id="login_form_magic_email" value="#{account.email}")
end
end
end

View file

@ -0,0 +1,109 @@
defmodule TarakanWeb.AccountLive.RegistrationTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Tarakan.AccountsFixtures
describe "Registration page" do
test "renders registration page", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/accounts/register")
assert html =~ "Join Tarakan"
assert html =~ "Log in"
end
test "redirects if already logged in", %{conn: conn} do
result =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/register")
|> follow_redirect(conn, ~p"/")
assert {:ok, _conn} = result
end
test "renders errors for invalid data", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/register")
result =
lv
|> element("#registration_form")
|> render_change(account: %{"handle" => "valid-handle", "email" => "with spaces"})
assert result =~ "Join Tarakan"
assert result =~ "must have the @ sign and no spaces"
end
end
describe "register account" do
test "creates an unconfirmed account and logs in immediately", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/register")
email = unique_account_email()
form = form(lv, "#registration_form", account: valid_account_attributes(email: email))
render_submit(form)
login_form = form(lv, "#registration_login_form")
conn = follow_trigger_action(login_form, conn)
assert get_session(conn, :account_token)
assert redirected_to(conn) == ~p"/"
# The instant session does not confirm the email: only the emailed login
# link proves mailbox control.
assert %{confirmed_at: nil} = Tarakan.Accounts.get_account_by_email(email)
end
test "does not reveal that an email is already registered", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/register")
account = account_fixture(%{email: "test@email.com"})
{:ok, _lv, html} =
lv
|> form("#registration_form",
account: %{"handle" => unique_account_handle(), "email" => account.email}
)
|> render_submit()
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "If an account matches those details, a sign-in link will arrive shortly."
refute html =~ "has already been taken"
refute html =~ account.email
end
test "does not reveal that a handle is already registered", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/register")
account = account_fixture()
email = unique_account_email()
{:ok, _lv, html} =
lv
|> form("#registration_form",
account: %{"handle" => account.handle, "email" => email}
)
|> render_submit()
|> follow_redirect(conn, ~p"/accounts/log-in")
assert html =~ "If an account matches those details, a sign-in link will arrive shortly."
refute html =~ "has already been taken"
refute Tarakan.Accounts.get_account_by_email(email)
end
end
describe "registration navigation" do
test "redirects to login page when the Log in button is clicked", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/register")
{:ok, _login_live, login_html} =
lv
|> element("main a", "Log in")
|> render_click()
|> follow_redirect(conn, ~p"/accounts/log-in")
assert login_html =~ "Log in"
end
end
end

View file

@ -0,0 +1,434 @@
defmodule TarakanWeb.AccountLive.SettingsTest do
use TarakanWeb.ConnCase, async: true
alias Tarakan.Accounts
import Phoenix.LiveViewTest
import Tarakan.AccountsFixtures
describe "Settings page" do
test "opens on the account panel", %{conn: conn} do
{:ok, view, html} =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/settings")
assert html =~ "Save email"
assert html =~ "Save password"
assert html =~ "Connected code hosts"
assert html =~ "GitHub"
assert html =~ "GitLab"
assert has_element?(view, "#settings-tab-account[aria-current=page]")
refute has_element?(view, "#api-reference")
refute has_element?(view, "#ssh-key-form")
end
test "moves between panels from the rail", %{conn: conn} do
{:ok, view, _html} =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/settings")
html =
view
|> element("#settings-tab-credentials")
|> render_click()
assert html =~ "Client credentials"
assert_patch(view, ~p"/accounts/settings/credentials")
assert has_element?(view, "#settings-tab-credentials[aria-current=page]")
refute has_element?(view, "#email_form")
view |> element("#settings-tab-ssh") |> render_click()
assert_patch(view, ~p"/accounts/settings/ssh-keys")
assert has_element?(view, "#ssh-key-form")
view |> element("#settings-tab-agents") |> render_click()
assert_patch(view, ~p"/accounts/settings/agents")
assert has_element?(view, "#settings-model-scoreboard", "None yet")
end
test "an unknown panel slug falls back to the account panel", %{conn: conn} do
{:ok, view, _html} =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/settings/nonsense")
assert has_element?(view, "#email_form")
assert has_element?(view, "#settings-tab-account[aria-current=page]")
end
test "renders the API reference on the credentials panel", %{conn: conn} do
{:ok, view, html} =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/settings/credentials")
assert has_element?(view, "#api-reference", "API reference")
assert has_element?(view, "#api-reference-base-url", "/api")
assert html =~ "/client-auth/exchange"
assert html =~ "/repositories"
assert html =~ "/jobs/:id/claim/renew"
assert html =~ "/:host/:owner/:name/reports"
end
test "marks an attached code-host identity as connected", %{conn: conn} do
{:ok, view, _html} =
conn
|> log_in_account(github_account_fixture())
|> live(~p"/accounts/settings")
assert has_element?(view, "#settings-github-identity", "Connected")
assert has_element?(view, "#settings-gitlab-identity", "Connect")
end
test "redirects if account is not logged in", %{conn: conn} do
assert {:error, redirect} = live(conn, ~p"/accounts/settings")
assert {:redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"/accounts/log-in"
assert %{"error" => "You must log in to access this page."} = flash
end
test "generates and independently revokes a client credential", %{conn: conn} do
account = account_fixture()
{:ok, view, html} =
conn
|> log_in_account(account)
|> live(~p"/accounts/settings/credentials")
assert html =~ "Client credentials"
refute has_element?(view, "#api-token-value")
view
|> form("#api-credential-form", %{
"credential" => %{
"name" => "Task reader",
"scopes" => ["tasks:read"]
}
})
|> render_submit()
token =
view
|> element("#api-token-value")
|> render()
|> String.replace(~r/<[^>]+>/, "")
|> String.trim()
assert {:ok, fetched} = Accounts.fetch_account_by_api_token(token)
assert fetched.id == account.id
[credential] = Tarakan.Accounts.ApiCredentials.list(account)
assert credential.name == "Task reader"
assert credential.scopes == ["tasks:read"]
assert has_element?(view, "#api-credential-#{credential.id}")
view
|> element("#revoke-api-credential-#{credential.id}")
|> render_click()
assert :error = Accounts.fetch_account_by_api_token(token)
refute has_element?(view, "#revoke-api-credential-#{credential.id}")
end
test "can bind a least-privilege credential to one repository", %{conn: conn} do
account = github_account_fixture()
repository = github_repository_fixture(account)
{:ok, view, _html} =
conn
|> log_in_account(account)
|> live(~p"/accounts/settings/credentials")
view
|> form("#api-credential-form", %{
"credential" => %{
"name" => "Codex reader",
"repository" => "openai/codex",
"scopes" => ["tasks:read"]
}
})
|> render_submit()
token =
view
|> element("#api-token-value")
|> render()
|> String.replace(~r/<[^>]+>/, "")
|> String.trim()
assert {:ok, ^account, credential} =
Tarakan.Accounts.ApiCredentials.authenticate(token)
assert credential.repository_id == repository.id
assert credential.scopes == ["tasks:read"]
end
test "redirects if account is not in sudo mode", %{conn: conn} do
stale_at = DateTime.add(DateTime.utc_now(:second), -9 * 60, :minute)
{:ok, conn} =
conn
|> log_in_account(account_fixture(), token_authenticated_at: stale_at)
|> live(~p"/accounts/settings")
|> follow_redirect(conn, ~p"/accounts/log-in?return_to=%2Faccounts%2Fsettings")
assert conn.resp_body =~ "sign-in older than 2 hours for sensitive settings"
end
test "prompts unconfirmed accounts to confirm email instead of credential forms", %{
conn: conn
} do
conn = log_in_account(conn, unconfirmed_account_fixture())
{:ok, view, html} = live(conn, ~p"/accounts/settings")
assert has_element?(view, "#email-confirmation-required")
assert has_element?(view, "#settings-tab-account", "unconfirmed")
assert html =~ "Confirm your email to unlock credentials"
refute has_element?(view, "#password_form")
{:ok, view, _html} = live(conn, ~p"/accounts/settings/credentials")
refute has_element?(view, "#api-credential-form")
{:ok, view, _html} = live(conn, ~p"/accounts/settings/ssh-keys")
refute has_element?(view, "#ssh-key-form")
end
end
describe "SSH keys" do
@describetag :tmp_dir
defp generate_public_key(tmp_dir) do
path = Path.join(tmp_dir, "key-#{System.unique_integer([:positive])}")
{_output, 0} = System.cmd("ssh-keygen", ["-t", "ed25519", "-f", path, "-N", "", "-q"])
File.read!(path <> ".pub")
end
test "adds and removes a key", %{conn: conn, tmp_dir: tmp_dir} do
account = account_fixture()
{:ok, view, html} =
conn
|> log_in_account(account)
|> live(~p"/accounts/settings/ssh-keys")
assert html =~ "SSH keys"
view
|> form("#ssh-key-form",
ssh_key: %{name: "laptop", public_key: generate_public_key(tmp_dir)}
)
|> render_submit()
assert has_element?(view, "#ssh-key-list", "laptop")
assert has_element?(view, "#ssh-key-list", "SHA256:")
[key] = Tarakan.Accounts.SshKeys.list_for_account(account)
view
|> element("#delete-ssh-key-#{key.id}")
|> render_click()
refute has_element?(view, "#ssh-key-list")
assert Tarakan.Accounts.SshKeys.list_for_account(account) == []
end
test "rejects an invalid key inline", %{conn: conn} do
{:ok, view, _html} =
conn
|> log_in_account(account_fixture())
|> live(~p"/accounts/settings/ssh-keys")
html =
view
|> form("#ssh-key-form", ssh_key: %{name: "junk", public_key: "not a key"})
|> render_submit()
assert html =~ "is not a supported OpenSSH public key"
end
end
describe "update email form" do
setup %{conn: conn} do
account = account_fixture()
%{conn: log_in_account(conn, account), account: account}
end
test "updates the account email", %{conn: conn, account: account} do
new_email = unique_account_email()
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
result =
lv
|> form("#email_form", %{
"account" => %{"email" => new_email}
})
|> render_submit()
assert result =~ "A link to confirm your email"
assert Accounts.get_account_by_email(account.email)
end
test "renders errors with invalid data (phx-change)", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
result =
lv
|> element("#email_form")
|> render_change(%{
"action" => "update_email",
"account" => %{"email" => "with spaces"}
})
assert result =~ "Save email"
assert result =~ "must have the @ sign and no spaces"
end
test "renders errors with invalid data (phx-submit)", %{conn: conn, account: account} do
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
result =
lv
|> form("#email_form", %{
"account" => %{"email" => account.email}
})
|> render_submit()
assert result =~ "Save email"
assert result =~ "did not change"
end
end
describe "update password form" do
setup %{conn: conn} do
account = account_fixture()
%{conn: log_in_account(conn, account), account: account}
end
test "updates the account password", %{conn: conn, account: account} do
new_password = valid_account_password()
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
form =
form(lv, "#password_form", %{
"account" => %{
"password" => new_password,
"password_confirmation" => new_password
}
})
render_submit(form)
new_password_conn = follow_trigger_action(form, conn)
assert redirected_to(new_password_conn) == ~p"/accounts/settings"
assert get_session(new_password_conn, :account_token) != get_session(conn, :account_token)
assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~
"Password updated successfully"
assert Accounts.get_account_by_email_and_password(account.email, new_password)
end
test "renders errors with invalid data (phx-change)", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
result =
lv
|> element("#password_form")
|> render_change(%{
"account" => %{
"password" => "too short",
"password_confirmation" => "does not match"
}
})
assert result =~ "Save password"
assert result =~ "should be at least 15 character(s)"
assert result =~ "does not match password"
end
test "renders errors with invalid data (phx-submit)", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/accounts/settings")
result =
lv
|> form("#password_form", %{
"account" => %{
"password" => "too short",
"password_confirmation" => "does not match"
}
})
|> render_submit()
assert result =~ "Save password"
assert result =~ "should be at least 15 character(s)"
assert result =~ "does not match password"
end
end
describe "confirm email" do
setup %{conn: conn} do
account = account_fixture()
email = unique_account_email()
token =
extract_account_token(fn url ->
Accounts.deliver_account_update_email_instructions(
%{account | email: email},
account.email,
url
)
end)
%{conn: log_in_account(conn, account), token: token, email: email, account: account}
end
test "updates the account email once", %{
conn: conn,
account: account,
token: token,
email: email
} do
{:error, redirect} = live(conn, ~p"/accounts/settings/confirm-email/#{token}")
assert {:live_redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"/accounts/settings"
assert %{"info" => message} = flash
assert message == "Email changed successfully."
refute Accounts.get_account_by_email(account.email)
assert Accounts.get_account_by_email(email)
# use confirm token again
{:error, redirect} = live(conn, ~p"/accounts/settings/confirm-email/#{token}")
assert {:live_redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"/accounts/settings"
assert %{"error" => message} = flash
assert message == "Email change link is invalid or it has expired."
end
test "does not update email with invalid token", %{conn: conn, account: account} do
{:error, redirect} = live(conn, ~p"/accounts/settings/confirm-email/oops")
assert {:live_redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"/accounts/settings"
assert %{"error" => message} = flash
assert message == "Email change link is invalid or it has expired."
assert Accounts.get_account_by_email(account.email)
end
test "redirects if account is not logged in", %{token: token} do
conn = build_conn()
{:error, redirect} = live(conn, ~p"/accounts/settings/confirm-email/#{token}")
assert {:redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"/accounts/log-in"
assert %{"error" => message} = flash
assert message == "You must log in to access this page."
end
end
end

View file

@ -0,0 +1,134 @@
defmodule TarakanWeb.AdminLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Accounts
alias Tarakan.Accounts.Account
alias Tarakan.Repo
setup %{conn: conn} do
admin =
account_fixture()
|> Account.authorization_changeset(%{
state: "active",
platform_role: "admin",
trust_tier: "reviewer"
})
|> Repo.update!()
%{admin: admin, conn: log_in_account(conn, admin)}
end
test "admin sees account summary, search, and management links", %{conn: conn} do
target = account_fixture(%{handle: "managed-account"})
{:ok, view, _html} = live(conn, ~p"/admin")
assert has_element?(view, "#admin-dashboard")
assert has_element?(view, "#admin-summary-admins")
assert has_element?(view, "#admin-account-filter")
assert has_element?(view, "#admin-account-#{target.id}-manage")
view
|> form("#admin-account-filter", filters: %{query: "managed-account"})
|> render_change()
assert has_element?(view, "#admin-account-#{target.id}-manage")
end
test "admin updates account standing, role, and trust tier", %{conn: conn} do
target = account_fixture()
{:ok, view, _html} = live(conn, ~p"/admin/accounts/#{target.id}")
assert has_element?(view, "#admin-account")
assert has_element?(view, "#admin-authorization-form")
view
|> form("#admin-authorization-form",
authorization: %{
state: "active",
platform_role: "moderator",
trust_tier: "reviewer"
}
)
|> render_submit()
updated = Accounts.get_account!(target.id)
assert updated.state == "active"
assert updated.platform_role == "moderator"
assert updated.trust_tier == "reviewer"
assert has_element?(view, "#admin-authorization-form")
end
test "ordinary members cannot open platform administration", %{conn: _admin_conn} do
member =
account_fixture() |> Account.authorization_changeset(%{state: "active"}) |> Repo.update!()
conn = log_in_account(build_conn(), member)
assert {:error, {:redirect, %{to: path, flash: flash}}} = live(conn, ~p"/admin")
assert path == ~p"/"
assert flash["error"] == "Administrator access is required."
end
test "last administrator cannot remove their own authority", %{conn: conn, admin: admin} do
{:ok, view, _html} = live(conn, ~p"/admin/accounts/#{admin.id}")
view
|> form("#admin-authorization-form",
authorization: %{
state: "active",
platform_role: "member",
trust_tier: "reviewer"
}
)
|> render_submit()
assert Accounts.get_account!(admin.id).platform_role == "admin"
assert has_element?(view, "#admin-authorization-form")
end
test "filtering requires a still-valid sudo session", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin")
expire_sudo_mode(view)
assert {:error, {:live_redirect, %{to: "/accounts/log-in?return_to=%2Fadmin"}}} =
view
|> form("#admin-account-filter", filters: %{query: "anything"})
|> render_change()
end
test "saving requires a still-valid sudo session", %{conn: conn} do
target = account_fixture()
{:ok, view, _html} = live(conn, ~p"/admin/accounts/#{target.id}")
expire_sudo_mode(view)
assert {:error,
{:live_redirect, %{to: "/accounts/log-in?return_to=%2Fadmin%2Faccounts%2F" <> _}}} =
view
|> form("#admin-authorization-form",
authorization: %{
state: "active",
platform_role: "moderator",
trust_tier: "reviewer"
}
)
|> render_submit()
assert Accounts.get_account!(target.id).platform_role == target.platform_role
end
# Simulates the two-hour sudo window expiring while the LiveView stays open.
defp expire_sudo_mode(view) do
stale = DateTime.add(DateTime.utc_now(), -3, :hour)
:sys.replace_state(view.pid, fn state ->
account = %{state.socket.assigns.current_scope.account | authenticated_at: stale}
put_in(state.socket.assigns.current_scope.account, account)
end)
end
end

View file

@ -0,0 +1,34 @@
defmodule TarakanWeb.AgentsLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "install, publish Reports, pick up Jobs", %{conn: conn} do
{:ok, view, html} = live(conn, ~p"/agents")
assert has_element?(view, "#agents-commands")
assert has_element?(view, "#agents-dump-commands")
assert has_element?(view, "#agents-pickup-commands")
assert has_element?(view, "#agents-report-api")
assert html =~ "/install.sh | bash"
assert html =~ "tarakan login"
assert html =~ "tarakan worker --agent kimi"
assert html =~ "tarakan --agent kimi --pickup"
assert html =~ "POST "
assert html =~ "/reports"
assert html =~ "Report"
assert html =~ "Check"
assert html =~ "Job"
refute html =~ "tarakan login --url"
end
test "install.sh covers worker and pickup", %{conn: conn} do
conn = get(conn, "/install.sh")
assert conn.status == 200
body = response(conn, 200)
assert body =~ "tarakan login"
assert body =~ "tarakan worker --agent kimi"
assert body =~ "tarakan --agent kimi --pickup"
end
end

View file

@ -0,0 +1,79 @@
defmodule TarakanWeb.AlertLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Billing
alias Tarakan.Repo
test "redirects anonymous visitors to login", %{conn: conn} do
assert {:error, {:redirect, %{to: "/accounts/log-in"}}} = live(conn, ~p"/alerts")
end
test "creates, edits, toggles, and deletes a watchlist", %{conn: conn} do
account = account_fixture()
{:ok, view, _html} = conn |> log_in_account(account) |> live(~p"/alerts")
assert has_element?(view, "#watchlists")
assert has_element?(view, "#watchlist-form")
# Create
view
|> form("#watchlist-form", watchlist: %{name: "Deps", entries: "openai/codex\nacme/widget"})
|> render_submit()
[watchlist] = Billing.list_watchlists(account)
assert watchlist.name == "Deps"
assert watchlist.entries == ["openai/codex", "acme/widget"]
assert has_element?(view, "#watchlist-#{watchlist.id}")
assert has_element?(view, "#watchlist-#{watchlist.id}", "openai/codex")
# Toggle notify off
view |> element("#watchlist-notify-#{watchlist.id}") |> render_click()
refute Repo.get!(Billing.Watchlist, watchlist.id).notify
# Edit
view |> element("#watchlist-edit-#{watchlist.id}") |> render_click()
view
|> form("#watchlist-form", watchlist: %{name: "Deps v2", entries: "openai/codex"})
|> render_submit()
updated = Repo.get!(Billing.Watchlist, watchlist.id)
assert updated.name == "Deps v2"
assert updated.entries == ["openai/codex"]
assert has_element?(view, "#watchlist-#{watchlist.id}", "Deps v2")
# Delete
view |> element("#watchlist-delete-#{watchlist.id}") |> render_click()
refute has_element?(view, "#watchlist-#{watchlist.id}")
assert Billing.list_watchlists(account) == []
end
test "invalid entries show an error and do not persist", %{conn: conn} do
account = account_fixture()
{:ok, view, _html} = conn |> log_in_account(account) |> live(~p"/alerts")
html =
view
|> form("#watchlist-form", watchlist: %{name: "Bad", entries: "not a ref"})
|> render_submit()
assert html =~ "owner/name"
assert Billing.list_watchlists(account) == []
end
test "watchlists are owner-scoped", %{conn: conn} do
owner = account_fixture()
{:ok, _} =
Billing.create_watchlist(Tarakan.Accounts.Scope.for_account(owner), %{
"name" => "Private",
"entries" => ["openai/codex"]
})
{:ok, view, _html} = conn |> log_in_account(account_fixture()) |> live(~p"/alerts")
refute has_element?(view, "#watchlists", "Private")
end
end

View file

@ -0,0 +1,88 @@
defmodule TarakanWeb.BillingLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Billing.Subscription
alias Tarakan.Repo
defp subscription_fixture(account, overrides) do
attrs =
Map.merge(
%{
account_id: account.id,
plan: "enterprise",
status: "active",
stripe_customer_id: "cus_lv",
stripe_subscription_id: "sub_lv_#{account.id}",
current_period_end: DateTime.utc_now()
},
overrides
)
%Subscription{}
|> Subscription.changeset(attrs)
|> Repo.insert!()
end
test "redirects anonymous visitors to login", %{conn: conn} do
assert {:error, {:redirect, %{to: "/accounts/log-in"}}} = live(conn, ~p"/accounts/billing")
end
test "shows the unsubscribed state with the managed-disclosure button", %{conn: conn} do
{:ok, view, _html} = conn |> log_in_account(account_fixture()) |> live(~p"/accounts/billing")
assert has_element?(view, "#billing-current")
assert has_element?(view, "#billing-plan", "None")
assert has_element?(view, "#billing-subscribe-enterprise")
refute has_element?(view, "#billing-manage-button")
end
test "shows the active plan, status, period end, and manage button", %{conn: conn} do
account = account_fixture()
subscription_fixture(account, %{})
{:ok, view, _html} = conn |> log_in_account(account) |> live(~p"/accounts/billing")
assert has_element?(view, "#billing-plan", "Managed disclosure")
assert has_element?(view, "#billing-status", "Active")
assert has_element?(view, "#billing-manage-button")
refute has_element?(view, "#billing-subscribe-enterprise")
end
test "renders past_due and canceled states", %{conn: conn} do
past_due = account_fixture()
subscription_fixture(past_due, %{status: "past_due"})
{:ok, view, _html} = conn |> log_in_account(past_due) |> live(~p"/accounts/billing")
assert has_element?(view, "#billing-status", "Past due")
canceled = account_fixture()
subscription_fixture(canceled, %{status: "canceled"})
{:ok, view, _html} = conn |> log_in_account(canceled) |> live(~p"/accounts/billing")
assert has_element?(view, "#billing-status", "Canceled")
assert has_element?(view, "#billing-subscribe-enterprise")
end
test "subscribe redirects to Stripe checkout", %{conn: conn} do
{:ok, view, _html} = conn |> log_in_account(account_fixture()) |> live(~p"/accounts/billing")
assert {:error, {:redirect, %{to: checkout_url}}} =
view |> element("#billing-subscribe-enterprise") |> render_click()
assert checkout_url =~ "https://checkout.stripe.com/"
end
test "manage redirects to the Stripe billing portal", %{conn: conn} do
account = account_fixture()
subscription_fixture(account, %{})
{:ok, view, _html} = conn |> log_in_account(account) |> live(~p"/accounts/billing")
assert {:error, {:redirect, %{to: portal_url}}} =
view |> element("#billing-manage-button") |> render_click()
assert portal_url =~ "https://billing.stripe.com/"
end
end

View file

@ -0,0 +1,155 @@
defmodule TarakanWeb.BountyLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Accounts.Scope
alias Tarakan.Market
alias Tarakan.Market.Bounty
alias Tarakan.Repo
defp open_bounty do
repository = listed_github_repository_fixture()
sponsor = active_account_fixture() |> fund_credits(1_000)
bounty = credits_bounty_fixture(Scope.for_account(sponsor), repository)
{bounty, sponsor, repository}
end
describe "index" do
test "renders open bounties for anonymous visitors", %{conn: conn} do
{bounty, sponsor, _repository} = open_bounty()
{:ok, view, html} = live(conn, ~p"/bounties")
assert html =~ "Contracts"
assert has_element?(view, "#bounties")
assert has_element?(view, "#bounty-#{bounty.public_id}")
assert html =~ bounty.title
assert html =~ "500 credits"
assert html =~ "@#{sponsor.handle}"
assert has_element?(view, "#bounty-filters")
end
test "shows the empty state when nothing is open", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/bounties")
assert has_element?(view, "#bounties-empty")
end
end
describe "new" do
test "redirects anonymous visitors to login", %{conn: conn} do
assert {:error, {:redirect, %{to: "/accounts/log-in"}}} = live(conn, ~p"/bounties/new")
end
test "creates a credits bounty and lands on the poster", %{conn: conn} do
repository = listed_github_repository_fixture()
sponsor = active_account_fixture() |> fund_credits(1_000)
conn = log_in_account(conn, sponsor)
{:ok, view, _html} = live(conn, ~p"/bounties/new")
assert has_element?(view, "#bounty-form")
assert has_element?(view, "#bounty-credit-balance")
# The target is picked from search, not typed.
view
|> form("#bounty-form", %{"bounty" => %{"repo_query" => repository.name}})
|> render_change()
assert view
|> element("#bounty-repo-results button", "#{repository.owner}/#{repository.name}")
|> render_click()
assert has_element?(
view,
"#bounty-selected-repository",
"#{repository.host}/#{repository.owner}/#{repository.name}"
)
{:ok, _show_view, html} =
view
|> form("#bounty-form", %{
"bounty" => %{
"target_type" => "repository",
"title" => "Harden the webhook verifier",
"description" => "Audit the signature verification path and ship a fix with tests.",
"funding" => "credits",
"amount" => "300"
}
})
|> render_submit()
|> follow_redirect(conn)
assert html =~ "Harden the webhook verifier"
assert html =~ "300 credits"
bounty = Repo.one!(Bounty)
assert bounty.status == "open"
assert bounty.credit_amount == 300
assert Tarakan.Credits.balance(sponsor) == 700
end
end
describe "show" do
test "anonymous visitors see the poster and a login prompt, not a claim button", %{
conn: conn
} do
{bounty, _sponsor, _repository} = open_bounty()
{:ok, view, html} = live(conn, ~p"/bounties/#{bounty.public_id}")
assert html =~ bounty.title
assert has_element?(view, "#bounty-status")
refute has_element?(view, "#bounty-claim-button")
assert has_element?(view, "#bounty-login-to-claim")
end
test "the sponsor sees cancel controls but no claim button", %{conn: conn} do
{bounty, sponsor, _repository} = open_bounty()
conn = log_in_account(conn, sponsor)
{:ok, view, _html} = live(conn, ~p"/bounties/#{bounty.public_id}")
refute has_element?(view, "#bounty-claim-button")
assert has_element?(view, "#bounty-cancel-button")
end
test "an eligible hunter sees and uses the claim button", %{conn: conn} do
{bounty, _sponsor, _repository} = open_bounty()
hunter = active_account_fixture()
conn = log_in_account(conn, hunter)
{:ok, view, _html} = live(conn, ~p"/bounties/#{bounty.public_id}")
assert has_element?(view, "#bounty-claim-button")
html = view |> element("#bounty-claim-button") |> render_click()
assert html =~ "claimed"
assert has_element?(view, "#bounty-claims")
bounty = Repo.get!(Bounty, bounty.id)
assert bounty.status == "claimed"
claim = Repo.one!(Tarakan.Market.BountyClaim)
assert claim.account_id == hunter.id
assert claim.review_task_id
end
end
describe "wanted banners" do
test "the repository security page shows the banner for its open bounty", %{conn: conn} do
{bounty, _sponsor, repository} = open_bounty()
{:ok, view, _html} =
live(conn, TarakanWeb.RepositoryPaths.repository_security_path(repository))
assert has_element?(view, "#wanted-banner")
bounty = Market.get_bounty!(bounty.public_id)
assert bounty.status == "open"
end
end
end

View file

@ -0,0 +1,79 @@
defmodule TarakanWeb.ClientAuthorizationLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Accounts.ClientAuthorizations
setup :register_and_log_in_account
test "approve/deny with no loaded authorization flashes instead of crashing", %{conn: conn} do
# An unknown/expired user code leaves @authorization nil and hides the
# buttons - but a queued or crafted event must not reach the context with
# nil and raise a FunctionClauseError.
{:ok, view, html} = live(conn, ~p"/client/authorize/UNKNOWNXY")
assert html =~ "invalid or expired"
assert render_click(view, "approve") =~ "expired or was already used"
assert render_click(view, "deny") =~ "expired or was already used"
end
describe "consent screen" do
test "attributes the client-supplied name instead of asserting it", %{conn: conn} do
{:ok, _device_code, user_code, _authorization} =
ClientAuthorizations.start(%{"client_name" => "Totally Official Thing"})
{:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")
# The name is presented as the request's own claim, next to a warning,
# rather than as a statement this page vouches for.
assert html =~ "Calls itself"
assert html =~ "Totally Official Thing"
assert html =~ "Nobody at Tarakan will"
end
test "a name carrying control or bidi characters is cleaned before display", %{conn: conn} do
hostile = "Tarakan" <> "\u202e" <> "lortnoC" <> "\u200b" <> " Client"
{:ok, _device_code, user_code, authorization} =
ClientAuthorizations.start(%{"client_name" => hostile})
refute authorization.client_name =~ "\u202e"
refute authorization.client_name =~ "\u200b"
{:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")
refute html =~ "\u202e"
end
test "a name of only unusable characters falls back to the default", %{conn: _conn} do
{:ok, _device_code, _user_code, authorization} =
ClientAuthorizations.start(%{"client_name" => "\u200b\u202e"})
assert authorization.client_name == "Tarakan Client"
end
test "a name with markup-shaped characters is refused outright" do
assert {:error, changeset} =
ClientAuthorizations.start(%{"client_name" => "Tarakan <script> Client"})
assert Keyword.has_key?(changeset.errors, :client_name)
end
end
describe "user code lookups" do
test "wrong guesses are budgeted per account", %{conn: conn} do
# Well past the per-account allowance; the page must stay uninformative
# rather than answering "exists / does not exist" indefinitely.
for index <- 1..35 do
{:ok, _view, html} = live(conn, ~p"/client/authorize/#{"AAAA000" <> to_string(index)}")
assert html =~ "invalid or expired"
end
# A real code now reads the same as a wrong one: throttled and missing
# are deliberately indistinguishable.
{:ok, _device_code, user_code, _authorization} = ClientAuthorizations.start()
{:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")
assert html =~ "invalid or expired"
end
end
end

View file

@ -0,0 +1,80 @@
defmodule TarakanWeb.CommunityLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
test "the homepage shows the shoutbox and gates posting", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
assert has_element?(view, "#registry-shoutbox")
refute has_element?(view, "#registry-presence")
assert has_element?(view, "#shoutbox-empty")
refute has_element?(view, "#shoutbox-form")
assert has_element?(view, ~s(#registry-shoutbox a[href="/accounts/log-in"]))
end
test "a signed-in contributor posts a live escaped shout", %{conn: conn} do
account = account_fixture()
{:ok, view, _html} = live(log_in_account(conn, account), ~p"/")
{:ok, observer, _html} = live(Phoenix.ConnTest.build_conn(), ~p"/")
assert has_element?(view, "#shoutbox-form")
assert has_element?(view, ~s(#shoutbox-form input[type="text"]))
html =
view
|> form("#shoutbox-form", shout: %{body: "Reviewing <script>alert(1)</script> auth."})
|> render_submit()
assert has_element?(
view,
"#shoutbox-messages article",
"Reviewing <script>alert(1)</script> auth."
)
assert has_element?(view, "#shoutbox-body-1[phx-mounted]")
assert has_element?(
observer,
"#shoutbox-messages article",
"Reviewing <script>alert(1)</script> auth."
)
assert html =~ "&lt;script&gt;"
refute html =~ "<script>alert(1)</script>"
end
test "moderators can remove a shout from the homepage", %{conn: conn} do
author = account_fixture()
{:ok, shout} =
Tarakan.Community.create_shout(Tarakan.Accounts.Scope.for_account(author), %{
"body" => "Off-topic shout"
})
moderator = moderator_account_fixture()
{:ok, view, _html} = live(log_in_account(conn, moderator), ~p"/")
view
|> element("#shout-#{shout.id} button", "Remove")
|> render_click()
assert has_element?(view, "#shout-#{shout.id}", "Removed by moderation")
refute has_element?(view, "#shout-#{shout.id} button", "Remove")
end
test "the compact composer rejects blank messages without an inline error", %{conn: conn} do
{:ok, view, _html} = live(log_in_account(conn, account_fixture()), ~p"/")
assert has_element?(view, "#shoutbox-form input[required]")
html =
view
|> form("#shoutbox-form", shout: %{body: " "})
|> render_submit()
assert html =~ "Write a message before sending."
refute has_element?(view, "#shoutbox-body-0-error")
refute html =~ "be blank"
end
end

View file

@ -0,0 +1,160 @@
defmodule TarakanWeb.ExploreLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
alias Tarakan.Activity
test "renders the wire: registrations and unverified public reviews", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
{:ok, view, _html} = live(conn, ~p"/explore")
assert has_element?(view, "#activity-wire")
assert has_element?(view, "#wire-reg-#{repository.id}", "openai/codex")
assert has_element?(view, "#wire-scan-#{scan.id}", "@#{submitter.handle}")
assert has_element?(view, "#wire-scan-#{scan.id}", "1 finding")
end
test "filters the wire by kind", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter)
{:ok, view, _html} = live(conn, ~p"/explore")
view |> element("#explore-filter button", "Registrations") |> render_click()
assert has_element?(view, "#wire-reg-#{repository.id}")
refute has_element?(view, "#wire-scan-#{scan.id}")
view |> element("#explore-filter button", "Reports") |> render_click()
assert has_element?(view, "#wire-scan-#{scan.id}")
refute has_element?(view, "#wire-reg-#{repository.id}")
end
test "new activity lands on the wire without a reload", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/explore")
assert has_element?(view, "#activity-wire-empty")
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
Activity.broadcast_registration(repository)
assert has_element?(view, "#wire-reg-#{repository.id}", "openai/codex")
end
test "verdicts on the wire show who ruled and how", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
reviewer = reviewer_account_fixture()
confirmation_fixture(scan, reviewer)
{:ok, view, _html} = live(conn, ~p"/explore")
assert render(view) =~ "@#{reviewer.handle}"
assert render(view) =~ "confirmed a finding on"
end
test "handles link to contributor profiles", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter)
{:ok, view, _html} = live(conn, ~p"/explore")
assert view
|> element("#wire-scan-#{scan.id} a[href='/#{submitter.handle}']")
|> render() =~ "@#{submitter.handle}"
end
test "comments join the wire live and on reload", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, view, _html} = live(conn, ~p"/explore")
commenter = account_fixture()
scope = Tarakan.Accounts.Scope.for_account(commenter)
{:ok, comment} =
Tarakan.Discussion.create_comment(scope, %{finding | scan: scan}, %{
"body" => "Reproduced on a clean checkout."
})
assert has_element?(view, "#wire-comment-#{comment.id}", "@#{commenter.handle}")
assert has_element?(view, "#wire-comment-#{comment.id}", "commented on")
{:ok, reloaded, _html} = live(conn, ~p"/explore")
assert has_element?(reloaded, "#wire-comment-#{comment.id}")
reloaded |> element("#explore-filter button", "Comments") |> render_click()
assert has_element?(reloaded, "#wire-comment-#{comment.id}")
refute has_element?(reloaded, "#wire-scan-#{scan.id}")
end
test "search narrows the wire by handle or repository", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter)
{:ok, view, _html} = live(conn, ~p"/explore")
view
|> form("#explore-search-form", %{"q" => submitter.handle})
|> render_change()
assert has_element?(view, "#wire-scan-#{scan.id}")
refute has_element?(view, "#wire-reg-#{repository.id}")
view
|> form("#explore-search-form", %{"q" => "no-such-thing"})
|> render_change()
assert has_element?(view, "#activity-wire-empty")
end
test "shows watcher presence", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/explore")
assert has_element?(view, "#explore-watchers")
assert view |> element("#explore-watchers") |> render() =~ "watching"
end
test "hot findings rail ranks upvoted findings and refreshes on votes", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
canonical = finding.canonical_finding
# Hot rail excludes pure single-run unconfirmed noise; second detection qualifies.
_second =
scan_fixture(repository, account_fixture(), %{
"findings_json" => findings_json_fixture(1),
"commit_sha" => scan.commit_sha
})
canonical = Tarakan.Repo.get!(Tarakan.Scans.CanonicalFinding, canonical.id)
{:ok, view, _html} = live(conn, ~p"/explore")
assert has_element?(view, "#hot-findings", "No findings upvoted this week.")
voter = Tarakan.Accounts.Scope.for_account(account_fixture())
{:ok, _summary} = Tarakan.Reputation.cast_vote(voter, "canonical_finding", canonical.id, 1)
assert has_element?(view, "#hot-finding-#{canonical.id}", "+1")
assert view
|> element("#hot-finding-#{canonical.id}")
|> render() =~ ~s(href="/findings/)
end
end

View file

@ -0,0 +1,112 @@
defmodule TarakanWeb.FindingDiscussionTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
alias Tarakan.Accounts.Scope
alias Tarakan.Discussion
setup do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
%{finding: %{finding | scan: scan}, repository: repository}
end
test "anonymous visitors see the discussion but are prompted to sign in", %{
conn: conn,
finding: finding
} do
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-discussion")
assert has_element?(view, "#finding-comment-login")
assert has_element?(view, "#finding-no-comments")
refute has_element?(view, "#finding-comment-form")
end
test "a signed-in account posts a comment", %{conn: conn, finding: finding} do
conn = log_in_account(conn, account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
view
|> form("#finding-comment-form", %{"body" => "Reproduced against the pinned commit."})
|> render_submit()
assert has_element?(view, "#finding-comment-count", "1 comment")
assert render(view) =~ "Reproduced against the pinned commit."
refute has_element?(view, "#finding-no-comments")
end
test "a reply nests under its parent", %{conn: conn, finding: finding} do
author = account_fixture()
{:ok, parent} =
Discussion.create_comment(Scope.for_account(author), finding, %{"body" => "Parent comment."})
conn = log_in_account(conn, account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
view |> element("#comment-#{parent.id} button", "Reply") |> render_click()
view
|> form("#reply-form-#{parent.id}", %{"body" => "A threaded reply."})
|> render_submit()
assert has_element?(view, "#comment-#{parent.id} #comment-#{last_comment_id()}")
assert has_element?(view, "#finding-comment-count", "2 comments")
end
test "a moderator can remove a comment and it shows as a placeholder", %{
conn: conn,
finding: finding
} do
author = account_fixture()
{:ok, comment} =
Discussion.create_comment(Scope.for_account(author), finding, %{"body" => "Spammy comment."})
conn = log_in_account(conn, moderator_account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#comment-#{comment.id} button", "Remove")
view
|> element("#comment-#{comment.id} button", "Remove")
|> render_click()
assert has_element?(view, "#comment-#{comment.id}", "Removed by moderation")
end
test "restricted findings expose no discussion to anonymous visitors", %{
conn: conn,
repository: repository
} do
submitter = github_account_fixture(%{handle: "restricted-author"})
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Scope.for_account(moderator_account_fixture()),
scan,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" =>
"Restricted in this test to confirm discussion follows finding disclosure."
}
)
assert_error_sent 404, fn -> get(conn, ~p"/findings/#{finding.public_id}") end
end
defp last_comment_id do
import Ecto.Query
Tarakan.Repo.one(
from c in Tarakan.Discussion.Comment, order_by: [desc: c.id], limit: 1, select: c.id
)
end
end

View file

@ -0,0 +1,420 @@
defmodule TarakanWeb.FindingLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
setup do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
%{repository: repository, submitter: submitter}
end
test "renders a publicly disclosed finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public")
[finding] = scan.findings
{:ok, view, html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-title", finding.title)
assert has_element?(view, "#finding-severity", "high")
assert has_element?(view, "#finding-description", "String-built SQL")
assert has_element?(view, "#finding-canonical-memory", "detected in 1 run")
assert has_element?(view, "#finding-source-link[href='/findings/#{finding.public_id}/code']")
assert has_element?(view, "#finding-verified-badge")
assert has_element?(view, "#finding-disclosure-badge")
assert has_element?(view, "#finding-copy-link")
assert has_element?(view, "#finding-cite", "/findings/#{finding.public_id}")
assert has_element?(view, "#finding-verdict-counts", "2 confirmed · 0 disputed")
assert html =~ scan.commit_sha
assert html =~ "Public"
assert has_element?(view, "#finding-record-link[href='/github.com/openai/codex/security']")
end
test "shows the raw model report exactly as submitted", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
assert scan.raw_document == findings_json_fixture(1)
{:ok, view, html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-raw-report")
assert html =~ "tarakan_scan_format"
end
test "a qualified contributor records a verdict from the finding page", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
reviewer =
account_fixture()
|> Tarakan.Accounts.Account.authorization_changeset(%{
state: "active",
platform_role: "member",
trust_tier: "reviewer"
})
|> Tarakan.Repo.update!()
conn = log_in_account(conn, reviewer)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-verdict-counts", "0 confirmed · 0 disputed")
view
|> form("#finding-verdict-form", %{
"notes" => "Reproduced against the pinned commit with the documented payload."
})
|> render_submit(%{"verdict" => "confirmed"})
assert has_element?(view, "#finding-verdict-counts", "1 confirmed · 0 disputed")
assert has_element?(view, "#finding-checks", "@#{reviewer.handle}")
assert has_element?(
view,
"#finding-checks",
"Reproduced against the pinned commit with the documented payload."
)
assert has_element?(view, "#finding-checks", "counts toward quorum")
refute has_element?(view, "#finding-verdict-form")
{:ok, public_view, _html} =
live(Phoenix.ConnTest.build_conn(), ~p"/findings/#{finding.public_id}")
assert has_element?(public_view, "#finding-checks", "@#{reviewer.handle}")
assert has_element?(public_view, "#finding-checks", "documented payload")
end
test "the submitter cannot verify their own finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
conn = log_in_account(conn, submitter)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
refute has_element?(view, "#finding-verdict-form")
end
test "the dead render carries the meta description and canonical URL", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public")
[finding] = scan.findings
html = conn |> get(~p"/findings/#{finding.public_id}") |> html_response(200)
assert html =~ ~s(rel="canonical")
assert html =~ "/findings/#{finding.public_id}"
assert html =~ "High in openai/codex"
assert get_resp_header(get(conn, ~p"/findings/#{finding.public_id}"), "x-robots-tag") == []
end
test "fresh submissions resolve for anonymous visitors immediately", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-title", finding.title)
refute has_element?(view, "#finding-verified-badge")
end
test "restricted findings do not resolve for anonymous visitors", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Tarakan.Accounts.Scope.for_account(moderator_account_fixture()),
scan,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" =>
"Deliberately restricted in this test to exercise the takedown boundary."
}
)
assert_error_sent 404, fn -> get(conn, ~p"/findings/#{finding.public_id}") end
end
test "public-summary findings do not resolve for anonymous visitors", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
publish_scan(scan, "public_summary")
assert_error_sent 404, fn -> get(conn, ~p"/findings/#{finding.public_id}") end
end
test "the submitter can still open their own restricted finding", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, _restricted} =
Tarakan.Scans.update_visibility(
Tarakan.Accounts.Scope.for_account(moderator_account_fixture()),
scan,
"restricted",
%{
"moderation_reason" => "takedown_review",
"moderation_notes" =>
"Deliberately restricted in this test to exercise submitter access."
}
)
conn = log_in_account(conn, submitter)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-title", finding.title)
end
test "the lifecycle timeline lists first-seen and check events", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
reviewer = reviewer_account_fixture()
conn = log_in_account(conn, reviewer)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-timeline-first-seen", "First seen")
refute has_element?(view, "li[id^='finding-timeline-check-']")
refute has_element?(view, "#finding-timeline-verified")
refute has_element?(view, "#finding-timeline-vendor-notified")
view
|> form("#finding-verdict-form", %{
"notes" => "Reproduced against the pinned commit with the documented payload."
})
|> render_submit(%{"verdict" => "confirmed"})
assert has_element?(view, "#finding-timeline li[id^='finding-timeline-check-']")
assert has_element?(view, "#finding-timeline", "@#{reviewer.handle}")
assert has_element?(view, "#finding-timeline", "confirmed")
end
test "the lifecycle timeline shows verified and vendor events when set", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
repository
|> scan_fixture(submitter, %{"findings_json" => findings_json_fixture(1)})
|> publish_scan("public")
[finding] = scan.findings
conn = log_in_account(conn, reviewer_account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(view, "#finding-timeline-first-seen")
assert has_element?(view, "#finding-timeline li[id^='finding-timeline-check-']")
assert has_element?(view, "#finding-timeline-verified", "Verified")
refute has_element?(view, "#finding-timeline-vendor-notified")
view
|> form("#finding-vendor-form", %{
"vendor_notification" => %{"notified_on" => "2026-01-15"}
})
|> render_submit()
assert has_element?(view, "#finding-timeline-vendor-notified", "Vendor notified")
canonical =
Tarakan.Repo.get_by!(Tarakan.Scans.CanonicalFinding, repository_id: repository.id)
assert canonical.vendor_notified_at == ~U[2026-01-15 00:00:00.000000Z]
view
|> form("#finding-vendor-form", %{"vendor_notification" => %{"notified_on" => ""}})
|> render_submit()
refute has_element?(view, "#finding-timeline-vendor-notified")
assert Tarakan.Repo.get!(Tarakan.Scans.CanonicalFinding, canonical.id).vendor_notified_at ==
nil
end
test "CWE and CVE badges link out and disclosure metadata renders", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan =
scan_fixture(repository, submitter, %{
"findings_json" => findings_json_with_disclosure_fixture()
})
[finding] = scan.findings
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
assert has_element?(
view,
"#finding-cwe-badge[href='https://cwe.mitre.org/data/definitions/89.html']",
"CWE-89"
)
assert has_element?(
view,
"#finding-cve-badge[href='https://nvd.nist.gov/vuln/detail/CVE-2026-12345']",
"CVE-2026-12345"
)
assert has_element?(view, "#finding-reproduction", "Run the payload")
assert has_element?(view, "#finding-affected-versions", ">= 1.0, < 2.0")
end
test "CWE and CVE badges and disclosure metadata are absent when unset", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
refute has_element?(view, "#finding-cwe-badge")
refute has_element?(view, "#finding-cve-badge")
refute has_element?(view, "#finding-reproduction")
refute has_element?(view, "#finding-affected-versions")
end
test "the vendor notification form is hidden from ordinary members", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
{:ok, anonymous_view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
refute has_element?(anonymous_view, "#finding-vendor-form")
conn = log_in_account(conn, submitter)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
refute has_element?(view, "#finding-vendor-form")
end
test "an unauthorized vendor notification event is rejected", %{
conn: conn,
repository: repository,
submitter: submitter
} do
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
conn = log_in_account(conn, submitter)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
render_hook(view, "record_vendor_notification", %{
"vendor_notification" => %{"notified_on" => "2026-01-15"}
})
assert has_element?(view, "#flash-group", "not authorized")
canonical =
Tarakan.Repo.get_by!(Tarakan.Scans.CanonicalFinding, repository_id: repository.id)
assert canonical.vendor_notified_at == nil
end
defp findings_json_with_disclosure_fixture do
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/example/module_1.ex",
"line_start" => 10,
"line_end" => 15,
"severity" => "high",
"title" => "Unsanitized input reaches interpolated query (1)",
"description" => "String-built SQL executed with request parameters.",
"reproduction" => "1. Check out the pinned commit\n2. Run the payload",
"affected_versions" => ">= 1.0, < 2.0",
"cwe" => "CWE-89",
"cve" => "CVE-2026-12345"
}
]
})
end
defp publish_scan(scan, visibility) do
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
scope = Tarakan.Accounts.Scope.for_account(moderator_account_fixture())
if visibility == "public" do
scan.repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
end
{:ok, scan} =
Tarakan.Scans.accept_scan(scope, scan, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Two independent reviewers supplied reproducible evidence for the pinned commit."
})
{:ok, scan} =
Tarakan.Scans.update_visibility(scope, scan, visibility, %{
"moderation_reason" => "disclosure_reviewed",
"moderation_notes" =>
"Disclosure was separately reviewed for scope, secrets, and personal data.",
"sensitive_data_reviewed" => "true"
})
scan
end
end

View file

@ -0,0 +1,110 @@
defmodule TarakanWeb.FindingVoteTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
alias Tarakan.Accounts.Scope
alias Tarakan.Discussion
setup do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
[finding] = scan.findings
%{finding: %{finding | scan: scan}, submitter: submitter}
end
test "a signed-in visitor upvotes a finding and the score updates", %{
conn: conn,
finding: finding
} do
conn = log_in_account(conn, account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
canonical_id = finding.canonical_finding.id
view
|> element(~s(#vote-canonical_finding-#{canonical_id} button[phx-value-vote="1"]))
|> render_click()
assert has_element?(view, "#vote-canonical_finding-#{canonical_id}", "1")
end
test "the submitter cannot vote on their own finding", %{
conn: conn,
finding: finding,
submitter: submitter
} do
conn = log_in_account(conn, submitter)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
canonical_id = finding.canonical_finding.id
html =
view
|> element(~s(#vote-canonical_finding-#{canonical_id} button[phx-value-vote="1"]))
|> render_click()
assert html =~ "cannot vote on your own"
assert has_element?(view, "#vote-canonical_finding-#{canonical_id}", "0")
end
test "anonymous vote clicks are gated to login", %{conn: conn, finding: finding} do
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
# Anonymous controls are disabled, so the buttons carry the disabled attr.
assert has_element?(
view,
~s(#vote-canonical_finding-#{finding.canonical_finding.id} button[disabled])
)
end
test "a comment can be voted on", %{conn: conn, finding: finding} do
{:ok, comment} =
Discussion.create_comment(Scope.for_account(account_fixture()), finding, %{
"body" => "This looks exploitable."
})
conn = log_in_account(conn, account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
view
|> element(~s(#vote-comment-#{comment.id} button[phx-value-vote="1"]))
|> render_click()
assert has_element?(view, "#vote-comment-#{comment.id}", "1")
end
test "malformed vote parameters are rejected without crashing", %{
conn: conn,
finding: finding
} do
conn = log_in_account(conn, account_fixture())
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}")
html =
render_click(view, "vote", %{
"type" => "canonical_finding",
"id" => "1 OR 1=1",
"vote" => "1"
})
assert html =~ "could not be recorded"
html =
render_click(view, "vote", %{
"type" => "canonical_finding",
"id" => "#{finding.canonical_finding.id}",
"vote" => "1; DROP"
})
assert html =~ "could not be recorded"
assert has_element?(view, "#vote-canonical_finding-#{finding.canonical_finding.id}", "0")
end
test "only the canonical finding on the security page carries a vote control", %{
finding: finding
} do
conn = Phoenix.ConnTest.build_conn()
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex/security")
assert has_element?(view, "#vote-canonical_finding-#{finding.canonical_finding.id}")
refute has_element?(view, "#vote-finding-#{finding.id}")
end
end

View file

@ -0,0 +1,180 @@
defmodule TarakanWeb.InfestationLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.FindingMemory
test "index and show render multi-repo patterns", %{conn: conn} do
submitter = github_account_fixture()
other = github_account_fixture()
repo_a = listed_github_repository_fixture(submitter)
{:ok, repo_b} = Tarakan.Repositories.register_github_repository("acme/widget", other)
repo_b = listed_repository_fixture(repo_b)
title = "Live infestation #{System.unique_integer([:positive])}"
findings = fn path ->
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => path,
"severity" => "critical",
"title" => title,
"description" => "Infestation liveview fixture."
}
]
})
end
scan_fixture(repo_a, submitter, %{"findings_json" => findings.("a.ex")})
scan_fixture(repo_b, other, %{"findings_json" => findings.("b.ex")})
# repo_a is hit twice by the same pattern at different paths, so the
# per-repo rollup and the per-finding ledger must disagree on row count.
scan_fixture(repo_a, submitter, %{"findings_json" => findings.("a2.ex")})
pattern = FindingMemory.pattern_key(title)
{:ok, index, html} = live(conn, ~p"/infestations")
assert html =~ "Infestations"
assert has_element?(index, "#infestations-constellation")
assert has_element?(index, "#infestation-#{pattern}")
{:ok, show, show_html} = live(conn, ~p"/infestations/#{pattern}")
assert show_html =~ title
assert has_element?(show, "#infestation-repo-count", "2")
assert has_element?(show, "#infestation-graph")
assert has_element?(show, "#infestation-instances")
# The repo table is a rollup: one row per repository, carrying the counts
# that distinguish it from the instance ledger below.
graph_rows = show |> element("#infestation-graph tbody") |> render()
assert graph_rows =~ "#{repo_a.owner}/#{repo_a.name}"
assert graph_rows =~ "#{repo_b.owner}/#{repo_b.name}"
row_a = show |> element("#infestation-graph-node-#{repo_a.id}") |> render() |> squish()
assert row_a =~ ">2</td>", "repo_a is hit twice and must report 2, not a blank cell"
row_b = show |> element("#infestation-graph-node-#{repo_b.id}") |> render() |> squish()
assert row_b =~ ">1</td>", "repo_b is hit once and must report 1"
# 2 repository rows, 3 instance rows: the two views are different grains.
ledger = show |> element("#infestation-ledger") |> render()
assert length(Regex.scan(~r/<li[^>]+id="ledger_instances-/, ledger)) == 3
end
describe "fix propagation" do
setup %{conn: conn} do
submitter = github_account_fixture()
other = github_account_fixture()
source = listed_github_repository_fixture(submitter)
{:ok, target} = Tarakan.Repositories.register_github_repository("acme/widget", other)
target = listed_repository_fixture(target)
title = "Propagatable finding #{System.unique_integer([:positive])}"
findings =
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/handler.ex",
"severity" => "critical",
"title" => title,
"description" => "Shared across both repositories."
}
]
})
source_scan = scan_fixture(source, submitter, %{"findings_json" => findings})
scan_fixture(target, other, %{"findings_json" => findings})
# Settle the source instance as fixed so a template can be captured.
[occurrence] = source_scan.findings
source_finding =
Tarakan.Repo.get!(Tarakan.Scans.CanonicalFinding, occurrence.canonical_finding_id)
|> Ecto.Changeset.change(%{
status: "fixed",
fixed_at: DateTime.utc_now(),
fixed_commit_sha: source_scan.commit_sha
})
|> Tarakan.Repo.update!()
%Tarakan.Scans.FindingCheck{}
|> Tarakan.Scans.FindingCheck.changeset(%{
commit_sha: source_scan.commit_sha,
verdict: "fixed",
provenance: "human",
notes: "Escaped the interpolated value and added a regression test."
})
|> Ecto.Changeset.put_change(:canonical_finding_id, source_finding.id)
|> Ecto.Changeset.put_change(:account_id, submitter.id)
|> Tarakan.Repo.insert!()
moderator = moderator_account_fixture()
{:ok, template} =
Tarakan.Fixes.capture_fix(
Tarakan.Accounts.Scope.for_account(moderator),
source_finding
)
%{
conn: log_in_account(conn, moderator),
pattern: FindingMemory.pattern_key(title),
source: source,
target: target,
template: template
}
end
test "shows the captured fix and carries it to the remaining repository", %{
conn: conn,
pattern: pattern,
source: source,
target: target,
template: template
} do
{:ok, view, html} = live(conn, ~p"/infestations/#{pattern}")
assert html =~ "Fixed on"
assert html =~ "#{source.owner}/#{source.name}"
assert html =~ "Escaped the interpolated value"
assert has_element?(view, "#infestation-propagate-fix", "Propagate to 1")
html = view |> element("#infestation-propagate-fix") |> render_click()
assert html =~ "Opened 1 fix job"
assert html =~ "carried to 1 repo"
# A real, public job now exists on the other repository, carrying the fix.
# Scan submission auto-opens a verification job too, so reach the fix job
# through the propagation rather than by repository alone.
assert [propagation] = Tarakan.Fixes.list_propagations(template)
assert propagation.repository_id == target.id
task = propagation.review_task
assert task.status == "open"
assert task.visibility == "public"
assert task.description =~ "Escaped the interpolated value"
end
test "anonymous visitors see the fix but cannot propagate it", %{pattern: pattern} do
{:ok, view, html} = live(build_conn(), ~p"/infestations/#{pattern}")
assert html =~ "Fixed on"
refute has_element?(view, "#infestation-propagate-fix")
end
end
# Collapse the whitespace HEEx leaves around interpolated cell values so
# assertions can match on the value itself.
defp squish(html) do
html
|> String.replace(~r/>\s+/, ">")
|> String.replace(~r/\s+</, "<")
end
end

View file

@ -0,0 +1,59 @@
defmodule TarakanWeb.JobsLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Tarakan.WorkFixtures
test "renders the public jobs queue for anonymous visitors", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/jobs")
assert html =~ "Jobs"
assert html =~ "tarakan login"
assert html =~ "tarakan --agent kimi --pickup"
assert html =~ "tarakan worker --agent kimi"
end
test "lists open public jobs", %{conn: conn} do
submitter = account_fixture()
repository = listed_github_repository_fixture(submitter)
task =
review_task_fixture(repository, submitter, %{
"title" => "Review auth middleware",
"visibility" => "public"
})
{:ok, view, html} = live(conn, ~p"/jobs")
assert has_element?(view, "#job-#{task.id}")
assert html =~ "Review auth middleware"
end
test "check jobs surface before ordinary open jobs", %{conn: conn} do
submitter = account_fixture()
repository = listed_github_repository_fixture(submitter)
ordinary =
review_task_fixture(repository, submitter, %{
"title" => "Ordinary code review job",
"kind" => "code_review",
"visibility" => "public"
})
# Publishing a Report with findings auto-opens a verify_findings Job.
_scan =
scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
check =
Tarakan.Work.list_open_public_tasks(20)
|> Enum.find(&(&1.kind == "verify_findings" and &1.repository_id == repository.id))
assert check
{:ok, _view, html} = live(conn, ~p"/jobs")
check_pos = :binary.match(html, "job-#{check.id}") |> elem(0)
ordinary_pos = :binary.match(html, "job-#{ordinary.id}") |> elem(0)
assert check_pos < ordinary_pos
end
end

View file

@ -0,0 +1,111 @@
defmodule TarakanWeb.LeaderboardLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
defp verified_contributor do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
confirmation_fixture(scan, reviewer_account_fixture())
confirmation_fixture(scan, reviewer_account_fixture())
{submitter, repository}
end
test "renders ranked contributors linking to their profiles", %{conn: conn} do
{submitter, _repository} = verified_contributor()
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert has_element?(view, "#leaderboard-rank-1")
assert has_element?(view, ~s(#leaderboard a[href="/#{submitter.handle}"]))
# The distinct verified canonical finding (+30) puts the submitter on top;
# the report occurrence itself is not a second reputation event.
assert has_element?(view, "#leaderboard-rank-1", submitter.handle)
assert has_element?(view, "#leaderboard-rank-1", "30")
end
test "the leaderboard can be re-sorted", %{conn: conn} do
verified_contributor()
{:ok, view, _html} = live(conn, ~p"/leaderboard")
html = view |> element("#leaderboard-sort button[phx-value-by='findings']") |> render_click()
assert_patch(view, ~p"/leaderboard?sort=findings")
assert html =~ ~s(aria-pressed="true")
end
test "filter controls render and patching filters the entries", %{conn: conn} do
{submitter, _repository} = verified_contributor()
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert has_element?(view, "#leaderboard-severity")
assert has_element?(view, "#leaderboard-window")
assert has_element?(view, "#leaderboard-rank-1")
# The fixture's findings are "high"; filtering to critical empties the board.
view |> element("#leaderboard-severity-form") |> render_change(%{"severity" => "critical"})
assert_patch(view, ~p"/leaderboard?severity=critical")
assert has_element?(view, "#leaderboard-empty")
refute has_element?(view, "#leaderboard-rank-1")
# Clearing the severity brings the contributor back; the window tabs patch too.
view |> element("#leaderboard-severity-form") |> render_change(%{"severity" => ""})
assert has_element?(view, "#leaderboard-rank-1", submitter.handle)
view |> element("#leaderboard-window button[phx-value-window='7']") |> render_click()
assert_patch(view, ~p"/leaderboard?window=7")
assert has_element?(view, "#leaderboard-rank-1", submitter.handle)
end
test "rows surface slashed stakes", %{conn: conn} do
{submitter, repository} = verified_contributor()
# A second report (a distinct finding) is refuted by a dispute quorum.
refuted =
scan_fixture(repository, submitter, %{
"findings_json" =>
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/example/other.ex",
"severity" => "high",
"title" => "Refuted finding #{System.unique_integer([:positive])}",
"description" => "Slashed stake fixture."
}
]
})
})
confirmation_fixture(refuted, reviewer_account_fixture(), "disputed")
confirmation_fixture(refuted, reviewer_account_fixture(), "disputed")
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert has_element?(view, "#leaderboard-rank-1", "1 slashed")
end
test "an empty record shows the empty state", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert has_element?(view, "#leaderboard-empty")
end
test "rows show earned badge chips after the handle", %{conn: conn} do
{submitter, _repository} = verified_contributor()
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert has_element?(view, "#leaderboard-rank-1-badge-first_blood", "First blood")
refute has_element?(view, "#leaderboard-rank-1-badge-century")
# The reviewer accounts checked the report but earned no badges, so their
# rows (if ranked) carry no chips.
html = render(view)
assert html =~ submitter.handle
end
test "the header links to the leaderboard", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/")
assert has_element?(view, ~s(a[href="/leaderboard"]))
end
end

View file

@ -0,0 +1,72 @@
defmodule TarakanWeb.ModelAnalyticsLiveTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Tarakan.Repo
alias Tarakan.Scans.CanonicalFinding
defp confirm_findings(scan) do
for occurrence <- scan.findings do
CanonicalFinding
|> Repo.get!(occurrence.canonical_finding_id)
|> Ecto.Changeset.change(status: "verified")
|> Repo.update!()
end
end
test "says so plainly when nothing has been reported", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/models")
assert html =~ "No model has reported enough findings yet"
end
# The scoreboard hides models below five reported findings, so both models
# need a real body of work before they show up at all.
defp unrelated_findings_json(count) do
findings =
for index <- 1..count do
%{
"file" => "lib/unrelated/module_#{index}.ex",
"line_start" => index * 7,
"line_end" => index * 7 + 2,
"severity" => "low",
"title" => "Unrelated observation (#{index})",
"description" => "Something else entirely."
}
end
Jason.encode!(%{"tarakan_scan_format" => 1, "findings" => findings})
end
test "scores models and lists the selected model's blind spots", %{conn: conn} do
account = github_account_fixture()
repository = github_repository_fixture(account)
found =
scan_fixture(repository, account, %{
"model" => "sharp-model",
"findings_json" => findings_json_fixture(5)
})
confirm_findings(found)
scan_fixture(repository, account, %{
"model" => "blunt-model",
"findings_json" => unrelated_findings_json(5)
})
{:ok, view, html} = live(conn, ~p"/models")
assert html =~ "sharp-model"
assert html =~ "blunt-model"
html =
view
|> element("#model-row-blunt-model")
|> render_click()
assert html =~ "Blind spots"
assert html =~ "Unsanitized input reaches interpolated query"
end
end

View file

@ -0,0 +1,207 @@
defmodule TarakanWeb.ModerationLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
alias Tarakan.Accounts
alias Tarakan.Accounts.Scope
alias Tarakan.Moderation
test "reporting requires an authenticated account", %{conn: conn} do
assert {:error, {:redirect, %{to: "/accounts/log-in"}}} =
live(conn, ~p"/moderation/report")
end
test "a participant submits a restricted report and can inspect its case", %{conn: conn} do
reporter = account_fixture()
subject = account_fixture()
conn = log_in_account(conn, reporter)
{:ok, view, _html} =
live(conn, ~p"/moderation/report?subject_type=account&subject_id=#{subject.id}")
assert has_element?(view, "#moderation-report-form")
assert has_element?(view, "#header-report-content")
refute has_element?(view, "#header-moderation-queue")
view
|> form("#moderation-report-form",
report: %{
subject_type: "account",
subject_id: subject.id,
reason: "harassment",
description: "This account is posting targeted harassment in public review evidence."
}
)
|> render_submit()
{path, flash} = assert_redirect(view)
assert path =~ ~r{^/moderation/cases/\d+$}
assert flash["info"]
{:ok, case_view, _html} = live(conn, path)
assert has_element?(case_view, "#moderation-case")
assert has_element?(case_view, "#moderation-case-status", "Open")
refute has_element?(case_view, "#moderation-case-actions")
refute has_element?(case_view, "#moderation-case-assignment")
end
test "an ordinary account cannot open the moderator queue", %{conn: conn} do
conn = log_in_account(conn, account_fixture())
assert {:error, {:redirect, %{to: "/"}}} = live(conn, ~p"/moderation/queue")
end
test "a moderator assigns and resolves a queued report", %{conn: conn} do
case_record = report_account!(account_fixture(), account_fixture())
moderator = moderator_account_fixture()
conn = log_in_account(conn, moderator)
{:ok, queue_view, _html} = live(conn, ~p"/moderation/queue")
assert has_element?(queue_view, "#header-moderation-queue")
assert has_element?(queue_view, "#moderation-case-count", "1 open")
assert has_element?(queue_view, "#assign-moderation-case-#{case_record.id}")
queue_view
|> element("#assign-moderation-case-#{case_record.id}")
|> render_click()
assert has_element?(queue_view, "#cases-#{case_record.id}", "In review")
{:ok, case_view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
assert has_element?(case_view, "#moderation-case-decision-form")
case_view
|> form("#moderation-case-decision-form",
resolution: %{
reason: "The evidence was independently reviewed and confirms a policy violation."
}
)
|> render_submit(%{"disposition" => "resolved"})
assert has_element?(case_view, "#moderation-case-status", "Resolved")
assert has_element?(case_view, "#moderation-case-resolution")
refute has_element?(case_view, "#moderation-case-decision-form")
end
test "the subject can appeal without receiving participant identity data", %{conn: conn} do
reporter = account_fixture()
subject = account_fixture()
resolver = moderator_account_fixture()
case_record =
reporter
|> report_account!(subject)
|> resolve_case!(resolver, "resolved")
conn = log_in_account(conn, subject)
{:ok, view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
assert has_element?(view, "#moderation-appeal-form")
refute has_element?(view, "#moderation-case-actions")
view
|> form("#moderation-appeal-form",
appeal: %{
reason: "The decision cites evidence from a different account and should be reconsidered."
}
)
|> render_submit()
assert has_element?(view, "#moderation-case-appeals")
assert has_element?(view, "#moderation-case-appeals", "Open")
refute has_element?(view, "#moderation-appeal-form")
refute has_element?(view, "#moderation-case-actions")
end
test "an independent moderator decides an appeal", %{conn: conn} do
reporter = account_fixture()
subject = account_fixture()
resolver = moderator_account_fixture()
appeal_moderator = moderator_account_fixture()
case_record =
reporter
|> report_account!(subject)
|> resolve_case!(resolver, "resolved")
{:ok, appeal} =
Moderation.appeal(Accounts.scope_for_account(subject), case_record, %{
"reason" =>
"The original decision relied on unrelated evidence and needs independent review."
})
conn = log_in_account(conn, appeal_moderator)
{:ok, view, _html} = live(conn, ~p"/moderation/cases/#{case_record.id}")
assert has_element?(view, "#moderation-appeal-decision-form-#{appeal.id}")
view
|> form("#moderation-appeal-decision-form-#{appeal.id}",
appeal_decision: %{
appeal_id: appeal.id,
reason: "Independent review confirms the original evidence was unrelated."
}
)
|> render_submit(%{"decision" => "upheld"})
assert has_element?(view, "#moderation-case-status", "Overturned")
assert has_element?(view, "#moderation-appeal-#{appeal.id}", "Upheld")
refute has_element?(view, "#moderation-appeal-decision-form-#{appeal.id}")
end
test "an expired sudo session redirects resolution to re-authentication", %{conn: conn} do
case_record = report_account!(account_fixture(), account_fixture())
moderator = moderator_account_fixture()
{:ok, assigned} = Moderation.assign(Scope.for_account(moderator), case_record)
# Outside the two-hour sudo window.
stale_at = DateTime.add(DateTime.utc_now(), -3, :hour)
conn = log_in_account(conn, moderator, token_authenticated_at: stale_at)
{:ok, case_view, _html} = live(conn, ~p"/moderation/cases/#{assigned.id}")
assert has_element?(case_view, "#moderation-case-decision-form")
assert {:error,
{:live_redirect, %{to: "/accounts/log-in?return_to=%2Fmoderation%2Fcases%2F" <> _}}} =
case_view
|> form("#moderation-case-decision-form",
resolution: %{
reason: "The evidence was independently reviewed and confirms a violation."
}
)
|> render_submit(%{"disposition" => "resolved"})
{:ok, unresolved} = Moderation.get_case(Scope.for_account(moderator), assigned.id)
assert unresolved.status == "in_review"
end
defp report_account!(reporter, subject) do
{:ok, case_record} =
Moderation.report(Scope.for_account(reporter), %{
"subject_type" => "account",
"subject_id" => subject.id,
"reason" => "fabricated_evidence",
"description" => "The submitted evidence appears fabricated and needs independent review."
})
case_record
end
defp resolve_case!(case_record, moderator, disposition) do
{:ok, assigned} = Moderation.assign(Scope.for_account(moderator), case_record)
{:ok, decided} =
Moderation.resolve(
Scope.for_account(moderator),
assigned,
disposition,
"The evidence was independently reviewed and supports this moderation decision."
)
decided
end
end

View file

@ -0,0 +1,98 @@
defmodule TarakanWeb.PageLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
test "the disclosure policy page renders its sections", %{conn: conn} do
html = conn |> get(~p"/policies/disclosure") |> html_response(200)
assert html =~ "Disclosure policy"
assert html =~ "Public by default"
assert html =~ "Visibility levels"
assert html =~ "public_summary"
assert html =~ "restricted"
assert html =~ "Verification quorum"
assert html =~ "Vendor notification"
assert html =~ "security.txt"
end
test "the content policy page renders its sections", %{conn: conn} do
html = conn |> get(~p"/policies/content") |> html_response(200)
assert html =~ "Content policy"
assert html =~ "Allowed content"
assert html =~ "Prohibited content"
assert html =~ "Secrets and credentials"
assert html =~ "Takedowns and appeals"
end
test "the pricing page renders the free record, bounties, and managed disclosure",
%{conn: conn} do
html = conn |> get(~p"/pricing") |> html_response(200)
assert html =~ ~s(id="pricing-free")
assert html =~ ~s(id="pricing-contracts")
assert html =~ ~s(id="pricing-managed")
assert html =~ "Tarakan keeps 10%"
assert html =~ "mailto:"
# No paid tiers, and the user-facing word is "contract", never "bounty".
refute html =~ ~s(id="pricing-team")
refute html =~ "Vaccine packs"
refute html =~ "bounty"
refute html =~ "Bounties"
end
test "the pricing checkout POST redirects anonymous visitors to login", %{conn: conn} do
conn = post(conn, ~p"/billing/checkout", %{"plan" => "team"})
assert redirected_to(conn) =~ "/accounts/log-in"
end
test "the managed disclosure page renders its sections", %{conn: conn} do
html = conn |> get(~p"/services/disclosure") |> html_response(200)
assert html =~ "Managed disclosure"
assert html =~ "What it is"
assert html =~ "Handling, not suppression"
assert html =~ "Intake"
assert html =~ ~s(id="managed-disclosure-contact")
assert html =~ "mailto:"
end
test "the pricing checkout POST redirects to Stripe for members", %{conn: conn} do
conn =
conn
|> log_in_account(account_fixture())
|> post(~p"/billing/checkout", %{"plan" => "enterprise"})
assert redirected_to(conn) =~ "https://checkout.stripe.com/"
end
# These were plain controller pages, the only routes in the shell without a
# LiveSocket. That made navigating to them a full reload and left them out of
# anything driven by live navigation.
test "the static pages are LiveViews", %{conn: conn} do
for path <- ["/pricing", "/policies/disclosure", "/policies/content", "/services/disclosure"] do
assert {:ok, _view, _html} = live(conn, path)
end
end
test "pricing marks its own nav entry active", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/pricing")
assert html =~ ~r/href="\/pricing"[^>]*aria-current="page"/
end
test "nav links live-navigate between a LiveView and a policy page", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/leaderboard")
assert {:ok, _pricing, html} =
view
|> element(~s(nav[aria-label="Primary"] a[href="/pricing"]))
|> render_click()
|> follow_redirect(conn, ~p"/pricing")
assert html =~ "Pricing"
end
end

View file

@ -0,0 +1,101 @@
defmodule TarakanWeb.ProfileLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
test "renders a contributor profile with public contribution counts", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(2)})
confirmation_fixture(scan, reviewer_account_fixture())
{:ok, view, _html} = live(conn, ~p"/#{submitter.handle}")
assert has_element?(view, "#profile-handle", submitter.handle)
assert has_element?(view, "#profile-review-count", "1")
assert has_element?(view, "#profile-finding-count", "2")
assert has_element?(view, "#profile-repo-count", "1")
assert has_element?(view, "#profile-reviews", "reviewed")
assert has_element?(view, "#profile-repositories", repository.name)
assert has_element?(view, "#profile-findings")
end
test "a moderator profile shows the role badge", %{conn: conn} do
moderator = moderator_account_fixture()
{:ok, view, _html} = live(conn, ~p"/#{moderator.handle}")
assert has_element?(view, "#profile-role", "Moderator")
end
test "an unknown handle returns not found", %{conn: conn} do
assert_error_sent 404, fn -> get(conn, ~p"/#{"ghost-nobody"}") end
end
test "a submitter handle on the security page links to the profile", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex/security")
assert has_element?(view, ~s(a[href="/#{submitter.handle}"]))
end
test "finding rows link to the finding page", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan = scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
finding = hd(scan.findings || Tarakan.Repo.preload(scan, :findings).findings)
{:ok, view, _html} = live(conn, ~p"/#{submitter.handle}")
assert has_element?(
view,
~s(#profile-finding-#{finding.public_id} a[href="/findings/#{finding.public_id}"])
)
end
test "renders the credits tile and the recent ledger", %{conn: conn} do
account = account_fixture()
{:ok, _entry} =
Tarakan.Credits.credit(account, :mint_finding_verified, {"canonical_finding", 1})
{:ok, _entry} = Tarakan.Credits.debit(account, :spend_priority_boost, nil, 20)
{:ok, view, _html} = live(conn, ~p"/#{account.handle}")
assert has_element?(view, "#profile-credit-balance", "30")
assert has_element?(view, "#profile-credits", "mint finding verified")
assert has_element?(view, "#profile-credits", "+50")
assert has_element?(view, "#profile-credits", "-20")
assert has_element?(view, "#profile-credits", "balance 30")
end
test "renders an empty credits ledger", %{conn: conn} do
account = account_fixture()
{:ok, view, _html} = live(conn, ~p"/#{account.handle}")
assert has_element?(view, "#profile-credit-balance", "0")
assert has_element?(view, "#profile-no-credits")
end
test "the badge strip renders earned badges only", %{conn: conn} do
submitter = github_account_fixture()
repository = listed_github_repository_fixture(submitter)
scan_fixture(repository, submitter, %{"findings_json" => findings_json_fixture(1)})
{:ok, view, _html} = live(conn, ~p"/#{submitter.handle}")
assert has_element?(view, "#profile-badges")
assert has_element?(view, "#profile-badge-first_blood", "First blood")
refute has_element?(view, "#profile-badge-century")
refute has_element?(view, "#profile-badge-sharpshooter")
end
test "a profile without earned badges renders no badge strip", %{conn: conn} do
account = account_fixture()
{:ok, view, _html} = live(conn, ~p"/#{account.handle}")
refute has_element?(view, "#profile-badges")
end
end

View file

@ -0,0 +1,411 @@
defmodule TarakanWeb.RepositoryCodeLiveTest do
use TarakanWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Tarakan.Accounts.Scope
alias Tarakan.RepositoryCode.Cache
alias Tarakan.Scans
@default_commit_sha String.duplicate("7", 40)
# develop branch tip in the GitHub stub (must remain a real branch tip).
@develop_commit_sha String.duplicate("8", 40)
# A fetchable commit that is not the tip of any published branch (and not a
# special-cased stub SHA such as all-c truncated trees).
@historical_commit_sha String.duplicate("b", 40)
setup do
Cache.clear()
repository = listed_github_repository_fixture()
%{repository: repository}
end
test "repository roots open the code browser by default", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex")
render_async(view, 1_000)
assert has_element?(view, "#repository-navigation")
assert has_element?(view, "#repository-code-tab[aria-current='page']", "Code")
assert has_element?(
view,
"#repository-overview-tab[href='/github.com/openai/codex/security']"
)
assert has_element?(view, "#code-tree")
end
test "the legacy code entry route still opens the default tree", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex/code")
render_async(view, 1_000)
assert has_element?(view, "#code-tree")
assert has_element?(view, "#code-commit-sha", String.slice(@default_commit_sha, 0, 7))
end
test "browses directories at the freshly resolved default commit", %{conn: conn} do
{:ok, view, _html} =
live(conn, ~p"/github.com/openai/codex/code/#{@default_commit_sha}")
render_async(view, 1_000)
assert has_element?(view, "#code-tree")
assert has_element?(view, "#code-tree-entries [id^='code-entry-']", "lib")
assert has_element?(
view,
"#code-tree-entries a[href='/github.com/openai/codex/code/#{@default_commit_sha}/README.md']"
)
end
test "round-trips encoded spaces, percent signs, fragments, and Unicode filenames", %{
conn: conn
} do
root = "/github.com/openai/codex/code/#{@default_commit_sha}"
{:ok, tree_view, _html} = live(conn, root)
render_async(tree_view, 1_000)
assert has_element?(
tree_view,
"a[href='#{root}/space%20%23%20100%25.txt']",
"space # 100%.txt"
)
assert has_element?(tree_view, "a[href='#{root}/%CE%BB.ex']", "λ.ex")
{:ok, encoded_view, _html} = live(conn, root <> "/space%20%23%20100%25.txt")
render_async(encoded_view, 1_000)
assert has_element?(encoded_view, "#code-file", "space # 100%.txt")
assert has_element?(encoded_view, "#L1 code", "# Codex")
{:ok, unicode_view, _html} = live(conn, root <> "/%CE%BB.ex")
render_async(unicode_view, 1_000)
assert has_element?(unicode_view, "#code-file", "λ.ex")
assert has_element?(unicode_view, "#L1 code", "defmodule Codex")
end
test "folder and file rows show findings for the pinned commit", %{
conn: conn,
repository: repository
} do
scan = finding_scan_fixture(repository, account_fixture(), @default_commit_sha)
_scan = publish_full_scan(repository, scan)
{:ok, root_view, _html} = live(conn, ~p"/github.com/openai/codex")
render_async(root_view, 1_000)
assert has_element?(root_view, "#code-entry-bGli-findings", "1 finding")
assert has_element?(root_view, "#code-reference", "1 finding")
{:ok, lib_view, _html} =
live(conn, ~p"/github.com/openai/codex/code/#{@default_commit_sha}/lib")
render_async(lib_view, 1_000)
assert has_element?(lib_view, "#code-entry-bGliL2NvZGV4LmV4-findings", "1 finding")
end
test "public summaries do not leak file paths through code-row badges", %{
conn: conn,
repository: repository
} do
scan = finding_scan_fixture(repository, account_fixture(), @default_commit_sha)
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
moderator_scope = Scope.for_account(moderator_account_fixture())
{:ok, accepted} = Scans.accept_scan(moderator_scope, scan, moderation_attributes())
{:ok, _summary} =
Scans.update_visibility(
moderator_scope,
accepted,
"public_summary",
moderation_attributes()
)
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex")
render_async(view, 1_000)
html = render(view)
refute has_element?(view, "[id$='-findings']")
refute html =~ "lib/codex.ex"
end
test "renders escaped plain-text lines with stable anchors and selection", %{conn: conn} do
{:ok, view, _html} =
live(
conn,
~p"/github.com/openai/codex/code/#{@default_commit_sha}/README.md?lines=1"
)
render_async(view, 1_000)
assert has_element?(view, "#code-file")
assert has_element?(view, "#L1.bg-panel")
assert has_element?(view, "#L1 code", "# Codex")
assert has_element?(
view,
"#L1 a[href='/github.com/openai/codex/code/#{@default_commit_sha}/README.md?lines=1#L1']"
)
end
test "rejects an out-of-range single-line selection", %{conn: conn} do
{:ok, view, _html} =
live(
conn,
~p"/github.com/openai/codex/code/#{@default_commit_sha}/README.md?lines=1000001"
)
render_async(view, 1_000)
assert has_element?(view, "#invalid-line-selection")
refute has_element?(view, "#code-lines .bg-panel")
end
test "renders hostile source as escaped text rather than executable markup", %{conn: conn} do
{:ok, view, _html} =
live(
conn,
~p"/github.com/openai/codex/code/#{@default_commit_sha}/unsafe.html"
)
render_async(view, 1_000)
html = render(view)
assert has_element?(view, "#L1 code", "</code><script id=\"source-xss\">alert(1)</script>")
refute has_element?(view, "script#source-xss")
assert html =~ "&lt;/code&gt;&lt;script id=&quot;source-xss&quot;&gt;"
end
test "rejects guessed historical SHAs on the generic browser", %{conn: conn} do
paths = [
~p"/github.com/openai/codex/code/#{@historical_commit_sha}",
~p"/github.com/openai/codex/code/#{@historical_commit_sha}/README.md"
]
for path <- paths do
{:ok, view, _html} = live(conn, path)
render_async(view, 1_000)
assert has_element?(view, "#code-browser-error")
assert has_element?(view, "#code-browser-error", "Source not found")
refute has_element?(view, "#code-tree")
refute has_element?(view, "#code-file")
end
end
test "branch switch opens another published branch tip", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/github.com/openai/codex/code/#{@default_commit_sha}")
render_async(view, 1_000)
assert has_element?(view, "#code-branch-select")
assert has_element?(view, "#code-tree")
view
|> form("#code-branch-form", %{branch: "develop"})
|> render_change()
path = ~p"/github.com/openai/codex/code/#{@develop_commit_sha}"
assert_redirect(view, path)
{:ok, switched, _html} = live(conn, path)
render_async(switched, 1_000)
assert has_element?(switched, "#code-tree")
assert has_element?(switched, "#code-commit-sha", String.slice(@develop_commit_sha, 0, 7))
assert has_element?(switched, ~s(#code-branch-select option[value="develop"][selected]))
end
test "deep link to a non-default branch tip works", %{conn: conn} do
{:ok, view, _html} =
live(conn, ~p"/github.com/openai/codex/code/#{@develop_commit_sha}/README.md")
render_async(view, 1_000)
assert has_element?(view, "#code-file")
assert has_element?(view, "#code-commit-sha", String.slice(@develop_commit_sha, 0, 7))
assert has_element?(view, ~s(#code-branch-select option[value="develop"][selected]))
end
test "rejects encoded traversal and backslash paths at the route boundary", %{conn: conn} do
unsafe_paths = [
"/github.com/openai/codex/code/#{@default_commit_sha}/lib/%2E%2E/README.md",
"/github.com/openai/codex/code/#{@default_commit_sha}/lib/%5C..%5Csecret"
]
for path <- unsafe_paths do
{:ok, view, _html} = live(conn, path)
assert has_element?(view, "#code-browser-error", "Invalid path")
refute has_element?(view, "#code-file")
end
end
test "an authorized opaque finding route opens historical source without leaking path in links",
%{
conn: conn,
repository: repository
} do
submitter = account_fixture()
scan = finding_scan_fixture(repository, submitter, @historical_commit_sha)
finding = hd(scan.findings)
{:ok, view, _html} =
live(log_in_account(conn, submitter), ~p"/findings/#{finding.public_id}/code")
render_async(view, 1_000)
assert has_element?(view, "#finding-code-context")
assert has_element?(view, "#code-file")
assert has_element?(view, "#L2.bg-panel")
refute has_element?(view, "#source-breadcrumbs a")
assert has_element?(view, "#L2 a[href='/findings/#{finding.public_id}/code#L2']")
end
test "the opaque route does not reveal a moderator-restricted finding to anonymous visitors",
%{
conn: conn,
repository: repository
} do
scan = finding_scan_fixture(repository, account_fixture(), @historical_commit_sha)
finding = hd(scan.findings)
moderator_scope = Scope.for_account(moderator_account_fixture())
{:ok, _restricted} =
Scans.update_visibility(moderator_scope, scan, "restricted", moderation_attributes())
assert_raise Ecto.NoResultsError, fn ->
live(conn, ~p"/findings/#{finding.public_id}/code")
end
end
test "marks a reported line range that does not exist at the pinned commit", %{
conn: conn,
repository: repository
} do
submitter = account_fixture()
scan = finding_scan_fixture(repository, submitter, @historical_commit_sha, 99)
finding = hd(scan.findings)
{:ok, view, _html} =
live(log_in_account(conn, submitter), ~p"/findings/#{finding.public_id}/code")
render_async(view, 1_000)
assert has_element?(view, "#finding-line-outside-file")
refute has_element?(view, "#code-lines .bg-panel")
end
test "finding source routes reject enumerable integer identifiers", %{conn: conn} do
assert_raise Ecto.NoResultsError, fn ->
live(conn, "/findings/1/code")
end
end
test "a connected finding view is evicted when a moderator restricts the scan", %{
conn: conn,
repository: repository
} do
scan = finding_scan_fixture(repository, account_fixture(), @historical_commit_sha)
scan = publish_full_scan(repository, scan)
finding = hd(scan.findings)
{:ok, view, _html} = live(conn, ~p"/findings/#{finding.public_id}/code")
render_async(view, 1_000)
assert has_element?(view, "#code-file")
moderator_scope = Scope.for_account(moderator_account_fixture())
assert {:ok, _restricted} =
Scans.update_visibility(moderator_scope, scan, "restricted", moderation_attributes())
assert_redirect(view, ~p"/github.com/openai/codex")
end
test "the default code page is uncached but remains indexable", %{conn: conn} do
conn = get(conn, ~p"/github.com/openai/codex")
assert html_response(conn, 200)
assert get_resp_header(conn, "cache-control") == ["no-store"]
assert get_resp_header(conn, "x-robots-tag") == []
end
test "commit-pinned code routes prohibit caches and search indexing", %{conn: conn} do
conn = get(conn, ~p"/github.com/openai/codex/code/#{@default_commit_sha}")
assert html_response(conn, 200)
assert get_resp_header(conn, "cache-control") == ["no-store"]
assert get_resp_header(conn, "x-robots-tag") == ["noindex, nofollow"]
end
test "opaque finding routes prohibit caches and search indexing", %{
conn: conn,
repository: repository
} do
scan = finding_scan_fixture(repository, account_fixture(), @historical_commit_sha)
finding = repository |> publish_full_scan(scan) |> Map.fetch!(:findings) |> hd()
conn = get(conn, ~p"/findings/#{finding.public_id}/code")
assert html_response(conn, 200)
assert get_resp_header(conn, "cache-control") == ["no-store"]
assert get_resp_header(conn, "x-robots-tag") == ["noindex, nofollow"]
end
defp finding_scan_fixture(repository, submitter, commit_sha, line_start \\ 2) do
document =
Jason.encode!(%{
"tarakan_scan_format" => 1,
"findings" => [
%{
"file" => "lib/codex.ex",
"line_start" => line_start,
"line_end" => line_start,
"severity" => "high",
"title" => "Authorization is not checked",
"description" => "A request reaches a privileged operation without a policy check."
}
]
})
scan_fixture(repository, submitter, %{
"commit_sha" => commit_sha,
"findings_json" => document
})
end
defp publish_full_scan(repository, scan) do
repository
|> Tarakan.Repositories.Repository.participation_changeset(%{
participation_mode: "maintainer_verified"
})
|> Tarakan.Repo.update!()
scan = confirmation_fixture(scan, reviewer_account_fixture())
scan = confirmation_fixture(scan, reviewer_account_fixture())
moderator_scope = Scope.for_account(moderator_account_fixture())
{:ok, accepted} = Scans.accept_scan(moderator_scope, scan, moderation_attributes())
{:ok, disclosed} =
Scans.update_visibility(
moderator_scope,
accepted,
"public",
moderation_attributes(%{"sensitive_data_reviewed" => "true"})
)
disclosed
end
defp moderation_attributes(overrides \\ %{}) do
Enum.into(overrides, %{
"moderation_reason" => "evidence_reviewed",
"moderation_notes" =>
"Independent evidence and disclosure boundaries were reviewed for this exact commit."
})
end
end

View file

@ -0,0 +1,273 @@
defmodule TarakanWeb.RepositoryCommitsLiveTest do
use TarakanWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Tarakan.Accounts
alias Tarakan.HostedRepositories
alias Tarakan.HostedRepositories.Storage
@moduletag :tmp_dir
setup %{tmp_dir: tmp_dir} do
on_exit(fn ->
File.rm_rf(Application.fetch_env!(:tarakan, Tarakan.HostedRepositories)[:root])
end)
Tarakan.RepositoryCode.Cache.clear()
account = account_fixture()
scope = Accounts.scope_for_account(account)
{:ok, repository} = HostedRepositories.create(scope, %{"name" => "history"})
%{repository: repository, tmp_dir: tmp_dir}
end
defp push_commit(repository, tmp_dir, dir_name, message) do
work = Path.join(tmp_dir, dir_name)
{_output, 0} =
System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
stderr_to_stdout: true
)
File.write!(Path.join(work, "README.md"), "# #{message}\n")
env = [
{"GIT_AUTHOR_NAME", "t"},
{"GIT_AUTHOR_EMAIL", "t@example.com"},
{"GIT_COMMITTER_NAME", "t"},
{"GIT_COMMITTER_EMAIL", "t@example.com"}
]
{_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
{_output, 0} = System.cmd("git", ["-C", work, "commit", "--quiet", "-m", message], env: env)
{_output, 0} =
System.cmd("git", ["-C", work, "push", "--quiet", "origin", "HEAD"],
env: env,
stderr_to_stdout: true
)
{sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
String.trim(sha)
end
defp push_merge_commit(repository, tmp_dir, dir_name, branch, message) do
work = Path.join(tmp_dir, dir_name)
{_output, 0} =
System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
stderr_to_stdout: true
)
env = [
{"GIT_AUTHOR_NAME", "t"},
{"GIT_AUTHOR_EMAIL", "t@example.com"},
{"GIT_COMMITTER_NAME", "t"},
{"GIT_COMMITTER_EMAIL", "t@example.com"}
]
{main, 0} = System.cmd("git", ["-C", work, "rev-parse", "--abbrev-ref", "HEAD"])
main = String.trim(main)
{_output, 0} = System.cmd("git", ["-C", work, "checkout", "--quiet", "-b", branch], env: env)
File.write!(Path.join(work, "side.txt"), "side\n")
{_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
{_output, 0} =
System.cmd("git", ["-C", work, "commit", "--quiet", "-m", "side change"], env: env)
{_output, 0} = System.cmd("git", ["-C", work, "checkout", "--quiet", main], env: env)
File.write!(Path.join(work, "main.txt"), "main\n")
{_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
{_output, 0} =
System.cmd("git", ["-C", work, "commit", "--quiet", "-m", "main change"], env: env)
{_output, 0} =
System.cmd("git", ["-C", work, "merge", "--quiet", "--no-ff", "-m", message, branch],
env: env,
stderr_to_stdout: true
)
{_output, 0} =
System.cmd("git", ["-C", work, "push", "--quiet", "origin", "HEAD"],
env: env,
stderr_to_stdout: true
)
{sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
String.trim(sha)
end
defp push_branch_commit(repository, tmp_dir, dir_name, branch, message) do
work = Path.join(tmp_dir, dir_name)
{_output, 0} =
System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
stderr_to_stdout: true
)
File.write!(Path.join(work, "FEATURE.md"), "# #{message}\n")
env = [
{"GIT_AUTHOR_NAME", "t"},
{"GIT_AUTHOR_EMAIL", "t@example.com"},
{"GIT_COMMITTER_NAME", "t"},
{"GIT_COMMITTER_EMAIL", "t@example.com"}
]
{_output, 0} = System.cmd("git", ["-C", work, "checkout", "--quiet", "-b", branch], env: env)
{_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
{_output, 0} = System.cmd("git", ["-C", work, "commit", "--quiet", "-m", message], env: env)
{_output, 0} =
System.cmd("git", ["-C", work, "push", "--quiet", "origin", branch],
env: env,
stderr_to_stdout: true
)
{sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
String.trim(sha)
end
test "lists the default branch history and links each commit to its detail page", %{
conn: conn,
repository: repository,
tmp_dir: tmp_dir
} do
first_sha = push_commit(repository, tmp_dir, "work-first", "initial")
tip = push_commit(repository, tmp_dir, "work-second", "second")
{:ok, view, _html} = live(conn, "/#{repository.owner}/#{repository.name}/commits")
render_async(view, 1_000)
assert has_element?(view, "#repository-commits-tab[aria-current='page']")
# The active tab is still a link: from a commit detail page it points at the
# commit list, which is the obvious way back and used to be unclickable.
assert has_element?(view, "a#repository-commits-tab[aria-current='page'][href]")
assert has_element?(view, "#commits-list a", "second")
assert has_element?(view, "#commits-list a", "initial")
assert has_element?(
view,
"#commits-list a[href='/#{repository.owner}/#{repository.name}/commits/#{tip}']"
)
assert has_element?(
view,
"#commits-list a[href='/#{repository.owner}/#{repository.name}/commits/#{first_sha}']"
)
refute has_element?(view, "#commits-load-more")
end
test "the commit detail page shows the full message, patch, and code link", %{
conn: conn,
repository: repository,
tmp_dir: tmp_dir
} do
_first_sha = push_commit(repository, tmp_dir, "work-first", "initial")
tip = push_commit(repository, tmp_dir, "work-second", "second")
{:ok, view, _html} =
live(conn, "/#{repository.owner}/#{repository.name}/commits/#{tip}")
render_async(view, 1_000)
assert has_element?(view, "#commit-subject", "second")
assert has_element?(view, "#commit-detail-sha", String.slice(tip, 0, 7))
assert has_element?(view, "#commit-patch", "+# second")
assert has_element?(
view,
"#commit-browse-code[href='/#{repository.owner}/#{repository.name}/code/#{tip}']"
)
# The code link opens the browser pinned at this exact commit.
{:ok, code_view, _html} = live(conn, "/#{repository.owner}/#{repository.name}/code/#{tip}")
render_async(code_view, 1_000)
assert has_element?(code_view, "#code-tree")
end
test "merge commits render with an empty patch state", %{
conn: conn,
repository: repository,
tmp_dir: tmp_dir
} do
_main_sha = push_commit(repository, tmp_dir, "work-first", "initial")
merge_sha = push_merge_commit(repository, tmp_dir, "work-merge", "side", "merge side")
{:ok, view, _html} =
live(conn, "/#{repository.owner}/#{repository.name}/commits/#{merge_sha}")
render_async(view, 1_000)
assert has_element?(view, "#commit-subject", "merge side")
assert has_element?(view, "#commit-patch-empty", "No textual changes")
refute has_element?(view, "#commits-error")
end
test "the commit detail page rejects unreachable commits", %{
conn: conn,
repository: repository,
tmp_dir: tmp_dir
} do
_sha = push_commit(repository, tmp_dir, "work-first", "initial")
unknown = String.duplicate("d", 40)
{:ok, view, _html} =
live(conn, "/#{repository.owner}/#{repository.name}/commits/#{unknown}")
render_async(view, 1_000)
assert has_element?(view, "#commits-error", "Commit not found")
refute has_element?(view, "#commit-detail")
end
test "the branch picker switches the listed history", %{
conn: conn,
repository: repository,
tmp_dir: tmp_dir
} do
_main_sha = push_commit(repository, tmp_dir, "work-first", "initial")
feature_sha =
push_branch_commit(repository, tmp_dir, "work-feature", "feature", "feature work")
{:ok, view, _html} = live(conn, "/#{repository.owner}/#{repository.name}/commits")
render_async(view, 1_000)
assert has_element?(view, "#commits-branch-select")
refute has_element?(view, "#commits-list a", "feature work")
view
|> form("#commits-branch-form", %{branch: "feature"})
|> render_change()
render_async(view, 1_000)
assert has_element?(view, "#commits-list a", "feature work")
assert has_element?(
view,
"#commits-list a[href='/#{repository.owner}/#{repository.name}/commits/#{feature_sha}']"
)
end
test "an empty repository shows an empty state", %{conn: conn, repository: repository} do
{:ok, view, _html} = live(conn, "/#{repository.owner}/#{repository.name}/commits")
render_async(view, 1_000)
assert has_element?(view, "#commits-error", "No commits yet")
refute has_element?(view, "#commits-list")
end
test "unknown repositories 404", %{conn: conn} do
assert_raise Ecto.NoResultsError, fn ->
live(conn, "/nobody/here/commits")
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,111 @@
defmodule TarakanWeb.RepositoryNewLiveTest do
use TarakanWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Tarakan.HostedRepositories
alias Tarakan.Repositories
setup :register_and_log_in_account
setup do
on_exit(fn ->
File.rm_rf(Application.fetch_env!(:tarakan, Tarakan.HostedRepositories)[:root])
end)
:ok
end
test "requires authentication" do
conn = Phoenix.ConnTest.build_conn()
assert {:error, {:redirect, %{to: path}}} = live(conn, ~p"/repositories/new")
assert path =~ "/accounts/log-in"
end
test "creates a hosted repository and navigates to it", %{conn: conn, account: account} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
assert has_element?(view, "#new-repository-form")
view
|> form("#new-repository-form", repository: %{name: "from-the-ui"})
|> render_submit()
assert_redirect(view, "/#{account.handle}/from-the-ui")
assert HostedRepositories.resolve(account.handle, "from-the-ui")
# the bare GitHub-style path must dispatch through the hosted scope
{:ok, repo_view, _html} = live(conn, "/#{account.handle}/from-the-ui")
assert has_element?(repo_view, "#repository-name")
end
test "shows validation errors inline", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
html =
view
|> form("#new-repository-form", repository: %{name: "bad name"})
|> render_submit()
assert html =~ "may only contain"
end
describe "remote registration" do
setup %{conn: conn} do
%{conn: log_in_account(conn, github_account_fixture())}
end
test "registers a repository and opens its public record", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
view
|> form("#register-remote-form", repository: %{url: "github.com/OpenAI/Codex"})
|> render_submit()
assert_redirect(view, ~p"/github.com/openai/codex")
assert Repositories.get_github_repository("openai", "codex")
{:ok, record_view, _html} = live(conn, ~p"/github.com/openai/codex/security")
assert has_element?(record_view, "#repository-name")
assert has_element?(record_view, "#repository-source-link")
assert has_element?(record_view, "#github-metadata")
assert has_element?(record_view, "#repository-submitter")
assert has_element?(record_view, "#scans-empty")
end
test "shows a useful error for an invalid repository", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
view
|> form("#register-remote-form",
repository: %{url: "https://example.com/not/github"}
)
|> render_submit()
assert has_element?(view, "#repository_url-error")
end
test "rejects a valid-looking repository that GitHub cannot find", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
view
|> form("#register-remote-form", repository: %{url: "missing/repository"})
|> render_submit()
assert has_element?(view, "#repository_url-error")
refute Repositories.get_github_repository("missing", "repository")
end
test "shows the same safe error for a private repository", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/repositories/new")
view
|> form("#register-remote-form", repository: %{url: "private/repository"})
|> render_submit()
assert has_element?(view, "#repository_url-error")
refute Repositories.get_github_repository("private", "repository")
end
end
end

View file

@ -0,0 +1,367 @@
defmodule TarakanWeb.ReviewTaskLiveTest do
use TarakanWeb.ConnCase
import Phoenix.LiveViewTest
setup %{conn: conn} do
creator = github_account_fixture()
repository = listed_github_repository_fixture(creator)
task = review_task_fixture(repository, creator)
%{conn: conn, creator: creator, repository: repository, task: task}
end
test "the public can inspect a task but cannot claim anonymously", %{conn: conn, task: task} do
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
assert has_element?(view, "#review-task-title")
assert has_element?(view, "#review-task-status", "Open")
refute has_element?(view, "#claim-review-task-button")
html = render_hook(view, "claim", %{})
assert html =~ "not authorized"
html = render_hook(view, "review", %{"action" => "invented", "decision" => %{}})
assert html =~ "review action is invalid"
assert {:error, {:live_redirect, %{to: "/accounts/log-in?return_to=%2Fjobs%2F" <> _}}} =
render_hook(view, "publish", %{"decision" => %{}})
end
test "the task creator may claim and perform their own job", %{
conn: conn,
creator: creator,
task: task
} do
conn = log_in_account(conn, creator)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
assert has_element?(view, "#claim-review-task-button")
refute has_element?(view, "#review-task-own-notice")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-status", "Claimed")
assert has_element?(view, "#review-task-completion-form")
refute has_element?(view, "#review-task-agent-path")
end
test "agent-required jobs show the CLI path instead of free-text completion", %{
conn: conn,
creator: creator,
repository: repository
} do
task =
review_task_fixture(repository, creator, %{
"kind" => "code_review",
"capability" => "agent",
"title" => "Agent code review of auth boundaries"
})
worker = account_fixture()
conn = log_in_account(conn, worker)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
assert has_element?(view, "#review-task-title")
assert has_element?(view, "#review-task", "Agent required")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-status", "Claimed")
assert has_element?(view, "#review-task-agent-path")
refute has_element?(view, "#review-task-completion-form")
assert render(view) =~ "tarakan report"
assert render(view) =~ "--job #{task.id}"
end
test "agent check jobs show only the check command and their target findings", %{
conn: conn,
creator: creator,
repository: repository
} do
report =
scan_fixture(repository, account_fixture(), %{"findings_json" => findings_json_fixture()})
task =
review_task_fixture(repository, creator, %{
"kind" => "verify_findings",
"capability" => "agent",
"target_review_id" => report.id,
"title" => "Independently check the report"
})
worker = reviewer_account_fixture()
{:ok, view, _html} =
conn
|> log_in_account(worker)
|> live(~p"/jobs/#{task.id}")
assert has_element?(view, "#review-task-target-report", "Target report ##{report.id}")
assert has_element?(view, "#review-task-target-report", "Unsanitized input")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-agent-path")
assert render(view) =~ "tarakan worker --agent grok --once --jobs-only"
refute render(view) =~ "tarakan report"
end
test "a hybrid claimant can verify a report from the web", %{
conn: conn,
creator: creator,
repository: repository
} do
report =
scan_fixture(repository, account_fixture(), %{"findings_json" => findings_json_fixture()})
task =
review_task_fixture(repository, creator, %{
"kind" => "verify_findings",
"capability" => "hybrid",
"target_review_id" => report.id,
"title" => "Reproduce the reported data flow"
})
worker = reviewer_account_fixture()
{:ok, view, _html} =
conn
|> log_in_account(worker)
|> live(~p"/jobs/#{task.id}")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-verification-form")
refute has_element?(view, "#review-task-completion-form")
view
|> form("#review-task-verification-form",
verification: %{
provenance: "hybrid",
verdict: "confirmed",
notes: "Reproduced every reported path against the exact pinned commit.",
evidence: "Ran the negative-path request and observed the authorization bypass."
}
)
|> render_submit()
assert has_element?(view, "#review-task-status", "Submitted")
assert has_element?(view, "#review-task-target-confirmations", "authorization bypass")
assert Tarakan.Work.get_task!(task.id).linked_review_id == report.id
end
test "agent fix jobs show the autonomous patch workflow", %{
conn: conn,
creator: creator,
repository: repository
} do
task =
review_task_fixture(repository, creator, %{
"kind" => "write_fix",
"capability" => "agent",
"title" => "Patch the authorization bypass"
})
worker = account_fixture()
{:ok, view, _html} =
conn
|> log_in_account(worker)
|> live(~p"/jobs/#{task.id}")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-agent-path", "reviewable unified diff")
assert render(view) =~ "tarakan worker --agent grok --once --jobs-only"
refute render(view) =~ "tarakan report"
end
test "a proposed fix is rendered as a patch artifact", %{
conn: conn,
creator: creator,
repository: repository
} do
task =
review_task_fixture(repository, creator, %{
"kind" => "write_fix",
"capability" => "human",
"title" => "Patch the authorization bypass"
})
worker = account_fixture()
{:ok, view, _html} =
conn
|> log_in_account(worker)
|> live(~p"/jobs/#{task.id}")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(
view,
"#review-task-completion-form",
"Proposed unified diff and test plan"
)
view
|> form("#review-task-completion-form",
contribution: %{
provenance: "human",
summary: "Require ownership before the state transition.",
evidence:
"diff --git a/lib/action.ex b/lib/action.ex\n--- a/lib/action.ex\n+++ b/lib/action.ex\n@@ -1 +1,2 @@\n run()\n+authorize()\n\nTest: mix test"
}
)
|> render_submit()
assert has_element?(view, "#review-task-fix-badge", "Patch proposal")
assert has_element?(view, "#review-task-contribution", "Proposed fix")
assert has_element?(view, "#review-task-contribution", "diff --git")
refute has_element?(view, "#review-task-historical-badge")
end
test "another contributor claims and submits the task for review", %{conn: conn, task: task} do
worker = account_fixture()
conn = log_in_account(conn, worker)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
view |> element("#claim-review-task-button") |> render_click()
assert has_element?(view, "#review-task-status", "Claimed")
assert has_element?(view, "#review-task-completion-form")
refute has_element?(view, "#review-task-agent-path")
view
|> form("#review-task-completion-form",
contribution: %{
provenance: "human",
summary: "The boundary is enforced consistently.",
evidence: "Reproduced the negative path with the repository test suite."
}
)
|> render_submit()
assert has_element?(view, "#review-task-status", "Submitted")
assert has_element?(view, "#review-task-review-pending")
assert has_element?(view, "#review-task-contribution", "Human")
assert has_element?(view, "#review-task-contribution", "boundary is enforced")
refute has_element?(view, "#review-task-completion-form")
end
test "a qualified independent reviewer accepts submitted evidence", %{
conn: conn,
task: task
} do
worker = account_fixture()
{:ok, task} = Tarakan.Work.claim_task(task, worker)
{:ok, task} =
Tarakan.Work.submit_task(task, worker, %{
"provenance" => "human",
"summary" => "The boundary is enforced consistently.",
"evidence" => "Reproduced the negative path with the repository test suite."
})
reviewer = moderator_account_fixture()
conn = log_in_account(conn, reviewer)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
assert has_element?(view, "#review-task-decision-form")
view
|> form("#review-task-decision-form",
decision: %{
reason: "The submitted behavior was reproduced independently.",
evidence: "Checked out the pinned SHA and ran the documented negative-path tests."
}
)
|> render_submit(%{"action" => "accept"})
assert has_element?(view, "#review-task-status", "Accepted")
assert has_element?(view, "#review-task-visibility", "Full evidence public")
assert has_element?(view, "#review-task-contribution")
assert has_element?(view, "#review-task-disclosure-form")
refute has_element?(view, "#review-task-decision-form")
assert Tarakan.Work.get_visible_task(task.id)
{:ok, accepted_view, accepted_html} =
live(Phoenix.ConnTest.build_conn(), ~p"/jobs/#{task.id}")
assert has_element?(accepted_view, "#review-task-status", "Accepted")
assert accepted_html =~ "Reproduced the negative path"
html =
view
|> form("#review-task-disclosure-form", disclosure: %{reason: "short"})
|> render_submit(%{"visibility" => "public_summary"})
assert html =~ "should be at least 10 character"
assert Tarakan.Work.get_task!(task.id).visibility == "public"
view
|> form("#review-task-disclosure-form",
disclosure: %{
reason: "The redacted result is safe and useful to publish without raw evidence."
}
)
|> render_submit(%{"visibility" => "public_summary"})
assert has_element?(view, "#review-task-visibility", "Public summary")
{:ok, public_view, public_html} =
live(Phoenix.ConnTest.build_conn(), ~p"/jobs/#{task.id}")
assert has_element?(public_view, "#review-task-status", "Accepted")
assert public_html =~ "boundary is enforced"
refute public_html =~ "Reproduced the negative path"
refute public_html =~ "documented negative-path tests"
end
test "disclosure requires recent authentication", %{conn: conn, task: task} do
worker = account_fixture()
{:ok, task} = Tarakan.Work.claim_task(task, worker)
{:ok, task} =
Tarakan.Work.submit_task(task, worker, %{
"provenance" => "human",
"summary" => "The boundary is enforced consistently.",
"evidence" => "Reproduced the negative path with the repository test suite."
})
reviewer = moderator_account_fixture()
{:ok, task} =
Tarakan.Work.accept_task(task, reviewer, %{
"reason" => "The submitted behavior was reproduced independently.",
"evidence" => "Checked out the pinned SHA and ran the documented negative-path tests."
})
# Outside the two-hour sudo window.
stale_at = DateTime.add(DateTime.utc_now(), -3, :hour)
conn = log_in_account(conn, reviewer, token_authenticated_at: stale_at)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
assert {:error, {:live_redirect, %{to: "/accounts/log-in?return_to=%2Fjobs%2F" <> _}}} =
view
|> form("#review-task-disclosure-form",
disclosure: %{
reason: "The redacted result is safe and useful to publish without raw evidence."
}
)
|> render_submit(%{"visibility" => "public_summary"})
assert Tarakan.Work.get_task!(task.id).visibility == "public"
end
test "a claimant can release work back to the queue", %{conn: conn, task: task} do
worker = account_fixture()
conn = log_in_account(conn, worker)
{:ok, view, _html} = live(conn, ~p"/jobs/#{task.id}")
view |> element("#claim-review-task-button") |> render_click()
view |> element("#release-review-task-button") |> render_click()
assert has_element?(view, "#review-task-status", "Open")
assert has_element?(view, "#claim-review-task-button")
end
end

View file

@ -0,0 +1,60 @@
defmodule TarakanWeb.NavActiveStateTest do
use TarakanWeb.ConnCase, async: true
import Phoenix.LiveViewTest
# The highlight used to be applied by JavaScript after paint, so the server
# rendered nothing active and dead routes never highlighted at all.
test "a LiveView marks its own nav entry active on first paint", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/leaderboard")
assert html =~ ~r/href="\/leaderboard"[^>]*aria-current="page"/
refute html =~ ~r/href="\/pricing"[^>]*aria-current="page"/
end
test "a dead controller route marks its nav entry active too", %{conn: conn} do
html = conn |> get(~p"/pricing") |> html_response(200)
assert html =~ ~r/href="\/pricing"[^>]*aria-current="page"/
refute html =~ ~r/href="\/leaderboard"[^>]*aria-current="page"/
end
test "a section stays active on its subtree" do
alias TarakanWeb.CurrentPath
assert CurrentPath.active?("/jobs/12", "/jobs")
assert CurrentPath.active?("/jobs", "/jobs")
refute CurrentPath.active?("/jobsomething", "/jobs")
# Root must not own every page.
refute CurrentPath.active?("/jobs", "/")
assert CurrentPath.active?("/", "/")
refute CurrentPath.active?(nil, "/jobs")
end
# Leaderboard, Models and Pricing used to be hidden below lg while the other
# five were not. The desktop bar starts at md and the hamburger stops at md,
# so between 768 and 1024 those three were unreachable from any navigation.
test "every primary nav entry is reachable at every width", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
nav =
Regex.run(~r/<nav[^>]*aria-label="Primary".*?<\/nav>/s, html)
|> List.first()
for path <- ~w(/explore /infestations /jobs /bounties /agents /leaderboard /models /pricing) do
assert nav =~ ~s(href="#{path}"), "#{path} missing from the primary nav"
end
refute nav =~ "hidden lg:", "no primary nav entry may be hidden at a breakpoint"
end
test "the mobile menu carries the same entries", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/")
menu = Regex.run(~r/<nav[^>]*aria-label="Site".*?<\/nav>/s, html) |> List.first()
for path <- ~w(/explore /infestations /jobs /bounties /agents /leaderboard /models /pricing) do
assert menu =~ ~s(href="#{path}"), "#{path} missing from the mobile menu"
end
end
end

View file

@ -0,0 +1,17 @@
defmodule TarakanWeb.OAuthRateLimitTest do
use TarakanWeb.ConnCase
test "OAuth routes are rate limited per client IP" do
for _ <- 1..20 do
conn = get(build_conn(), ~p"/auth/github")
assert conn.status == 302
end
conn = get(build_conn(), ~p"/auth/github")
assert conn.status == 429
assert text_response(conn, 429) =~ "too many requests"
conn = get(build_conn(), ~p"/auth/gitlab")
assert conn.status == 429
end
end

View file

@ -0,0 +1,85 @@
defmodule TarakanWeb.Plugs.ApiRateLimitTest do
use Tarakan.DataCase, async: true
import Plug.Conn
import Plug.Test
alias Tarakan.Accounts.Scope
alias TarakanWeb.Plugs.ApiRateLimit
defp call_ip(conn, opts) do
ApiRateLimit.call(conn, ApiRateLimit.init([mode: :ip] ++ opts))
end
defp call_actor(conn, scope, opts) do
conn
|> assign(:current_scope, scope)
|> ApiRateLimit.call(ApiRateLimit.init([mode: :actor] ++ opts))
end
defp credential_scope(account, token_id) do
Scope.for_account(account, token_id: token_id, authentication_method: :api_credential)
end
test "requests carrying a Bearer token count against the IP bucket" do
opts = [request_limit: 2, mutation_limit: 2, window_seconds: 60]
for _ <- 1..2 do
conn =
conn(:get, "/api/repositories")
|> put_req_header("authorization", "Bearer garbage-token")
|> call_ip(opts)
refute conn.halted
end
conn =
conn(:get, "/api/repositories")
|> put_req_header("authorization", "Bearer garbage-token")
|> call_ip(opts)
assert conn.halted
assert conn.status == 429
assert Jason.decode!(conn.resp_body)["error"] == "rate limit exceeded"
end
test "IPv6 clients in the same /64 share one IP bucket" do
opts = [request_limit: 1, mutation_limit: 1, window_seconds: 60]
first =
conn(:get, "/api/repositories")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0, 0, 0, 1})
|> call_ip(opts)
refute first.halted
second =
conn(:get, "/api/repositories")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0xAAAA, 0xBBBB, 0xCCCC, 0xDDDD})
|> call_ip(opts)
assert second.halted
assert second.status == 429
end
test "plain requests still count against the IP bucket" do
opts = [request_limit: 1, mutation_limit: 1, window_seconds: 60]
refute conn(:get, "/api/repositories") |> call_ip(opts) |> Map.fetch!(:halted)
assert conn(:get, "/api/repositories") |> call_ip(opts) |> Map.fetch!(:halted)
end
test "authenticated actors get the multiplied actor budget" do
opts = [request_limit: 2, mutation_limit: 2, window_seconds: 60]
scope = credential_scope(account_fixture(), 11_001)
# 2 × @actor_multiplier (4) = 8 requests fit; the 9th is rejected.
for _ <- 1..8 do
refute conn(:get, "/api/jobs") |> call_actor(scope, opts) |> Map.fetch!(:halted)
end
limited = conn(:get, "/api/jobs") |> call_actor(scope, opts)
assert limited.halted
assert limited.status == 429
end
end

View file

@ -0,0 +1,114 @@
defmodule TarakanWeb.Plugs.ClientIpTest do
use ExUnit.Case, async: false
import Plug.Conn
import Plug.Test
alias Tarakan.TrustedProxies
alias TarakanWeb.Plugs.ClientIp
setup do
previous = Application.get_env(:tarakan, :trusted_proxies, [])
on_exit(fn ->
Application.put_env(:tarakan, :trusted_proxies, previous)
end)
:ok
end
test "does not trust X-Forwarded-For without configured proxies" do
Application.put_env(:tarakan, :trusted_proxies, [])
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {10, 0, 0, 5})
|> put_req_header("x-forwarded-for", "203.0.113.9")
|> ClientIp.call([])
assert conn.remote_ip == {10, 0, 0, 5}
end
test "rewrites remote_ip when the peer is a trusted proxy" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {10, 0, 0, 5})
|> put_req_header("x-forwarded-for", "203.0.113.9, 10.0.0.5")
|> ClientIp.call([])
assert conn.remote_ip == {203, 0, 113, 9}
end
test "ignores forwarded headers from untrusted peers" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {198, 51, 100, 1})
|> put_req_header("x-forwarded-for", "203.0.113.9")
|> ClientIp.call([])
assert conn.remote_ip == {198, 51, 100, 1}
end
test "picks the rightmost untrusted hop, not a client-spoofed leftmost entry" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
# Attacker sends a forged leftmost value; the trusted proxy appends the real
# peer it observed. remote_ip must be the real hop, never the forged one.
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {10, 0, 0, 5})
|> put_req_header("x-forwarded-for", "203.0.113.9, 198.51.100.7")
|> ClientIp.call([])
assert conn.remote_ip == {198, 51, 100, 7}
end
test "strips multiple trailing trusted proxies down to the client" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {10, 0, 0, 9})
|> put_req_header("x-forwarded-for", "203.0.113.9, 10.0.0.5, 10.0.0.9")
|> ClientIp.call([])
assert conn.remote_ip == {203, 0, 113, 9}
end
test "matches an IPv4 trusted proxy against an IPv6-mapped IPv4 peer" do
# On a dual-stack (`::`) listener, an IPv4 proxy arrives as ::ffff:10.0.0.5.
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {0, 0, 0, 0, 0, 0xFFFF, 0x0A00, 0x0005})
|> put_req_header("x-forwarded-for", "203.0.113.9")
|> ClientIp.call([])
assert conn.remote_ip == {203, 0, 113, 9}
end
test "normalizes an IPv6-mapped remote_ip to plain IPv4 for rate-limit keys" do
conn = %Plug.Conn{remote_ip: {0, 0, 0, 0, 0, 0xFFFF, 0xCB00, 0x7109}}
assert ClientIp.remote_ip_string(conn) == "203.0.113.9"
end
test "remote_ip_bucket keeps the full IPv4 address" do
conn = %Plug.Conn{remote_ip: {203, 0, 113, 9}}
assert ClientIp.remote_ip_bucket(conn) == {:v4, 203, 0, 113, 9}
end
test "remote_ip_bucket truncates IPv6 to the /64 prefix" do
conn = %Plug.Conn{remote_ip: {0x2001, 0x0DB8, 0x0001, 0x0002, 1, 2, 3, 4}}
assert ClientIp.remote_ip_bucket(conn) == {:v6, 0x2001, 0x0DB8, 0x0001, 0x0002}
end
test "remote_ip_bucket folds IPv4-mapped IPv6 into the IPv4 bucket" do
conn = %Plug.Conn{remote_ip: {0, 0, 0, 0, 0, 0xFFFF, 0xCB00, 0x7109}}
assert ClientIp.remote_ip_bucket(conn) == {:v4, 203, 0, 113, 9}
end
end

View file

@ -0,0 +1,68 @@
defmodule TarakanWeb.Plugs.CodeBrowserRateLimitTest do
use Tarakan.DataCase, async: true
import Plug.Conn
import Plug.Test
alias TarakanWeb.Plugs.CodeBrowserRateLimit
test "allows requests under the per-IP budget" do
opts = CodeBrowserRateLimit.init(limit: 2, window_seconds: 60)
for path <- [
"/findings/55d5c681-240a-4e3a-a1f8-45933a30c4ef/code",
"/github.com/openai/codex"
] do
conn = conn(:get, path) |> CodeBrowserRateLimit.call(opts)
refute conn.halted
end
end
test "answers 429 once the per-IP budget is exceeded" do
opts = CodeBrowserRateLimit.init(limit: 2, window_seconds: 60)
for _ <- 1..2 do
conn = conn(:get, "/github.com/openai/codex") |> CodeBrowserRateLimit.call(opts)
refute conn.halted
end
conn = conn(:get, "/github.com/openai/codex") |> CodeBrowserRateLimit.call(opts)
assert conn.halted
assert conn.status == 429
assert [retry_after] = get_resp_header(conn, "retry-after")
assert String.to_integer(retry_after) > 0
end
test "IPv6 clients in the same /64 share one bucket" do
opts = CodeBrowserRateLimit.init(limit: 1, window_seconds: 60)
first =
conn(:get, "/github.com/openai/codex")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0, 0, 0, 1})
|> CodeBrowserRateLimit.call(opts)
refute first.halted
second =
conn(:get, "/github.com/openai/codex")
|> Map.put(:remote_ip, {0x2001, 0x0DB8, 0, 0, 0xFFFF, 0xEEEE, 0xDDDD, 0xCCCC})
|> CodeBrowserRateLimit.call(opts)
assert second.halted
assert second.status == 429
end
test "admins and moderators are exempt from the budget" do
opts = CodeBrowserRateLimit.init(limit: 1, window_seconds: 60)
for role <- ["admin", "moderator"], _ <- 1..3 do
conn =
conn(:get, "/github.com/openai/codex")
|> assign(:current_scope, %{platform_role: role})
|> CodeBrowserRateLimit.call(opts)
refute conn.halted
end
end
end

View file

@ -0,0 +1,75 @@
defmodule TarakanWeb.Plugs.ForwardedProtoTest do
use ExUnit.Case, async: false
import Plug.Conn
import Plug.Test
alias Tarakan.TrustedProxies
alias TarakanWeb.Plugs.ForwardedProto
setup do
previous = Application.get_env(:tarakan, :trusted_proxies, [])
on_exit(fn ->
Application.put_env(:tarakan, :trusted_proxies, previous)
end)
:ok
end
test "rewrites the scheme when the peer is a trusted proxy" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("127.0.0.1,::1"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {127, 0, 0, 1})
|> put_req_header("x-forwarded-proto", "https")
|> ForwardedProto.call([])
assert conn.scheme == :https
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {0, 0, 0, 0, 0, 0, 0, 1})
|> Map.put(:scheme, :https)
|> put_req_header("x-forwarded-proto", "http")
|> ForwardedProto.call([])
assert conn.scheme == :http
end
test "ignores X-Forwarded-Proto from untrusted peers" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("10.0.0.0/8"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {198, 51, 100, 1})
|> put_req_header("x-forwarded-proto", "https")
|> ForwardedProto.call([])
assert conn.scheme == :http
end
test "ignores X-Forwarded-Proto without configured proxies" do
Application.put_env(:tarakan, :trusted_proxies, [])
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {127, 0, 0, 1})
|> put_req_header("x-forwarded-proto", "https")
|> ForwardedProto.call([])
assert conn.scheme == :http
end
test "leaves the scheme untouched without the header" do
Application.put_env(:tarakan, :trusted_proxies, TrustedProxies.parse("127.0.0.1"))
conn =
conn(:get, "/")
|> Map.put(:remote_ip, {127, 0, 0, 1})
|> ForwardedProto.call([])
assert conn.scheme == :http
end
end

View file

@ -0,0 +1,37 @@
defmodule TarakanWeb.SafeRedirectTest do
use ExUnit.Case, async: true
alias TarakanWeb.SafeRedirect
test "keeps same-origin absolute paths" do
assert SafeRedirect.local_path("/") == "/"
assert SafeRedirect.local_path("/accounts/settings?tab=identities") ==
"/accounts/settings?tab=identities"
end
test "rejects authority paths, backslashes, controls, and encoded variants" do
malicious_paths = [
"https://attacker.example/",
"//attacker.example/",
"/\\attacker.example/",
"/%5cattacker.example/",
"/%255cattacker.example/",
"/%25255cattacker.example/",
"/%2f%2fattacker.example/",
"/%252f%252fattacker.example/",
"/safe%0d%0aLocation:%20https://attacker.example/"
]
for path <- malicious_paths do
assert SafeRedirect.local_path(path) == "/", "expected #{inspect(path)} to be rejected"
end
end
test "uses the caller's fallback for invalid input" do
assert SafeRedirect.local_path("//attacker.example", "/accounts/log-in") ==
"/accounts/log-in"
assert SafeRedirect.local_path(nil, "/accounts/log-in") == "/accounts/log-in"
end
end

View file

@ -0,0 +1,22 @@
defmodule TarakanWeb.ThemeBootTest do
use TarakanWeb.ConnCase, async: true
test "the theme script is blocking and precedes the deferred bundle", %{conn: conn} do
html = conn |> get(~p"/") |> html_response(200)
theme_at = :binary.match(html, "/assets/js/theme") |> elem(0)
app_at = :binary.match(html, "/assets/js/app") |> elem(0)
assert theme_at < app_at, "theme.js must load before app.js"
# The whole point: it must not be deferred, or it runs after first paint
# and the toggle flashes the wrong option again.
[theme_tag] = Regex.run(~r|<script[^>]*/assets/js/theme[^>]*>|, html)
refute theme_tag =~ "defer"
refute theme_tag =~ "type=\"module\""
# And the bundle that needs to stay deferred still is.
[app_tag] = Regex.run(~r|<script[^>]*/assets/js/app[^>]*>|, html)
assert app_tag =~ "defer"
end
end