Files
mesh-host/internal/link/access_act_test.go
T
jschoubben f81e6147ae
mesh/delivery merged: source.main moved to novox/mesh-host@1ec73915b807
mesh/delivery-group group revert/774-l3 merged: every member is merged
mesh/check being carried to f81e6147aee1 on 0a4c35f731a7 | from mesh-catalog 1555241393fe | novox/mesh-host#174 in group revert/774-l3
Revert L3 (mesh-host, merge 0a4c35f): its node-engine removed every held module on novox, the bus server among them, at 08:54 CET on 2026-10-29 (issue 774)
L3 lands again with two fixes: a held module is never removed, and the grants cover L3's new subjects.
2026-10-29 09:42:29 +01:00

88 lines
4.0 KiB
Go

package link
import (
"crypto/ed25519"
"encoding/json"
"strings"
"testing"
"time"
)
// 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
// signing context of its own. The controller's side holds the same names and words.
func TestTheTakeActsAskKeepsItsFieldNamesAndContext(t *testing.T) {
body, _ := json.Marshal(AccessActAsk{Act: ActTake, Access: "radarr.movies", Warrant: "w"})
if got := keysOf(t, body); got != "access act warrant" {
t.Fatalf("the act's fields are %q", got)
}
if AskAccessActSubject("ace") != "mesh.node.ace.ask.access-act" {
t.Fatalf("the subject is %q", AskAccessActSubject("ace"))
}
if AccessActContext != "novox-mesh access-act v2\n" {
t.Fatalf("the signing context is %q", AccessActContext)
}
}
// signedAct is an act's ask as the controller makes it: the hand-over's fields beside the act's, signed over the
// act's context and its bytes.
func signedAct(t *testing.T, key ed25519.PrivateKey, ask HandOverAsk, act AccessActAsk) []byte {
t.Helper()
raw, _ := json.Marshal(struct {
HandOverAsk
AccessActAsk
}{ask, act})
body, _ := json.Marshal(SignedHandOver{Ask: raw, Signature: ed25519.Sign(key, append([]byte(AccessActContext), raw...))})
return body
}
// **Nothing is done without a warrant**, nor for any act but take, nor signed as a hand-over (ADR 0325 §8).
func TestTheTakeActRunsOnlyOnAWarrant(t *testing.T) {
m, key := aMember(t)
now := time.Now()
var given []AccessAct
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
ask := func(nonce string) HandOverAsk {
return HandOverAsk{Node: m.Node, Path: "/srv/media/movies", By: "the operator", Expires: now.Add(HandOverGood),
Nonce: nonce}
}
for why, body := range map[string][]byte{
"no warrant": signedAct(t, key, ask(aNonce+"01"), AccessActAsk{Act: ActTake, Access: "radarr.movies"}),
"another act": signedAct(t, key, ask(aNonce+"02"), AccessActAsk{Act: "share", Access: "radarr.movies", Warrant: "w"}),
"no access": signedAct(t, key, ask(aNonce+"03"), AccessActAsk{Act: ActTake, Warrant: "w"}),
"a hand-over's": signedAsk(t, key, ask(aNonce+"04")),
"a slash in the warrant": signedAct(t, key, ask(aNonce+"05"), AccessActAsk{Act: ActTake, Access: "radarr.movies",
Warrant: "w/1"}),
"two lines as access": signedAct(t, key, ask(aNonce+"07"), AccessActAsk{Act: ActTake, Access: "radarr.\nmovies",
Warrant: "w"}),
} {
if a := AnswerAccessAct(check, handle, body); a.Refused == "" || !strings.HasSuffix(a.Refused, "nothing was changed") {
t.Errorf("%s: not refused, or refused without saying nothing was changed: %+v", why, a)
}
}
if len(given) != 0 {
t.Fatalf("a refused ask reached the act: %+v", given)
}
a := AnswerAccessAct(check, handle, signedAct(t, key, ask(aNonce+"06"), AccessActAsk{Act: ActTake,
Access: "radarr.movies", Warrant: "w-1"}))
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)
}
}
// The controller names an access by its declared resource id exactly, which may hold a `/`: it reaches the act,
// which matches it against what was declared.
func TestATakeActNamesItsAccessByItsDeclaredId(t *testing.T) {
m, key := aMember(t)
var given []AccessAct
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
ask := HandOverAsk{Node: m.Node, Path: "/services/media/movies", By: "the operator",
Expires: time.Now().Add(HandOverGood), Nonce: aNonce + "08"}
id := "radarr.access-services/media/movies"
a := AnswerAccessAct(check, handle, signedAct(t, key, ask, AccessActAsk{Act: ActTake, Access: id, Warrant: "w-1"}))
if a.Refused != "" || len(given) != 1 || given[0].Access != id {
t.Fatalf("an access id with a slash did not reach the act as declared: %+v %+v", a, given)
}
}