The list reached the bus only through a want of the bus module, which nobody writes (ADR 0539 §15): on 2026-10-25 it stood from 11:23 to 20:50 and was put back by hand. The node agent now follows its composition bucket for the newest signed composition carrying the user list, writes that file alone through the apply's file step when it differs (mode 0600, read back, recorded as the module's), moves nothing that reads it (the bus module's entrypoint reloads the server in place), never goes back to an older list, looks again after every apply of the bus module, and says source, revision, wanted and written digests, users and any error in the bus module's have (mesh-sdk UserListWritten). Read with mesh-delivery-view.node_haves (module nats); the bus's own nats_user_can says in_force once it reloaded.
91 lines
3.5 KiB
Go
91 lines
3.5 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0547 §8: the bus node writes the bus's user list alone, with no want, through the apply's own
|
|
// file step: only when it differs, mode 0600, read back and recorded as the module's, and nothing else moves.
|
|
func TestAFileWrittenAloneMovesNothingElse(t *testing.T) {
|
|
const was, now = "user a\n", "user a\nuser b\n"
|
|
for _, c := range []struct {
|
|
name, fields, before string
|
|
recorded bool
|
|
recordedAs string // the id the record has, when not the composition's
|
|
action, refused string
|
|
after string
|
|
}{
|
|
{name: "no record of it", fields: `"content":%q,"mode":"0600"`, before: was, refused: "holds no record", after: was},
|
|
{name: "changed", fields: `"content":%q,"mode":"0600"`, before: was, recorded: true, action: "updated", after: now},
|
|
{name: "the same", fields: `"content":%q,"mode":"0600"`, before: now, recorded: true, action: "unchanged", after: now},
|
|
{name: "recorded under the id from before the switch", fields: `"content":%q,"mode":"0600"`, before: was,
|
|
recorded: true, recordedAs: "bus-users", action: "updated", after: now},
|
|
{name: "absent with a record", fields: `"content":%q,"mode":"0600"`, recorded: true, action: "created", after: now},
|
|
{name: "written into", fields: `"content":%q,"into":"block","owner-at-runtime":"managed"`, before: was,
|
|
recorded: true, refused: "only such a file", after: was},
|
|
{name: "sealed", fields: `"sealed":"c2VhbGVk"`, before: was, recorded: true,
|
|
refused: "only such a file", after: was},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "accounts.conf")
|
|
if c.before != "" {
|
|
if err := os.WriteFile(path, []byte(c.before), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
fields := strings.Replace(c.fields, "%q", fmt.Sprintf("%q", now), 1)
|
|
d := parseTrusted(t, fmt.Sprintf(`{"declaration":1,"resources":[{"id":"nats.bus-users","module":"nats",
|
|
"type":"file","path":%q,`+fields+`}]}`, path))
|
|
file := d.Resources[0].(*declaration.File)
|
|
known := store.State{}
|
|
id := file.ID
|
|
if c.recordedAs != "" {
|
|
id = c.recordedAs
|
|
}
|
|
if c.recorded {
|
|
known.Record(store.Applied{ID: id, Type: string(declaration.TypeFile), Module: "nats", Target: path,
|
|
Origin: store.OriginDeclared, Wrote: digestOf(c.before)})
|
|
}
|
|
calls := 0
|
|
counting := func(context.Context, string, ...string) (string, error) { calls++; return "", nil }
|
|
out, after, err := WriteAlone(context.Background(), archHost(t), file, known, counting, nil)
|
|
if c.refused != "" {
|
|
if err == nil || !strings.Contains(err.Error(), c.refused) {
|
|
t.Fatalf("not refused with %q: %v", c.refused, err)
|
|
}
|
|
} else if err != nil {
|
|
t.Fatal(err)
|
|
} else if out.Action != c.action {
|
|
t.Errorf("action %q, want %q", out.Action, c.action)
|
|
}
|
|
if got := readText(t, path); got != c.after {
|
|
t.Errorf("file holds %q, want %q", got, c.after)
|
|
}
|
|
if calls != 0 {
|
|
t.Errorf("%d commands were run: a file written alone moves nothing", calls)
|
|
}
|
|
if c.refused != "" {
|
|
return
|
|
}
|
|
if m := modeAt(t, path); m != 0o600 {
|
|
t.Errorf("mode %o", m)
|
|
}
|
|
rec, ok := after.Find(id)
|
|
if !ok || rec.Wrote != digestOf(now) || rec.Target != path {
|
|
t.Errorf("record %+v (found %v), want it to say %s was written", rec, ok, digestOf(now))
|
|
}
|
|
if n := len(after.Resources); n != 1 {
|
|
t.Errorf("%d records of one file", n)
|
|
}
|
|
})
|
|
}
|
|
}
|