136 lines
6.1 KiB
Go
136 lines
6.1 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
func TestSetOnAWarrantJudgesTheLayerAndKeepsWhoApprovedIt(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
register(t, open, catalogue.Manifest{Module: "notes", Version: "1",
|
|
// y is a preference with a default, so a layer may leave it out; x is the operator's own (ADR 0262).
|
|
Settings: map[string]catalogue.SettingDeclaration{"y": {Kind: "preference", Default: "10", Why: "a size"}},
|
|
Resources: []map[string]any{{"id": "data", "type": "directory", "mode": "0755"},
|
|
// A trusted file (unmarked), so its keys are the terminal's — and now the warrant's (novox/hq ADR 0277).
|
|
{"id": "rc", "type": "file", "path": "/etc/notes.conf", "mode": "0644", "content": "x = ${setting:x}\ny = ${setting:y}\n"}}})
|
|
if _, err := assign(ctx, open, "laptop", "notes"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
set := setLayerIn(open)
|
|
now := time.Now()
|
|
first := map[string]any{"x": "1", "y": "2"}
|
|
p := settingsProposal{Module: "notes", Node: "laptop", Values: first, From: "g14/claude-code", At: now,
|
|
Digest: layerDigest(first), BeforeDigest: layerDigest(nil)}
|
|
changed, err := set(ctx, p, "s9", "approved by the operator, as telegram identity 42 via telegram at 14:05 (ask s9)")
|
|
if err != nil || !strings.Contains(changed, "+ x") {
|
|
t.Fatalf("%q %v", changed, err)
|
|
}
|
|
layer, has, err := open.inventory.Layer(ctx, "laptop", "notes")
|
|
if err != nil || !has || layer["x"] != "1" {
|
|
t.Fatalf("the layer: %v %v %v", layer, has, err)
|
|
}
|
|
setBy, _, has, err := open.inventory.LayerOrigin(ctx, "laptop", "notes")
|
|
if err != nil || !has || !strings.HasPrefix(setBy, "approved by the operator, as telegram identity 42 via telegram") {
|
|
t.Fatalf("who set it: %q %v", setBy, err)
|
|
}
|
|
// Shown by `settings show`, at the terminal and through the verb.
|
|
out := captureStdout(t, func() {
|
|
if err := atTheTerminal(t, "settings", "show", "notes", "--node", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
if !strings.Contains(out, "approved by the operator, as telegram identity 42 via telegram") {
|
|
t.Errorf("settings show says:\n%s", out)
|
|
}
|
|
// The same proposal again: the layer is no longer the one the operator was shown it against.
|
|
if _, err := set(ctx, p, "s9", "approved …"); err == nil || !strings.Contains(err.Error(), "changed since the operator was shown") {
|
|
t.Errorf("a stale proposal: %v", err)
|
|
}
|
|
// A removal not meant is refused; one meant is taken, and the history says who had set the layer.
|
|
second := map[string]any{"x": "1"}
|
|
q := settingsProposal{Module: "notes", Node: "laptop", Values: second, Before: first, HadLayer: true,
|
|
From: "g14/claude-code", At: now, Digest: layerDigest(second), BeforeDigest: layerDigest(first)}
|
|
if _, err := set(ctx, q, "s10", "approved …"); err == nil || !strings.Contains(err.Error(), "removal was not meant") {
|
|
t.Errorf("a silent removal: %v", err)
|
|
}
|
|
// A layer the mesh refuses is refused here too, with the same words as at the terminal.
|
|
bad := q
|
|
bad.Values, bad.Digest = map[string]any{"x": "a\nb", "y": "2"}, layerDigest(map[string]any{"x": "a\nb", "y": "2"})
|
|
if _, err := set(ctx, bad, "s11", "approved …"); err == nil || !strings.Contains(err.Error(), "line break") {
|
|
t.Errorf("a line break on a warrant: %v", err)
|
|
}
|
|
if layer, _, _ := open.inventory.Layer(ctx, "laptop", "notes"); layer["y"] != "2" {
|
|
t.Fatalf("a refused act changed the layer: %v", layer)
|
|
}
|
|
q.Replace = true
|
|
if _, err := set(ctx, q, "s10", "approved later"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
past, err := open.inventory.SettingsHistory(ctx, "laptop", "notes")
|
|
if err != nil || len(past) != 1 || !strings.HasPrefix(past[0].SetBy, "approved by the operator") {
|
|
t.Errorf("the history: %+v %v", past, err)
|
|
}
|
|
// And a clear, keeping who cleared it with the copy.
|
|
c := settingsProposal{Module: "notes", Node: "laptop", Clear: true, Before: second, HadLayer: true, From: "x", At: now,
|
|
Digest: layerDigest(second), BeforeDigest: layerDigest(second)}
|
|
if _, err := set(ctx, c, "s12", "approved by the operator at 15:00"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, has, _ := open.inventory.Layer(ctx, "laptop", "notes"); has {
|
|
t.Error("the layer was not cleared")
|
|
}
|
|
past, _ = open.inventory.SettingsHistory(ctx, "laptop", "notes")
|
|
if len(past) != 2 || !strings.Contains(past[0].SetBy, "cleared approved by the operator at 15:00") {
|
|
t.Errorf("the history after a clear: %+v", past)
|
|
}
|
|
}
|
|
|
|
func TestALayerSaysWhoSetIt(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
register(t, open, catalogue.Manifest{Module: "notes", Version: "1",
|
|
Resources: []map[string]any{{"id": "rc", "type": "file", "path": "/etc/notes.conf", "mode": "0644", "trusted": false,
|
|
"content": "x = ${setting:x}\n"}}})
|
|
if _, err := assign(ctx, open, "laptop", "notes"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := atTheTerminal(t, "settings", "set", "notes", `{"x":"1"}`, "--node", "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
setBy, _, _, _ := open.inventory.LayerOrigin(ctx, "laptop", "notes")
|
|
if !strings.HasPrefix(setBy, "set at the controller's terminal by ") {
|
|
t.Errorf("at the terminal: %q", setBy)
|
|
}
|
|
if err := throughVerb(t, "settings", map[string]any{"module": "notes", "node": "laptop", "values": `{"x":"2"}`}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
setBy, _, _, _ = open.inventory.LayerOrigin(ctx, "laptop", "notes")
|
|
if !strings.HasPrefix(setBy, "set by ") || !strings.Contains(setBy, "through settings") {
|
|
t.Errorf("through the verb: %q", setBy)
|
|
}
|
|
}
|
|
|
|
func TestATrustedSettingThroughAVerbNamesTheProposal(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
register(t, open, catalogue.Manifest{Module: "notes", Version: "1",
|
|
Resources: []map[string]any{{"id": "data", "type": "directory", "mode": "0755"}}})
|
|
if _, err := assign(ctx, open, "laptop", "notes"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := throughVerb(t, "settings", map[string]any{"module": "notes", "node": "laptop",
|
|
"values": `{"places":{"data":{"path":"/srv/notes","owner":"1001:1001"}}}`})
|
|
if err == nil || !strings.Contains(err.Error(), "issue 339") || !strings.Contains(err.Error(), "propose") {
|
|
t.Errorf("%v", err)
|
|
}
|
|
if _, has, _ := open.inventory.Layer(ctx, "laptop", "notes"); has {
|
|
t.Error("a layer was kept")
|
|
}
|
|
}
|