Files
orchestra/internal/provider/gitea_test.go
T
kami 6ccc755999 Stop the Gitea source ingesting Orchestra's own pull requests
The issues endpoint returns pull requests alongside issues, and nothing
filtered them. The first submission this deployment ever made, kami/test-e2e#8,
came straight back as task 06G4E83E4KRXM8DS90M2648MGM with the submission
packet as its description. That task would have implemented, reviewed and
submitted again, opening a pull request per cycle.

The webhook had the same hole from the other side: a pull_request delivery
leaves the issue key empty, so it would have appended a task numbered 0 with
no title. Both routes now refuse a pull request, and the poll count reports
what was ingested rather than what was listed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
2026-08-28 10:51:22 +04:00

181 lines
6.2 KiB
Go

package provider
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
"net/http/httptest"
"orchestra/internal/domain"
"os"
"path/filepath"
"testing"
)
func TestGiteaSourceNameDefaultsToRepo(t *testing.T) {
if got := (Gitea{Repo: "orchestra"}).SourceName(); got != "gitea" {
t.Fatalf("expected legacy unnamespaced source, got %q", got)
}
if got := (Gitea{Repo: "orchestra", Project: "correx"}).SourceName(); got != "gitea:correx" {
t.Fatalf("expected namespaced source, got %q", got)
}
}
func TestGiteaIngestWebhookTagsProject(t *testing.T) {
g := Gitea{Owner: "kami", Repo: "correx-repo", Project: "correx", WebhookSecret: "s3cret"}
body, _ := json.Marshal(map[string]any{
"action": "opened",
"issue": map[string]any{"number": 42, "title": "fix thing", "body": "", "state": "open"},
})
mac := hmac.New(sha256.New, []byte("s3cret"))
mac.Write(body)
sig := hex.EncodeToString(mac.Sum(nil))
sk := &sink{}
if err := g.IngestWebhook(body, sig, sk); err != nil {
t.Fatalf("ingest: %v", err)
}
if len(sk.events) != 1 {
t.Fatalf("expected 1 event, got %d", len(sk.events))
}
var p struct {
Project string `json:"project"`
Source string `json:"source"`
ExternalID string `json:"external_id"`
}
if err := json.Unmarshal(sk.events[0].Payload, &p); err != nil {
t.Fatalf("unmarshal payload: %v", err)
}
if p.Project != "correx" || p.Source != "gitea:correx" || p.ExternalID != "42" {
t.Fatalf("unexpected payload: %+v", p)
}
}
func TestGiteaIngestWebhookRejectsBadSignature(t *testing.T) {
g := Gitea{Owner: "kami", Repo: "r", Project: "p", WebhookSecret: "s3cret"}
if err := g.IngestWebhook([]byte(`{"action":"opened","issue":{"number":1}}`), "wrong", &sink{}); err == nil {
t.Fatal("expected signature rejection")
}
}
func TestMultiGiteaReflectDispatchesByTaskSource(t *testing.T) {
var hitCorrex, hitMaven bool
correxSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hitCorrex = true
w.WriteHeader(200)
}))
defer correxSrv.Close()
mavenSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hitMaven = true
w.WriteHeader(200)
}))
defer mavenSrv.Close()
m := MultiGitea{Sources: map[string]Gitea{
"gitea:correx": {BaseURL: correxSrv.URL, Owner: "kami", Repo: "correx-repo", Project: "correx"},
"gitea:maven": {BaseURL: mavenSrv.URL, Owner: "kami", Repo: "maven-repo", Project: "maven"},
}}
if err := m.ReflectTask(domain.Task{Source: "gitea:correx", ExternalID: "7"}, domain.Event{Type: "TaskCompleted"}); err != nil {
t.Fatalf("reflect to correx: %v", err)
}
if !hitCorrex || hitMaven {
t.Fatalf("expected only correx server hit, got correx=%v maven=%v", hitCorrex, hitMaven)
}
hitCorrex, hitMaven = false, false
if err := m.ReflectTask(domain.Task{Source: "gitea:maven", ExternalID: "3"}, domain.Event{Type: "TaskFailed"}); err != nil {
t.Fatalf("reflect to maven: %v", err)
}
if hitCorrex || !hitMaven {
t.Fatalf("expected only maven server hit, got correx=%v maven=%v", hitCorrex, hitMaven)
}
// A task from a non-Gitea source (or an unregistered Gitea project) must
// be a silent no-op, not an error.
if err := m.ReflectTask(domain.Task{Source: "jsonl"}, domain.Event{Type: "TaskCompleted"}); err != nil {
t.Fatalf("unmatched source should no-op, got %v", err)
}
}
func TestLoadGiteaConfigsValidatesAndRejectsDuplicates(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "gitea.json")
good := `[
{"project":"correx","base_url":"https://gitea.internal","owner":"kami","repo":"correx"},
{"project":"maven","base_url":"https://gitea.internal","owner":"kami","repo":"maven"}
]`
if err := os.WriteFile(path, []byte(good), 0644); err != nil {
t.Fatal(err)
}
cfgs, err := LoadGiteaConfigs(path)
if err != nil || len(cfgs) != 2 {
t.Fatalf("cfgs=%d err=%v", len(cfgs), err)
}
dupPath := filepath.Join(dir, "dup.json")
dup := `[
{"project":"correx","base_url":"https://gitea.internal","owner":"kami","repo":"correx"},
{"project":"correx","base_url":"https://gitea.internal","owner":"kami","repo":"other"}
]`
if err := os.WriteFile(dupPath, []byte(dup), 0644); err != nil {
t.Fatal(err)
}
if _, err := LoadGiteaConfigs(dupPath); err == nil {
t.Fatal("expected duplicate project rejection")
}
incompletePath := filepath.Join(dir, "incomplete.json")
if err := os.WriteFile(incompletePath, []byte(`[{"project":"x"}]`), 0644); err != nil {
t.Fatal(err)
}
if _, err := LoadGiteaConfigs(incompletePath); err == nil {
t.Fatal("expected missing-field rejection")
}
}
// Orchestra opens pull requests itself. The Gitea issues endpoint returns them
// alongside issues, so a poller that accepts them ingests its own submission
// back as a task whose description is the submission packet, and that task
// submits again. Seen live on 2026-08-28: pull request #8 became task
// 06G4E83E4KRXM8DS90M2648MGM.
func TestPollIgnoresPullRequests(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode([]map[string]any{
{"number": 7, "title": "a real issue", "body": "", "state": "open"},
{"number": 8, "title": "orchestra submission", "body": "", "state": "open",
"pull_request": map[string]any{"merged": false}},
})
}))
defer srv.Close()
sk := &sink{}
n, err := (Gitea{BaseURL: srv.URL, Owner: "kami", Repo: "test-e2e"}).Poll(context.Background(), sk)
if err != nil {
t.Fatalf("poll: %v", err)
}
if n != 1 || len(sk.events) != 1 {
t.Fatalf("ingested %d events (reported %d), want exactly the issue", len(sk.events), n)
}
}
func TestIngestWebhookIgnoresAPullRequestDelivery(t *testing.T) {
g := Gitea{Owner: "kami", Repo: "test-e2e", WebhookSecret: "s3cret"}
body, _ := json.Marshal(map[string]any{
"action": "opened",
"pull_request": map[string]any{"number": 8, "title": "orchestra submission", "state": "open"},
})
mac := hmac.New(sha256.New, []byte("s3cret"))
mac.Write(body)
sk := &sink{}
if err := g.IngestWebhook(body, hex.EncodeToString(mac.Sum(nil)), sk); err != nil {
t.Fatalf("ingest: %v", err)
}
if len(sk.events) != 0 {
t.Fatalf("expected no task from a pull request delivery, got %d", len(sk.events))
}
}