4c40a183cf
- Add events table (migration #4): stores normalized (action, object, ts) triples extracted from facts, indexed for recurrence detection. - Add proposed_routines table: stores inferred recurring patterns with proposed/accepted/dismissed status and optional linked reminder. - Add pattern package: Extractor normalizes fact text into (action, object) pairs with TTS normalization; Detector groups events to find recurring patterns and proposes routines. - Add internal/ttsnorm: text normalization pipeline for Russian/English (lowercase, punctuation strip, number normalization, stopword removal). - Add chat seed file for LLM phraser.
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCreateAndAcceptProposedRoutine(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
// Create
|
|
id, err := s.CreateProposedRoutine(ctx, "refill", "cat_water", 7.0, now)
|
|
if err != nil {
|
|
t.Fatalf("CreateProposedRoutine: %v", err)
|
|
}
|
|
if id == 0 {
|
|
t.Fatal("expected non-zero id")
|
|
}
|
|
|
|
// Duplicate should fail
|
|
_, err = s.CreateProposedRoutine(ctx, "refill", "cat_water", 7.0, now)
|
|
if !errors.Is(err, ErrProposedRoutineExists) {
|
|
t.Fatalf("want ErrProposedRoutineExists, got %v", err)
|
|
}
|
|
|
|
// Lookup
|
|
r, err := s.LookupProposedRoutine(ctx, "refill", "cat_water")
|
|
if err != nil {
|
|
t.Fatalf("LookupProposedRoutine: %v", err)
|
|
}
|
|
if r == nil {
|
|
t.Fatal("want non-nil routine")
|
|
}
|
|
if r.Action != "refill" || r.Object != "cat_water" || r.Status != "proposed" {
|
|
t.Fatalf("got %+v", r)
|
|
}
|
|
if r.IntervalDays != 7.0 {
|
|
t.Fatalf("want interval_days=7.0, got %f", r.IntervalDays)
|
|
}
|
|
|
|
// Accept
|
|
// First create a reminder to link
|
|
remID, err := s.CreateReminder(ctx, now.Add(7*24*time.Hour), `{"text":"refill cat water"}`, "0 10 * * 0")
|
|
if err != nil {
|
|
t.Fatalf("CreateReminder: %v", err)
|
|
}
|
|
if err := s.AcceptProposedRoutine(ctx, id, remID); err != nil {
|
|
t.Fatalf("AcceptProposedRoutine: %v", err)
|
|
}
|
|
|
|
// Verify accepted
|
|
r, err = s.LookupProposedRoutine(ctx, "refill", "cat_water")
|
|
if err != nil {
|
|
t.Fatalf("LookupProposedRoutine: %v", err)
|
|
}
|
|
if r.Status != "accepted" {
|
|
t.Fatalf("want status=accepted, got %s", r.Status)
|
|
}
|
|
if r.ReminderID == nil || *r.ReminderID != remID {
|
|
t.Fatalf("want reminder_id=%d, got %v", remID, r.ReminderID)
|
|
}
|
|
}
|
|
|
|
func TestDismissProposedRoutine(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
id, err := s.CreateProposedRoutine(ctx, "feed", "cat", 1.0, now)
|
|
if err != nil {
|
|
t.Fatalf("CreateProposedRoutine: %v", err)
|
|
}
|
|
|
|
if err := s.DismissProposedRoutine(ctx, id); err != nil {
|
|
t.Fatalf("DismissProposedRoutine: %v", err)
|
|
}
|
|
|
|
r, err := s.LookupProposedRoutine(ctx, "feed", "cat")
|
|
if err != nil {
|
|
t.Fatalf("LookupProposedRoutine: %v", err)
|
|
}
|
|
if r.Status != "dismissed" {
|
|
t.Fatalf("want status=dismissed, got %s", r.Status)
|
|
}
|
|
}
|
|
|
|
func TestListProposedRoutines(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
// No routines yet
|
|
list, err := s.ListProposedRoutines(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListProposedRoutines: %v", err)
|
|
}
|
|
if len(list) != 0 {
|
|
t.Fatalf("want 0, got %d", len(list))
|
|
}
|
|
|
|
// Create two
|
|
_, err = s.CreateProposedRoutine(ctx, "refill", "water", 7.0, now)
|
|
if err != nil {
|
|
t.Fatalf("CreateProposedRoutine: %v", err)
|
|
}
|
|
_, err = s.CreateProposedRoutine(ctx, "feed", "cat", 1.0, now.Add(time.Hour))
|
|
if err != nil {
|
|
t.Fatalf("CreateProposedRoutine: %v", err)
|
|
}
|
|
|
|
list, err = s.ListProposedRoutines(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListProposedRoutines: %v", err)
|
|
}
|
|
if len(list) != 2 {
|
|
t.Fatalf("want 2, got %d", len(list))
|
|
}
|
|
|
|
// Dismiss one
|
|
_ = s.DismissProposedRoutine(ctx, list[0].ID)
|
|
list, err = s.ListProposedRoutines(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListProposedRoutines: %v", err)
|
|
}
|
|
if len(list) != 1 {
|
|
t.Fatalf("want 1 proposed after dismissing one, got %d", len(list))
|
|
}
|
|
}
|
|
|
|
func TestLookupMissingProposedRoutine(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
r, err := s.LookupProposedRoutine(ctx, "nonexistent", "nothing")
|
|
if err != nil {
|
|
t.Fatalf("LookupProposedRoutine: %v", err)
|
|
}
|
|
if r != nil {
|
|
t.Fatal("want nil for missing routine")
|
|
}
|
|
}
|