Monorepo for Tangled
tangled.org
1package db
2
3import (
4 "context"
5 "strings"
6 "testing"
7 "time"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "tangled.org/core/appview/models"
11 "tangled.org/core/orm"
12 spindle "tangled.org/core/spindle/models"
13 "tangled.org/core/workflow"
14)
15
16// seedPipeline inserts a trigger + pipeline row and returns the pipeline.
17func seedPipeline(t *testing.T, d *DB, knot, rkey, repoDid string) models.Pipeline {
18 t.Helper()
19 sha := strings.Repeat("a", 40)
20 ref := "refs/heads/main"
21 newSha := sha
22 oldSha := strings.Repeat("0", 40)
23 trigger := models.Trigger{
24 Kind: workflow.TriggerKindPush,
25 PushRef: &ref,
26 PushNewSha: &newSha,
27 PushOldSha: &oldSha,
28 }
29 tx, err := d.Begin()
30 if err != nil {
31 t.Fatalf("Begin: %v", err)
32 }
33 triggerID, err := AddTrigger(tx, trigger)
34 if err != nil {
35 tx.Rollback()
36 t.Fatalf("AddTrigger: %v", err)
37 }
38 pipeline := models.Pipeline{
39 Knot: knot,
40 Rkey: rkey,
41 RepoOwner: syntax.DID("did:plc:owner"),
42 RepoName: "repo",
43 RepoDid: repoDid,
44 TriggerId: int(triggerID),
45 Sha: sha,
46 }
47 if err := AddPipeline(tx, pipeline); err != nil {
48 tx.Rollback()
49 t.Fatalf("AddPipeline: %v", err)
50 }
51 if err := tx.Commit(); err != nil {
52 t.Fatalf("Commit: %v", err)
53 }
54 return pipeline
55}
56
57// seedStatus inserts a pipeline_status row directly.
58func seedStatus(t *testing.T, d *DB, spindleInstance, rkey, pipelineKnot, pipelineRkey, workflow string) {
59 t.Helper()
60 status := models.PipelineStatus{
61 Spindle: spindleInstance,
62 Rkey: rkey,
63 PipelineKnot: pipelineKnot,
64 PipelineRkey: pipelineRkey,
65 Workflow: workflow,
66 Status: spindle.StatusKindSuccess,
67 Created: time.Now(),
68 }
69 if err := AddPipelineStatus(context.Background(), d, status); err != nil {
70 t.Fatalf("AddPipelineStatus: %v", err)
71 }
72}
73
74// TestGetPipelineStatuses_SpindleValidation verifies that GetPipelineStatuses
75// only returns statuses emitted by the spindle registered for the pipeline's
76// repo, and silently drops statuses from a rogue spindle.
77func TestGetPipelineStatuses_SpindleValidation(t *testing.T) {
78 d := newTestDB(t)
79
80 const (
81 knot = "knot.example.com"
82 correctSpindle = "spindle.example.com"
83 rogueSpindle = "evil.example.com"
84 repoDid = "did:plc:testrepo"
85 pipelineRkey = "pipeline1"
86 )
87
88 // seed repo with the correct spindle
89 repo := seedRepo(t, d, "did:plc:owner", knot, "repo", "repo", repoDid)
90 if err := UpdateSpindle(d, repo.RepoDid, &[]string{correctSpindle}[0]); err != nil {
91 t.Fatalf("UpdateSpindle: %v", err)
92 }
93
94 // seed the pipeline for this repo
95 seedPipeline(t, d, knot, pipelineRkey, repoDid)
96
97 // insert one status from the correct spindle, one from a rogue spindle
98 seedStatus(t, d, correctSpindle, "status-valid", knot, pipelineRkey, "build")
99 seedStatus(t, d, rogueSpindle, "status-rogue", knot, pipelineRkey, "build")
100
101 pipelines, err := GetPipelineStatuses(d, 10, orm.FilterEq("p.repo_did", repoDid))
102 if err != nil {
103 t.Fatalf("GetPipelineStatuses: %v", err)
104 }
105 if len(pipelines) != 1 {
106 t.Fatalf("expected 1 pipeline, got %d", len(pipelines))
107 }
108
109 statuses := pipelines[0].Statuses["build"].Data
110 if len(statuses) != 1 {
111 t.Fatalf("expected 1 status (from correct spindle), got %d", len(statuses))
112 }
113 if statuses[0].Spindle != correctSpindle {
114 t.Errorf("expected spindle %q, got %q", correctSpindle, statuses[0].Spindle)
115 }
116}