internal/httpd/builds_test.go
147 lines · 5132 bytes
1package httpd
2
3import (
4 "reflect"
5 "testing"
6
7 "gitbay.org/gitbay/internal/control"
8)
9
10// groupRuns folds consecutive same-commit builds (the list is newest
11// first, so a commit's jobs are adjacent) into one run per commit, and
12// gives the run a combined status: worst first (failure beats everything,
13// then cancelled, running, pending), success only when every job is (#224).
14func TestGroupRunsCombinesByCommit(t *testing.T) {
15 builds := []control.BuildOut{
16 {Number: 3, Job: "lint", Status: "success", SHA: "bbb", Ref: "main", CreatedAt: "t2"},
17 {Number: 2, Job: "unit", Status: "failure", SHA: "aaa", Ref: "main", CreatedAt: "t1"},
18 {Number: 1, Job: "lint", Status: "success", SHA: "aaa", Ref: "main", CreatedAt: "t1"},
19 }
20 runs := groupRuns(builds)
21 if len(runs) != 2 {
22 t.Fatalf("groupRuns returned %d runs, want 2: %+v", len(runs), runs)
23 }
24 if runs[0].SHA != "bbb" || len(runs[0].Builds) != 1 || runs[0].Status != "success" {
25 t.Errorf("first run: %+v", runs[0])
26 }
27 if runs[1].SHA != "aaa" || len(runs[1].Builds) != 2 || runs[1].Status != "failure" {
28 t.Errorf("second run: %+v", runs[1])
29 }
30 // Order within a run is preserved from the input.
31 if runs[1].Builds[0].Job != "unit" || runs[1].Builds[1].Job != "lint" {
32 t.Errorf("run builds out of order: %+v", runs[1].Builds)
33 }
34}
35
36func TestGroupRunsEmpty(t *testing.T) {
37 if runs := groupRuns(nil); len(runs) != 0 {
38 t.Errorf("groupRuns(nil) = %+v, want empty", runs)
39 }
40}
41
42// Two builds on the same sha but on different refs (a fast-forward merge
43// can leave the commit reachable from more than one branch) are not
44// adjacent unless the list happens to put them there; groupRuns only folds
45// what is actually adjacent, so this documents that a same-sha, same-ref
46// pair from one push is what gets folded, not "any build of this sha ever".
47func TestGroupRunsCombinedStatusPriority(t *testing.T) {
48 cases := []struct {
49 statuses []string
50 want string
51 }{
52 {[]string{"success"}, "success"},
53 {[]string{"success", "pending"}, "pending"},
54 {[]string{"pending", "running"}, "running"},
55 {[]string{"running", "cancelled"}, "cancelled"},
56 {[]string{"cancelled", "failure"}, "failure"},
57 {[]string{"success", "success", "failure"}, "failure"},
58 }
59 for _, tc := range cases {
60 var builds []control.BuildOut
61 for _, s := range tc.statuses {
62 builds = append(builds, control.BuildOut{SHA: "x", Status: s})
63 }
64 runs := groupRuns(builds)
65 if len(runs) != 1 || runs[0].Status != tc.want {
66 t.Errorf("statuses %v: combined %+v, want %q", tc.statuses, runs, tc.want)
67 }
68 }
69}
70
71// filterLinks builds the nav.filters row: one link that clears both status
72// and job, one per known status and one per known job, each preserving the
73// other two query parameters and marking itself active (#224).
74func TestFilterLinksPreservesOtherParamsAndMarksActive(t *testing.T) {
75 links := filterLinks(buildFilter{Ref: "main", Status: "success", Job: "lint"},
76 []control.JobOut{{Name: "lint"}, {Name: "unit"}})
77
78 byLabel := map[string]buildFilterLink{}
79 for _, l := range links {
80 byLabel[l.Label] = l
81 }
82 all, ok := byLabel["all"]
83 if !ok {
84 t.Fatal("no \"all\" link")
85 }
86 if all.Active {
87 t.Error(`"all" is active while a status/job filter is set`)
88 }
89 if all.Href != "?ref=main" {
90 t.Errorf(`"all" href = %q, want "?ref=main" (clears status and job, keeps ref)`, all.Href)
91 }
92
93 success, ok := byLabel["success"]
94 if !ok || !success.Active {
95 t.Errorf("success link: %+v, want present and active", success)
96 }
97 if success.Href != "?job=lint&ref=main&status=success" {
98 t.Errorf("success href = %q", success.Href)
99 }
100
101 lint, ok := byLabel["lint"]
102 if !ok || !lint.Active {
103 t.Errorf("lint link: %+v, want present and active", lint)
104 }
105 if lint.Href != "?job=lint&ref=main&status=success" {
106 t.Errorf("lint href = %q", lint.Href)
107 }
108
109 unit, ok := byLabel["unit"]
110 if !ok || unit.Active {
111 t.Errorf("unit link: %+v, want present and inactive", unit)
112 }
113 if unit.Href != "?job=unit&ref=main&status=success" {
114 t.Errorf("unit href = %q", unit.Href)
115 }
116}
117
118// With no filter at all, "all" is the active link.
119func TestFilterLinksAllActiveWhenUnfiltered(t *testing.T) {
120 links := filterLinks(buildFilter{}, nil)
121 for _, l := range links {
122 if l.Label == "all" && !l.Active {
123 t.Error(`"all" is not active with no filter set`)
124 }
125 }
126}
127
128// distinctRefs lists each ref once, in the order builds carry them, and
129// always includes the current filter value even if it matched nothing —
130// it powers the branch field's suggestions, not a strict "what exists" list.
131func TestDistinctRefsDedupesAndIncludesCurrent(t *testing.T) {
132 builds := []control.BuildOut{{Ref: "main"}, {Ref: "feature"}, {Ref: "main"}}
133 got := distinctRefs(builds, "release")
134 want := []string{"main", "feature", "release"}
135 if !reflect.DeepEqual(got, want) {
136 t.Errorf("distinctRefs = %v, want %v", got, want)
137 }
138}
139
140func TestDistinctRefsNoDuplicateWhenCurrentAlreadyPresent(t *testing.T) {
141 builds := []control.BuildOut{{Ref: "main"}}
142 got := distinctRefs(builds, "main")
143 want := []string{"main"}
144 if !reflect.DeepEqual(got, want) {
145 t.Errorf("distinctRefs = %v, want %v", got, want)
146 }
147}