internal/httpd/builds_test.go

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

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}