166 lines
7.9 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|