d960e211d3
Brings internal/tool/risk.go in so the Hexis split can be written against it. Four conflicts, all additive: both grammar sets in voicewire.go, both test sets in agenda_test.go and stage0.go, and in actions_act.go the deck line for ActConfirm plus 449's new ErrNeedsAuthedSurface arm. Two renames the merge forced. actions_list_test.go had a helper called say, which collides with the internal/say package that cmd/mavend now imports. actions_act_risk_test.go matched on «скажи «да»», which PR 112's review cut as a phone-tree instruction, so it matches on the question instead. --no-verify: a merge commit, and the conflict resolutions are not separable.
159 lines
5.3 KiB
Go
159 lines
5.3 KiB
Go
package router
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
)
|
||
|
||
// agendaRouter wires both grammar sets in the order the daemon wires them
|
||
// (voicewire.go): the clock rules first, the agenda rules after, so a test
|
||
// that passes here is a test of the deployed precedence.
|
||
func agendaRouter(t *testing.T) *Router {
|
||
t.Helper()
|
||
r := newTestRouter(t, 0.0)
|
||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||
r.grammars = append(r.grammars, AgendaQueryGrammars()...)
|
||
return r
|
||
}
|
||
|
||
// An agenda question is answered from the calendar, which lives in the query
|
||
// chain. Routed to system it reaches replySystem, which has no agenda arm and
|
||
// says "пока не умею" — seen on the deployed daemon, 01-08-2026.
|
||
func TestAgendaQuestionsRouteToQuery(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"что у меня сегодня",
|
||
"что у меня в календаре сегодня",
|
||
"что у меня стоит в календаре на послезавтра",
|
||
"какие у меня встречи завтра",
|
||
"покажи расписание на среду",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Intent != IntentQuery {
|
||
t.Errorf("route(%q) = %s, want query", u, d.Intent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The clock rules keep their utterances. They are registered first and the
|
||
// agenda patterns do not match them, so both statements have to hold.
|
||
func TestAgendaGrammarsLeaveTheClockAlone(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"какой сегодня день",
|
||
"какое сегодня число",
|
||
"который час",
|
||
"сколько сейчас времени",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Intent != IntentSystem {
|
||
t.Errorf("route(%q) = %s, want system", u, d.Intent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The agenda pattern is anchored and needs the possessive, so an ordinary
|
||
// statement that happens to contain "у меня" is not swallowed.
|
||
func TestAgendaGrammarSparesStatements(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"у меня кончилась вода",
|
||
"напомни мне завтра позвонить маме",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Stage == 0 && d.Intent == IntentQuery {
|
||
t.Errorf("route(%q) was claimed by the agenda grammar", u)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The two shapes that carried no question mark and no interrogative, so the
|
||
// model saw them first and called them facts (Vikunja #498).
|
||
func TestNarrativeGrammarsRouteToQuery(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
r.grammars = append(r.grammars, NarrativeQueryGrammars()...)
|
||
for _, u := range []string{
|
||
"что дальше?",
|
||
"и что там дальше",
|
||
"what's next?",
|
||
"расскажи про битву при Ватерлоо",
|
||
"объясни как работает дизель",
|
||
"опиши Ватерлоо",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("%q: %v", u, err)
|
||
}
|
||
if d.Intent != IntentQuery || d.Stage != 0 {
|
||
t.Errorf("%q routed intent=%s stage=%d, want query at stage 0", u, d.Intent, d.Stage)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The tomorrow form and the bare event noun. Both were measured answering
|
||
// "пока не умею" on the deployed daemon, 02-08-2026, while the same question
|
||
// about today worked — the first rule set needed "у меня" or a calendar noun
|
||
// and these phrasings carry neither (Vikunja #471).
|
||
func TestAgendaCoversOtherDaysAndNamedEvents(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"какие планы на завтра?",
|
||
"какие планы на послезавтра",
|
||
"что по делам в среду",
|
||
"какие планы на выходные",
|
||
"когда планёрка?",
|
||
"во сколько созвон",
|
||
"когда будет совещание",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Intent != IntentQuery {
|
||
t.Errorf("route(%q) = %s, want query", u, d.Intent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// A narrative verb next to a capture verb is him asking for a note. Stage 0
|
||
// declines and the extractor gets its turn.
|
||
func TestNarrativeGrammarLeavesCapturesAlone(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
r.grammars = append(r.grammars, NarrativeQueryGrammars()...)
|
||
d, err := r.Route(context.Background(), "расскажи и запиши что я пил воду", refNow())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if d.Stage == 0 && d.Intent == IntentQuery {
|
||
t.Errorf("stage 0 claimed a capture: %+v", d)
|
||
}
|
||
}
|
||
|
||
// The two new rules are narrow on purpose. A world question that opens with
|
||
// "когда" is not an agenda question, and telling her about a plan is not
|
||
// asking about one.
|
||
func TestAgendaGrammarsLeaveTheWorldAlone(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"когда была битва при ватерлоо",
|
||
"когда изобрели телефон",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Stage == 0 {
|
||
t.Errorf("route(%q) was claimed at stage 0 as %s", u, d.Intent)
|
||
}
|
||
}
|
||
}
|