package runner
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// A RUNNER WITH --branch RUNS THAT BRANCH'S CHECK-INS AND REFUSES THE
// REST BEFORE FETCHING THEM.
//
// It is the deploy runner: it holds a production secret, and any
// check-in's pipelines can name its task type, so which check-ins it
// runs is decided here, from the forge's answer, not by the pipeline.
func TestABranchOnlyRunnerRefusesOtherCheckins(t *testing.T) {
forge := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := strings.TrimPrefix(r.URL.Path, "/api/v1/r/alpha/checkin/")
branch := map[string]string{"t1": "trunk", "b1": "feat"}[h]
if branch == "" {
http.NotFound(w, r)
return
}
json.NewEncoder(w).Encode(map[string]string{"hash": h, "branch": branch})
}))
defer forge.Close()
manifest := `{"tasks":{"t-deploy":["sh","-c","echo deployed"]}}`
fetched := map[string]string{}
b := &fakeBridge{queue: map[string][]polled{
"t-deploy": {task("trunk", "t1"), task("branch", "b1"), task("pair", "pair:b1:t1"), task("unknown", "u1")},
}, resolved: map[string]map[string]any{}, logs: map[string]string{}}
srv := httptest.NewServer(b.handler())
defer srv.Close()
r, err := New(Config{
Bridge: srv.URL, Token: "WT", Tasks: []string{"t-deploy"}, MaxTasks: 1,
WorkDir: t.TempDir(), Checkout: recordingCheckout{fakeCheckout{map[string]string{"t1": manifest, "b1": manifest, "pair:b1:t1": manifest, "u1": manifest}}, fetched},
Heartbeat: 50 * time.Millisecond, Logf: t.Logf,
Branch: "trunk", BranchOf: ForgeBranch{API: forge.URL + "/api/v1/r/alpha"}.BranchOf,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() { r.Run(ctx); close(done) }()
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
b.mu.Lock()
n := len(b.resolved)
b.mu.Unlock()
if n == 4 {
break
}
}
cancel()
<-done
b.mu.Lock()
defer b.mu.Unlock()
if got := b.resolved["trunk"]; got["action"] != "complete" || !strings.Contains(b.logs["trunk"], "deployed") {
t.Errorf("FAIL: a trunk check-in resolved %v with log %q; want it run", got, b.logs["trunk"])
}
for _, id := range []string{"branch", "pair", "unknown"} {
if got := b.resolved[id]; got["action"] != "fail" {
t.Errorf("FAIL: the %s task resolved %v; want a refusal", id, got)
}
if strings.Contains(b.logs[id], "deployed") {
t.Errorf("FAIL: the %s task ran: %q", id, b.logs[id])
}
}
if len(fetched) != 1 || fetched["t1"] == "" {
t.Errorf("FAIL: fetched %v; want only t1, nothing of a refused check-in", fetched)
}
if _, err := New(Config{Bridge: srv.URL, Token: "WT", Tasks: []string{"x"}, WorkDir: "w", Checkout: fakeCheckout{}, Branch: "trunk"}); err == nil {
t.Error("FAIL: a branch-only runner with no way to ask the branch was accepted")
}
t.Log("ok: the trunk check-in ran; a branch check-in, a merge and an unknown check-in were refused unfetched")
}
// recordingCheckout notes each check-in it fetched.
type recordingCheckout struct {
fakeCheckout
fetched map[string]string
}
func (c recordingCheckout) Checkout(ctx context.Context, dir, checkin string) error {
c.fetched[checkin] = dir
return c.fakeCheckout.Checkout(ctx, dir, checkin)
}