Files
mesh-delivery/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/tiers_rows_test.go
T

121 lines
6.4 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/provisions"
"mesh-delivery-view/internal/view"
)
// encoded is v as its contract's encoder writes it, failing the test when it refuses.
func encoded(t *testing.T, v []byte, err error) string {
t.Helper()
if err != nil {
t.Fatal(err)
}
return string(v)
}
// tiersToolSaysEachModulesWait: view_tiers orders a source revision's modules by the module graph and names each one's
// wait (ADR 0603 §6): a consumer after its provider, held by the assigner's provision rule and its provision not provided;
// a module whose build waits for a package; a provider with no wait; a node offline named away; older modules left out.
func tiersToolSaysEachModulesWait(t *testing.T) {
fp := func(c string) string { return "src2:" + strings.Repeat(c, 64) }
manifest := strings.Repeat("d", 64)
mod := func(f string, src uint64) string {
b, err := delivery.EncodeModule(delivery.Module{FP2: f, Manifest: manifest, Src: src, FPSrc: src, Repository: "novox/mesh-catalog", Commit: "c"})
return encoded(t, b, err)
}
node := func(g delivery.GraphNode) string {
b, err := delivery.EncodeGraphNode(g)
return encoded(t, b, err)
}
build := func(b delivery.BuilderBuild) string {
v, err := delivery.EncodeBuilderBuild(b)
return encoded(t, v, err)
}
want := func(w delivery.Want) string {
v, err := delivery.EncodeWant(w)
return encoded(t, v, err)
}
have := func(h delivery.Have) string {
v, err := delivery.EncodeHave(h)
return encoded(t, v, err)
}
key := func(k string, err error) string {
if err != nil {
t.Fatal(err)
}
return k
}
since := time.Date(2026, 11, 3, 16, 40, 0, 0, time.UTC)
r := fake{"db": mod(fp("a"), 9), "web": mod(fp("b"), 9), "lib": mod(fp("c"), 9), "old": mod(fp("e"), 5)}.reader()
r.GraphState = fake{
"db": node(delivery.GraphNode{FP: fp("a"), Repository: "novox/mesh-catalog", Provides: []string{"database"}}),
"web": node(delivery.GraphNode{FP: fp("b"), Repository: "novox/mesh-catalog", Requires: []string{"database"}, Awaits: []string{"database"}}),
"lib": node(delivery.GraphNode{FP: fp("c"), Repository: "novox/mesh-catalog"}),
}.reader().State
r.Builds = fake{
key(delivery.BuilderBuildKey("db", fp("a"))): build(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: manifest}),
key(delivery.BuilderBuildKey("web", fp("b"))): build(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: manifest}),
key(delivery.BuilderBuildKey("lib", fp("c"))): build(delivery.BuilderBuild{State: delivery.BuildQueued, Waits: "@novox/x@1.2.0"}),
}.reader().State
r.WantsState = fake{
key(delivery.WantKey("ace", "db")): want(delivery.Want{FP: fp("a"), Self: true}),
key(delivery.WantKey("ace", "web")): want(delivery.Want{Self: true, FP: fp("f"), Newest: fp("b"), Rule: delivery.RuleProvisions, Words: "the provision ace.web.database of web on ace is asked, not provided"}),
key(delivery.WantKey("g14", "web")): want(delivery.Want{Self: true, FP: fp("f"), Newest: fp("b"), Rule: delivery.RuleCanary, Words: "the canary"}),
}.reader().State
r.WantsState.History = func(string) ([]view.Revision, error) { return []view.Revision{{At: since}}, nil }
r.HavesState = fake{key(delivery.HaveKey("ace", "db")): have(delivery.Have{FP: fp("a"), Phase: delivery.PhaseRunning, Health: delivery.HealthOK})}.reader().State
p, err := provisions.Encode(provisions.Provision{Provision: "database", Consumer: provisions.End{Module: "web", Node: "ace"},
Provider: provisions.End{Module: "db", Node: "ace"}, State: provisions.Asked, Why: "the build db runs does not offer it", At: since})
r.ProvisionsState = fake{key(provisions.Key("ace", "web", "database", "")): encoded(t, p, err)}.reader().State
off, err := delivery.EncodePresence(delivery.Presence{Online: false})
r.PresenceState = fake{key(delivery.PresenceNodeKey("g14")): encoded(t, off, err)}.reader().State
out, err := run(t, r, view.TiersTool, map[string]any{"revision": float64(9)})
got, _ := out.(view.Tiers)
if err != nil || got.Revision != 9 || got.Modules != 3 || len(got.Tiers) != 2 || !reflect.DeepEqual(got.Away, []string{"g14"}) || got.Graph != "" {
t.Fatalf("view_tiers 9 = %+v, %v", out, err)
}
first := fmt.Sprint(got.Tiers[0])
if len(got.Tiers[0]) != 2 || got.Tiers[0][0].Module != "db" || len(got.Tiers[0][0].Waits) != 0 || got.Tiers[0][1].Module != "lib" ||
len(got.Tiers[0][1].Waits) != 1 || !strings.Contains(got.Tiers[0][1].Waits[0].Words, "waiting for the package @novox/x@1.2.0") {
t.Errorf("the first tier = %s, want db with no wait and lib's build waiting for its package", first)
}
web := got.Tiers[1][0]
if web.Module != "web" || !reflect.DeepEqual(web.After, []string{"db"}) || len(web.Waits) != 2 ||
web.Waits[0].Edge != "want" || web.Waits[0].Node != "ace" || web.Waits[0].Rule != delivery.RuleProvisions || !web.Waits[0].Since.Equal(since) ||
web.Waits[1].Edge != "provision database" || !strings.Contains(web.Waits[1].Words, "asked: the build db runs does not offer it") {
t.Errorf("web = %+v, want after db, held on ace by the provision rule and its provision asked (g14 is away)", web)
}
// No revision named: the gate's newest, its source key's revision.
r.SourceState = view.StateRead{GetEntry: func(string) (json.RawMessage, uint64, error) { return json.RawMessage(`{}`), 9, nil }}
if out, err := run(t, r, view.TiersTool, map[string]any{}); err != nil || out.(view.Tiers).Revision != 9 || out.(view.Tiers).Modules != 3 {
t.Errorf("view_tiers of the gate's newest = %+v, %v", out, err)
}
// The graph not readable: every module in the first tier, said; past 500 modules the answer says it is cut short.
r.GraphState = view.StateRead{Keys: func() ([]string, error) { return nil, errors.New("no bucket") }, Get: r.GraphState.Get}
if out, err := run(t, r, view.TiersTool, map[string]any{"revision": float64(9)}); err != nil || len(out.(view.Tiers).Tiers) != 1 ||
!strings.Contains(out.(view.Tiers).Graph, "cannot be read") {
t.Errorf("view_tiers with no graph = %+v, %v", out, err)
}
many := fake{}
for i := 0; i < view.TiersMost+2; i++ {
many[fmt.Sprintf("m%03d", i)] = mod(fp("a"), 9)
}
r.State = many.reader().State
if out, err := run(t, r, view.TiersTool, map[string]any{"revision": "9"}); err != nil || out.(view.Tiers).Modules != view.TiersMost+2 ||
!strings.Contains(out.(view.Tiers).Cut, "the first 500") {
t.Errorf("view_tiers past its bound = %+v, %v", out.(view.Tiers).Cut, err)
}
}