package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
"craftdesign.group/quarry/internal/ci"
"craftdesign.group/quarry/internal/events"
"craftdesign.group/quarry/internal/oltp"
)
// A completed run raises ci.completed on the event plane, carrying the
// runId — the one place a run's terminal transition is learned, so
// every live page can wait on the event instead of asking dagnats on
// its own timer (RFC 0005).
func TestPollCIRaisesCICompletedWithRunID(t *testing.T) {
dagnats := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
json.NewEncoder(w).Encode(ci.Run{
RunID: "r1",
Status: "completed",
CompletedAt: "2026-08-29T12:00:00Z",
})
}))
defer dagnats.Close()
engine := ci.New(dagnats.URL)
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := db.AddCIRun(oltp.CIRun{RunID: "r1", Repo: "alpha", Checkin: "c1",
Workflow: "w", Pipeline: "ci/p.json", Status: "running"}); err != nil {
t.Fatal(err)
}
ev, err := events.Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
sub, err := ev.Conn().SubscribeSync("quarry.evt.alpha.ci.completed")
if err != nil {
t.Fatal(err)
}
defer sub.Unsubscribe()
ev.Conn().Flush()
pollCI(engine, db, nil, false, t.TempDir(), ev)
msg, err := sub.NextMsg(3 * time.Second)
if err != nil {
t.Fatalf("ci.completed not raised: %v", err)
}
var got struct {
Kind string `json:"kind"`
Repo string `json:"repo"`
Payload map[string]any `json:"payload"`
}
if err := json.Unmarshal(msg.Data, &got); err != nil {
t.Fatal(err)
}
if got.Kind != "ci.completed" || got.Repo != "alpha" {
t.Fatalf("event = %+v, want kind ci.completed repo alpha", got)
}
if got.Payload["runId"] != "r1" {
t.Fatalf("payload = %+v, want runId r1", got.Payload)
}
rows, err := db.CIRuns("alpha", 10)
if err != nil {
t.Fatal(err)
}
if len(rows) != 1 || rows[0].Status != "completed" || rows[0].Completed == "" {
t.Fatalf("run not recorded terminal: %+v", rows)
}
}