Fresh repository history for elektrine/tarakan-client hosted at https://git.elektrine.com/elektrine/tarakan-client.
390 lines
16 KiB
Go
390 lines
16 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const testToken = "test-secret-that-must-never-be-logged"
|
|
|
|
func TestListOpenJobsUsesGlobalQueueRoute(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
if request.Method != http.MethodGet || request.URL.Path != "/api/jobs" {
|
|
t.Fatalf("unexpected request %s %s", request.Method, request.URL.Path)
|
|
}
|
|
if got := request.Header.Get("Authorization"); got != "Bearer secret-token" {
|
|
t.Fatalf("authorization = %q", got)
|
|
}
|
|
_, _ = response.Write([]byte(`{"jobs":[{"id":3,"status":"open","kind":"code_review","capability":"agent","title":"x","repository":{"owner":"a","name":"b"}}],"tasks":[{"id":3}],"requests":[{"id":3}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := &Client{baseURL: server.URL, token: "secret-token", httpClient: server.Client()}
|
|
jobs, err := client.ListOpenJobs(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(jobs) != 1 || jobs[0].ID != 3 || jobs[0].Repository.Slug() != "a/b" {
|
|
t.Fatalf("jobs = %#v", jobs)
|
|
}
|
|
}
|
|
|
|
func TestListTasksUsesRepositoryRouteAndBearerToken(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
if request.Method != http.MethodGet || request.URL.Path != "/api/github.com/openai/codex/jobs" {
|
|
t.Fatalf("request = %s %s", request.Method, request.URL.Path)
|
|
}
|
|
if got := request.Header.Get("Authorization"); got != "Bearer "+testToken {
|
|
t.Fatalf("authorization header = %q", got)
|
|
}
|
|
response.Header().Set("Content-Type", "application/json")
|
|
_, _ = response.Write([]byte(`{"jobs":[{"id":7,"repository":{"owner":"openai","name":"codex"},"commit_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","capability":"agent","status":"open"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := New(server.URL, testToken, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tasks, err := client.ListTasks(context.Background(), "openai", "codex")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(tasks) != 1 || tasks[0].ID != 7 || tasks[0].Repository.Slug() != "openai/codex" {
|
|
t.Fatalf("tasks = %#v", tasks)
|
|
}
|
|
}
|
|
|
|
func TestTaskMutationsMatchContract(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
call func(*Client) (Task, error)
|
|
body map[string]string
|
|
}{
|
|
{name: "show", method: http.MethodGet, path: "/api/jobs/9", call: func(client *Client) (Task, error) { return client.GetTask(context.Background(), 9) }},
|
|
{name: "claim", method: http.MethodPost, path: "/api/jobs/9/claim", call: func(client *Client) (Task, error) { return client.ClaimTask(context.Background(), 9) }},
|
|
{name: "release", method: http.MethodDelete, path: "/api/jobs/9/claim", call: func(client *Client) (Task, error) { return client.ReleaseTask(context.Background(), 9) }},
|
|
{name: "renew", method: http.MethodPost, path: "/api/jobs/9/claim/renew", call: func(client *Client) (Task, error) { return client.RenewTaskClaim(context.Background(), 9) }},
|
|
{name: "submit", method: http.MethodPost, path: "/api/jobs/9/complete", body: map[string]string{"provenance": "human", "summary": "confirmed", "evidence": "test output"}, call: func(client *Client) (Task, error) {
|
|
return client.SubmitTask(context.Background(), 9, Submission{Provenance: "human", Summary: "confirmed", Evidence: "test output"})
|
|
}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
if request.Method != test.method || request.URL.Path != test.path {
|
|
t.Fatalf("request = %s %s", request.Method, request.URL.Path)
|
|
}
|
|
if test.body != nil {
|
|
var body map[string]string
|
|
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for key, want := range test.body {
|
|
if body[key] != want {
|
|
t.Fatalf("body[%q] = %q, want %q", key, body[key], want)
|
|
}
|
|
}
|
|
}
|
|
_, _ = response.Write([]byte(`{"id":9,"status":"claimed","repository":{"owner":"openai","name":"codex"}}`))
|
|
}))
|
|
defer server.Close()
|
|
client, err := New(server.URL, testToken, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task, err := test.call(client)
|
|
if err != nil || task.ID != 9 {
|
|
t.Fatalf("task = %#v, err = %v", task, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskResponseAcceptsDevelopmentWrapper(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
_, _ = response.Write([]byte(`{"task":{"id":12,"status":"open"}}`))
|
|
}))
|
|
defer server.Close()
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
task, err := client.GetTask(context.Background(), 12)
|
|
if err != nil || task.ID != 12 {
|
|
t.Fatalf("task = %#v, err = %v", task, err)
|
|
}
|
|
}
|
|
|
|
func TestTaskResponseDecodesWebContract(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
_, _ = response.Write([]byte(`{
|
|
"id":42,
|
|
"kind":"privacy_review",
|
|
"capability":"hybrid",
|
|
"title":"Map deletion",
|
|
"description":"Trace retained data",
|
|
"status":"submitted",
|
|
"visibility":"restricted",
|
|
"commit_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"commit_committed_at":"2026-07-10T12:00:00Z",
|
|
"repository":{"id":3,"host":"github","owner":"openai","name":"codex","canonical_url":"https://github.com/openai/codex","participation_mode":"maintainer_verified","record_url":"https://tarakan.lol/github/openai/codex"},
|
|
"creator":{"id":8,"handle":"finder"},
|
|
"claimant":{"id":9,"handle":"reviewer"},
|
|
"lease":{"claimed_at":"2026-07-10T12:01:00Z","expires_at":"2026-07-10T14:01:00Z","active":false},
|
|
"contribution":{"id":6,"provenance":"hybrid","summary":"Confirmed","evidence":"steps","contributor":{"id":9,"handle":"reviewer"},"submitted_at":"2026-07-10T12:30:00Z"},
|
|
"completed_at":"2026-07-10T12:30:00Z",
|
|
"inserted_at":"2026-07-10T12:00:00Z",
|
|
"updated_at":"2026-07-10T12:30:00Z",
|
|
"task_url":"https://tarakan.lol/work/42"
|
|
}`))
|
|
}))
|
|
defer server.Close()
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
task, err := client.GetTask(context.Background(), 42)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if task.Repository.Host != "github" || task.Repository.CanonicalURL != "https://github.com/openai/codex" {
|
|
t.Fatalf("repository = %#v", task.Repository)
|
|
}
|
|
if task.Repository.ParticipationMode != "maintainer_verified" || task.Status != "submitted" || task.Visibility != "restricted" {
|
|
t.Fatalf("repository/status contract = %#v / %q", task.Repository, task.Status)
|
|
}
|
|
if task.Creator == nil || task.Creator.ID != 8 || task.Creator.Handle != "finder" {
|
|
t.Fatalf("creator = %#v", task.Creator)
|
|
}
|
|
if task.Lease == nil || task.Lease.Active {
|
|
t.Fatalf("lease = %#v", task.Lease)
|
|
}
|
|
if task.Contribution == nil || task.Contribution.SubmittedAt == "" || task.Contribution.Contributor.Handle != "reviewer" {
|
|
t.Fatalf("contribution = %#v", task.Contribution)
|
|
}
|
|
}
|
|
|
|
func TestAPIErrorDoesNotExposeToken(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
response.WriteHeader(http.StatusUnauthorized)
|
|
_, _ = response.Write([]byte(`{"error":"missing or invalid API token"}`))
|
|
}))
|
|
defer server.Close()
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
_, err := client.GetTask(context.Background(), 1)
|
|
if err == nil || strings.Contains(err.Error(), testToken) {
|
|
t.Fatalf("unsafe error = %v", err)
|
|
}
|
|
var apiError *APIError
|
|
if !errors.As(err, &apiError) || apiError.StatusCode != http.StatusUnauthorized {
|
|
t.Fatalf("error = %#v", err)
|
|
}
|
|
}
|
|
|
|
func TestNewRequiresTokenAndTLSAwayFromLoopback(t *testing.T) {
|
|
if _, err := New(DefaultBaseURL, "", nil); !errors.Is(err, ErrTokenRequired) {
|
|
t.Fatalf("missing token error = %v", err)
|
|
}
|
|
if _, err := New("http://tarakan.lol", testToken, nil); err == nil || !strings.Contains(err.Error(), "HTTPS") {
|
|
t.Fatalf("insecure URL error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClientDoesNotFollowRedirectsWithAuthorization(t *testing.T) {
|
|
redirected := false
|
|
destination := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
|
redirected = true
|
|
}))
|
|
defer destination.Close()
|
|
|
|
source := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
|
http.Redirect(response, nil, destination.URL, http.StatusTemporaryRedirect)
|
|
}))
|
|
defer source.Close()
|
|
|
|
client, _ := New(source.URL, testToken, source.Client())
|
|
_, err := client.GetTask(context.Background(), 1)
|
|
if err == nil || redirected {
|
|
t.Fatalf("err = %v, redirected = %v", err, redirected)
|
|
}
|
|
}
|
|
|
|
func TestListReviewableRepositoriesQueriesStatus(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet || r.URL.Path != "/api/repositories" {
|
|
t.Fatalf("request = %s %s", r.Method, r.URL.Path)
|
|
}
|
|
if got := r.URL.Query().Get("status"); got != "unscanned" {
|
|
t.Fatalf("status = %q", got)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"repositories":[{"host":"github.com","owner":"snyk-labs","name":"nodejs-goof","status":"unscanned"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := New(server.URL, testToken, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repos, err := client.ListReviewableRepositories(context.Background(), "unscanned")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(repos) != 1 || repos[0].Slug() != "snyk-labs/nodejs-goof" || repos[0].Status != "unscanned" {
|
|
t.Fatalf("repos = %#v", repos)
|
|
}
|
|
}
|
|
|
|
func TestListScansSurfacesFindings(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet || r.URL.Path != "/api/github/snyk-labs/nodejs-goof/reports" {
|
|
t.Fatalf("request = %s %s", r.Method, r.URL.Path)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"scans":[{"id":15,"commit_sha":"add14ba","review_status":"quarantined","verified":false,"details_visible":true,"findings_count":1,"submitter":"modela","findings":[{"file":"app.js","line_start":83,"severity":"high","title":"Hardcoded secret"}]}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
scans, err := client.ListScans(context.Background(), "snyk-labs", "nodejs-goof")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(scans) != 1 || scans[0].ID != 15 || len(scans[0].Findings) != 1 || scans[0].Findings[0].File != "app.js" {
|
|
t.Fatalf("scans = %#v", scans)
|
|
}
|
|
}
|
|
|
|
func TestSubmitScanAndVerdictSendExpectedBodies(t *testing.T) {
|
|
t.Run("scan", func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost || r.URL.Path != "/api/github/snyk-labs/nodejs-goof/reports" {
|
|
t.Fatalf("request = %s %s", r.Method, r.URL.Path)
|
|
}
|
|
var body ScanSubmission
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
if body.CommitSHA != "add14ba" || body.Document.Format != 1 || len(body.Document.Findings) != 1 {
|
|
t.Fatalf("body = %#v", body)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"id":16,"review_status":"quarantined"}`))
|
|
}))
|
|
defer server.Close()
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
scan, err := client.SubmitScan(context.Background(), "snyk-labs", "nodejs-goof", ScanSubmission{
|
|
CommitSHA: "add14ba", Provenance: "agent", ReviewKind: "code_review",
|
|
Document: ScanDocument{Format: 1, Findings: []ScanFinding{{File: "app.js", Severity: "high", Title: "x", Description: "y"}}},
|
|
})
|
|
if err != nil || scan.ID != 16 {
|
|
t.Fatalf("scan = %#v err = %v", scan, err)
|
|
}
|
|
})
|
|
|
|
t.Run("verdict", func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost || r.URL.Path != "/api/github/snyk-labs/nodejs-goof/reports/15/verdict" {
|
|
t.Fatalf("request = %s %s", r.Method, r.URL.Path)
|
|
}
|
|
var body Verdict
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
if body.Verdict != "confirmed" || body.Provenance != "hybrid" || body.Evidence == "" {
|
|
t.Fatalf("body = %#v", body)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"id":15,"verified":true,"confirmations":[{"verdict":"confirmed","provenance":"hybrid"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
scan, err := client.SubmitVerdict(context.Background(), "snyk-labs", "nodejs-goof", 15, Verdict{
|
|
Verdict: "confirmed", Provenance: "hybrid", Notes: "confirmed the finding", Evidence: "poc here",
|
|
})
|
|
if err != nil || !scan.Verified {
|
|
t.Fatalf("scan = %#v err = %v", scan, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestHostAwareReportPathsSupportTarakanHostedRepositories(t *testing.T) {
|
|
var paths []string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
paths = append(paths, r.URL.Path)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch {
|
|
case strings.HasSuffix(r.URL.Path, "/memory"):
|
|
_ = json.NewEncoder(w).Encode(RepositoryMemory{Findings: []CanonicalFindingMemory{}})
|
|
case r.Method == http.MethodGet:
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"scans": []Scan{}})
|
|
default:
|
|
w.WriteHeader(http.StatusCreated)
|
|
_ = json.NewEncoder(w).Encode(Scan{ID: 1})
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
client, err := New(server.URL, testToken, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := context.Background()
|
|
_, _ = client.GetRepositoryMemoryForHost(ctx, "tarakan.lol", "alice", "demo", "abc")
|
|
_, _ = client.ListScansForHost(ctx, "tarakan.lol", "alice", "demo")
|
|
_, _ = client.SubmitScanForHost(ctx, "tarakan.lol", "alice", "demo", ScanSubmission{})
|
|
for _, got := range paths {
|
|
if !strings.HasPrefix(got, "/api/tarakan.lol/alice/demo/") {
|
|
t.Fatalf("host-aware path = %q", got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRepositoryMemoryAndFindingCheckContracts(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == http.MethodGet && r.URL.Path == "/api/github/openai/codex/memory":
|
|
if r.URL.Query().Get("commit_sha") != "abc" {
|
|
t.Fatalf("commit_sha = %q", r.URL.Query().Get("commit_sha"))
|
|
}
|
|
_, _ = w.Write([]byte(`{"repository":"openai/codex","findings":[{"public_id":"finding-1","status":"open","file_path":"auth.go","detections_count":7}]}`))
|
|
|
|
case r.Method == http.MethodPost && r.URL.Path == "/api/github/openai/codex/findings/finding-1/check":
|
|
var body FindingVerdict
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
if body.CommitSHA != "abc" || body.Verdict != "confirmed" {
|
|
t.Fatalf("body = %#v", body)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"public_id":"finding-1","status":"open"}`))
|
|
|
|
default:
|
|
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := New(server.URL, testToken, server.Client())
|
|
memory, err := client.GetRepositoryMemory(context.Background(), "openai", "codex", "abc")
|
|
if err != nil || len(memory.Findings) != 1 || memory.Findings[0].DetectionsCount != 7 {
|
|
t.Fatalf("memory = %#v, err = %v", memory, err)
|
|
}
|
|
err = client.SubmitFindingVerdict(context.Background(), "openai", "codex", "finding-1", FindingVerdict{
|
|
CommitSHA: "abc", Verdict: "confirmed", Provenance: "human", Notes: "independent evidence",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestNewRunIDIsUnique(t *testing.T) {
|
|
first, err := NewRunID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := NewRunID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first == second || !strings.HasPrefix(first, "run_") {
|
|
t.Fatalf("run ids = %q, %q", first, second)
|
|
}
|
|
}
|