internal/httpd/builds_test.go
151 lines · 5355 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 // A status outside runStatusPriority (a future state such as
59 // "skipped") is still not success: it must not fall through to
60 // the "success" default and read as green.
61 {[]string{"success", "skipped"}, "skipped"},
62 }
63 for _, tc := range cases {
64 var builds []control.BuildOut
65 for _, s := range tc.statuses {
66 builds = append(builds, control.BuildOut{SHA: "x", Status: s})
67 }
68 runs := groupRuns(builds)
69 if len(runs) != 1 || runs[0].Status != tc.want {
70 t.Errorf("statuses %v: combined %+v, want %q", tc.statuses, runs, tc.want)
71 }
72 }
73}
74
75// filterLinks builds the nav.filters row: one link that clears both status
76// and job, one per known status and one per known job, each preserving the
77// other two query parameters and marking itself active (#224).
78func TestFilterLinksPreservesOtherParamsAndMarksActive(t *testing.T) {
79 links := filterLinks(buildFilter{Ref: "main", Status: "success", Job: "lint"},
80 []control.JobOut{{Name: "lint"}, {Name: "unit"}})
81
82 byLabel := map[string]buildFilterLink{}
83 for _, l := range links {
84 byLabel[l.Label] = l
85 }
86 all, ok := byLabel["all"]
87 if !ok {
88 t.Fatal("no \"all\" link")
89 }
90 if all.Active {
91 t.Error(`"all" is active while a status/job filter is set`)
92 }
93 if all.Href != "?ref=main" {
94 t.Errorf(`"all" href = %q, want "?ref=main" (clears status and job, keeps ref)`, all.Href)
95 }
96
97 success, ok := byLabel["success"]
98 if !ok || !success.Active {
99 t.Errorf("success link: %+v, want present and active", success)
100 }
101 if success.Href != "?job=lint&ref=main&status=success" {
102 t.Errorf("success href = %q", success.Href)
103 }
104
105 lint, ok := byLabel["lint"]
106 if !ok || !lint.Active {
107 t.Errorf("lint link: %+v, want present and active", lint)
108 }
109 if lint.Href != "?job=lint&ref=main&status=success" {
110 t.Errorf("lint href = %q", lint.Href)
111 }
112
113 unit, ok := byLabel["unit"]
114 if !ok || unit.Active {
115 t.Errorf("unit link: %+v, want present and inactive", unit)
116 }
117 if unit.Href != "?job=unit&ref=main&status=success" {
118 t.Errorf("unit href = %q", unit.Href)
119 }
120}
121
122// With no filter at all, "all" is the active link.
123func TestFilterLinksAllActiveWhenUnfiltered(t *testing.T) {
124 links := filterLinks(buildFilter{}, nil)
125 for _, l := range links {
126 if l.Label == "all" && !l.Active {
127 t.Error(`"all" is not active with no filter set`)
128 }
129 }
130}
131
132// distinctRefs lists each ref once, in the order builds carry them, and
133// always includes the current filter value even if it matched nothing —
134// it powers the branch field's suggestions, not a strict "what exists" list.
135func TestDistinctRefsDedupesAndIncludesCurrent(t *testing.T) {
136 builds := []control.BuildOut{{Ref: "main"}, {Ref: "feature"}, {Ref: "main"}}
137 got := distinctRefs(builds, "release")
138 want := []string{"main", "feature", "release"}
139 if !reflect.DeepEqual(got, want) {
140 t.Errorf("distinctRefs = %v, want %v", got, want)
141 }
142}
143
144func TestDistinctRefsNoDuplicateWhenCurrentAlreadyPresent(t *testing.T) {
145 builds := []control.BuildOut{{Ref: "main"}}
146 got := distinctRefs(builds, "main")
147 want := []string{"main"}
148 if !reflect.DeepEqual(got, want) {
149 t.Errorf("distinctRefs = %v, want %v", got, want)
150 }
151}