Let a bundle write a key with its own time to live and hear its expiry, so pulses, settles and bounds expire on the bus through the runtime (issue 1952)

This commit is contained in:
2026-10-29 22:14:42 +01:00
parent 20cac82ab7
commit bc6c8a6d24
3 changed files with 82 additions and 1 deletions
+43
View File
@@ -3,6 +3,8 @@ package stdio
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"testing"
@@ -33,6 +35,12 @@ func TestABundleKeepsAndWatchesStateThroughTheRuntime(t *testing.T) {
return State("servers").Put(a["key"].(string), a["value"])
}},
{Name: "recall", Run: func(a map[string]any) (any, error) { return State("servers").Get(a["key"].(string)) }},
{Name: "pulse", Run: func(a map[string]any) (any, error) {
if a["create"] == true {
return State("servers").CreateTTL(a["key"].(string), 1, 90*time.Second)
}
return State("servers").PutTTL(a["key"].(string), 1, 90*time.Second)
}},
}, inR, outW)
}()
lines := bufio.NewScanner(outR)
@@ -87,6 +95,15 @@ func TestABundleKeepsAndWatchesStateThroughTheRuntime(t *testing.T) {
if err := <-watched; err != nil {
t.Fatal(err)
}
// 1952 (the runtime's per-key time to live, mesh-tools 1583fa85): an expiry is handed over as its own operation.
send(map[string]any{"jsonrpc": "2.0", "id": 49, "method": "mesh/state", "params": map[string]any{"watch": watch["id"],
"change": map[string]any{"state": "servers", "key": "all.gone", "op": OpExpire}}})
if r := next(); r["id"] != float64(49) || r["error"] != nil {
t.Fatalf("an expiry was answered %v", r)
}
if s := <-seen; s != "expire all.gone " {
t.Fatalf("handled %q", s)
}
// A change for a watch this bundle never asked for is refused.
send(map[string]any{"jsonrpc": "2.0", "id": 51, "method": "mesh/state", "params": map[string]any{"watch": "bundle-999",
"change": map[string]any{"key": "x", "op": "put"}}})
@@ -105,6 +122,32 @@ func TestABundleKeepsAndWatchesStateThroughTheRuntime(t *testing.T) {
if r := next(); r["id"] != float64(2) || r["result"].(map[string]any)["content"].([]any)[0].(map[string]any)["text"] != "9" {
t.Fatalf("the put answered %v", r)
}
// PutTTL and CreateTTL ask the runtime's put and create with the key's time to live in whole seconds; a create the
// runtime refuses as changed (-32010) is ErrChanged.
for i, create := range []bool{false, true} {
send(map[string]any{"jsonrpc": "2.0", "id": 20 + i, "method": "tools/call", "params": map[string]any{"name": "pulse",
"arguments": map[string]any{"key": "pulse.S1", "create": create}}})
asked := next()
p := asked["params"].(map[string]any)
want := map[bool]string{false: "mesh/state.put", true: "mesh/state.create"}[create]
if asked["method"] != want || p["ttl_seconds"] != float64(90) || p["key"] != "pulse.S1" {
t.Fatalf("asked %v", asked)
}
if create {
send(map[string]any{"jsonrpc": "2.0", "id": asked["id"], "error": map[string]any{"code": CodeChanged, "message": "exists"}})
if r := next(); r["id"] != float64(21) || !strings.Contains(fmt.Sprint(r), "exists") {
t.Fatalf("a refused create answered %v", r)
}
continue
}
send(map[string]any{"jsonrpc": "2.0", "id": asked["id"], "result": map[string]any{"revision": 4}})
if r := next(); r["id"] != float64(20) {
t.Fatalf("the put answered %v", r)
}
}
if !errors.Is(&refused{code: CodeChanged, message: "x"}, ErrChanged) {
t.Fatal("a refusal coded -32010 is not ErrChanged")
}
// Get of a key with no value is nil.
send(map[string]any{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": map[string]any{"name": "recall",
"arguments": map[string]any{"key": "nothing"}}})