diff --git a/cmd/mavend/ecosystem_acts.go b/cmd/mavend/ecosystem_acts.go index 94f41c2..3f6f851 100644 --- a/cmd/mavend/ecosystem_acts.go +++ b/cmd/mavend/ecosystem_acts.go @@ -22,8 +22,9 @@ import ( // already knows which one it was talking to — it records the same name in the // trace (Vikunja #521). const ( - serviceNexus = "Nexus" - serviceHexis = "Hexis" + serviceNexus = "Nexus" + servicePraxis = "Praxis" + serviceHexis = "Hexis" ) // serviceVars — the one-key map the eco_down and eco_denied lines take. @@ -141,7 +142,13 @@ type praxisItemAction struct { verbs []string ask string // reply when no item id was given op string // trace + log name of the operation - failure string // reply when the Praxis call errors + // failure is the first half of the reply when the Praxis call errors: which + // operation did not happen. ecosystemGap supplies the second half, which + // names Praxis and splits a refused token from an outage — those two used to + // produce the identical sentence and neither said "Praxis" (Vikunja #588). + // The verb is kept alongside the service name because the trace is the only + // other place it exists, and he is not reading the trace. + failure string success string call func(ctx context.Context, px *praxisClient, id string) error } @@ -158,7 +165,7 @@ func (a praxisItemAction) handle(ctx context.Context, h *reactiveHandler, px *pr log.Printf("ecosystem: praxis %s %s: %v", a.op, id, err) h.recordEcosystemTrace(ctx, "praxis", a.op, traceStatusForError(err), started, mergeFields(traceErrorFields(err), map[string]any{"item_id": id})) - return a.failure + return a.failure + " " + ecosystemGap(servicePraxis, err) } h.recordPraxisTrace(ctx, a.op, started, map[string]any{"item_id": id}) return a.success diff --git a/cmd/mavend/praxis_gap_test.go b/cmd/mavend/praxis_gap_test.go new file mode 100644 index 0000000..51a38bd --- /dev/null +++ b/cmd/mavend/praxis_gap_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "strings" + "testing" + + "github.com/kami/maven/internal/phraser" +) + +// A Praxis lifecycle failure used to return a hardcoded constant that named the +// verb and never the service, so an outage, a refused token and a contract +// mismatch all produced the identical sentence (Vikunja #588). The helpers and +// the Hexis half of the same defect are in ecosystem_gap_test.go. + +func TestPraxisLifecycle401NamesPraxis(t *testing.T) { + ctx := context.Background() + praxis := newFakePraxis(t, fixturePraxisAttentionItems()) + h := newPraxisTestHandler(t, praxis) + + praxis.SetFault(401) + reply := h.handlePraxisAct(ctx, praxisItemDec("resolve_item", "item_1")) + if !strings.Contains(reply, servicePraxis) { + t.Fatalf("praxis failure does not name Praxis: %q", reply) + } + if !strings.Contains(reply, phraser.A(phraser.EcoDenied, serviceVars(servicePraxis))) { + t.Fatalf("401 from praxis: got %q, want the denied line", reply) + } + // The verb that did not happen is still said: the trace is the only other + // place it exists and he is not reading the trace. + if !strings.Contains(reply, "не получилось отметить сделанным.") { + t.Errorf("reply dropped the operation that failed: %q", reply) + } +} + +// TestPraxisLifecycleOutageDiffersFrom401 — the identity that was the bug. +func TestPraxisLifecycleOutageDiffersFrom401(t *testing.T) { + ctx := context.Background() + praxis := newFakePraxis(t, fixturePraxisAttentionItems()) + h := newPraxisTestHandler(t, praxis) + + praxis.SetFault(401) + refused := h.handlePraxisAct(ctx, praxisItemDec("acknowledge_item", "item_1")) + + h.ecosystem = &ecosystemWiring{praxis: newPraxisClient(unreachableURL)} + outage := h.handlePraxisAct(ctx, praxisItemDec("acknowledge_item", "item_1")) + + if refused == outage { + t.Fatalf("a refused token and an outage still say the same thing: %q", refused) + } + if !strings.Contains(outage, phraser.A(phraser.EcoDown, serviceVars(servicePraxis))) { + t.Fatalf("praxis outage: got %q, want the outage line naming Praxis", outage) + } +}