1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package kvstore_test
4
5import (
6 "context"
7 "testing"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11
12 "bnema.dev/ermite/internal/domain"
13 "bnema.dev/ermite/internal/ports"
14)
15
16const wfRelease = ".github/workflows/release.yml"
17
18func TestCIWorkflowDisabledState(t *testing.T) {
19 ctx := context.Background()
20 s := openStore(t, t.TempDir())
21 ci := s.CI()
22 a, b := name(t, "a"), name(t, "b")
23 for _, n := range []domain.RepoName{a, b} {
24 require.NoError(t, s.Create(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: created, Settings: domain.DefaultRepoSettings()}))
25 }
26
27 got, err := ci.DisabledWorkflows(ctx, a)
28 require.NoError(t, err)
29 assert.Empty(t, got)
30
31 require.NoError(t, ci.DisableWorkflow(ctx, a, wfRelease, created))
32 require.NoError(t, ci.DisableWorkflow(ctx, a, wfRelease, created), "idempotent")
33 require.NoError(t, ci.DisableWorkflow(ctx, a, ".github/workflows/aur.yml", created))
34 got, err = ci.DisabledWorkflows(ctx, a)
35 require.NoError(t, err)
36 assert.Equal(t, []string{".github/workflows/aur.yml", wfRelease}, got)
37 got, _ = ci.DisabledWorkflows(ctx, b)
38 assert.Empty(t, got, "per repository")
39
40 require.NoError(t, ci.EnableWorkflow(ctx, a, wfRelease))
41 require.NoError(t, ci.EnableWorkflow(ctx, a, wfRelease), "idempotent")
42 got, _ = ci.DisabledWorkflows(ctx, a)
43 assert.Equal(t, []string{".github/workflows/aur.yml"}, got)
44
45 assert.ErrorIs(t, ci.DisableWorkflow(ctx, name(t, "gone"), wfRelease, created), domain.ErrNotFound)
46
47 // The state goes with the repository: a new one under the same name starts enabled.
48 require.NoError(t, s.Delete(ctx, a))
49 require.NoError(t, s.Create(ctx, domain.Repo{Name: a, Visibility: domain.VisibilityPrivate, CreatedAt: created, Settings: domain.DefaultRepoSettings()}))
50 got, _ = ci.DisabledWorkflows(ctx, a)
51 assert.Empty(t, got)
52}
53
54// A run is refused for a disabled workflow in the very transaction that stores
55// it: a disable that lands between the read of the state and the write of the
56// run (here, from inside build) cannot be overtaken, and no run is left.
57func TestCreateRunRefusesAWorkflowDisabledMeanwhile(t *testing.T) {
58 ctx := context.Background()
59 s := openStore(t, t.TempDir())
60 ci := s.CI()
61 n := name(t, "a")
62 require.NoError(t, s.Create(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: created, Settings: domain.DefaultRepoSettings()}))
63 draft := func(id, number int64) ports.CIRunDraft {
64 return ports.CIRunDraft{Run: domain.CIRun{Repo: n, ID: id, Number: number, Attempt: 1, WorkflowPath: wfRelease, Status: domain.CIQueued, CreatedAt: created}}
65 }
66
67 // Enabled: a run is created.
68 _, err := ci.CreateRun(ctx, n, wfRelease, draft)
69 require.NoError(t, err)
70
71 // Disabled from inside build, after the state was read.
72 calls := 0
73 _, err = ci.CreateRun(ctx, n, wfRelease, func(id, number int64) ports.CIRunDraft {
74 calls++
75 if calls == 1 {
76 require.NoError(t, ci.DisableWorkflow(ctx, n, wfRelease, created))
77 }
78 return draft(id, number)
79 })
80 assert.ErrorIs(t, err, domain.ErrWorkflowDisabled)
81 assert.Equal(t, 1, calls, "refused on the retry, before build")
82 runs, err := ci.ListRuns(ctx, n, 0, 10)
83 require.NoError(t, err)
84 assert.Len(t, runs, 1, "no run was stored by the refused call")
85
86 // Other workflows are not affected, and enabling restores it.
87 _, err = ci.CreateRun(ctx, n, ".github/workflows/other.yml", draft)
88 require.NoError(t, err)
89 require.NoError(t, ci.EnableWorkflow(ctx, n, wfRelease))
90 _, err = ci.CreateRun(ctx, n, wfRelease, draft)
91 require.NoError(t, err)
92}