package router import "testing" func TestParseTaskStatus(t *testing.T) { cases := []struct { utterance string ok bool status string text string }{ // The shapes that reached nothing before this rule. {"закрой задачу купить молоко", true, "done", "купить молоко"}, {"задачу купить молоко закрой", true, "done", "купить молоко"}, {"убери из задач купить молоко", true, "dropped", "купить молоко"}, {"убери из моих задач купить молоко", true, "dropped", "купить молоко"}, {"отмени задачу оплатить интернет", true, "dropped", "оплатить интернет"}, {"please close task buy milk", true, "done", "buy milk"}, {"не мог бы ты закрыть задачу купить молоко?", true, "done", "купить молоко"}, // Referents come from the bounded command frame. Interior prepositions // remain part of a real title; marker verbs and topic prepositions do not. {"закрой задачу сходить в банк", true, "done", "сходить в банк"}, {"mark task about backups as done", true, "done", "backups"}, // Question-shaped prose containing the same change word is not an // instruction. This parser feeds a mutating stage-0 decision, so its // cautious direction is to decline and let the query cascade answer. {"как отменить задачу купить хлеб?", false, "", ""}, {"как отменить задачу купить хлеб", false, "", ""}, {"отменить задачу купить хлеб? пожалуйста", false, "", ""}, {"how do I cancel task buy bread", false, "", ""}, {"что будет если удалить задачу купить хлеб", false, "", ""}, {"объясни как закрыть задачу купить хлеб", false, "", ""}, // Capture verbs normally outrank question shape. They do not get that // exception here, because this mutating grammar runs before capture. {"запиши как отменить задачу купить хлеб", false, "", ""}, {"отметь как отменить задачу купить хлеб", false, "", ""}, // The established state-marker command is not a question: как means // "as" inside a closed marker frame here. {"отметь задачу про бэкапы как сделано", true, "done", "бэкапы"}, {"отметь задачу про бэкапы как сделанную", true, "done", "бэкапы"}, // A marker-shaped verb can itself belong to the title. Frame position, // not global word subtraction, decides which occurrence is control. {"отметь задачу отметить выходные как сделанную", true, "done", "отметить выходные"}, {"задачу отметить выходные отметь как сделанную", true, "done", "отметить выходные"}, // Token boundaries matter in both directions. Interrogative roots inside // a word or an indefinite compound do not turn a direct command into a // question, and an interrogative in the already-commanded task title is // part of its name. {"отмени задачу купить какао", true, "dropped", "купить какао"}, {"закрой задачу чтобы купить хлеб", true, "done", "чтобы купить хлеб"}, {"отмени задачу купить что-нибудь", true, "dropped", "купить что-нибудь"}, {"отмени задачу узнать когда рейс", true, "dropped", "узнать когда рейс"}, // The referent may be missing. The turn is still his, and the daemon has // the list to ask about. {"закрой задачу", true, "done", ""}, {"убери задачу", true, "dropped", ""}, // No board noun: ordinary speech, and every one of these means something // else. "закрой" alone belongs to Praxis. {"готово", false, "", ""}, {"закрой", false, "", ""}, {"убери со стола", false, "", ""}, {"я всё сделал", false, "", ""}, {"закрой шторы в комнате", false, "", ""}, // Reports, plans and desires name a transition but do not address Maven. // The old mixed-mood accessor lemma-matched every one into a write. {"я закрыл задачу купить молоко", false, "", ""}, {"я отменил задачу купить молоко", false, "", ""}, {"I closed the task buy milk", false, "", ""}, {"I cancelled the task buy milk", false, "", ""}, {"надо закрыть задачу купить молоко", false, "", ""}, {"я хочу закрыть задачу купить молоко", false, "", ""}, {"I want to close task buy milk", false, "", ""}, // Direct negative authority and the ambiguous don't-forget board idiom // fail closed before any transition slot is exposed. {"не закрывай задачу купить молоко", false, "", ""}, {"не закрой задачу купить молоко", false, "", ""}, {"не закрыть задачу купить молоко", false, "", ""}, {"don't close task buy milk", false, "", ""}, {"never cancel task buy milk", false, "", ""}, {"не забудь закрыть задачу купить молоко", false, "", ""}, // The board noun with no status word is a list query, not a move. {"какие у меня задачи", false, "", ""}, {"добавь в задачи купить молоко", false, "", ""}, // Two transitions in one sentence. Asking beats picking. {"задачу купить молоко готово убери", false, "", ""}, {"", false, "", ""}, } for _, c := range cases { got, ok := ParseTaskStatus(c.utterance) if ok != c.ok { t.Errorf("ParseTaskStatus(%q) ok = %v, want %v", c.utterance, ok, c.ok) continue } if !ok { continue } if got.Status != c.status || got.Text != c.text { t.Errorf("ParseTaskStatus(%q) = %+v, want status %q text %q", c.utterance, got, c.status, c.text) } } } func TestTaskStatusGrammarFillsTheFnSlot(t *testing.T) { g := TaskStatusGrammar() dec, matched, ok := g.Evaluate("закрой задачу купить молоко") if !matched || !ok { t.Fatal("structural grammar declined") } if dec.Intent != IntentAct || !dec.Slots.HasFn || dec.Slots.Fn != TaskStatusFn { t.Fatalf("decision = %+v, want act with fn %q", dec, TaskStatusFn) } if dec.Slots.Value != "done" || dec.Slots.Text != "купить молоко" { t.Fatalf("slots = %+v, want value done text \"купить молоко\"", dec.Slots) } } func TestTaskStatusGrammarAcceptsInflectedDoneMarker(t *testing.T) { g := TaskStatusGrammar() dec, matched, accepted := g.Evaluate("отметь задачу про бэкапы как сделанную") if !matched || !accepted { t.Fatalf("inflected state-marker command matched=%v accepted=%v", matched, accepted) } if dec.Intent != IntentAct || dec.Slots.Fn != TaskStatusFn || dec.Slots.Value != "done" || dec.Slots.Text != "бэкапы" { t.Fatalf("decision = %+v, want task_status/done with the backup task referent", dec) } } func TestTaskStatusGrammarDeclinesQuestionsWithoutMutationSlots(t *testing.T) { g := TaskStatusGrammar() for _, utterance := range []string{ "как отменить задачу купить хлеб?", "how do I drop task buy bread", "запиши как отменить задачу купить хлеб", } { dec, matched, accepted := g.Evaluate(utterance) if matched || accepted { t.Errorf("TaskStatusGrammar(%q) matched=%v accepted=%v; a structural refusal must reach the query cascade", utterance, matched, accepted) } if dec.Slots.HasFn || dec.Slots.Fn != "" { t.Errorf("TaskStatusGrammar(%q) exposed mutation slots: %+v", utterance, dec.Slots) } } }