191 lines
8.9 KiB
Go
191 lines
8.9 KiB
Go
package view
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
const (
|
|
hA = "0123456789abcdef0123456789abcdef01234567"
|
|
hB = "89abcdef0123456789abcdef0123456789abcdef"
|
|
fF = "src1:1111111111111111111111111111111111111111111111111111111111111111"
|
|
fG = "src1:2222222222222222222222222222222222222222222222222222222222222222"
|
|
)
|
|
|
|
func candidate(t *testing.T, group string, pulls ...delivery.CandidatePull) Entry {
|
|
t.Helper()
|
|
heads := make([]delivery.Head, len(pulls))
|
|
for i, p := range pulls {
|
|
heads[i] = p.Head
|
|
}
|
|
hash, err := delivery.HeadsHash(heads)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := delivery.EncodeCandidate(delivery.Candidate{Group: group, HeadsHash: hash, Pulls: pulls})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return Entry{Bucket: delivery.CandidatesBucket, Key: group, Value: b, At: t0}
|
|
}
|
|
|
|
func pullOf(repo string, n int, head string, modules ...string) delivery.CandidatePull {
|
|
if modules == nil {
|
|
modules = []string{}
|
|
}
|
|
return delivery.CandidatePull{Head: delivery.Head{Repository: repo, Number: n, Head: head}, Modules: modules}
|
|
}
|
|
|
|
func modEntry(t *testing.T, name string, m delivery.Module) Entry {
|
|
t.Helper()
|
|
b, err := delivery.EncodeModule(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return Entry{Bucket: delivery.ModulesBucket, Key: name, Value: b, At: t0}
|
|
}
|
|
|
|
// trunkBuild is a build record as the controller writes it (mesh-sdk go/delivery/testdata/build-record-build-agent.json is
|
|
// the captured shape: id, state, repository, ref, module, commit, path, source-fingerprint, source-inputs, at).
|
|
func trunkBuild(module, id, ref, state, fp, at string) []Entry {
|
|
rec := `{"id":"` + id + `","state":"` + state + `","repository":"novox/mesh-delivery","ref":"` + ref + `","module":"` + module + `","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20","path":"modules/` + module + `","at":"` + at + `"`
|
|
if fp != "" {
|
|
rec += `,"source-fingerprint":"` + fp + `"`
|
|
}
|
|
rec += `}`
|
|
return []Entry{
|
|
{Bucket: BuildsBucket, Key: module + ".build." + id, Value: []byte(rec), At: t0},
|
|
{Bucket: BuildsBucket, Key: module + ".newest", Value: []byte(id), At: t0},
|
|
}
|
|
}
|
|
|
|
func fpRow(t *testing.T, s Snapshot, module string) FingerprintRow {
|
|
t.Helper()
|
|
for _, f := range s.Fingerprints {
|
|
if f.Module == module {
|
|
return f
|
|
}
|
|
}
|
|
t.Fatalf("no fingerprint row for %s in %+v", module, s.Fingerprints)
|
|
return FingerprintRow{}
|
|
}
|
|
|
|
func TestSnapshotListsTheCandidateGroups(t *testing.T) { // 1241/AC1
|
|
m := New()
|
|
fill(m,
|
|
candidate(t, "feat/x", pullOf("novox/mesh-delivery", 12, hA, "a", "b"), pullOf("novox/mesh-catalog", 300, hB, "c")),
|
|
candidate(t, "feat/y", pullOf("novox/mesh-delivery", 13, hB)))
|
|
g := m.Snapshot().Groups
|
|
if len(g) != 2 || g[0].Group != "feat/x" || g[1].Group != "feat/y" {
|
|
t.Fatalf("groups = %+v", g)
|
|
}
|
|
// sorted by repository: mesh-catalog before mesh-delivery
|
|
if len(g[0].Pulls) != 2 || g[0].Pulls[0].Repository != "novox/mesh-catalog" || g[0].Pulls[0].Number != 300 {
|
|
t.Fatalf("pulls = %+v", g[0].Pulls)
|
|
}
|
|
p := g[0].Pulls[1]
|
|
if p.Repository != "novox/mesh-delivery" || p.Number != 12 || p.Head != hA[:12] || strings.Join(p.Modules, ",") != "a,b" {
|
|
t.Errorf("pull = %+v, want #12 with head %s and modules a,b", p, hA[:12])
|
|
}
|
|
if len(g[1].Pulls) != 1 || len(g[1].Pulls[0].Modules) != 0 {
|
|
t.Errorf("feat/y = %+v", g[1])
|
|
}
|
|
m.Apply(Entry{Bucket: delivery.CandidatesBucket, Key: "feat/x", Deleted: true})
|
|
if g := m.Snapshot().Groups; len(g) != 1 || g[0].Group != "feat/y" {
|
|
t.Errorf("after the delete: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestFingerprintAgainstTheNewestTrunkBuild(t *testing.T) { // 1241/AC2
|
|
m := New()
|
|
fill(m, trunkBuild("a", "build-1", "main", "built", fF, "2026-10-19T10:00:00Z")...)
|
|
fill(m, trunkBuild("d", "build-2", "main", "built", fF, "2026-10-19T10:00:00Z")...)
|
|
fill(m,
|
|
modEntry(t, "a", delivery.Module{FP: fF, Repository: "novox/mesh-delivery", Commit: hA}),
|
|
modEntry(t, "d", delivery.Module{FP: fG, Repository: "novox/mesh-delivery", Commit: hA}),
|
|
modEntry(t, "b", delivery.Module{None: "an artifact compiles a Go program from a build source that git trees do not pin: no fingerprint before release 4", Repository: "novox/mesh-delivery", Commit: hA}),
|
|
modEntry(t, "e", delivery.Module{FP: fG, Repository: "novox/mesh-delivery", Commit: hA}))
|
|
s := m.Snapshot()
|
|
if r := fpRow(t, s, "a"); r.State != FPSame || r.Indexer != fF || r.Build != fF {
|
|
t.Errorf("a = %+v, want same", r)
|
|
}
|
|
if r := fpRow(t, s, "d"); r.State != FPDiffers || r.Indexer != fG || r.Build != fF {
|
|
t.Errorf("d = %+v, want differs with both", r)
|
|
}
|
|
if r := fpRow(t, s, "b"); r.State != FPNone || !strings.Contains(r.Reason, "release 4") || r.Indexer != "" {
|
|
t.Errorf("b = %+v, want none with the reason", r)
|
|
}
|
|
if r := fpRow(t, s, "e"); r.State != FPNoBuild || r.Indexer != fG || r.Build != "" {
|
|
t.Errorf("e = %+v, want no build with the indexer's alone", r)
|
|
}
|
|
}
|
|
|
|
func TestOnlyTheNewestSuccessfulTrunkBuildCounts(t *testing.T) { // 1241/AC2 (the newest key is only a hint)
|
|
m := New()
|
|
fill(m, trunkBuild("a", "build-1", "main", "built", fG, "2026-10-19T09:00:00Z")...)
|
|
fill(m, trunkBuild("a", "build-2", "main", "built", fF, "2026-10-19T10:00:00Z")...)
|
|
fill(m, trunkBuild("a", "build-3", "main", "failed", fG, "2026-10-19T11:00:00Z")...) // newer, not built
|
|
fill(m, trunkBuild("a", "build-4", "feat/x", "built", fG, "2026-10-19T12:00:00Z")...) // newer, not trunk; also the newest key
|
|
fill(m, modEntry(t, "a", delivery.Module{FP: fF, Repository: "novox/mesh-delivery", Commit: hA}))
|
|
if r := fpRow(t, m.Snapshot(), "a"); r.State != FPSame || r.Build != fF {
|
|
t.Errorf("a = %+v, want same with the 10:00 trunk build's fingerprint", r)
|
|
}
|
|
}
|
|
|
|
func TestBuildFingerprintIsReadWithTheSDKRecord(t *testing.T) { // 1241/AC3
|
|
// The record as the controller's encoder wrote it (mesh-sdk go/delivery/testdata/build-record-build-agent.json), whole,
|
|
// with its source-inputs under their Go field names: the view reads source-fingerprint from it.
|
|
const rec = `{"id":"build-1792395447088936520","state":"built","repository":"novox/mesh-controller","ref":"main","module":"build-agent","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20","path":"modules/build-agent","manifest-digest":"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","source-fingerprint":"src1:e846efe73716b78e80860baaac96153de2cb76551325753838ca245d2160a3d7","source-inputs":{"Module":"build-agent","Tree":"11c0b4f90cae0d6e4ef9cc48cbfdf9f6dbe52bca","Bases":["sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc"],"Contexts":{"server":"narrow:d51ca744fd11af59d616c858f76c27b3e58e16e11fbdc0c720eb9d7d19d55fac"},"Toolchains":null,"Unpinned":""},"made":[{"name":"server","kind":"image","reference":"registry.example/build-agent@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc"}],"at":"2026-10-19T09:38:00Z"}`
|
|
const fp = "src1:e846efe73716b78e80860baaac96153de2cb76551325753838ca245d2160a3d7"
|
|
m := New()
|
|
fill(m, Entry{Bucket: BuildsBucket, Key: delivery.BuildKey("build-agent", "build-1792395447088936520"), Value: []byte(rec), At: t0},
|
|
Entry{Bucket: BuildsBucket, Key: delivery.NewestKey("build-agent"), Value: []byte("build-1792395447088936520"), At: t0},
|
|
modEntry(t, "build-agent", delivery.Module{FP: fp, Repository: "novox/mesh-controller", Commit: hA}))
|
|
if r := fpRow(t, m.Snapshot(), "build-agent"); r.State != FPSame || r.Build != fp {
|
|
t.Errorf("build-agent = %+v, want same with %s", r, fp)
|
|
}
|
|
}
|
|
|
|
func shown(t *testing.T) *Model {
|
|
m := New()
|
|
fill(m, trunkBuild("a", "build-1", "main", "built", fF, "2026-10-19T10:00:00Z")...)
|
|
fill(m,
|
|
candidate(t, "feat/x", pullOf("novox/mesh-delivery", 12, hA, "a"), pullOf("novox/mesh-catalog", 300, hB, "c")),
|
|
modEntry(t, "a", delivery.Module{FP: fG, Repository: "novox/mesh-delivery", Commit: hA}),
|
|
modEntry(t, "b", delivery.Module{None: "no tree", Repository: "novox/mesh-delivery", Commit: hA}))
|
|
return m
|
|
}
|
|
|
|
func TestTableAndPagePrintTheGroupsAndTheComparison(t *testing.T) { // 1241/AC6
|
|
m := shown(t)
|
|
var out bytes.Buffer
|
|
Table(&out, m.Snapshot())
|
|
for _, want := range []string{"CANDIDATES", "feat/x", "novox/mesh-delivery#12", hA[:12], "novox/mesh-catalog#300", "FINGERPRINT", "differs", "indexer " + fG, "build " + fF, "no tree"} {
|
|
if !strings.Contains(out.String(), want) {
|
|
t.Errorf("the table lacks %q:\n%s", want, out.String())
|
|
}
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
Web(m).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
for _, want := range []string{"CANDIDATES", "feat/x", "novox/mesh-delivery#12", hA[:12], "FINGERPRINTS", "differs", fG, fF, "no tree"} {
|
|
if !strings.Contains(rec.Body.String(), want) {
|
|
t.Errorf("the page lacks %q:\n%s", want, rec.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnreadableIndexerValuesAreNamed(t *testing.T) { // never dropped silently
|
|
m := New()
|
|
fill(m, Entry{Bucket: delivery.CandidatesBucket, Key: "feat/x", Value: []byte("{"), At: t0},
|
|
Entry{Bucket: delivery.ModulesBucket, Key: "a", Value: []byte("{"), At: t0})
|
|
s := m.Snapshot()
|
|
if len(s.Unreadable) != 2 || len(s.Groups) != 0 || len(s.Fingerprints) != 0 {
|
|
t.Errorf("snapshot = %+v", s)
|
|
}
|
|
}
|