hsa-app/internal/storage/classifications_test.go
Jean-Michel Tremblay c715c8e0c0
All checks were successful
Build and Test / build-and-test (push) Successful in 37s
AI classifier correction notes + misread review (AI tab)
Add a global, temporal ai_notes list appended to the classifier prompt
(seeded once from no-PII defaults, documented in README), managed inline
on a new AI tab with a read-only view of the assembled prompt. Every
AI-run upload records the browser-round-tripped suggestion blob + model;
misreads are derived (final field != AI guess) and reviewed one by one
(image + per-field guess-vs-entered + notes-since), attributing which
note fixed each or closing unresolved. Update SPEC (new section 10),
DESIGN item 15, README, and changelog (0.0.3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:04:30 -04:00

69 lines
1.8 KiB
Go

package storage
import (
"path/filepath"
"testing"
"time"
)
func classifyTestStore(t *testing.T) *Store {
t.Helper()
s, err := Open(filepath.Join(t.TempDir(), "c.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { s.Close() })
return s
}
func TestClassifications_InsertListReview(t *testing.T) {
s := classifyTestStore(t)
insertReceipt(t, s, "r1")
if err := s.InsertClassification("r1", "haiku", `{"model":"haiku"}`, time.Now().UTC()); err != nil {
t.Fatal(err)
}
rows, err := s.ListUnreviewedClassifications()
if err != nil {
t.Fatal(err)
}
if len(rows) != 1 || rows[0].ReceiptID != "r1" || rows[0].Model != "haiku" {
t.Fatalf("ListUnreviewedClassifications = %+v", rows)
}
if rows[0].FinalAmountCents != 100 { // insertReceipt sets 100 cents
t.Errorf("joined final amount = %d, want 100", rows[0].FinalAmountCents)
}
// Review it, crediting a note as the fix.
noteID, _ := s.AddNote("the fix")
if err := s.MarkClassificationReviewed("r1", "fixed", []int64{noteID}); err != nil {
t.Fatal(err)
}
rows, _ = s.ListUnreviewedClassifications()
if len(rows) != 0 {
t.Errorf("reviewed row still in unreviewed queue: %+v", rows)
}
got, err := s.GetClassification("r1")
if err != nil {
t.Fatal(err)
}
if !got.Reviewed || got.Resolution != "fixed" || got.ReviewedAt == nil {
t.Errorf("review state not persisted: %+v", got)
}
}
func TestClassifications_DeletedReceiptDropsFromQueue(t *testing.T) {
s := classifyTestStore(t)
insertReceipt(t, s, "r1")
if err := s.InsertClassification("r1", "m", `{}`, time.Now().UTC()); err != nil {
t.Fatal(err)
}
if err := s.SoftDelete("r1"); err != nil {
t.Fatal(err)
}
rows, _ := s.ListUnreviewedClassifications()
if len(rows) != 0 {
t.Errorf("soft-deleted receipt should not appear in review queue: %+v", rows)
}
}