150 lines
4.5 KiB
Go
150 lines
4.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var listNow = time.Date(2026, 8, 4, 12, 0, 0, 0, time.UTC)
|
|
|
|
func TestAddListItemDedupesTheOpenList(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
first, err := s.AddListItem(ctx, ListItem{Item: "молоко", Source: "tap:voice", CreatedTs: listNow})
|
|
if err != nil {
|
|
t.Fatalf("add: %v", err)
|
|
}
|
|
if !first.Created {
|
|
t.Fatal("the first молоко did not create a row")
|
|
}
|
|
again, err := s.AddListItem(ctx, ListItem{Item: " Молоко ", Source: "tap:voice", CreatedTs: listNow})
|
|
if err != nil {
|
|
t.Fatalf("add again: %v", err)
|
|
}
|
|
if again.Created {
|
|
t.Error("молоко was added twice")
|
|
}
|
|
if again.ID != first.ID {
|
|
t.Errorf("second add points at %d; want the existing %d", again.ID, first.ID)
|
|
}
|
|
if _, err := s.AddListItem(ctx, ListItem{Item: " "}); !errors.Is(err, ErrListItemEmpty) {
|
|
t.Errorf("empty item: %v; want ErrListItemEmpty", err)
|
|
}
|
|
}
|
|
|
|
// A crossed-off item does not block the next one: buying milk again next week
|
|
// is a new line, the way saying an errand again is a new task.
|
|
func TestCrossedOffItemComesBack(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
first, err := s.AddListItem(ctx, ListItem{Item: "молоко", CreatedTs: listNow})
|
|
if err != nil {
|
|
t.Fatalf("add: %v", err)
|
|
}
|
|
if err := s.SetListItemStatus(ctx, first.ID, ListItemDone, listNow); err != nil {
|
|
t.Fatalf("cross off: %v", err)
|
|
}
|
|
next, err := s.AddListItem(ctx, ListItem{Item: "молоко", CreatedTs: listNow.Add(time.Hour)})
|
|
if err != nil {
|
|
t.Fatalf("add after: %v", err)
|
|
}
|
|
if !next.Created || next.ID == first.ID {
|
|
t.Errorf("second молоко reused row %d; want a new one", next.ID)
|
|
}
|
|
open, err := s.ListItems(ctx, "", "")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(open) != 1 || open[0].ID != next.ID {
|
|
t.Errorf("open list %+v; want only the new row", open)
|
|
}
|
|
}
|
|
|
|
// Lists are separate stores under one table: the same word on two lists is two
|
|
// items, and reading one never reads the other.
|
|
func TestListsDoNotSeeEachOther(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
if _, err := s.AddListItem(ctx, ListItem{List: "покупки", Item: "вода", CreatedTs: listNow}); err != nil {
|
|
t.Fatalf("add: %v", err)
|
|
}
|
|
if _, err := s.AddListItem(ctx, ListItem{List: "Аптека", Item: "вода", CreatedTs: listNow}); err != nil {
|
|
t.Fatalf("add: %v", err)
|
|
}
|
|
for _, c := range []struct{ list, want string }{
|
|
{"покупки", "покупки"},
|
|
{"аптека", "аптека"},
|
|
{"", "покупки"},
|
|
} {
|
|
got, err := s.ListItems(ctx, c.list, "")
|
|
if err != nil {
|
|
t.Fatalf("list %q: %v", c.list, err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("list %q has %d items; want 1", c.list, len(got))
|
|
}
|
|
if got[0].List != c.want {
|
|
t.Errorf("list %q returned tag %q; want %q", c.list, got[0].List, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClearListCrossesOffEverythingOpen(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
for _, item := range []string{"молоко", "хлеб", "яйца"} {
|
|
if _, err := s.AddListItem(ctx, ListItem{Item: item, CreatedTs: listNow}); err != nil {
|
|
t.Fatalf("add %s: %v", item, err)
|
|
}
|
|
}
|
|
if _, err := s.AddListItem(ctx, ListItem{List: "аптека", Item: "бинт", CreatedTs: listNow}); err != nil {
|
|
t.Fatalf("add: %v", err)
|
|
}
|
|
n, err := s.ClearList(ctx, "покупки", listNow)
|
|
if err != nil {
|
|
t.Fatalf("clear: %v", err)
|
|
}
|
|
if n != 3 {
|
|
t.Errorf("cleared %d; want 3", n)
|
|
}
|
|
left, err := s.ListItems(ctx, "покупки", "")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(left) != 0 {
|
|
t.Errorf("%d items still open; want none", len(left))
|
|
}
|
|
done, err := s.ListItems(ctx, "покупки", ListItemDone)
|
|
if err != nil {
|
|
t.Fatalf("list done: %v", err)
|
|
}
|
|
if len(done) != 3 || done[0].ResolvedTs == nil {
|
|
t.Errorf("done list %+v; want 3 rows carrying a resolved time", done)
|
|
}
|
|
other, err := s.ListItems(ctx, "аптека", "")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(other) != 1 {
|
|
t.Error("clearing покупки touched аптека")
|
|
}
|
|
}
|
|
|
|
func TestSetListItemStatusRejectsWhatIsNotAStatus(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
if err := s.SetListItemStatus(ctx, 1, "куплено", listNow); !errors.Is(err, ErrListItemStatus) {
|
|
t.Errorf("bad status: %v; want ErrListItemStatus", err)
|
|
}
|
|
if err := s.SetListItemStatus(ctx, 999, ListItemDone, listNow); !errors.Is(err, ErrListItemNotFound) {
|
|
t.Errorf("missing row: %v; want ErrListItemNotFound", err)
|
|
}
|
|
}
|