The node seat node-engine served on the engine's link: its verbs with their proven caller, its acts kept in its own state, a hand-over or access act asked of the operator as node-engine-<node> and performed only on the warrant (issue 774, ADR 0563 §1-§3)
This commit is contained in:
@@ -3,9 +3,14 @@ package link
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/asker"
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
)
|
||||
|
||||
// The take act's ask (novox/hq ADR 0325 §8): a hand-over's fields and the act's own in one signed object, under a
|
||||
@@ -68,6 +73,102 @@ func TestTheTakeActRunsOnlyOnAWarrant(t *testing.T) {
|
||||
if a.Said != "started" || len(given) != 1 || given[0].Warrant != "w-1" || given[0].Path != "/srv/media/movies" {
|
||||
t.Fatalf("a warranted ask did not reach the act whole: %+v %+v", a, given)
|
||||
}
|
||||
// 774 (ADR 0563 §3): a hand-over asked through the node seat is the engine's own ask, performed only on the
|
||||
// operator's warrant to it: never on the call, not on a warrant for another digest, once on a warrant heard twice,
|
||||
// and not when the disk changed since it was asked.
|
||||
t.Run("the node seat's hand-over acts only on the operator's warrant", func(t *testing.T) {
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
warrant func(asked asks.Ask, w asks.Warrant) asks.Warrant
|
||||
diskAfter string
|
||||
twice bool
|
||||
done int
|
||||
state string
|
||||
}{
|
||||
{name: "no warrant", done: 0, state: HandAsked},
|
||||
{name: "the operator's yes", warrant: yes, done: 1, state: HandDone},
|
||||
{name: "the yes heard twice", warrant: yes, twice: true, done: 1, state: HandDone},
|
||||
{name: "a warrant for another digest", warrant: func(a asks.Ask, w asks.Warrant) asks.Warrant {
|
||||
w = yes(a, w)
|
||||
w.AskDigest = "sha256:another"
|
||||
return w
|
||||
}, done: 0, state: HandAsked},
|
||||
{name: "a no", warrant: func(a asks.Ask, w asks.Warrant) asks.Warrant {
|
||||
w = yes(a, w)
|
||||
w.Option, w.Label, w.Level = "no", "No, leave it", asks.Acknowledge
|
||||
return w
|
||||
}, done: 0, state: HandRefused},
|
||||
{name: "the disk changed", warrant: yes, diskAfter: "0:0 700 9:2", done: 0, state: HandRefused},
|
||||
{name: "expired", warrant: func(a asks.Ask, w asks.Warrant) asks.Warrant {
|
||||
return asks.Warrant{Ask: a.ID, Asker: w.Asker, Outcome: asks.OutcomeExpired, At: w.At}
|
||||
}, done: 0, state: HandRefused},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
h, asked, done := anEngine(t)
|
||||
disk := "1000:1000 755 9:1"
|
||||
h.JudgeHandOver = func(path string) (Judged, error) {
|
||||
return Judged{Subject: path, Path: path, Sets: "owner jo, group jo, mode 0750", Disk: disk,
|
||||
Words: "A directory used as found."}, nil
|
||||
}
|
||||
if _, err := h.Call(EngineCall{Verb: ActHandOver, Caller: "shanks.node-tools",
|
||||
Args: map[string]any{"path": "/srv/x", "why": "a test"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*asked) != 1 || *done != 0 {
|
||||
t.Fatalf("the call asked %d and performed %d", len(*asked), *done)
|
||||
}
|
||||
if _, err := h.Call(EngineCall{Verb: ActHandOver, Caller: "x", Args: map[string]any{"path": "/srv/x"}}); err == nil {
|
||||
t.Fatal("a second ask of the same path was asked while one is open")
|
||||
}
|
||||
if row.diskAfter != "" {
|
||||
disk = row.diskAfter
|
||||
}
|
||||
if row.warrant != nil {
|
||||
w := row.warrant((*asked)[0], asks.Warrant{Asker: asks.EngineAsker("n1"), At: time.Now()})
|
||||
h.Heard(w)
|
||||
if row.twice {
|
||||
h.Heard(w)
|
||||
}
|
||||
}
|
||||
got, _ := h.Acts(0)
|
||||
acts := got.(map[string]any)["acts"].([]HandAct)
|
||||
if *done != row.done || len(acts) != 1 || acts[0].State != row.state {
|
||||
t.Fatalf("performed %d (want %d), acts %+v (want %s)", *done, row.done, acts, row.state)
|
||||
}
|
||||
if row.state == HandDone && (acts[0].By != "operator" || acts[0].Via == "" || acts[0].Ask == "") {
|
||||
t.Fatalf("the act does not record the warrant: %+v", acts[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// yes is the operator's yes to an ask, as the router writes it.
|
||||
func yes(a asks.Ask, w asks.Warrant) asks.Warrant {
|
||||
w.Ask, w.Outcome, w.Option, w.Label, w.Level = a.ID, asks.OutcomeChosen, "yes", "Yes", asks.Approve
|
||||
w.By = &asks.Person{Who: asks.Operator, Kind: "telegram", Verified: "user id verified"}
|
||||
w.AskDigest, w.Proofs = a.Digest(), []string{"P1"}
|
||||
return w
|
||||
}
|
||||
|
||||
// anEngine is a node seat with its own state in a temporary folder, a router that keeps what it was asked, and a
|
||||
// hand-over that counts what it performed.
|
||||
func anEngine(t *testing.T) (*HandActs, *[]asks.Ask, *int) {
|
||||
t.Helper()
|
||||
var asked []asks.Ask
|
||||
done := 0
|
||||
state := &FileState{Path: filepath.Join(t.TempDir(), HandActsFile)}
|
||||
h := &HandActs{Node: "n1", State: state,
|
||||
HandOver: func(path, by string) (string, error) { done++; return path + " handed over by " + by, nil }}
|
||||
h.Asker = &asker.Client{Module: asks.EngineAsker("n1"), State: state,
|
||||
Publish: func(subject string, body any, id string) error {
|
||||
if a, ok := body.(asks.Ask); ok && subject == asks.AskSubject(asks.EngineAsker("n1")) {
|
||||
asked = append(asked, a)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Record: func(string) (*stdio.StateEntry, error) { return nil, nil }}
|
||||
return h, &asked, &done
|
||||
}
|
||||
|
||||
// The controller names an access by its declared resource id exactly, which may hold a `/`: it reaches the act,
|
||||
|
||||
Reference in New Issue
Block a user