internal/control/buildlistpage_test.go
139 lines · 4229 bytes
1package control
2
3import (
4 "bytes"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "gitbay.org/gitbay/internal/protocol"
12 "gitbay.org/gitbay/internal/store"
13)
14
15// A run row led with a sha and said nothing about what the commit was
16// (#241). build list carries the subject now, for every surface at once.
17func TestBuildListCarriesCommitSubjects(t *testing.T) {
18 st, repo, uid := newQueueTestRepo(t)
19 git := gitRunner(t)
20 root := t.TempDir()
21
22 src := filepath.Join(root, "src")
23 os.MkdirAll(src, 0o755)
24 git(root, "init", "-q", "-b", "main", "src")
25 os.WriteFile(filepath.Join(src, "a"), []byte("one\n"), 0o644)
26 git(src, "add", ".")
27 git(src, "commit", "-qm", "runner: cap a build's container")
28 sha := strings.TrimSpace(git(src, "rev-parse", "HEAD"))
29
30 dir := RepoDir(root, repo.OwnerName, repo.Name)
31 os.MkdirAll(filepath.Dir(dir), 0o755)
32 git(root, "clone", "-q", "--bare", src, dir)
33
34 if _, err := st.CreateBuild(repo.ID, "unit", sha, "main", `["true"]`, "", "", true); err != nil {
35 t.Fatal(err)
36 }
37 // A build whose commit is gone, as a force-push leaves behind.
38 if _, err := st.CreateBuild(repo.ID, "lint", strings.Repeat("1", 40), "main", `["true"]`, "", "", true); err != nil {
39 t.Fatal(err)
40 }
41
42 c, errOut := pruneCtx(st, root, store.User{ID: uid})
43 c.JSON = true
44 if code := Dispatch(c, []string{"build", "list", repo.Path()}); code != protocol.ExitOK {
45 t.Fatalf("build list: exit %d: %s", code, errOut.String())
46 }
47 var got []BuildOut
48 decodeData(t, c.Stdout.(*bytes.Buffer).Bytes(), &got)
49 if len(got) != 2 {
50 t.Fatalf("builds: %+v", got)
51 }
52 for _, b := range got {
53 switch b.Job {
54 case "unit":
55 if b.Subject != "runner: cap a build's container" {
56 t.Errorf("unit subject = %q", b.Subject)
57 }
58 case "lint":
59 if b.Subject != "" {
60 t.Errorf("a build whose commit is gone claims a subject: %q", b.Subject)
61 }
62 }
63 }
64}
65
66// The builds list was capped at 50 with no way past it, so filtering to a
67// sparse status reached further back and appeared to raise the total
68// (#244). --limit and --cursor page it like every other list command.
69func TestBuildListPages(t *testing.T) {
70 st, repo, uid := newQueueTestRepo(t)
71 for i := 0; i < 5; i++ {
72 if _, err := st.CreateBuild(repo.ID, "unit", "aaa", "main", `["true"]`, "", "", true); err != nil {
73 t.Fatal(err)
74 }
75 }
76 c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid})
77 c.JSON = true
78
79 page := func(args ...string) (nums []int64, next string) {
80 t.Helper()
81 out := c.Stdout.(*bytes.Buffer)
82 out.Reset()
83 errOut.Reset()
84 argv := append([]string{"build", "list", repo.Path()}, args...)
85 if code := Dispatch(c, argv); code != protocol.ExitOK {
86 t.Fatalf("build list %v: exit %d: %s", args, code, errOut.String())
87 }
88 var got struct {
89 Items []BuildOut `json:"items"`
90 Next string `json:"next"`
91 }
92 decodeData(t, out.Bytes(), &got)
93 for _, b := range got.Items {
94 nums = append(nums, b.Number)
95 }
96 return nums, got.Next
97 }
98
99 nums, next := page("--limit", "2")
100 if len(nums) != 2 || nums[0] != 5 || nums[1] != 4 {
101 t.Fatalf("first page: %v", nums)
102 }
103 if next == "" {
104 t.Fatal("first page offers no cursor with three builds left")
105 }
106 nums, next = page("--limit", "2", "--cursor", next)
107 if len(nums) != 2 || nums[0] != 3 || nums[1] != 2 {
108 t.Fatalf("second page: %v", nums)
109 }
110 nums, next = page("--limit", "2", "--cursor", next)
111 if len(nums) != 1 || nums[0] != 1 {
112 t.Fatalf("last page: %v", nums)
113 }
114 if next != "" {
115 t.Errorf("last page offers a cursor: %q", next)
116 }
117
118 // A cursor minted by another command is not a build cursor.
119 out := c.Stdout.(*bytes.Buffer)
120 out.Reset()
121 errOut.Reset()
122 if code := Dispatch(c, []string{"build", "list", repo.Path(), "--cursor", encodeCursor("issue", "3")}); code != protocol.ExitUsage {
123 t.Fatalf("foreign cursor: exit %d, want %d", code, protocol.ExitUsage)
124 }
125}
126
127// decodeData unwraps the protocol envelope the JSON emitters write.
128func decodeData(t *testing.T, b []byte, into any) {
129 t.Helper()
130 var env struct {
131 Data json.RawMessage `json:"data"`
132 }
133 if err := json.Unmarshal(b, &env); err != nil {
134 t.Fatalf("envelope: %v\n%s", err, b)
135 }
136 if err := json.Unmarshal(env.Data, into); err != nil {
137 t.Fatalf("data: %v\n%s", err, env.Data)
138 }
139}