package conditions import ( "context" "encoding/json" "errors" "testing" "time" ) // **A look that sees a condition as it was writes nothing to the bucket** (novox/hq issue 420, ADR 0289 // §1 and §4): on 2026-10-10 the bucket took 131 647 writes in four days for twelve open conditions, // because every look rewrote the count, the last look and the evidence. The bucket is written when the // condition is raised and when what it says changes; the looks are kept beside it, and `conditions` and // the events still carry them. func TestALookThatChangesNothingWritesNothingToTheBucket(t *testing.T) { k, store, told, c := keeper(t) ctx := t.Context() for range 5 { if _, err := k.Observe(ctx, silent("ace")); err != nil { t.Fatal(err) } c.pass(30 * time.Second) } if store.Writes() != 1 { t.Fatalf("five looks at one unchanged condition wrote the bucket %d times, want 1 (its raising)", store.Writes()) } got, open, err := k.Get(ctx, "machine.ace.silent") if err != nil || !open { t.Fatalf("open %v: %v", open, err) } if got.Observations != 5 || len(got.Evidence) != 5 || !got.LastObserved.Equal(c.now().Add(-30*time.Second)) || got.LooksUnread != "" { t.Fatalf("the verb reads %d observation(s), %d evidence line(s), last %s (%q)", got.Observations, len(got.Evidence), got.LastObserved, got.LooksUnread) } list, err := k.Open(ctx) if err != nil || len(list) != 1 || list[0].Observations != 5 { t.Fatalf("the list reads %+v: %v", list, err) } // The bucket's value holds what the condition says, and none of its looks. e, _, _ := store.Get(ctx, "machine.ace.silent") var fields map[string]json.RawMessage if err := json.Unmarshal(e.Value, &fields); err != nil { t.Fatal(err) } for _, f := range lookFields { if _, held := fields[f]; held { t.Errorf("the bucket's value holds %q, which changes on every look", f) } } for _, f := range []string{"key", "kind", "severity", "resolver", "silenced", "raised", "headline", "needs", "epoch"} { if _, held := fields[f]; !held { t.Errorf("the bucket's value lacks %q", f) } } // A change of what it says is written, once, and its event carries the looks. worse := silent("ace") worse.Severity = Urgent if _, err := k.Observe(ctx, worse); err != nil { t.Fatal(err) } if _, err := k.Observe(ctx, worse); err != nil { t.Fatal(err) } if store.Writes() != 2 { t.Fatalf("a change of severity and one more look wrote the bucket %d times in all, want 2", store.Writes()) } said := settled(t, told, 2) if said[1].Change != ChangeSeverity || said[1].Observations != 6 || len(said[1].Evidence) != 6 { t.Fatalf("the severity's event says %s with %d observation(s) and %d evidence line(s)", said[1].Change, said[1].Observations, len(said[1].Evidence)) } // Its clearing forgets its looks, and its event carries them as they were. if cleared, err := k.Clear(ctx, "machine.ace.silent", "heard"); err != nil || !cleared { t.Fatalf("cleared %v: %v", cleared, err) } if _, kept, _ := store.Looked(ctx, "machine.ace.silent"); kept { t.Fatal("a cleared condition's looks are still kept") } said = settled(t, told, 3) if said[2].Change != ChangeCleared || said[2].Observations != 7 { t.Fatalf("the clearing says %s with %d observation(s)", said[2].Change, said[2].Observations) } } // **A condition written before its looks moved out keeps its count**: its value says them, the first // look counts on from there, and writes the value once without them. func TestAConditionWrittenWithItsLooksMovesThemOutOnce(t *testing.T) { k, store, _, c := keeper(t) ctx := t.Context() old := Condition{Key: "machine.ace.silent", Kind: "silent", Subject: Subject{Scope: ScopeMachine, ID: "ace", Machine: "ace"}, Severity: Warning, Summary: "ace has not been heard from", Source: "S1", Raised: c.now().Add(-time.Hour), LastObserved: c.now().Add(-time.Minute), Observations: 7003, Count: 1, Resolver: ResolverSelf, Evidence: []Evidence{{At: c.now().Add(-time.Minute), Said: "ace has not been heard from"}}} body, _ := json.Marshal(old) if err := store.Create(ctx, old.Key, body); err != nil { t.Fatal(err) } if got, _, _ := k.Get(ctx, old.Key); got.Observations != 7003 || got.LooksUnread != "" { t.Fatalf("before its first look it reads %d observation(s) (%q)", got.Observations, got.LooksUnread) } for range 3 { if _, err := k.Observe(ctx, silent("ace")); err != nil { t.Fatal(err) } c.pass(30 * time.Second) } if store.Writes() != 2 { t.Fatalf("three looks wrote the bucket %d times after its creation, want 1", store.Writes()-1) } got, _, _ := k.Get(ctx, old.Key) if got.Observations != 7006 || len(got.Evidence) != 4 { t.Fatalf("it counts %d observation(s) and %d evidence line(s), want 7006 and 4", got.Observations, len(got.Evidence)) } } // failingLooks is a controller's store that is away. type failingLooks struct{} func (failingLooks) KeepLooks(context.Context, string, Looks) error { return errors.New("store away") } func (failingLooks) Looked(context.Context, string) (Looks, bool, error) { return Looks{}, false, errors.New("store away") } func (failingLooks) AllLooks(context.Context) (map[string]Looks, error) { return nil, errors.New("store away") } func (failingLooks) ForgetLooks(context.Context, string) error { return errors.New("store away") } // **A condition is raised while the controller's store is away**, and says its looks are unread rather // than none: a store gone is exactly what a condition must still be raised for. func TestLooksThatCannotBeKeptAreSaidNotCountedAsNone(t *testing.T) { store, told := NewInMemory(), &Told{} var said []string k := NewKeeper(t.Context(), Options{Store: store, History: store, Teller: told, Looks: failingLooks{}, Say: func(f string, a ...any) { said = append(said, f) }}) t.Cleanup(func() { k.Close(context.Background()) }) if _, err := k.Observe(t.Context(), silent("ace")); err != nil { t.Fatalf("a condition was not raised for a store away: %v", err) } if _, err := k.Observe(t.Context(), silent("ace")); err != nil { t.Fatal(err) } got, open, err := k.Get(t.Context(), "machine.ace.silent") if err != nil || !open || got.LooksUnread == "" { t.Fatalf("open %v, looks unread %q: %v", open, got.LooksUnread, err) } list, err := k.Open(t.Context()) if err != nil || len(list) != 1 || list[0].LooksUnread == "" { t.Fatalf("the list reads %+v: %v", list, err) } if e := settled(t, told, 1); e[0].LooksUnread == "" { t.Fatal("the raising says nothing of its looks being unkept") } if len(said) == 0 { t.Fatal("looks that could not be kept were not said") } }