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

166 lines
7.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
"mesh-delivery-view/internal/view"
"sort"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-sdk/go/views"
)
// source is a fake owner state as a views.Source: what the owner's read tool answers from.
type source map[string]string
func (s source) Keys() ([]string, error) {
var out []string
for k := range s {
out = append(out, k)
}
sort.Strings(out)
return out, nil
}
func (s source) Get(key string) (*views.Entry, bool, error) {
v, ok := s[key]
if !ok {
return nil, false, nil
}
return &views.Entry{Value: json.RawMessage(v), Revision: uint64(len(key))}, true, nil
}
// kept is a fake owner state that keeps its keys' history, as views.KeptSource does: what the owner's read tool answers a
// `history` from.
type kept struct {
source
history map[string][]views.Revision
}
func (k kept) History(key string) ([]views.Revision, error) { return k.history[key], nil }
// ownersAnswerTheToolsAsBefore is DLV-V1: every view tool over the indexer's and the assigner's states answers through
// the owners' read tools (the real owner side, views.Read, over a fake state) what it answered reading the states.
func ownersAnswerTheToolsAsBefore(t *testing.T) {
cand := `{"v":1,"group":"feat/x","base":3,"heads":"h","pulls":[{"modules":["zsh"],"fps":{"zsh":{"fp2":"` + fp2B + `","manifest":"m"}}}]}`
pkgKey, err := view.PackagesKey("git.novox.be/novox/mesh-sdk/go")
if err != nil {
t.Fatal(err)
}
states := map[string]source{
view.IndexerModule + ".modules": {"a": withFP2, "b": withNone},
view.IndexerModule + ".candidates": {"feat/x": cand, "fix": `{"v":1,"group":"fix","base":3,"heads":"h","pulls":[{"modules":["netcheck"]}]}`},
view.AssignerModule + ".wants": {"want.ace.zsh": `{"fp":"` + fp2A + `"}`, "want.novox.zsh": `{"fp":"` + fp2B + `"}`,
"held." + keyB: `{"node":"ace","words":"failed"}`},
view.AssignerModule + ".differences": {"ace.zsh": `{"unexplained":true}`, "novox.zsh": `{"rule":3}`},
view.IndexerModule + "." + view.IndexerPredefinedState: {view.PredefinedListKey: `{"v":1,"keys":["dir:state"]}`},
view.IndexerModule + "." + view.IndexerConfigCandidatesState: {"novox.r.7": `{"v":1,"pull":"novox/r#7"}`},
view.IndexerModule + "." + view.PackagesState: {pkgKey: `{"v":1,"module":"git.novox.be/novox/mesh-sdk/go","commit":"c1"}`},
view.IndexerModule + "." + graph.State: {},
}
// The kept history of the indexer's modules and heard and the assigner's wants (each declared with more than one
// revision in its owner's manifest), which indexer_module_history, indexer_heard and view_tiers' times read.
at := time.Date(2026, 11, 4, 9, 0, 0, 0, time.UTC)
const heard1 = `{"v":1,"subject":"s","sequence":41,"repository":"novox/mesh-catalog","commit":"9cdac09a"}`
states[view.IndexerModule+".heard"] = source{"forge": heard1}
histories := map[string]map[string][]views.Revision{
view.IndexerModule + ".modules": {"a": {{Revision: 7, At: at, Op: "put", Value: json.RawMessage(withNone)},
{Revision: 9, At: at.Add(time.Minute), Op: "put", Value: json.RawMessage(withFP2)}}},
view.IndexerModule + ".heard": {"forge": {{Revision: 3, At: at, Op: "put", Value: json.RawMessage(heard1)}}},
view.AssignerModule + ".wants": {"want.ace.zsh": {{Revision: 12, At: at, Op: "put", Value: json.RawMessage(`{"fp":"` + fp2A + `"}`)}}},
}
// The direct reading, as the tools read the states before (the reads the manifest no longer has).
st := func(name string) view.StateRead { return fake(states[name]).reader().State }
direct := view.Reader{State: st(view.IndexerModule + ".modules"), CandidatesState: st(view.IndexerModule + ".candidates"),
WantsState: st(view.AssignerModule + ".wants"), DifferencesState: st(view.AssignerModule + ".differences"),
PredefinedState: st(view.IndexerModule + "." + view.IndexerPredefinedState),
ConfigCandidatesState: st(view.IndexerModule + "." + view.IndexerConfigCandidatesState),
PackagesState: st(view.IndexerModule + "." + view.PackagesState),
GraphState: st(view.IndexerModule + "." + graph.State)}
// The owners' read tools, answered by the owner's own Read over the same state; any other address is refused.
tools := map[string]string{}
for name := range states {
owner, state, _ := strings.Cut(name, ".")
short := map[string]string{view.IndexerModule: "indexer_read_", view.AssignerModule: "assigner_read_"}[owner]
tools[owner+"."+short+strings.ReplaceAll(state, "-", "_")] = name
}
asked := map[string]int{}
ask := func(key string, body any) (json.RawMessage, error) {
name, ok := tools[key]
if !ok {
return nil, fmt.Errorf("no tool %s", key)
}
asked[key]++
args, _ := body.(map[string]any)
k, _ := args["key"].(string)
all, _ := args["values"].(bool)
after, _ := args["after"].(string)
owner, state, _ := strings.Cut(name, ".")
if h, _ := args["history"].(bool); h {
// The join (J4): the owner's read tool answers a key's kept history.
a, err := views.ReadHistory(owner, state, kept{states[name], histories[name]}, k)
if err != nil {
return nil, err
}
return json.Marshal(a)
}
a, err := views.Read(owner, state, states[name], nil, k, all, after, time.Unix(0, 0))
if err != nil {
return nil, err
}
return json.Marshal(a)
}
owned := ownersRead(ask)
for _, row := range []struct {
tool string
args map[string]any
}{
{view.ModuleReadTool, map[string]any{"module": "a"}},
{view.ModuleReadTool, map[string]any{"module": "gone"}},
{view.ModulesReadTool, map[string]any{}},
{view.ModulesReadTool, map[string]any{"none2": true}},
{view.CandidateReadTool, map[string]any{"group": "feat/x"}},
{view.CandidatesReadTool, map[string]any{}},
{view.CandidatesReadTool, map[string]any{"module": "zsh"}},
{view.WantsReadTool, map[string]any{}},
{view.WantsReadTool, map[string]any{"node": "ace"}},
{view.WantReadTool, map[string]any{"node": "ace", "module": "zsh"}},
{view.DifferencesReadTool, map[string]any{"node": "novox"}},
{view.PredefinedReadTool, map[string]any{}},
{view.ConfigCandidatesReadTool, map[string]any{}},
{view.ConfigCandidatesReadTool, map[string]any{"key": "novox.r.7"}},
{view.PackagesReadTool, map[string]any{"module": "git.novox.be/novox/mesh-sdk/go"}},
{view.PackagesReadTool, nil},
} {
want, werr := run(t, direct, row.tool, row.args)
got, gerr := run(t, owned, row.tool, row.args)
wj, _ := json.Marshal(want)
gj, _ := json.Marshal(got)
if string(wj) != string(gj) || fmt.Sprint(werr) != fmt.Sprint(gerr) {
t.Errorf("%s %v through the owner's read tool = %s, %v; reading the state it was %s, %v", row.tool, row.args, gj, gerr, wj, werr)
}
}
// A key's history is asked of the owner's read tool and answered from its kept values (A2j finding 1, J4).
out, err := run(t, owned, view.ModuleHistoryTool, map[string]any{"module": "a"})
if h, _ := out.(view.ModuleHistory); err != nil || len(h.Values) != 2 || h.Values[0].Revision != 9 || h.Values[1].FP2 != "" || !h.Values[1].At.Equal(at) {
t.Errorf("indexer_module_history a through the indexer's read tool = %+v, %v", out, err)
}
out, err = run(t, owned, view.HeardTool, map[string]any{})
if rows, _ := out.(view.HeardAnswer); err != nil || len(rows.Heard) != 1 || rows.Heard[0].Revision != 3 || string(rows.Heard[0].Heard) != heard1 {
t.Errorf("indexer_heard through the indexer's read tool = %+v, %v", out, err)
}
// view_tiers' Since of a want is its first kept revision's time, read through the assigner's read tool.
if revs, err := owned.WantsState.History("want.ace.zsh"); err != nil || len(revs) != 1 || !revs[0].At.Equal(at) || revs[0].Revision != 12 {
t.Errorf("the want's history through the assigner's read tool = %+v, %v", revs, err)
}
// Each owner state the tools read was asked of its owner's read tool.
for tool, name := range tools {
if name != view.IndexerModule+"."+graph.State && asked[tool] == 0 {
t.Errorf("%s was never asked", tool)
}
}
}