/trace grows a turn-decisions table under the rule trace (V-564)
Both tables answer the same question, who won and who lost, one about nudges and the other about utterances, so they share a page rather than splitting the nav. A turn is one collapsible row; never_asked is coloured like a block, because it usually is one. A read failure is logged and the rule trace above it still renders: a daemon too old to know the method is the ordinary case during a rolling deploy.
This commit is contained in:
@@ -65,6 +65,7 @@ type fakeCore struct {
|
||||
// for handleTrace tests
|
||||
tickTrace ipc.TickTrace
|
||||
traceErr error
|
||||
turns []ipc.TurnDecision
|
||||
|
||||
// for handleChatAPI tests
|
||||
chatText string
|
||||
@@ -173,6 +174,10 @@ func (f *fakeCore) RevertFact(_ context.Context, key string) (int64, error) {
|
||||
return f.revertNewID, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) TurnDecisions(_ context.Context, _ int) ([]ipc.TurnDecision, error) {
|
||||
return f.turns, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) TickTrace(_ context.Context) (ipc.TickTrace, error) {
|
||||
if f.traceErr != nil {
|
||||
return ipc.TickTrace{}, f.traceErr
|
||||
@@ -825,6 +830,41 @@ func TestHandleTrace(t *testing.T) {
|
||||
t.Error("rendered 'nothing fired' but a winner was set")
|
||||
}
|
||||
})
|
||||
|
||||
// The turn arbitration shares this page (V-564). A reader must see the
|
||||
// winner, a loser and the claimants that were never asked, because the last
|
||||
// of those is what the hardcoded ordering hides.
|
||||
t.Run("renders the turn decision record", func(t *testing.T) {
|
||||
core := &fakeCore{turns: []ipc.TurnDecision{{
|
||||
Ts: time.Date(2025, 6, 1, 12, 0, 0, 0, time.UTC),
|
||||
Utterance: "какая погода в риме",
|
||||
Winner: "query:weather",
|
||||
Claims: []ipc.TurnClaim{
|
||||
{Stage: "query", Claimant: "weather", Intent: "query", Outcome: "won"},
|
||||
{Stage: "query", Claimant: "calendar", Outcome: "declined", Reason: "no answer"},
|
||||
{Stage: "query", Claimant: "kiwix", Outcome: "never_asked"},
|
||||
},
|
||||
}}}
|
||||
rr := httptest.NewRecorder()
|
||||
handleTrace(rr, httptest.NewRequest(http.MethodGet, "/trace", nil), core)
|
||||
body := rr.Body.String()
|
||||
for _, want := range []string{"какая погода в риме", "query:weather", "calendar", "kiwix", "never_asked"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("rendered page is missing %q", want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no turns renders the empty note, not an error", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleTrace(rr, httptest.NewRequest(http.MethodGet, "/trace", nil), &fakeCore{})
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rr.Code)
|
||||
}
|
||||
if !strings.Contains(rr.Body.String(), "no turn has run") {
|
||||
t.Error("empty ring did not render its note")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- handleRevert ---
|
||||
|
||||
Reference in New Issue
Block a user