C-2: a node follows its licence binding by binding.changed and a first view (issue 1409)
The agent module consumes the manager's binding.changed, acting only on the one naming its node's consumer, and each time it hears those events anew asks the seat's bindings for its own consumer as its first view; its read of the manager's bindings state goes. Builds: design 39 bus-and-switch §22, B9, B10, B22 Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -524,3 +525,58 @@ func TestALowerGenerationAfterTheManagerWasRebuiltIsStillApplied(t *testing.T) {
|
||||
t.Fatalf("asked %v, credentials %v: a lower generation was ignored", asked, creds(t, p))
|
||||
}
|
||||
}
|
||||
|
||||
// design 39 bus-and-switch §22/AC1, B22: binding.changed, decoded from a copy of the manager's form, is acted on only
|
||||
// when it names this node's consumer; an event for another consumer is ignored.
|
||||
func TestAnEventForAnotherConsumerIsIgnored(t *testing.T) {
|
||||
p, w := node(t, "server")
|
||||
var asked []string
|
||||
ask := seat(t, "api", "at-3", 3, &asked)
|
||||
said, err := os.ReadFile("testdata/binding-changed.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if done, err := OnBindingChanged(p, said, ask, writer(w)); err != nil || done != "" || len(asked) != 0 {
|
||||
t.Fatalf("an event for laptop was acted on by server: %q %v, asked %v", done, err, asked)
|
||||
}
|
||||
p, w = node(t, "laptop")
|
||||
if done, err := OnBindingChanged(p, said, ask, writer(w)); err != nil || !strings.Contains(done, `"generation":3`) || len(asked) != 1 {
|
||||
t.Fatalf("laptop's own event: %q %v, asked %v", done, err, asked)
|
||||
}
|
||||
if done, _ := OnBindingChanged(p, []byte(`{"consumer":"laptop","licence":"","generation":3,"from":"api"}`), ask, writer(w)); !strings.Contains(done, "released") {
|
||||
t.Fatalf("a release said %q", done)
|
||||
}
|
||||
}
|
||||
|
||||
// design 39 bus-and-switch §22, B10: at its start the module asks the seat's bindings for its own consumer and applies
|
||||
// that first view; its manifest consumes binding.changed and no longer reads the manager's state.
|
||||
func TestTheFirstViewOfTheBindingIsApplied(t *testing.T) {
|
||||
p, w := node(t, "laptop")
|
||||
var asked []string
|
||||
current := seat(t, "api", "at-9", 9, &asked)
|
||||
ask := func(address string, args any) (json.RawMessage, error) {
|
||||
if address == SeatVerb("bindings") {
|
||||
asked = append(asked, address)
|
||||
if args.(map[string]any)["consumer"] != "laptop" {
|
||||
t.Fatalf("bindings asked with %v", args)
|
||||
}
|
||||
return json.RawMessage(`[{"consumer":"laptop","licence":"api","key":"api","kind":"api-key","generation":9,"why":"carried over","by":"claude-licence-manager"}]`), nil
|
||||
}
|
||||
return current(address, args)
|
||||
}
|
||||
if done, err := FirstBinding(p, ask, writer(w)); err != nil || !strings.Contains(done, `"generation":9`) {
|
||||
t.Fatalf("the first view: %q %v, asked %v", done, err, asked)
|
||||
}
|
||||
if len(asked) != 2 || asked[0] != SeatVerb("bindings") || creds(t, p)["accessToken"] != "at-9" {
|
||||
t.Fatalf("asked %v, credentials %v", asked, creds(t, p))
|
||||
}
|
||||
raw, _ := os.ReadFile("../../module.json")
|
||||
var m struct {
|
||||
Consumes, Invokes []string
|
||||
Reads []string
|
||||
}
|
||||
_ = json.Unmarshal(raw, &m)
|
||||
if !slices.Contains(m.Consumes, BindingChanged) || !slices.Contains(m.Invokes, SeatVerb("bindings")) || len(m.Reads) != 0 {
|
||||
t.Fatalf("the manifest consumes %v, invokes %v, reads %v", m.Consumes, m.Invokes, m.Reads)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user