Go · 8254 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package lifecycle
4
5 import (
6 "context"
7 "strings"
8 "testing"
9
10 "github.com/jackc/pgx/v5/pgtype"
11
12 actionsdb "github.com/tenseleyFlow/shithub/internal/actions/sqlc"
13 reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
14 "github.com/tenseleyFlow/shithub/internal/testing/dbtest"
15 usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
16 )
17
18 const fixtureHash = "$argon2id$v=19$m=16384,t=1,p=1$" +
19 "AAAAAAAAAAAAAAAA$" +
20 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
21
22 func TestCancelRunCancelsQueuedJobsAndCompletesRun(t *testing.T) {
23 ctx := context.Background()
24 pool := dbtest.NewTestDB(t)
25 repoID, userID := setupLifecycleRepo(t, pool)
26 q := actionsdb.New()
27 run := insertLifecycleRun(t, pool, repoID, userID, 1)
28 job, err := q.InsertWorkflowJob(ctx, pool, actionsdb.InsertWorkflowJobParams{
29 RunID: run.ID,
30 JobIndex: 0,
31 JobKey: "build",
32 JobName: "Build",
33 RunsOn: "ubuntu-latest",
34 NeedsJobs: []string{},
35 TimeoutMinutes: 30,
36 Permissions: []byte(`{}`),
37 JobEnv: []byte(`{}`),
38 })
39 if err != nil {
40 t.Fatalf("InsertWorkflowJob: %v", err)
41 }
42 step, err := q.InsertWorkflowStep(ctx, pool, actionsdb.InsertWorkflowStepParams{
43 JobID: job.ID,
44 StepIndex: 0,
45 RunCommand: "go test ./...",
46 StepEnv: []byte(`{}`),
47 StepWith: []byte(`{}`),
48 })
49 if err != nil {
50 t.Fatalf("InsertWorkflowStep: %v", err)
51 }
52
53 result, err := CancelRun(ctx, Deps{Pool: pool}, run.ID, CancelReasonUser)
54 if err != nil {
55 t.Fatalf("CancelRun: %v", err)
56 }
57 if len(result.ChangedJobs) != 1 || !result.RunCompleted || result.RunConclusion != actionsdb.CheckConclusionCancelled {
58 t.Fatalf("result: %+v", result)
59 }
60 gotJob, err := q.GetWorkflowJobByID(ctx, pool, job.ID)
61 if err != nil {
62 t.Fatalf("GetWorkflowJobByID: %v", err)
63 }
64 if gotJob.Status != actionsdb.WorkflowJobStatusCancelled || !gotJob.CancelRequested ||
65 !gotJob.Conclusion.Valid || gotJob.Conclusion.CheckConclusion != actionsdb.CheckConclusionCancelled {
66 t.Fatalf("job: %+v", gotJob)
67 }
68 gotStep, err := q.GetWorkflowStepByID(ctx, pool, step.ID)
69 if err != nil {
70 t.Fatalf("GetWorkflowStepByID: %v", err)
71 }
72 if gotStep.Status != actionsdb.WorkflowStepStatusCancelled ||
73 !gotStep.Conclusion.Valid || gotStep.Conclusion.CheckConclusion != actionsdb.CheckConclusionCancelled {
74 t.Fatalf("step: %+v", gotStep)
75 }
76 gotRun, err := q.GetWorkflowRunByID(ctx, pool, run.ID)
77 if err != nil {
78 t.Fatalf("GetWorkflowRunByID: %v", err)
79 }
80 if gotRun.Status != actionsdb.WorkflowRunStatusCompleted ||
81 !gotRun.Conclusion.Valid || gotRun.Conclusion.CheckConclusion != actionsdb.CheckConclusionCancelled {
82 t.Fatalf("run: %+v", gotRun)
83 }
84 }
85
86 func TestCancelJobRequestsRunningJobWithoutTerminalOverwrite(t *testing.T) {
87 ctx := context.Background()
88 pool := dbtest.NewTestDB(t)
89 repoID, userID := setupLifecycleRepo(t, pool)
90 q := actionsdb.New()
91 run := insertLifecycleRun(t, pool, repoID, userID, 2)
92 job, err := q.InsertWorkflowJob(ctx, pool, actionsdb.InsertWorkflowJobParams{
93 RunID: run.ID,
94 JobIndex: 0,
95 JobKey: "test",
96 JobName: "Test",
97 RunsOn: "ubuntu-latest",
98 NeedsJobs: []string{},
99 TimeoutMinutes: 30,
100 Permissions: []byte(`{}`),
101 JobEnv: []byte(`{}`),
102 })
103 if err != nil {
104 t.Fatalf("InsertWorkflowJob: %v", err)
105 }
106 runner, err := q.InsertRunner(ctx, pool, actionsdb.InsertRunnerParams{
107 Name: "runner-1",
108 Labels: []string{"ubuntu-latest"},
109 Capacity: 1,
110 })
111 if err != nil {
112 t.Fatalf("InsertRunner: %v", err)
113 }
114 if _, err := pool.Exec(ctx, `UPDATE workflow_jobs SET runner_id = $1, status = 'running', started_at = now() WHERE id = $2`, runner.ID, job.ID); err != nil {
115 t.Fatalf("mark job running: %v", err)
116 }
117
118 result, err := CancelJob(ctx, Deps{Pool: pool}, job.ID, CancelReasonUser)
119 if err != nil {
120 t.Fatalf("CancelJob: %v", err)
121 }
122 if len(result.ChangedJobs) != 1 || result.RunCompleted {
123 t.Fatalf("result: %+v", result)
124 }
125 gotJob, err := q.GetWorkflowJobByID(ctx, pool, job.ID)
126 if err != nil {
127 t.Fatalf("GetWorkflowJobByID: %v", err)
128 }
129 if gotJob.Status != actionsdb.WorkflowJobStatusRunning || !gotJob.CancelRequested || gotJob.Conclusion.Valid {
130 t.Fatalf("job: %+v", gotJob)
131 }
132 gotRun, err := q.GetWorkflowRunByID(ctx, pool, run.ID)
133 if err != nil {
134 t.Fatalf("GetWorkflowRunByID: %v", err)
135 }
136 if gotRun.Status != actionsdb.WorkflowRunStatusRunning || gotRun.Conclusion.Valid {
137 t.Fatalf("run: %+v", gotRun)
138 }
139
140 again, err := CancelJob(ctx, Deps{Pool: pool}, job.ID, CancelReasonUser)
141 if err != nil {
142 t.Fatalf("CancelJob repeat: %v", err)
143 }
144 if len(again.ChangedJobs) != 0 {
145 t.Fatalf("repeat was not idempotent: %+v", again)
146 }
147 }
148
149 func TestListActiveWorkflowRunsForAdminFiltersActiveRuns(t *testing.T) {
150 ctx := context.Background()
151 pool := dbtest.NewTestDB(t)
152 repoID, userID := setupLifecycleRepo(t, pool)
153 q := actionsdb.New()
154
155 queued := insertLifecycleRun(t, pool, repoID, userID, 1)
156 running := insertLifecycleRun(t, pool, repoID, userID, 2)
157 running, err := q.StartWorkflowRun(ctx, pool, running.ID)
158 if err != nil {
159 t.Fatalf("StartWorkflowRun: %v", err)
160 }
161 completed := insertLifecycleRun(t, pool, repoID, userID, 3)
162 if _, err := q.CompleteWorkflowRun(ctx, pool, actionsdb.CompleteWorkflowRunParams{
163 ID: completed.ID,
164 Conclusion: actionsdb.CheckConclusionSuccess,
165 }); err != nil {
166 t.Fatalf("CompleteWorkflowRun: %v", err)
167 }
168 otherRepoID, otherUserID := setupNamedLifecycleRepo(t, pool, "bob", "other")
169 otherRepoRun := insertLifecycleRun(t, pool, otherRepoID, otherUserID, 1)
170
171 all, err := q.ListActiveWorkflowRunsForAdmin(ctx, pool, actionsdb.ListActiveWorkflowRunsForAdminParams{
172 RepoID: 0,
173 LimitCount: 10,
174 })
175 if err != nil {
176 t.Fatalf("ListActiveWorkflowRunsForAdmin all: %v", err)
177 }
178 assertRunIDs(t, all, queued.ID, running.ID, otherRepoRun.ID)
179
180 repoOnly, err := q.ListActiveWorkflowRunsForAdmin(ctx, pool, actionsdb.ListActiveWorkflowRunsForAdminParams{
181 RepoID: repoID,
182 LimitCount: 10,
183 })
184 if err != nil {
185 t.Fatalf("ListActiveWorkflowRunsForAdmin repo: %v", err)
186 }
187 assertRunIDs(t, repoOnly, queued.ID, running.ID)
188
189 limited, err := q.ListActiveWorkflowRunsForAdmin(ctx, pool, actionsdb.ListActiveWorkflowRunsForAdminParams{
190 RepoID: 0,
191 LimitCount: 1,
192 })
193 if err != nil {
194 t.Fatalf("ListActiveWorkflowRunsForAdmin limited: %v", err)
195 }
196 assertRunIDs(t, limited, queued.ID)
197 }
198
199 func setupLifecycleRepo(t *testing.T, db actionsdb.DBTX) (repoID, userID int64) {
200 t.Helper()
201 return setupNamedLifecycleRepo(t, db, "alice", "demo")
202 }
203
204 func setupNamedLifecycleRepo(t *testing.T, db actionsdb.DBTX, username, repoName string) (repoID, userID int64) {
205 t.Helper()
206 ctx := context.Background()
207 user, err := usersdb.New().CreateUser(ctx, db, usersdb.CreateUserParams{
208 Username: username,
209 DisplayName: username,
210 PasswordHash: fixtureHash,
211 })
212 if err != nil {
213 t.Fatalf("CreateUser: %v", err)
214 }
215 repo, err := reposdb.New().CreateRepo(ctx, db, reposdb.CreateRepoParams{
216 OwnerUserID: pgtype.Int8{Int64: user.ID, Valid: true},
217 Name: repoName,
218 DefaultBranch: "trunk",
219 Visibility: reposdb.RepoVisibilityPublic,
220 })
221 if err != nil {
222 t.Fatalf("CreateRepo: %v", err)
223 }
224 return repo.ID, user.ID
225 }
226
227 func assertRunIDs(t *testing.T, runs []actionsdb.WorkflowRun, want ...int64) {
228 t.Helper()
229 if len(runs) != len(want) {
230 t.Fatalf("got %d runs, want %d: %+v", len(runs), len(want), runs)
231 }
232 for i := range want {
233 if runs[i].ID != want[i] {
234 t.Fatalf("run[%d] id=%d, want %d; runs=%+v", i, runs[i].ID, want[i], runs)
235 }
236 }
237 }
238
239 func insertLifecycleRun(t *testing.T, db actionsdb.DBTX, repoID, userID, runIndex int64) actionsdb.WorkflowRun {
240 t.Helper()
241 run, err := actionsdb.New().InsertWorkflowRun(context.Background(), db, actionsdb.InsertWorkflowRunParams{
242 RepoID: repoID,
243 RunIndex: runIndex,
244 WorkflowFile: ".shithub/workflows/ci.yml",
245 WorkflowName: "CI",
246 HeadSha: strings.Repeat("a", 40),
247 HeadRef: "trunk",
248 Event: actionsdb.WorkflowRunEventPush,
249 EventPayload: []byte(`{}`),
250 ActorUserID: pgtype.Int8{Int64: userID, Valid: true},
251 })
252 if err != nil {
253 t.Fatalf("InsertWorkflowRun: %v", err)
254 }
255 return run
256 }
257