Merge branch 'feat/47-G-fix11-a3h' into feat/47-G-join11
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package operatorchannelclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
)
|
||||
|
||||
// asksRead is a state of another module read by key: process.State is one.
|
||||
type asksRead interface {
|
||||
Get(key string) (*stdio.StateEntry, error)
|
||||
}
|
||||
|
||||
// NewGateChannel is the operator channel as a process holder's gate reaches it over its own connection (design 47 G §677,
|
||||
// §700; lead answers A-272, A-273): an ask and its cancel are sent on the seat operator-channel's accept subjects through
|
||||
// conn's Accept, and the router's record is read by a get of the key `<module>.<id>` in the seat's state asks, whose bucket
|
||||
// is formed from holder (the module the seat list names for the seat operator-channel) and the contract's StateAsks, on the
|
||||
// process's own account. state keeps the asks, under keyPrefix (the gate's state approvals and its node).
|
||||
func NewGateChannel(conn *process.Conn, state Store, keyPrefix string, holder func() (string, error)) *GateChannel {
|
||||
return gateChannelOver(conn.Accept, func(read string) asksRead { return conn.Read(read) }, state, keyPrefix, holder)
|
||||
}
|
||||
|
||||
// gateChannelOver is NewGateChannel over the two things it reaches a connection through.
|
||||
func gateChannelOver(accept func(seat, kind string, body any, opts ...stdio.EmitOptions) error, read func(read string) asksRead,
|
||||
state Store, keyPrefix string, holder func() (string, error)) *GateChannel {
|
||||
return &GateChannel{State: state, KeyPrefix: keyPrefix,
|
||||
Publish: func(module, subject string, body any, id string) error {
|
||||
var kind string
|
||||
switch subject {
|
||||
case seat.AcceptSubjectAsk(module):
|
||||
kind = seat.AcceptAsk
|
||||
case seat.AcceptSubjectCancel(module):
|
||||
kind = seat.AcceptCancel
|
||||
default:
|
||||
return fmt.Errorf("%s is no accept subject of the seat %s that %s may use", subject, seat.Seat, module)
|
||||
}
|
||||
return accept(seat.Seat, kind, body, stdio.EmitOptions{MessageID: id})
|
||||
},
|
||||
ReadRecord: func(module, id string) (*stdio.StateEntry, error) {
|
||||
held, err := holder()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the router's record of %s cannot be read: %w", id, err)
|
||||
}
|
||||
if held == "" {
|
||||
return nil, fmt.Errorf("the router's record of %s cannot be read: the seat list names no holder of the seat %s", id, seat.Seat)
|
||||
}
|
||||
return read(held + "." + seat.StateAsks).Get(seat.AsksByAskerIDKey(module, id))
|
||||
}}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package operatorchannelclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/approval"
|
||||
)
|
||||
|
||||
type readOf struct {
|
||||
state string
|
||||
vals map[string]json.RawMessage
|
||||
}
|
||||
|
||||
func (r readOf) Get(key string) (*stdio.StateEntry, error) {
|
||||
if v, ok := r.vals[r.state+"|"+key]; ok {
|
||||
return &stdio.StateEntry{Key: key, Value: v}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// The gate's channel over a process connection (design 47 G lead answers A-272, A-273): an ask and its cancel go out as
|
||||
// accepted kinds of the seat operator-channel with the ask's id as the message id, the router's record is the get of
|
||||
// `<module>.<id>` in the state asks of the seat's holder, and nothing else is reached: another subject, or a seat with no
|
||||
// holder, is refused.
|
||||
func TestTheGateChannelOverAConnectionAsksAndReadsOnlyThroughTheSeat(t *testing.T) {
|
||||
type sent struct{ seat, kind, id string }
|
||||
var out []sent
|
||||
accept := func(s, kind string, _ any, opts ...stdio.EmitOptions) error {
|
||||
out = append(out, sent{s, kind, opts[0].MessageID})
|
||||
return nil
|
||||
}
|
||||
raw, _ := json.Marshal(seat.AsksByAskerIDValue{Asker: "shell", State: "open", Opened: FormatTime(now)})
|
||||
vals := map[string]json.RawMessage{"messenger_holder.asks|shell.a1": raw, "other.asks|shell.a1": json.RawMessage(`{"state":"wrong"}`)}
|
||||
read := func(r string) asksRead { return readOf{state: r, vals: vals} }
|
||||
holder := func() (string, error) { return "messenger_holder", nil }
|
||||
st := &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}}
|
||||
g := gateChannelOver(accept, read, st, "n1.", holder)
|
||||
g.Now = func() time.Time { return now }
|
||||
|
||||
digest, _ := ActDigest(releaseHQ)
|
||||
a := approval.Ask{ID: "a1", About: digest, Headline: "Run wipe?", Explanation: "Wipes.", Expires: FormatTime(now.Add(time.Hour)),
|
||||
OnExpiry: "nothing runs", Options: []approval.Option{{ID: "approve", Label: "Approve", Does: "runs", Binds: digest}}}
|
||||
if err := g.Ask("shell", a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := g.Cancel("shell", "a1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 2 || out[0] != (sent{seat.Seat, seat.AcceptAsk, "a1"}) || out[1] != (sent{seat.Seat, seat.AcceptCancel, "cancel.a1"}) {
|
||||
t.Fatalf("sent %+v, want the ask and its cancel on the seat's accepts", out)
|
||||
}
|
||||
if rec, err := g.Record("shell", "a1"); err != nil || rec == nil || rec.State != "open" {
|
||||
t.Fatalf("record %+v %v, want the one in the holder's state asks", rec, err)
|
||||
}
|
||||
if rec, err := g.Record("shell", "zz"); err != nil || rec != nil {
|
||||
t.Fatalf("a record not there: %+v %v", rec, err)
|
||||
}
|
||||
if err := g.Publish("shell", "mesh.seat.operator-channel.accept.ask.other", nil, "x"); err == nil {
|
||||
t.Fatal("an accept subject naming another caller was sent")
|
||||
}
|
||||
for _, c := range []func() (string, error){func() (string, error) { return "", nil }, func() (string, error) { return "", errors.New("unread") }} {
|
||||
if _, err := gateChannelOver(accept, read, st, "n1.", c).Record("shell", "a1"); err == nil {
|
||||
t.Fatal("a record was read with no holder of the seat")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user