Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296package db
import ( "context" "encoding/json" "slices" "testing" "time"
"tangled.org/core/api/tangled" "tangled.org/core/notifier" "tangled.org/core/spindle/models" pipelinecodec "tangled.org/core/spindle/pipeline" "tangled.org/core/workflow")
func seedPipeline(t *testing.T, d *DB, rkey, repoDid, kind string) { t.Helper() repo := repoDid tm := &tangled.Pipeline_TriggerMetadata{ Kind: kind, Repo: &tangled.Pipeline_TriggerRepo{Knot: "knot.test", RepoDid: &repo, Did: repoDid}, } switch kind { case "push": tm.Push = &tangled.Pipeline_PushTriggerData{NewSha: "sha-" + rkey, Ref: "refs/heads/main"} case "pull_request": tm.PullRequest = &tangled.Pipeline_PullRequestTriggerData{SourceSha: "sha-" + rkey, SourceBranch: "feature", TargetBranch: "main"} case "manual": tm.Manual = &tangled.Pipeline_ManualTriggerData{Sha: "sha-" + rkey} } raw := tangled.Pipeline{ TriggerMetadata: tm, Workflows: []*tangled.Pipeline_Workflow{{Name: "ci.yml"}}, } record, err := pipelinecodec.FromTangled(models.PipelineId(rkey), time.Now(), raw) if err != nil { t.Fatalf("map pipeline %s: %v", rkey, err) } if err := d.CreatePipeline(record); err != nil { t.Fatalf("seed pipeline %s: %v", rkey, err) }}
func TestQueryPipelines_FilterByKind(t *testing.T) { d := newTestDB(t) ctx := context.Background() repo := "did:plc:boltless"
seedPipeline(t, d, "p-push", repo, "push") seedPipeline(t, d, "p-pull", repo, "pull_request") seedPipeline(t, d, "p-manual", repo, "manual")
cases := []struct { kinds []string wantTotal int64 wantKinds []string }{ {nil, 3, []string{"manual", "pull_request", "push"}}, {[]string{"push"}, 1, []string{"push"}}, {[]string{"pull_request"}, 1, []string{"pull_request"}}, {[]string{"manual"}, 1, []string{"manual"}}, {[]string{"push", "pull_request"}, 2, []string{"pull_request", "push"}}, }
for _, tc := range cases { pipelines, _, total, err := d.QueryPipelines(ctx, repo, nil, "", tc.kinds, 30) if err != nil { t.Fatalf("kinds=%v: QueryPipelines: %v", tc.kinds, err) } if total != tc.wantTotal { t.Errorf("kinds=%v: total = %d, want %d", tc.kinds, total, tc.wantTotal) } var gotKinds []string for _, p := range pipelines { gotKinds = append(gotKinds, triggerKindOf(p)) } slices.Sort(gotKinds) if !slices.Equal(gotKinds, tc.wantKinds) { t.Errorf("kinds=%v: returned %v, want %v", tc.kinds, gotKinds, tc.wantKinds) } }}
func TestQueryPipelines_KindScopedToRepo(t *testing.T) { d := newTestDB(t) ctx := context.Background()
seedPipeline(t, d, "a-push", "did:plc:alice", "push") seedPipeline(t, d, "b-push", "did:plc:bob", "push")
pipelines, _, total, err := d.QueryPipelines(ctx, "did:plc:alice", nil, "", []string{"push"}, 30) if err != nil { t.Fatalf("QueryPipelines: %v", err) } if total != 1 || len(pipelines) != 1 { t.Fatalf("total=%d len=%d, want exactly alice's single push pipeline", total, len(pipelines)) } if pipelines[0].RepoDID != "did:plc:alice" { t.Errorf("returned pipeline repo = %q, want did:plc:alice", pipelines[0].RepoDID) }}
func triggerKindOf(p *models.PipelineRecord) string { return p.Trigger.Kind}
func TestQueryPipelines_WorkflowStatuses(t *testing.T) { d := newTestDB(t) ctx := context.Background() repo := "did:plc:boltless" n := notifier.New()
raw := tangled.Pipeline{ TriggerMetadata: &tangled.Pipeline_TriggerMetadata{ Kind: "push", Repo: &tangled.Pipeline_TriggerRepo{Knot: "knot.test", RepoDid: &repo, Did: repo}, Push: &tangled.Pipeline_PushTriggerData{NewSha: "sha1", Ref: "refs/heads/main"}, }, Workflows: []*tangled.Pipeline_Workflow{{Name: "a"}, {Name: "b"}}, } record, err := pipelinecodec.FromTangled(models.PipelineId("pl1"), time.Now(), raw) if err != nil { t.Fatalf("map pipeline: %v", err) } if err := d.CreatePipeline(record); err != nil { t.Fatalf("CreatePipeline: %v", err) }
widA := models.WorkflowId{PipelineId: models.PipelineId("pl1"), Name: "a"} widB := models.WorkflowId{PipelineId: models.PipelineId("pl1"), Name: "b"}
for _, step := range []func() error{ func() error { return d.StatusPending(widA, &n) }, func() error { return d.StatusRunning(widA, &n) }, func() error { return d.StatusSuccess(widA, &n) }, func() error { return d.StatusPending(widB, &n) }, } { if err := step(); err != nil { t.Fatalf("seed status: %v", err) } }
pipelines, _, total, err := d.QueryPipelines(ctx, repo, nil, "", nil, 30) if err != nil { t.Fatalf("QueryPipelines: %v", err) } if total != 1 || len(pipelines) != 1 { t.Fatalf("total = %d, len = %d; want 1, 1", total, len(pipelines)) }
byName := map[string]*models.PipelineWorkflow{} for _, wf := range pipelines[0].Workflows { byName[wf.Name] = wf }
a, ok := byName["a"] if !ok { t.Fatal("workflow a missing") } if a.Status != string(models.StatusKindSuccess) { t.Errorf("a status = %q, want success", a.Status) } if a.StartedAt == nil { t.Error("a startedAt = nil, want the running transition's timestamp") } if a.FinishedAt == nil { t.Error("a finishedAt = nil, want the terminal transition's timestamp") }
b, ok := byName["b"] if !ok { t.Fatal("workflow b missing") } if b.Status != string(models.StatusKindPending) { t.Errorf("b status = %q, want pending", b.Status) } if b.StartedAt != nil || b.FinishedAt != nil { t.Errorf("b times = (%q, %q), want both unset", derefStr(b.StartedAt), derefStr(b.FinishedAt)) }}
func derefStr(s *string) string { if s == nil { return "" } return *s}
func TestToCiPipeline_TriggerKinds(t *testing.T) { repo := "did:plc:boltless" sha := "1111111111111111111111111111111111111111"
tests := []struct { kind workflow.TriggerKind meta func(*tangled.Pipeline_TriggerMetadata) wantUnion func(*tangled.CiPipeline_Trigger) bool }{ { kind: workflow.TriggerKindPush, meta: func(m *tangled.Pipeline_TriggerMetadata) { m.Push = &tangled.Pipeline_PushTriggerData{NewSha: sha, Ref: "refs/heads/main"} }, wantUnion: func(tr *tangled.CiPipeline_Trigger) bool { return tr.CiTrigger_Push != nil }, }, { kind: workflow.TriggerKindPullRequest, meta: func(m *tangled.Pipeline_TriggerMetadata) { m.PullRequest = &tangled.Pipeline_PullRequestTriggerData{ SourceSha: sha, SourceBranch: "feature", TargetBranch: "main", } }, wantUnion: func(tr *tangled.CiPipeline_Trigger) bool { return tr.CiTrigger_PullRequest != nil }, }, { kind: workflow.TriggerKindManual, meta: func(m *tangled.Pipeline_TriggerMetadata) { m.Manual = &tangled.Pipeline_ManualTriggerData{Sha: sha} }, wantUnion: func(tr *tangled.CiPipeline_Trigger) bool { return tr.CiTrigger_Manual != nil }, }, { kind: workflow.TriggerKindSchedule, meta: func(m *tangled.Pipeline_TriggerMetadata) { m.Schedule = &tangled.Pipeline_ScheduleTriggerData{ Sha: sha, Ref: "refs/heads/main", ScheduledAt: "2026-08-10T09:00:00Z", } }, wantUnion: func(tr *tangled.CiPipeline_Trigger) bool { return tr.CiTrigger_Schedule != nil }, }, }
for _, tt := range tests { t.Run(string(tt.kind), func(t *testing.T) { meta := &tangled.Pipeline_TriggerMetadata{ Kind: string(tt.kind), Repo: &tangled.Pipeline_TriggerRepo{Knot: "knot.test", RepoDid: &repo, Did: repo}, } tt.meta(meta)
record, err := pipelinecodec.FromTangled("rk1", time.Now(), tangled.Pipeline{ TriggerMetadata: meta, Workflows: []*tangled.Pipeline_Workflow{{Name: "ci.yml"}}, }) if err != nil { t.Fatalf("map pipeline: %v", err) } if record.Trigger.Kind != string(tt.kind) { t.Errorf("kind = %q, want %q", record.Trigger.Kind, tt.kind) } if record.RepoDID != repo { t.Errorf("record.RepoDID = %q, want %q", record.RepoDID, repo) } if record.Commit != sha { t.Errorf("record.Commit = %q, want %q", record.Commit, sha) } wire := pipelinecodec.ToTangled(record) if !tt.wantUnion(wire.Trigger) { t.Errorf("wrong trigger union populated for kind %q", tt.kind) } if _, err := json.Marshal(record); err != nil { t.Errorf("marshal payload: %v", err) } }) }}
func TestQueryPipelinesSkipsMalformedRowsAndAdvancesCursor(t *testing.T) { d := newTestDB(t) ctx := context.Background() repo := "did:plc:malformed-page" seedPipeline(t, d, "oldest", repo, "push") seedPipeline(t, d, "broken", repo, "push") seedPipeline(t, d, "newest", repo, "push") if _, err := d.Exec(`update pipelines set payload = '{' where pipeline_id = 'broken'`); err != nil { t.Fatal(err) }
first, cursor, total, err := d.QueryPipelines(ctx, repo, nil, "", nil, 2) if err != nil { t.Fatal(err) } if total != 3 || len(first) != 1 || first[0].ID != "newest" || cursor == "" { t.Fatalf("first page = (%+v, %q, %d)", first, cursor, total) } second, _, _, err := d.QueryPipelines(ctx, repo, nil, cursor, nil, 2) if err != nil { t.Fatal(err) } if len(second) != 1 || second[0].ID != "oldest" { t.Fatalf("second page = %+v", second) }}