The three mesh-controller condition-event consumes, the invoke of mesh-controller.conditions, the follow switch (takeover.go) and its read of mesh-conditions.takeover are gone: the messenger consumes mesh-conditions' events and reads its open conditions from seat mesh-conditions.conditions at its start. An ask of the keeper is shown on a condition's message like the controller's was. The manifest test now refuses any controller entry; the event test refuses the controller's events; the words fixture's action verb names the keeper's. No test added. (issue 774, 774/S12) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
100 lines
3.9 KiB
Go
100 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The contract with the condition keeper's condition events (to-be 45 §2), as condition.go states it.
|
|
|
|
func TestTheEventsAreTheKeepersSeat(t *testing.T) {
|
|
for key, want := range map[string]string{
|
|
"mesh-conditions.condition-raised": EventRaised,
|
|
"mesh-conditions.condition-changed": EventChanged,
|
|
"mesh-conditions.condition-cleared": EventCleared,
|
|
} {
|
|
if got, ok := eventOf(key); !ok || got != want {
|
|
t.Errorf("%s: %q %v", key, got, ok)
|
|
}
|
|
}
|
|
for _, key := range []string{"mesh-controller.condition-raised", "mesh-conditions.applied", "gitea.condition-raised", "condition-raised"} {
|
|
if _, ok := eventOf(key); ok {
|
|
t.Errorf("%s read as a condition event", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAConditionAsTheStoreHoldsIt(t *testing.T) {
|
|
body := `{
|
|
"key": "machine.ace.silent", "kind": "silent",
|
|
"subject": {"scope": "machine", "id": "ace", "machine": "ace"},
|
|
"severity": "urgent", "summary": "the home server has not been heard for 15 min",
|
|
"evidence": [{"at": "2026-10-06T12:00:00Z", "what": "last heartbeat"}],
|
|
"source": "S1", "raised": "2026-10-06T12:15:00Z", "last-observed": "2026-10-06T12:16:00Z",
|
|
"observations": 3, "tried": [], "resolver": "self",
|
|
"silenced": {"until": "2026-10-06T14:00:00Z", "by": "operator", "why": "moving it"},
|
|
"epoch": 57
|
|
}`
|
|
c, err := DecodeCondition(EventRaised, []byte(body))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Key != "machine.ace.silent" || c.Kind != "silent" || c.Scope != "machine" || c.ID != "ace" ||
|
|
c.Severity != Urgent || c.Source != "S1" || c.Observations != 3 || c.Resolver != "self" {
|
|
t.Fatalf("%+v", c)
|
|
}
|
|
if !c.Raised.Equal(time.Date(2026, 10, 6, 12, 15, 0, 0, time.UTC)) || c.LastObserved.IsZero() {
|
|
t.Fatalf("times: %+v", c)
|
|
}
|
|
if !c.SilencedAt(time.Date(2026, 10, 6, 13, 0, 0, 0, time.UTC)) || c.SilencedAt(time.Date(2026, 10, 6, 15, 0, 0, 0, time.UTC)) {
|
|
t.Fatalf("silenced: %v", c.SilencedTill)
|
|
}
|
|
if c.SubjectWords() != "machine ace" {
|
|
t.Fatalf("subject words: %q", c.SubjectWords())
|
|
}
|
|
}
|
|
|
|
func TestOtherSpellingsAndAKeyMadeFromItsParts(t *testing.T) {
|
|
c, err := DecodeCondition(EventChanged, []byte(`{"kind":"stalled","subject":{"scope":"plan","id":"41"},
|
|
"severity":"warning","summary":"plan 41 waits","last_observed":"2026-10-06T12:00:00Z","silenced":null,
|
|
"source":{"row":"S3"}}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Key != "plan.41.stalled" || c.Source != "S3" || c.LastObserved.IsZero() || !c.SilencedTill.IsZero() {
|
|
t.Fatalf("%+v", c)
|
|
}
|
|
c, err = DecodeCondition(EventRaised, []byte(`{"key":"provider.keycloak.ace.gitea.failing","subject":"keycloak",
|
|
"severity":"warning","summary":"the identity provider fails gitea"}`))
|
|
if err != nil || c.Scope != "provider" || c.Kind != "failing" || c.ID != "keycloak" {
|
|
t.Fatalf("%+v %v", c, err)
|
|
}
|
|
}
|
|
|
|
func TestAClearedEventNeedsOnlyItsKey(t *testing.T) {
|
|
c, err := DecodeCondition(EventCleared, []byte(`{"key":"machine.ace.silent","cleared":"2026-10-06T12:30:00Z"}`))
|
|
if err != nil || c.Key != "machine.ace.silent" || c.Cleared.IsZero() {
|
|
t.Fatalf("%+v %v", c, err)
|
|
}
|
|
}
|
|
|
|
func TestWhatCannotBeReadIsRefusedByName(t *testing.T) {
|
|
for body, says := range map[string]string{
|
|
`not json`: "not a JSON object",
|
|
`null`: "null",
|
|
`{"severity":"urgent","summary":"x"}`: "no key",
|
|
`{"key":"a.b.c","summary":"x"}`: "no severity",
|
|
`{"key":"a.b.c","severity":"critical","summary":"x"}`: "neither urgent nor warning",
|
|
`{"key":"a.b.c","severity":"urgent"}`: "no summary",
|
|
`{"key":"a.b.c","severity":"urgent","summary":"x","raised":"yesterday"}`: "not an RFC 3339 time",
|
|
`{"key":7}`: "key is not a string",
|
|
`{"key":"a.b.c","severity":"urgent","summary":"x","silenced":{"until":"soon"}}`: "silenced.until",
|
|
} {
|
|
_, err := DecodeCondition(EventRaised, []byte(body))
|
|
if err == nil || !strings.Contains(err.Error(), says) {
|
|
t.Errorf("%s: %v, want %q", body, err, says)
|
|
}
|
|
}
|
|
}
|