Files
mesh-delivery/modules/mesh-delivery-view/internal/view/fingerprints_test.go
T
jschoubben 0de534c692 Names N3 (view): the status view names its own buckets from its credential's module
Its states answers, reach and waits (viewstate WaitsBucket, ReachBucket, ViewAnswersBucket), its
first views and its marks were named from the constant ViewModule; they are now
`<cred.Module>_<state>` (viewstate.BucketsOf), and the model is made for its module.

Builds: design 47 G A-296

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 19:14:55 +01:00

147 lines
5.9 KiB
Go

package view
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
)
const (
hA = "0123456789abcdef0123456789abcdef01234567"
hB = "89abcdef0123456789abcdef0123456789abcdef"
fF = "src1:1111111111111111111111111111111111111111111111111111111111111111"
fG = "src1:2222222222222222222222222222222222222222222222222222222222222222"
)
func candidate(t *testing.T, group string, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) Entry {
t.Helper()
heads := make([]meshdeliveryclient.Head, len(pulls))
for i, p := range pulls {
heads[i] = meshdeliveryclient.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head}
}
hash, err := meshdeliveryclient.HeadsHash(heads)
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(meshcatalog.CandidatesByGroupValue{Group: group, Heads: hash, Pulls: pulls})
if err != nil {
t.Fatal(err)
}
return Entry{Bucket: CandidatesBucket, Key: group, Value: b, At: t0}
}
func pullOf(repo string, n int, head string, modules ...string) meshcatalog.CandidatesByGroupValuePullsItem {
if modules == nil {
modules = []string{}
}
return meshcatalog.CandidatesByGroupValuePullsItem{Repository: repo, Number: int64(n), Head: head, Modules: modules}
}
func modEntry(t *testing.T, name string, m meshcatalog.ModulesByModuleValue) Entry {
t.Helper()
b, err := meshcatalogclient.EncodeModule(m)
if err != nil {
t.Fatal(err)
}
return Entry{Bucket: ModulesBucket, Key: name, Value: b, 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(viewstate.ViewModule)
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: 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)
}
}
// 1955/L7 (ADR 0571 §1): the view reads no build record of the controller's: a module with no fingerprint of the new
// form is said so, never compared with the controller's builds.
func TestAModuleOfTheOldFormIsComparedWithNoBuild(t *testing.T) {
m := New(viewstate.ViewModule)
fill(m,
modEntry(t, "a", meshcatalog.ModulesByModuleValue{Fp: fF, Repository: "novox/mesh-delivery", Commit: hA}),
modEntry(t, "b", meshcatalog.ModulesByModuleValue{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}))
s := m.Snapshot()
for _, name := range []string{"a", "b"} {
if r := fpRow(t, s, name); r.State != FPNone || !(strings.Contains(r.Reason, "new form") || strings.Contains(r.Reason, "release 4")) || r.Build != "" {
t.Errorf("%s = %+v, want none: no build of the builder's is compared", name, r)
}
}
}
func shown(t *testing.T) *Model {
m := New(viewstate.ViewModule)
fill(m,
candidate(t, "feat/x", pullOf("novox/mesh-delivery", 12, hA, "a"), pullOf("novox/mesh-catalog", 300, hB, "c")),
modEntry(t, "a", meshcatalog.ModulesByModuleValue{Fp2: fp2A, Repository: "novox/mesh-delivery", Commit: hA}),
builderKey(t, "a", fp2A, builds.BuilderBuild{State: builds.BuildFailed, Error: "the compile failed"}),
modEntry(t, "b", meshcatalog.ModulesByModuleValue{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", "the compile failed", fp2A, "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", "the compile failed", fp2A, "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(viewstate.ViewModule)
fill(m, Entry{Bucket: CandidatesBucket, Key: "feat/x", Value: []byte("{"), At: t0},
Entry{Bucket: 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)
}
}