Design 25 promised a dead-letter stream that did not exist: a message a consumer gave up on stayed only in its source, which drops it after a week, and its condition cleared when the advisories stopped (hq issue 330, ADR 0264).
93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
package link
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
// **A reader's own consumer gone is not a consumer lost**: every bucket watch and stream read-back
|
|
// makes and deletes one, many a minute, and the bus says so each time (found running the controller
|
|
// against a real bus: five a tick).
|
|
func TestOnlyTheMeshsOwnConsumersAreSaidLost(t *testing.T) {
|
|
deleted := func(stream, consumer string) bool {
|
|
_, ok := ReadAdvisory("$JS.EVENT.ADVISORY.CONSUMER.DELETED."+stream+"."+consumer,
|
|
[]byte(`{"type":"io.nats.jetstream.advisory.v1.consumer_action","stream":"`+stream+`","consumer":"`+consumer+`","action":"delete"}`))
|
|
return ok
|
|
}
|
|
for _, c := range [][2]string{{"EVENTS", "controller"}, {"CONTROL", "controller"}, {"NODES", "anchor"},
|
|
{"EVENTS", "anchor_shop"}, {"SEAT_NODE_BUILD_AGENT", "SEAT_NODE_BUILD_AGENT_worker"}} {
|
|
if !deleted(c[0], c[1]) {
|
|
t.Errorf("%s on %s deleted is not said", c[1], c[0])
|
|
}
|
|
}
|
|
for _, c := range [][2]string{{"KV_mesh-controller_conditions", "381UWW5Y"}, {"EVENTS", "E7vVYoe6"},
|
|
{"SEAT_NODE_BUILD_AGENT", "Rcnt6jla"}, {"ASSIGNMENTS", "x"}} {
|
|
if deleted(c[0], c[1]) {
|
|
t.Errorf("a reader's own consumer %s on %s is said lost", c[1], c[0])
|
|
}
|
|
}
|
|
a, ok := ReadAdvisory("$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.EVENTS.anchor_shop",
|
|
[]byte(`{"stream":"EVENTS","consumer":"anchor_shop","stream_seq":7,"deliveries":5}`))
|
|
if !ok || a.Kind != AdvisoryMaxDeliveries || a.ID != "EVENTS.anchor_shop" ||
|
|
a.Said != "how shop on anchor hears what it consumes handed message 7 over 5 times and gave up on it: "+
|
|
"what it asked for was not done, and the controller keeps it in DEAD_LETTERS until it is delivered "+
|
|
"again or dropped" {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
}
|
|
|
|
// **A question the bus refuses is answered as refused at once**, not after its timeout: against a
|
|
// server whose only user may not publish where it asks.
|
|
func TestNatsARefusedQuestionIsSaidAtOnce(t *testing.T) {
|
|
url := os.Getenv("MESH_TEST_NATS_REFUSING")
|
|
if url == "" {
|
|
t.Skip("MESH_TEST_NATS_REFUSING unset: a server whose user may not publish mesh.seat.>")
|
|
}
|
|
conn, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
started := time.Now()
|
|
_, err = AskSeatTool(t.Context(), conn, "node-intrusion-prevention", "banned", "anchor", map[string]any{}, 10*time.Second)
|
|
if err == nil || !strings.Contains(err.Error(), "the bus refused") {
|
|
t.Fatalf("answered %v", err)
|
|
}
|
|
if took := time.Since(started); took > 3*time.Second {
|
|
t.Fatalf("a refusal took %s to be known", took)
|
|
}
|
|
}
|
|
|
|
// The refusal reaches whoever waits on that subject, and only them.
|
|
func TestNatsARefusalReachesItsWaiter(t *testing.T) {
|
|
url := testbus.URL(t)
|
|
conn, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
mine, stop := refusalsOf(conn, "mesh.seat.s.tool.v.anchor")
|
|
defer stop()
|
|
other, stopOther := refusalsOf(conn, "mesh.seat.s.tool.v.laptop")
|
|
defer stopOther()
|
|
conn.ErrorHandler()(conn, nil, fmt.Errorf("%w: Permissions Violation for Publish to %q",
|
|
nats.ErrPermissionViolation, "mesh.seat.s.tool.v.anchor"))
|
|
select {
|
|
case <-mine:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("the waiter was not told")
|
|
}
|
|
select {
|
|
case <-other:
|
|
t.Fatal("another subject's waiter was told")
|
|
default:
|
|
}
|
|
}
|