internal/store/reposettings_test.go

bd5cf5d7d1f34fa780660fd7562b9ffd9746ee27
gitbay/internal/store/reposettings_test.go history · blame · raw

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}