Files
mesh-controller/internal/inventory/chain_assignments_key_test.go
T
jschoubben e16cb4207b Review round 1 of milestone 5: the move settles changed rows under the lock; reports counted by kind; route-proxy at the new SDK; the plan pass never revives an ended assignment; the store's mirror is refused once moved (issues 1090, 1092, 1093)
1090/1092: the move tests pick each report by kind and expect three, all already, on the second run. 1090-1093: under the share lock the switch reads the rows again and writes every key whose value differs from its row (and ends an assignment the store dropped) before recording the kind, so an update the old controller made after the first pass is not lost. 1093: route-proxy is pinned at mesh-sdk f185aedb and vendored; the controller uses only the SDK's asks package. KeepAssignmentPlans decides each key by keepStep, which never makes or revives an assignment. AssignmentsMirrorToWrite refuses once the assignments are moved.
2026-10-18 03:36:05 +02:00

82 lines
3.7 KiB
Go

package inventory
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/novox/mesh-controller/internal/chain"
)
// **An assignment's key is `<node>.<module>`, as the bucket has always had it** (ADR 0408 §3): its readers parse nothing
// but it.
func TestAnAssignmentsKeyIsTheNodeAndTheModule(t *testing.T) {
if got := assignmentKey("anchor", "notes"); got != "anchor.notes" {
t.Fatalf("the key is %q", got)
}
if got := assignmentKey("g14.example", "dunst"); got != "g14.example.dunst" {
t.Fatalf("the key of a machine whose name holds a dot is %q", got)
}
}
// **A plan that is absent and a null one are one** (the provisioner's plan in an assignment's value): the pass that keeps
// the plans writes only where they differ.
func TestAnAbsentPlanAndANullOneAreTheSame(t *testing.T) {
for name, tc := range map[string]struct {
a, b json.RawMessage
same bool
}{
"both absent": {nil, nil, true},
"null and absent": {json.RawMessage(`null`), nil, true},
"the same plan": {json.RawMessage(`{"files":[]}`), json.RawMessage(` {"files":[]} `), true},
"another plan": {json.RawMessage(`{"files":[]}`), json.RawMessage(`{"files":[1]}`), false},
"a plan and none": {json.RawMessage(`{"files":[]}`), nil, false},
"none and a plan": {nil, json.RawMessage(`{"files":[]}`), false},
} {
if got := sameJSON(tc.a, tc.b); got != tc.same {
t.Errorf("%s: %v", name, got)
}
}
}
// **A pass that keeps the plans never brings back an assignment that ended** (issue 1093): it reads what it wants and what
// the bucket holds in two reads, and an unassignment between them leaves a key that ended while the pass still wants it
// assigned.
func TestAPassThatKeepsThePlansNeverRevivesAnEndedAssignment(t *testing.T) {
at := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC)
plan := json.RawMessage(`{"files":[]}`)
assigned := chain.Assignment{Node: "a", Module: "m", Assigned: at, Provisioning: plan}
ended := chain.Assignment{Node: "a", Module: "m", Assigned: at, Ended: at.Add(time.Minute)}
pulledIn := chain.Assignment{Node: "a", Module: "p", RequiredBy: "m", Provisioning: plan}
pulledInEnded := chain.Assignment{Node: "a", Module: "p", Ended: at}
for name, tc := range map[string]struct {
has bool
cur, next chain.Assignment
want string
}{
"the interleaving: wanted assigned, ended since": {true, ended, assigned, keepSkip},
"wanted assigned, not in the bucket yet": {false, chain.Assignment{}, assigned, keepSkip},
"a pulled-in module, not in the bucket": {false, chain.Assignment{}, pulledIn, keepCreate},
"a pulled-in module that ended, pulled again": {true, pulledInEnded, pulledIn, keepFresh},
"an assignment ended, a plan for it": {true, ended, chain.Assignment{Node: "a", Module: "m", RequiredBy: "x", Provisioning: plan}, keepSkip},
"the plan is the same": {true, assigned, assigned, keepSkip},
"the plan changed": {true, chain.Assignment{Node: "a", Module: "m", Assigned: at}, assigned, keepField},
} {
if got := keepStep(tc.has, tc.cur, tc.next); got != tc.want {
t.Errorf("%s: %s, want %s", name, got, tc.want)
}
}
}
// **The store's mirror of the assignments is refused to its writer once the assignments are moved** (ADR 0408 §4, issue
// 1093): asked at the moment of the write, so a tick that decided before the switch lays nothing after it.
func TestTheStoresMirrorOfTheAssignmentsIsRefusedToItsWriterOnceTheyAreMoved(t *testing.T) {
inv := &Inventory{moved: &movedKinds{kind: map[string]bool{KindAssignments: true}}}
inv.UseChain(chain.InMemory(nil))
if _, err := inv.AssignmentsMirrorToWrite(context.Background()); !errors.Is(err, ErrAssignmentsMoved) {
t.Fatalf("the writer was given the store's rows: %v", err)
}
}