internal/store/reposettings_test.go
120 lines · 3263 bytes
1package store
2
3import (
4 "sync"
5 "testing"
6)
7
8func settingsFixture(t *testing.T) (*Store, int64) {
9 t.Helper()
10 s := open(t)
11 if err := s.MigrateUp(); err != nil {
12 t.Fatal(err)
13 }
14 uid, err := s.CreateUser("cmc", true)
15 if err != nil {
16 t.Fatal(err)
17 }
18 repoID, err := s.CreateRepo("user", uid, "lib", "public")
19 if err != nil {
20 t.Fatal(err)
21 }
22 return s, repoID
23}
24
25func TestUpdateRepoSettingsRoundTrip(t *testing.T) {
26 s, repoID := settingsFixture(t)
27 got, err := s.UpdateRepoSettings(repoID, func(set *RepoSettings) { set.RequireApprovals = 2 })
28 if err != nil {
29 t.Fatal(err)
30 }
31 if got.RequireApprovals != 2 {
32 t.Fatalf("returned %+v", got)
33 }
34 repo, err := s.RepoByID(repoID)
35 if err != nil {
36 t.Fatal(err)
37 }
38 if repo.Settings.RequireApprovals != 2 {
39 t.Fatalf("stored %+v", repo.Settings)
40 }
41
42 // Turning a flag back off must persist: the JSON tags are omitempty,
43 // so "false" is an absent key, and a patch-style write would drop it.
44 if _, err := s.UpdateRepoSettings(repoID, func(set *RepoSettings) { set.RequireChecks = true }); err != nil {
45 t.Fatal(err)
46 }
47 got, err = s.UpdateRepoSettings(repoID, func(set *RepoSettings) { set.RequireChecks = false })
48 if err != nil {
49 t.Fatal(err)
50 }
51 if got.RequireChecks {
52 t.Fatal("require_checks stayed on")
53 }
54 repo, _ = s.RepoByID(repoID)
55 if repo.Settings.RequireChecks || repo.Settings.RequireApprovals != 2 {
56 t.Fatalf("stored %+v", repo.Settings)
57 }
58}
59
60func TestUpdateRepoSettingsMissingRepo(t *testing.T) {
61 s, _ := settingsFixture(t)
62 if _, err := s.UpdateRepoSettings(9999, func(*RepoSettings) {}); err != ErrNotFound {
63 t.Fatalf("err = %v, want ErrNotFound", err)
64 }
65}
66
67// Concurrent updates to different fields both survive. Read-modify-write
68// through the caller lost one of them: each read the same blob and the
69// later write put back what it had read for the other's field.
70func TestUpdateRepoSettingsConcurrent(t *testing.T) {
71 s, repoID := settingsFixture(t)
72 var wg sync.WaitGroup
73 errs := make(chan error, 2)
74 wg.Add(2)
75 go func() {
76 defer wg.Done()
77 _, err := s.UpdateRepoSettings(repoID, func(set *RepoSettings) { set.RequireApprovals = 3 })
78 errs <- err
79 }()
80 go func() {
81 defer wg.Done()
82 _, err := s.UpdateRepoSettings(repoID, func(set *RepoSettings) { set.RequireResolved = true })
83 errs <- err
84 }()
85 wg.Wait()
86 close(errs)
87 for err := range errs {
88 if err != nil {
89 t.Fatal(err)
90 }
91 }
92 repo, err := s.RepoByID(repoID)
93 if err != nil {
94 t.Fatal(err)
95 }
96 if repo.Settings.RequireApprovals != 3 || !repo.Settings.RequireResolved {
97 t.Fatalf("one update was lost: %+v", repo.Settings)
98 }
99}
100
101// The protected-branch list is read and rewritten inside the update, so
102// two admins protecting different branches at once both land.
103func TestProtectedBranchesConcurrent(t *testing.T) {
104 s, repoID := settingsFixture(t)
105 var wg sync.WaitGroup
106 for _, branch := range []string{"main", "release"} {
107 wg.Add(1)
108 go func(b string) {
109 defer wg.Done()
110 s.UpdateRepoSettings(repoID, func(set *RepoSettings) {
111 set.ProtectedBranches = append(set.ProtectedBranches, b)
112 })
113 }(branch)
114 }
115 wg.Wait()
116 repo, _ := s.RepoByID(repoID)
117 if len(repo.Settings.ProtectedBranches) != 2 {
118 t.Fatalf("protected branches = %v, want both", repo.Settings.ProtectedBranches)
119 }
120}