package store import ( "context" "errors" "testing" "time" ) func seedTrace(t *testing.T, s *Store, utterance, intent string, now time.Time) int64 { t.Helper() id, err := s.WriteRoutingTrace(context.Background(), RoutingTrace{ Ts: now, Utterance: utterance, Intent: intent, Source: "tap:text", EncoderID: "e5-small", }) if err != nil { t.Fatal(err) } return id } // The label carries the pair, and it is what survives the transcript. func TestCorrectTurnPromotesTheLabel(t *testing.T) { s := newTestStore(t) ctx := context.Background() now := time.Date(2026, 8, 6, 12, 0, 0, 0, time.UTC) id := seedTrace(t, s, "поужинал", "query", now) if err := s.CorrectTurn(ctx, id, "fact", now); err != nil { t.Fatal(err) } labels, err := s.RoutingLabels(ctx, 10) if err != nil { t.Fatal(err) } if len(labels) != 1 { t.Fatalf("got %d labels, want 1", len(labels)) } l := labels[0] if l.Utterance != "поужинал" || l.Was != "query" || l.ShouldBe != "fact" { t.Errorf("label %+v: the pair is what names the confusion", l) } if l.EncoderID != "e5-small" { t.Errorf("encoder_id %q: a fitted distance means nothing without the body", l.EncoderID) } // The trace is stamped too, so the same turn cannot be corrected twice into // two labels without the surface knowing. traces, err := s.RecentRoutingTraces(ctx, 10) if err != nil { t.Fatal(err) } if traces[0].Correction != "fact" { t.Errorf("trace correction %q, want fact", traces[0].Correction) } } // "Wrong, and I am not telling you what it was" is the cheap half of the // gesture, and it must not cost more than the full answer. func TestCorrectTurnWithNoTarget(t *testing.T) { s := newTestStore(t) ctx := context.Background() now := time.Date(2026, 8, 6, 12, 0, 0, 0, time.UTC) id := seedTrace(t, s, "закрывай", "act", now) if err := s.CorrectTurn(ctx, id, " ", now); err != nil { t.Fatal(err) } labels, err := s.RoutingLabels(ctx, 10) if err != nil { t.Fatal(err) } if len(labels) != 1 || labels[0].ShouldBe != "" { t.Fatalf("labels %+v: an untargeted negative is still a label", labels) } traces, _ := s.RecentRoutingTraces(ctx, 10) if traces[0].Correction != "wrong" { t.Errorf("trace correction %q, want wrong", traces[0].Correction) } } // His second answer is the one he meant, so a re-correction replaces. func TestCorrectTurnTwiceReplaces(t *testing.T) { s := newTestStore(t) ctx := context.Background() now := time.Date(2026, 8, 6, 12, 0, 0, 0, time.UTC) first := seedTrace(t, s, "поужинал", "query", now) second := seedTrace(t, s, "поужинал", "chat", now.Add(time.Minute)) if err := s.CorrectTurn(ctx, first, "note", now); err != nil { t.Fatal(err) } if err := s.CorrectTurn(ctx, second, "fact", now.Add(time.Minute)); err != nil { t.Fatal(err) } labels, err := s.RoutingLabels(ctx, 10) if err != nil { t.Fatal(err) } if len(labels) != 1 { t.Fatalf("got %d labels for one sentence, want 1", len(labels)) } if labels[0].ShouldBe != "fact" || labels[0].Was != "chat" { t.Errorf("label %+v, want the second correction", labels[0]) } } // A turn past the 14-day bound cannot be corrected, and the surface has to be // able to say that rather than report a broken database. func TestCorrectTurnUnknownTrace(t *testing.T) { s := newTestStore(t) err := s.CorrectTurn(context.Background(), 999, "fact", time.Now()) if !errors.Is(err, ErrNoSuchTrace) { t.Fatalf("err %v, want ErrNoSuchTrace", err) } labels, _ := s.RoutingLabels(context.Background(), 10) if len(labels) != 0 { t.Errorf("wrote %d labels for a trace that does not exist", len(labels)) } }