Files
Maven/internal/router/timementions_test.go
T
claude 01c78ef369 a time slot naming no hour is asked about, never filled (V-579)
Both parsers answer a bare day word with that day at the current minute, so "на
завтра" set a reminder at 01:38, the minute he happened to be speaking. The gate
is textual now: NamesAnHour reads the sentence, and the slot stays empty when
nobody said an hour.

Beside it, NamesAnInterval and HourIsAmbiguous, which the owner's commit rule
reads. "на" joins "в" as a frame around a spoken hour in both parsers, a clock
keeps its meaning with a full stop after it, and the stub applies a day word and
a part-of-day qualifier from anywhere in the sentence rather than only from the
token after the hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 02:20:13 +04:00

126 lines
4.8 KiB
Go

package router
import (
"context"
"testing"
"time"
)
func TestMentionsTime(t *testing.T) {
for _, s := range []string{
"напомни в семь вечера позвонить маме",
"напомни в 19:30 позвонить маме",
"напомни без четверти восемь выходить",
"напомни в половине первого пообедать",
"разбуди меня полвосьмого",
"напомни завтра принять лекарство",
"напомни в пятницу забрать заказ",
"напомни через двадцать минут",
"напомни утром выпить таблетку",
"remind me at noon to stretch",
} {
if !MentionsTime(s) {
t.Errorf("MentionsTime(%q) = false; this sentence names a time", s)
}
}
}
// TestNamesAnHour — the gate on the reminder's time slot (V-577, V-579). A day
// word is not an hour, and a sentence that names no hour is asked about rather
// than completed from the clock.
func TestNamesAnHour(t *testing.T) {
for _, s := range []string{
"в 11:00", "на 9", "в 9", "в девять", "в 9 утра", "завтра в 9",
"через час", "через двадцать минут", "в половине восьмого",
"без четверти восемь", "remind me at noon", "вечером",
} {
if !NamesAnHour(s) {
t.Errorf("NamesAnHour(%q) = false; this names an hour or an interval", s)
}
}
for _, s := range []string{
"на завтра", "что у меня сегодня?", "напомни завтра позвонить маме",
"в пятницу", "позвонить маме", "",
} {
if NamesAnHour(s) {
t.Errorf("NamesAnHour(%q) = true; no hour was spoken, so she has to ask", s)
}
}
}
// TestHourIsAmbiguous — the owner's rule of 2026-08-06. A bare hour is either
// half of the day and gets asked about; a qualifier, a written clock, an hour
// above twelve or an interval settles it and goes straight through.
func TestHourIsAmbiguous(t *testing.T) {
for _, s := range []string{
"напомни завтра в 3 заказать цветы", "в 9", "на 9", "в девять", "в 11 позвонить маме",
} {
if !HourIsAmbiguous(s) {
t.Errorf("HourIsAmbiguous(%q) = false; the hour could be either half of the day", s)
}
}
for _, s := range []string{
"напомни в 9 вечера разгрузить стиралку", "завтра в 15:00", "в 21", "в 11:00",
"через час", "через 10 минут", "remind me at noon", "напомни позвонить маме",
} {
if HourIsAmbiguous(s) {
t.Errorf("HourIsAmbiguous(%q) = true; this time reads only one way", s)
}
}
}
// TestNamesAnInterval — an interval resolves to one instant, so it answers the
// hour and the day at once and is never asked about.
func TestNamesAnInterval(t *testing.T) {
for _, s := range []string{"через час", "через 10 минут", "через полчаса", "in 30 minutes"} {
if !NamesAnInterval(s) {
t.Errorf("NamesAnInterval(%q) = false", s)
}
}
for _, s := range []string{"завтра в 15:00", "в 9 вечера", ""} {
if NamesAnInterval(s) {
t.Errorf("NamesAnInterval(%q) = true", s)
}
}
}
// TestReminderSlotRefusesAnHourNobodySaid — the same rule where it bites. The
// parser answers a bare day word with that day at the current minute, and the
// slot must stay empty so the daemon asks.
func TestReminderSlotRefusesAnHourNobodySaid(t *testing.T) {
now := time.Date(2026, 8, 6, 1, 38, 0, 0, time.UTC)
ex := Extractor{Time: clockEchoParser{}}
if got := ex.Extract(context.Background(), IntentReminder, "на завтра", now); got.HasTime {
t.Errorf("«на завтра» filled the time slot with %s, which is the clock", got.Time.Format("15:04"))
}
if got := ex.Extract(context.Background(), IntentReminder, "на 9", now); !got.HasTime {
t.Error("«на 9» names an hour and must still fill the slot")
}
}
// clockEchoParser stands in for what both real parsers do with a bare day word:
// it answers with the current time of day.
type clockEchoParser struct{}
func (clockEchoParser) Parse(_ context.Context, _ string, now time.Time) (time.Time, bool, error) {
return now.AddDate(0, 0, 1), true, nil
}
// A sentence with no time in it must not read as one, or a real follow-up stops
// inheriting the hour it meant.
func TestMentionsTimeIgnoresSentencesWithoutOne(t *testing.T) {
for _, s := range []string{
"напомни позвонить маме",
"и ещё полить цветы",
"напомни про счёт за свет",
"купить три яблока",
"перезапусти докер",
"счёт 3:2 в нашу пользу",
"",
} {
if MentionsTime(s) {
t.Errorf("MentionsTime(%q) = true; there is no time in it", s)
}
}
}