130 lines
4.0 KiB
Go
130 lines
4.0 KiB
Go
//go:build integration
|
|
|
|
package firstview
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/views"
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
|
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
|
|
)
|
|
|
|
// issue 1966 TLS-5 [i] (ADR 0615 §2, §4, §6): against the real bus, with the owners down and then up, the runner's own
|
|
// consumer carries the manifest's four `consumes` to the owners' views events: the first views are asked until answered,
|
|
// the unanswered ones are findings that the answer clears, and each owner's next event reaches its picture.
|
|
func TestTheRunnersConsumerCarriesTheOwnersViewsFamiliesWhenTheOwnersAreDownThenUp(t *testing.T) {
|
|
mesh := mt.New(t)
|
|
url := mt.URL(t)
|
|
|
|
// The consumer is the mesh's, made from the manifest's `consumes` as the mesh makes it: one filter per family.
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var manifest struct {
|
|
Consumes []string `json:"consumes"`
|
|
}
|
|
if err := json.Unmarshal(raw, &manifest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var filters []string
|
|
for _, c := range manifest.Consumes {
|
|
emitter, event, _ := strings.Cut(c, ".views.")
|
|
filters = append(filters, "mesh.mod."+emitter+".event.views."+event)
|
|
}
|
|
if len(filters) != len(Specs) {
|
|
t.Fatalf("the manifest consumes %d families, the runner holds %d first views", len(filters), len(Specs))
|
|
}
|
|
mesh.Consumer(t, "anchor", "node-tools", filters, 2*time.Second)
|
|
|
|
conn, err := bus.Connect(bus.Credential{URL: url, Module: "node-tools", Node: "anchor"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conn.Logf = func(string, ...any) {}
|
|
t.Cleanup(conn.Close)
|
|
|
|
var mu sync.Mutex
|
|
findings := map[string]bool{}
|
|
deps := DepsOf(conn)
|
|
deps.Bound, deps.Wait = 100*time.Millisecond, 20*time.Millisecond
|
|
deps.PutFinding = func(key string, _ json.RawMessage) error { mu.Lock(); findings[key] = true; mu.Unlock(); return nil }
|
|
deps.DeleteFinding = func(key string) error { mu.Lock(); delete(findings, key); mu.Unlock(); return nil }
|
|
v, stop := StartWith(context.Background(), deps, func(string, ...any) {})
|
|
t.Cleanup(stop)
|
|
|
|
// The owners are down: no tool answers, nothing is claimed, and past the bound each view is a finding.
|
|
if _, err := v.State(Assigner + ".placement"); err == nil {
|
|
t.Fatal("a state its owner has not answered is claimed")
|
|
}
|
|
mt.Until(t, func() error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if len(findings) != len(Specs) {
|
|
return errFindings(len(findings))
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// The owners come up and answer their first views.
|
|
nc, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(nc.Close)
|
|
for _, s := range Specs {
|
|
s := s
|
|
if _, err := nc.Subscribe("mesh.mod."+s.Owner+".tool."+s.Tool, func(m *nats.Msg) {
|
|
answer, _ := json.Marshal(views.Answer{V: 1, Owner: s.Owner, State: s.State, Revision: 4, Created: time.Unix(1000, 0).UTC(),
|
|
Values: map[string]json.RawMessage{"first": json.RawMessage(`{"n":1}`)}})
|
|
body, _ := json.Marshal(map[string]json.RawMessage{"result": answer})
|
|
_ = m.Respond(body)
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
_ = nc.Flush()
|
|
for _, s := range Specs {
|
|
name := s.Name()
|
|
mt.Until(t, func() error { _, err := v.Entry(name, "first"); return err })
|
|
}
|
|
mt.Until(t, func() error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if len(findings) != 0 {
|
|
return errFindings(len(findings))
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Each owner's next event arrives on the runner's consumer: all four families are bound.
|
|
for _, s := range Specs {
|
|
ev, _ := json.Marshal(views.Event{V: 1, Owner: s.Owner, State: s.State, Key: "next", Revision: 5, Created: time.Unix(1000, 0).UTC(),
|
|
Value: json.RawMessage(`{"n":2}`)})
|
|
mesh.Emit(t, "mesh.mod."+s.Owner+".event.views."+s.State, json.RawMessage(ev))
|
|
}
|
|
for _, s := range Specs {
|
|
name := s.Name()
|
|
mt.Until(t, func() error {
|
|
got, err := v.Entry(name, "next")
|
|
if err != nil || string(got) != `{"n":2}` {
|
|
return errFindings(-1)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
type errFindings int
|
|
|
|
func (e errFindings) Error() string { return "not yet" }
|