2026-10-24 09:54:40 +02:00
|
|
|
package test
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// Process-boundary tests for the length and age laws: queue.max_text
|
|
|
|
|
// truncates overlong texts on admission (response truncated, CLI note, the
|
|
|
|
|
// provider sees the truncated version); queue.max_age discards
|
|
|
|
|
// announcements that parked too long (expired, never spoken).
|
2026-10-24 09:54:40 +02:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestMaxTextTruncates: a 60-word text against max_text=40 — /speak reports
|
|
|
|
|
// truncated, the CLI warns on stderr, the fake provider receives at most 40
|
|
|
|
|
// characters, and /status shows the limit.
|
|
|
|
|
func TestMaxTextTruncates(t *testing.T) {
|
2026-10-24 09:54:40 +02:00
|
|
|
root := t.TempDir()
|
|
|
|
|
fake := newFakeProvider(t, 0)
|
|
|
|
|
d := startDaemon(t, root, fake.srv.URL, map[string]string{"TALK_MAX_TEXT": "40"})
|
2026-10-24 11:15:35 +02:00
|
|
|
long := strings.Repeat("far too long an answer ", 12)
|
2026-10-24 09:54:40 +02:00
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
code, m := speak(t, d, `{"text":"`+long+`"}`)
|
2026-10-24 09:54:40 +02:00
|
|
|
if code != 202 || m["id"] == "" || m["truncated"] != true {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Fatalf("speak: %d %v, want 202 with id and truncated=true", code, m)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
hits := fake.waitCompleted(t, 1)
|
2026-10-24 11:15:35 +02:00
|
|
|
if n := len([]rune(hits[0].Text)); n > 40 || !strings.HasPrefix(hits[0].Text, "far too long an answer") {
|
|
|
|
|
t.Errorf("provider received %q (%d characters), want ≤ 40 characters from the start", hits[0].Text, n)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
2026-10-24 11:15:35 +02:00
|
|
|
res := runTalk(t, env, "say", long)
|
|
|
|
|
if res.code != 0 || !strings.Contains(res.stderr, "truncated") {
|
|
|
|
|
t.Errorf("talk say (long): exit %d, stderr %q — want the truncation note", res.code, res.stderr)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
fake.waitCompleted(t, 2)
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
res = runTalk(t, env, "say", "short enough")
|
|
|
|
|
if strings.Contains(res.stderr, "truncated") {
|
|
|
|
|
t.Errorf("short text with truncation note: %q", res.stderr)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
fake.waitCompleted(t, 3)
|
|
|
|
|
|
|
|
|
|
res = runTalk(t, env, "status")
|
2026-10-24 11:15:35 +02:00
|
|
|
if !strings.Contains(res.stdout, "limits: max 40 characters") {
|
|
|
|
|
t.Errorf("talk status without limits line: %q", res.stdout)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestMaxAgeDiscardsParked: rate limit 1 s, max_age 1.5 s, three normal
|
|
|
|
|
// announcements right after one another: the first plays immediately, the
|
|
|
|
|
// second parks 1 s (fresh enough), the third would be due only after 2 s —
|
|
|
|
|
// stale, discarded, never at the provider.
|
|
|
|
|
func TestMaxAgeDiscardsParked(t *testing.T) {
|
2026-10-24 09:54:40 +02:00
|
|
|
root := t.TempDir()
|
|
|
|
|
fake := newFakeProvider(t, 0)
|
|
|
|
|
d := startDaemon(t, root, fake.srv.URL, map[string]string{
|
|
|
|
|
"TALK_RATE_LIMIT": "1s",
|
|
|
|
|
"TALK_MAX_AGE": "1500ms",
|
|
|
|
|
})
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
for _, text := range []string{"one", "two", "three"} {
|
2026-10-24 09:54:40 +02:00
|
|
|
code, m := speak(t, d, `{"text":"`+text+`"}`)
|
|
|
|
|
if code != 202 || m["id"] == "" {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Fatalf("speak %s: %d %v — nothing may expire on admission", text, code, m)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hits := fake.waitCompleted(t, 2)
|
2026-10-24 11:15:35 +02:00
|
|
|
if hits[0].Text != "one" || hits[1].Text != "two" {
|
|
|
|
|
t.Errorf("spoken = %q, %q; want one, two", hits[0].Text, hits[1].Text)
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
fake.assertNoFurtherArrivals(t, 1500*time.Millisecond)
|
|
|
|
|
waitStatusIdle(t, d, 3*time.Second)
|
|
|
|
|
if !strings.Contains(d.stderr.String(), "max_age") {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Errorf("daemon log does not mention the expiry:\n%s", d.stderr.String())
|
2026-10-24 09:54:40 +02:00
|
|
|
}
|
|
|
|
|
}
|