internal/httpd/builds_test.go

v1.31.1
gitbay/internal/httpd/builds_test.go history · blame · raw

210 lines · 7577 bytes

  1package httpd
  2
  3import (
  4	"fmt"
  5	"reflect"
  6	"testing"
  7
  8	"gitbay.org/gitbay/internal/control"
  9)
 10
 11// groupRuns folds consecutive same-commit builds (the list is newest
 12// first, so a commit's jobs are adjacent) into one run per commit, and
 13// gives the run a combined status: worst first (failure beats everything,
 14// then cancelled, running, pending), success only when every job is (#224).
 15func TestGroupRunsCombinesByCommit(t *testing.T) {
 16	builds := []control.BuildOut{
 17		{Number: 3, Job: "lint", Status: "success", SHA: "bbb", Ref: "main", CreatedAt: "t2"},
 18		{Number: 2, Job: "unit", Status: "failure", SHA: "aaa", Ref: "main", CreatedAt: "t1"},
 19		{Number: 1, Job: "lint", Status: "success", SHA: "aaa", Ref: "main", CreatedAt: "t1"},
 20	}
 21	runs := groupRuns(builds)
 22	if len(runs) != 2 {
 23		t.Fatalf("groupRuns returned %d runs, want 2: %+v", len(runs), runs)
 24	}
 25	if runs[0].SHA != "bbb" || len(runs[0].Builds) != 1 || runs[0].Status != "success" {
 26		t.Errorf("first run: %+v", runs[0])
 27	}
 28	if runs[1].SHA != "aaa" || len(runs[1].Builds) != 2 || runs[1].Status != "failure" {
 29		t.Errorf("second run: %+v", runs[1])
 30	}
 31	// Order within a run is preserved from the input.
 32	if runs[1].Builds[0].Job != "unit" || runs[1].Builds[1].Job != "lint" {
 33		t.Errorf("run builds out of order: %+v", runs[1].Builds)
 34	}
 35}
 36
 37func TestGroupRunsEmpty(t *testing.T) {
 38	if runs := groupRuns(nil); len(runs) != 0 {
 39		t.Errorf("groupRuns(nil) = %+v, want empty", runs)
 40	}
 41}
 42
 43// Two builds on the same sha but on different refs (a fast-forward merge
 44// can leave the commit reachable from more than one branch) are not
 45// adjacent unless the list happens to put them there; groupRuns only folds
 46// what is actually adjacent, so this documents that a same-sha, same-ref
 47// pair from one push is what gets folded, not "any build of this sha ever".
 48func TestGroupRunsCombinedStatusPriority(t *testing.T) {
 49	cases := []struct {
 50		statuses []string
 51		want     string
 52	}{
 53		{[]string{"success"}, "success"},
 54		{[]string{"success", "pending"}, "pending"},
 55		{[]string{"pending", "running"}, "running"},
 56		{[]string{"running", "cancelled"}, "cancelled"},
 57		{[]string{"cancelled", "failure"}, "failure"},
 58		{[]string{"success", "success", "failure"}, "failure"},
 59		// A status outside runStatusPriority (a future state such as
 60		// "skipped") is still not success: it must not fall through to
 61		// the "success" default and read as green.
 62		{[]string{"success", "skipped"}, "skipped"},
 63	}
 64	for _, tc := range cases {
 65		var builds []control.BuildOut
 66		for _, s := range tc.statuses {
 67			builds = append(builds, control.BuildOut{SHA: "x", Status: s})
 68		}
 69		runs := groupRuns(builds)
 70		if len(runs) != 1 || runs[0].Status != tc.want {
 71			t.Errorf("statuses %v: combined %+v, want %q", tc.statuses, runs, tc.want)
 72		}
 73	}
 74}
 75
 76// filterLinks builds the nav.filters row: one link that clears both status
 77// and job, one per known status and one per known job, each preserving the
 78// other two query parameters and marking itself active (#224).
 79func TestFilterLinksPreservesOtherParamsAndMarksActive(t *testing.T) {
 80	links := filterLinks(buildFilter{Ref: "main", Status: "success", Job: "lint"},
 81		[]control.JobOut{{Name: "lint"}, {Name: "unit"}})
 82
 83	byLabel := map[string]buildFilterLink{}
 84	for _, l := range links {
 85		byLabel[l.Label] = l
 86	}
 87	all, ok := byLabel["all"]
 88	if !ok {
 89		t.Fatal("no \"all\" link")
 90	}
 91	if all.Active {
 92		t.Error(`"all" is active while a status/job filter is set`)
 93	}
 94	if all.Href != "?ref=main" {
 95		t.Errorf(`"all" href = %q, want "?ref=main" (clears status and job, keeps ref)`, all.Href)
 96	}
 97
 98	success, ok := byLabel["success"]
 99	if !ok || !success.Active {
100		t.Errorf("success link: %+v, want present and active", success)
101	}
102	if success.Href != "?job=lint&ref=main&status=success" {
103		t.Errorf("success href = %q", success.Href)
104	}
105
106	lint, ok := byLabel["lint"]
107	if !ok || !lint.Active {
108		t.Errorf("lint link: %+v, want present and active", lint)
109	}
110	if lint.Href != "?job=lint&ref=main&status=success" {
111		t.Errorf("lint href = %q", lint.Href)
112	}
113
114	unit, ok := byLabel["unit"]
115	if !ok || unit.Active {
116		t.Errorf("unit link: %+v, want present and inactive", unit)
117	}
118	if unit.Href != "?job=unit&ref=main&status=success" {
119		t.Errorf("unit href = %q", unit.Href)
120	}
121}
122
123// With no filter at all, "all" is the active link.
124func TestFilterLinksAllActiveWhenUnfiltered(t *testing.T) {
125	links := filterLinks(buildFilter{}, nil)
126	for _, l := range links {
127		if l.Label == "all" && !l.Active {
128			t.Error(`"all" is not active with no filter set`)
129		}
130	}
131}
132
133// distinctRefs lists each ref once, in the order builds carry them, and
134// always includes the current filter value even if it matched nothing —
135// it powers the branch field's suggestions, not a strict "what exists" list.
136func TestDistinctRefsDedupesAndIncludesCurrent(t *testing.T) {
137	builds := []control.BuildOut{{Ref: "main"}, {Ref: "feature"}, {Ref: "main"}}
138	got := distinctRefs(builds, "release")
139	want := []string{"main", "feature", "release"}
140	if !reflect.DeepEqual(got, want) {
141		t.Errorf("distinctRefs = %v, want %v", got, want)
142	}
143}
144
145func TestDistinctRefsNoDuplicateWhenCurrentAlreadyPresent(t *testing.T) {
146	builds := []control.BuildOut{{Ref: "main"}}
147	got := distinctRefs(builds, "main")
148	want := []string{"main"}
149	if !reflect.DeepEqual(got, want) {
150		t.Errorf("distinctRefs = %v, want %v", got, want)
151	}
152}
153
154// The builds column groups the same links filterLinks makes: "all" and
155// the statuses, then the jobs, then the branches seen (desktop layout spec).
156func TestBuildFacetsGroups(t *testing.T) {
157	f := buildFilter{Ref: "main", Status: "success"}
158	groups := buildFacets(f, []control.JobOut{{Name: "lint"}}, []string{"main", "dev"})
159	if len(groups) != 3 || groups[0].Title != "Status" || groups[1].Title != "Jobs" || groups[2].Title != "Branches" {
160		t.Fatalf("groups: %+v", groups)
161	}
162	if groups[0].Items[0].Label != "all" || groups[0].Items[0].Href != "?ref=main" || groups[0].Items[0].Active {
163		t.Errorf("all: %+v", groups[0].Items[0])
164	}
165	if s := groups[0].Items[3]; s.Label != "success" || !s.Active {
166		t.Errorf("success: %+v", s)
167	}
168	if j := groups[1].Items[0]; j.Label != "lint" || j.Href != "?job=lint&ref=main&status=success" || j.Active {
169		t.Errorf("lint: %+v", j)
170	}
171	if b := groups[2].Items[0]; b.Label != "main" || !b.Active || b.Href != "?status=success" {
172		t.Errorf("active branch clears itself: %+v", b)
173	}
174	if b := groups[2].Items[1]; b.Label != "dev" || b.Active || b.Href != "?ref=dev&status=success" {
175		t.Errorf("dev: %+v", b)
176	}
177}
178
179// The branch group caps, the way topicFacets caps topics: a page of
180// builds can name dozens of refs and the column is not a branch
181// listing. The ref in force is kept whatever its position (#237).
182func TestBuildFacetsCapsBranches(t *testing.T) {
183	var refs []string
184	for i := 0; i < 30; i++ {
185		refs = append(refs, fmt.Sprintf("b%02d", i))
186	}
187	groups := buildFacets(buildFilter{}, nil, refs)
188	branches := groups[2].Items
189	if len(branches) != maxBranchFacets {
190		t.Fatalf("branches: %d", len(branches))
191	}
192	if branches[0].Label != "b00" || branches[len(branches)-1].Label != "b09" {
193		t.Errorf("kept the wrong refs: %+v", branches)
194	}
195
196	groups = buildFacets(buildFilter{Ref: "b29"}, nil, refs)
197	branches = groups[2].Items
198	if len(branches) != maxBranchFacets {
199		t.Fatalf("branches with an active ref: %d", len(branches))
200	}
201	var active *facetItem
202	for i := range branches {
203		if branches[i].Label == "b29" {
204			active = &branches[i]
205		}
206	}
207	if active == nil || !active.Active || active.Href != "?" {
208		t.Errorf("active ref past the cap: %+v", branches)
209	}
210}