package main import ( "context" "strings" "testing" ) // "отметь второй пункт" names a position, and only the daemon knows which item // that is. The router fills the value slot with "2"; this is where it becomes an // item id (Vikunja #516). func TestPositionResolvesAgainstTheLastSpokenList(t *testing.T) { praxis := newFakePraxis(t, `[ {"id":"item_a","title":"диск заканчивается"}, {"id":"item_b","title":"бэкап не прошёл"}, {"id":"item_c","title":"сертификат истекает"} ]`) h := newPraxisTestHandler(t, praxis) if reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention")); reply == "" { t.Fatal("attention returned nothing") } cases := []struct{ ref, wantItem string }{ {"2", "item_b"}, {"1", "item_a"}, {"last", "item_c"}, } for _, c := range cases { praxis.ResetRequests() reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", c.ref)) if !strings.Contains(reply, "принято") { t.Errorf("ref %q: reply %q", c.ref, reply) } if !requestedPathContaining(praxis, c.wantItem) { t.Errorf("ref %q did not acknowledge %s; paths %v", c.ref, c.wantItem, paths(praxis)) } } } // A position past the end must not acknowledge the wrong item. It asks. func TestPositionPastTheEndAsksInsteadOfGuessing(t *testing.T) { praxis := newFakePraxis(t, `[{"id":"item_a","title":"диск заканчивается"}]`) h := newPraxisTestHandler(t, praxis) h.handlePraxisAct(context.Background(), praxisActDec("list_attention")) praxis.ResetRequests() reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "4")) if !strings.Contains(reply, "какой пункт") { t.Errorf("a position with no item should ask, got %q", reply) } if requestedPathContaining(praxis, "item_a") { t.Error("the only surfaced item was resolved for a position that did not name it") } } // No digest yet means no positions. Nothing is mutated. func TestPositionWithNoSpokenListAsks(t *testing.T) { praxis := newFakePraxis(t, `[]`) h := newPraxisTestHandler(t, praxis) reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "1")) if !strings.Contains(reply, "какой пункт") { t.Errorf("want the ask, got %q", reply) } } // An explicit id is not a position and passes through untouched. func TestExplicitItemIDIsNotRewritten(t *testing.T) { praxis := newFakePraxis(t, `[{"id":"item_a","title":"диск"}]`) h := newPraxisTestHandler(t, praxis) h.handlePraxisAct(context.Background(), praxisActDec("list_attention")) praxis.ResetRequests() h.handlePraxisAct(context.Background(), praxisItemDec("pin_item", "item_zz")) if !requestedPathContaining(praxis, "item_zz") { t.Errorf("the id he gave was not the one called; paths %v", paths(praxis)) } } // An item Praxis sent without a title is never spoken, so it holds no position. func TestUnspokenItemsHoldNoPosition(t *testing.T) { praxis := newFakePraxis(t, `[ {"id":"item_silent"}, {"id":"item_said","title":"бэкап не прошёл"} ]`) h := newPraxisTestHandler(t, praxis) h.handlePraxisAct(context.Background(), praxisActDec("list_attention")) praxis.ResetRequests() h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "1")) if !requestedPathContaining(praxis, "item_said") { t.Errorf("position 1 is the first item she SAID; paths %v", paths(praxis)) } } // The item id travels in the POST body, so that is what these read. func paths(f *fakeServer) []string { var out []string for _, r := range f.Requests() { out = append(out, r.Path+" "+string(r.Body)) } return out } func requestedPathContaining(f *fakeServer, want string) bool { for _, r := range f.Requests() { if strings.Contains(string(r.Body), want) { return true } } return false } // "отметь это как сделанное" after a one-item digest points at that item. func TestDemonstrativeResolvesWhenOneItemWasSpoken(t *testing.T) { praxis := newFakePraxis(t, `[{"id":"item_only","title":"бэкап не прошёл"}]`) h := newPraxisTestHandler(t, praxis) h.handlePraxisAct(context.Background(), praxisActDec("list_attention")) praxis.ResetRequests() reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "this")) if !strings.Contains(reply, "принято") { t.Errorf("reply %q", reply) } if !requestedPathContaining(praxis, "item_only") { t.Errorf("the one surfaced item was not acknowledged; paths %v", paths(praxis)) } } // Pointing at one of several is a guess, and a wrong guess transitions the wrong // item. The turn goes back to the cascade instead. func TestDemonstrativeWithSeveralItemsGivesTheTurnBack(t *testing.T) { praxis := newFakePraxis(t, `[ {"id":"item_a","title":"диск"}, {"id":"item_b","title":"бэкап"} ]`) h := newPraxisTestHandler(t, praxis) h.handlePraxisAct(context.Background(), praxisActDec("list_attention")) praxis.ResetRequests() if reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "this")); reply != "" { t.Errorf("want a fall-through, got %q", reply) } for _, p := range paths(praxis) { if strings.Contains(p, "resolve") { t.Error("an ambiguous demonstrative resolved an item anyway") } } } // "я это сделал" with no digest behind it is a sentence about his day. func TestDemonstrativeWithNoDigestGivesTheTurnBack(t *testing.T) { praxis := newFakePraxis(t, `[]`) h := newPraxisTestHandler(t, praxis) if reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "this")); reply != "" { t.Errorf("want a fall-through, got %q", reply) } }