126 lines
4.9 KiB
Go
126 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// An empty attention list used to be answered "ничего не требует внимания"
|
|
// unconditionally, which is an all-clear Maven had no way to know was true
|
|
// (ECOSYSTEM-SPEC §2.6, Vikunja #540).
|
|
func TestAttentionEmptyWithHealthySourcesIsAllClear(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t, `[]`, `[{"source_id":"src_ntfy","health":"ok"}]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention"), routeCandidate("list_attention"))
|
|
if !strings.Contains(reply, "ничего не требует внимания") {
|
|
t.Fatalf("healthy and quiet should be an all-clear, got %q", reply)
|
|
}
|
|
}
|
|
|
|
func TestAttentionEmptyWithAFailedSourceHedges(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t, `[]`, `[
|
|
{"source_id":"src_ntfy","health":"ok"},
|
|
{"source_id":"src_llamacpp","health":"failed"},
|
|
{"source_id":"src_imap","health":"stale"}
|
|
]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention"), routeCandidate("list_attention"))
|
|
if strings.Contains(reply, "ничего не требует внимания") {
|
|
t.Fatalf("a failed source must not read as all-clear, got %q", reply)
|
|
}
|
|
for _, want := range []string{"src_llamacpp", "src_imap"} {
|
|
if !strings.Contains(reply, want) {
|
|
t.Errorf("reply names no %s: %q", want, reply)
|
|
}
|
|
}
|
|
if strings.Contains(reply, "src_ntfy") {
|
|
t.Errorf("the healthy source is named as a problem: %q", reply)
|
|
}
|
|
}
|
|
|
|
// A Praxis that polls nothing knows nothing, which is the state the box is in.
|
|
func TestAttentionEmptyWithNoSourcesHedges(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t, `[]`, `[]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention"), routeCandidate("list_attention"))
|
|
if strings.Contains(reply, "ничего не требует внимания") {
|
|
t.Fatalf("a Praxis with no sources must not answer all-clear, got %q", reply)
|
|
}
|
|
if !strings.Contains(reply, "источник") {
|
|
t.Errorf("reply does not say why she cannot tell: %q", reply)
|
|
}
|
|
}
|
|
|
|
// The spec's own mechanism, which the deployed Praxis does not send yet: the
|
|
// envelope's degraded array is believed without a second call.
|
|
func TestAttentionDegradedEnvelopeIsReadWithoutASourcesCall(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t,
|
|
`{"items":[],"degraded":["src_metrics"]}`,
|
|
`[{"source_id":"src_ntfy","health":"ok"}]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention"), routeCandidate("list_attention"))
|
|
if !strings.Contains(reply, "src_metrics") {
|
|
t.Fatalf("the envelope's degraded source is not named: %q", reply)
|
|
}
|
|
for _, r := range praxis.Requests() {
|
|
if r.Path == "/api/v1/sources" {
|
|
t.Error("sources was read even though the response carried degraded")
|
|
}
|
|
}
|
|
}
|
|
|
|
// A sources endpoint that errors is not evidence of a fault: the attention call
|
|
// itself succeeded, and hedging on it would make her permanently uncertain.
|
|
func TestAttentionKeepsAllClearWhenSourcesCannotBeRead(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t, `[]`, `[]`)
|
|
praxis.SetRouteFault("/api/v1/sources", 500)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention"), routeCandidate("list_attention"))
|
|
if !strings.Contains(reply, "ничего не требует внимания") {
|
|
t.Fatalf("an unreadable sources list should leave the answer alone, got %q", reply)
|
|
}
|
|
}
|
|
|
|
// Both response shapes decode, because the spec says one and the box sends the
|
|
// other.
|
|
func TestPraxisAttentionDecodesBothShapes(t *testing.T) {
|
|
var bare praxisAttention
|
|
if err := json.Unmarshal([]byte(`[{"id":"item_1"}]`), &bare); err != nil {
|
|
t.Fatalf("bare array: %v", err)
|
|
}
|
|
if len(bare.Items) != 1 || len(bare.Degraded) != 0 {
|
|
t.Errorf("bare array decoded as %+v", bare)
|
|
}
|
|
|
|
var env praxisAttention
|
|
if err := json.Unmarshal([]byte(`{"items":[{"id":"item_2"}],"degraded":["src_a"]}`), &env); err != nil {
|
|
t.Fatalf("envelope: %v", err)
|
|
}
|
|
if len(env.Items) != 1 || len(env.Degraded) != 1 || env.Degraded[0] != "src_a" {
|
|
t.Errorf("envelope decoded as %+v", env)
|
|
}
|
|
}
|
|
|
|
// A source that reports no health at all counts as healthy. A Praxis that never
|
|
// fills the field would otherwise make every quiet turn a hedge.
|
|
func TestUnhealthySourcesTreatsAnAbsentHealthFieldAsHealthy(t *testing.T) {
|
|
praxis := newFakePraxisWithSources(t, `[]`, `[{"source_id":"src_a"},{"id":"src_b","health":"stale"}]`)
|
|
bad, total, err := newPraxisClient(praxis.URL).UnhealthySources(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("UnhealthySources: %v", err)
|
|
}
|
|
if total != 2 {
|
|
t.Errorf("total = %d, want 2", total)
|
|
}
|
|
if len(bad) != 1 || bad[0] != "src_b" {
|
|
t.Errorf("bad = %v, want [src_b]", bad)
|
|
}
|
|
}
|