The keeper keeps each transition from its own goroutine, which reads the memory store's Fail field under the store's lock; tests assigned the exported field bare, so TestAnUnreadableStoreClearsNothing failed under -race whenever the goroutine appended in that window. The field is now set only through SetFail (and Told's likewise), and a test makes the race certain rather than rare. Test-only: the controller runs the bus store, never InMemory.
108 lines
4.2 KiB
Go
108 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
)
|
|
|
|
// The verbs of the condition store (novox/hq to-be 45 §2) as the console reaches them, and status led
|
|
// by what is open.
|
|
|
|
func TestTheConditionsVerbComposesEachShape(t *testing.T) {
|
|
for _, c := range []struct {
|
|
args map[string]any
|
|
want string
|
|
}{
|
|
{map[string]any{}, "conditions --json"},
|
|
{map[string]any{"severity": "urgent", "machine": "ace"}, "conditions --json --severity urgent --machine ace"},
|
|
{map[string]any{"key": "machine.ace.silent"}, "conditions show machine.ace.silent --json"},
|
|
{map[string]any{"history": "true", "days": "3", "key": "machine.ace.silent"},
|
|
"conditions history --json --days 3 --key machine.ace.silent"},
|
|
{map[string]any{"silence": "machine.ace.silent", "for": "2h", "why": "on the train"},
|
|
"conditions silence machine.ace.silent --for 2h --why on the train"},
|
|
{map[string]any{"run": "true"}, "doctor run --json"},
|
|
{map[string]any{}, "doctor --json"},
|
|
} {
|
|
verb := "conditions"
|
|
if strings.HasPrefix(c.want, "doctor") {
|
|
verb = "doctor"
|
|
}
|
|
argv, err := argvFor(verb, c.args)
|
|
if err != nil || strings.Join(argv, " ") != c.want {
|
|
t.Errorf("%s %v composed %q (%v), want %q", verb, c.args, strings.Join(argv, " "), err, c.want)
|
|
}
|
|
}
|
|
for _, c := range []struct {
|
|
verb string
|
|
args map[string]any
|
|
}{
|
|
{"conditions", map[string]any{"silence": "machine.ace.silent", "why": "x"}}, // no for
|
|
{"doctor", map[string]any{"run": "true", "signals": "true"}}, // two at once
|
|
{"conditions", map[string]any{"silence": "machine.ace.silent", "for": "1h", "why": "x", "days": "3"}}, // passed over
|
|
} {
|
|
if argv, err := argvFor(c.verb, c.args); err == nil {
|
|
t.Errorf("%s %v composed %v", c.verb, c.args, argv)
|
|
}
|
|
}
|
|
if repairingCommand([]string{"conditions", "silence", "k"}) != "conditions silence" {
|
|
t.Error("a silence is not a hand act")
|
|
}
|
|
}
|
|
|
|
// **A silence through the verb is recorded and bounded**; the condition stays open.
|
|
func TestASilenceThroughTheVerbHoldsAndTheConditionStaysOpen(t *testing.T) {
|
|
k, _ := withConditionsInMemory(t)
|
|
ctx := t.Context()
|
|
if _, err := k.Observe(ctx, conditions.Observation{Scope: conditions.ScopeMachine, ID: "ace", Kind: "silent",
|
|
Severity: conditions.Warning, Summary: "ace is silent", Source: "S1"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := conditionsCommand(ctx, []string{"silence", "machine.ace.silent", "--for", "2d"}); err == nil {
|
|
t.Fatal("silenced without saying why")
|
|
}
|
|
if err := conditionsCommand(ctx, []string{"silence", "machine.ace.silent", "--for", "8d", "--why", "x"}); err == nil {
|
|
t.Fatal("silenced for more than a week")
|
|
}
|
|
said := printed(t, func() error {
|
|
return conditionsCommand(ctx, []string{"silence", "machine.ace.silent", "--for", "2d", "--why", "on the train"})
|
|
})
|
|
if !strings.Contains(said, "is silenced until") {
|
|
t.Fatalf("%s", said)
|
|
}
|
|
c, found, _ := k.Get(ctx, "machine.ace.silent")
|
|
if !found || c.Silenced == nil || c.Silenced.Why != "on the train" {
|
|
t.Fatalf("%+v", c)
|
|
}
|
|
listed := printed(t, func() error { return conditionsCommand(ctx, nil) })
|
|
if !strings.Contains(listed, "machine.ace.silent") || !strings.Contains(listed, "silenced until") {
|
|
t.Fatalf("%s", listed)
|
|
}
|
|
}
|
|
|
|
// **Conditions that cannot be read are not none open**: status says so, and is not well.
|
|
func TestUnreadableConditionsAreNotAWellMesh(t *testing.T) {
|
|
open := aMesh(t)
|
|
_, store := withConditionsInMemory(t)
|
|
store.SetFail(errors.New("the bus is away"))
|
|
asked, err := theThreeQuestions(t.Context(), open)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if asked.well() || asked.conditionsUnread == "" {
|
|
t.Fatalf("well with its conditions unread: %+v", asked.conditionsUnread)
|
|
}
|
|
said := printed(t, func() error { return printStatus(asked) })
|
|
if !strings.HasPrefix(said, "the open conditions could NOT be read") || strings.Contains(said, "no open conditions") {
|
|
t.Fatalf("%s", said)
|
|
}
|
|
store.SetFail(nil)
|
|
asked, _ = theThreeQuestions(t.Context(), open)
|
|
said = printed(t, func() error { return printStatus(asked) })
|
|
if asked.well() && !strings.Contains(said, "no open conditions;") {
|
|
t.Fatalf("the all-well sentence does not say no conditions are open:\n%s", said)
|
|
}
|
|
}
|