package main import ( "encoding/json" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // The controller hands a moved module to the provisioner, and counts it applied only once its files are placed // (design 57 P6, issue 672; novox/hq ADR 0349 §4, §15, ADR 0362). // A declaration that hands files says which, and whose client fills them; one that hands none is byte for byte // what it was. func TestADeclarationNamesTheFilledFilesOnlyWhenItHandsAny(t *testing.T) { plain := sendable{Resources: []map[string]any{{"id": "notify.configuration", "type": "file", "path": "/x", "content": "a"}}, Sequence: 4} before, err := plain.Body() if err != nil { t.Fatal(err) } if strings.Contains(string(before), "filled_files") || strings.Contains(string(before), "provisioner_account") { t.Fatalf("a declaration handing nothing names filled files: %s", before) } handing := plain handing.Filled, handing.Provisioner = []string{"/x"}, "mesh-provisioner-client" body, err := handing.Body() if err != nil { t.Fatal(err) } var envelope struct { Filled []string `json:"filled_files"` Provisioner string `json:"provisioner_account"` } if err := json.Unmarshal(body, &envelope); err != nil || len(envelope.Filled) != 1 || envelope.Filled[0] != "/x" || envelope.Provisioner != "mesh-provisioner-client" { t.Fatalf("the handing declaration says %s", body) } } func TestTheProvisionersAccountIsTheClientsProcessUser(t *testing.T) { client := catalogue.Manifest{Module: "mesh-provisioner-client", OwnSecrets: catalogue.OwnSecrets{"provisioner-broker": {Path: "/var/lib/x/broker"}}, Claims: []catalogue.Claim{{Name: "node-provisioner", Scope: "node"}}, Resources: []map[string]any{{"id": "account", "type": "user", "name": "mesh-provisioner-client"}, {"id": "client", "type": "process", "name": "mesh-provisioner-client", "user": "mesh-provisioner-client"}}} if got := provisionerAccount(catalogue.Resolution{Modules: []catalogue.Manifest{client}}); got != "mesh-provisioner-client" { t.Fatalf("the client's account is %q", got) } if got := provisionerAccount(catalogue.Resolution{}); got != "" { t.Fatalf("a machine without a client names %q", got) } } // A machine whose provisioner client failed to place a module's files has a failed apply, said as apply-failing // once the failure outlasts an apply interval, with the client's reason (ADR 0362 §3). func TestAFailedPlacingRaisesApplyFailing(t *testing.T) { now := time.Date(2026, 10, 4, 14, 0, 0, 0, time.UTC) f := calm(now) f.provFailed = []inventory.ProvReport{{Node: "anchor", Module: "dunst", Status: inventory.ProvFailed, Reason: "the templates cannot be fetched", At: now.Add(-time.Minute)}} if got := watchApplyFailing(f); len(got) != 0 { t.Fatalf("raised within an apply interval: %+v", got) } f.provFailed[0].At = now.Add(-10 * time.Minute) got := watchApplyFailing(f) if len(got) != 1 || got[0].Key() != "machine.anchor.apply-failing" || !strings.Contains(got[0].Summary, "dunst") || !strings.Contains(got[0].Said, "the templates cannot be fetched") { t.Fatalf("a failed placing is not raised as anchor's apply-failing: %+v", got) } }