refactor: switch the whole project to English
talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.
Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.
Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d30bd95ad
commit
85fa64cfe6
95 files changed
+4707
-4854
No files matched your search
+31
-31
@@ -1,9 +1,9 @@
|
||||
package test
|
||||
|
||||
// Prozessgrenzen-Tests für die Längen- und Alters-Gesetze: queue.max_text
|
||||
// kürzt überlange Texte beim Eintritt (Antwort truncated, CLI-Hinweis, der
|
||||
// Provider sieht die gekürzte Fassung); queue.max_age verwirft Ansagen, die
|
||||
// zu lange geparkt haben (expired, nie gesprochen).
|
||||
// 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).
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -11,48 +11,48 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestMaxTextKuerzt: Ein 60-Wörter-Text gegen max_text=40 — /speak meldet
|
||||
// truncated, die CLI warnt auf stderr, der Fake-Provider bekommt höchstens
|
||||
// 40 Zeichen, und /status zeigt das Limit.
|
||||
func TestMaxTextKuerzt(t *testing.T) {
|
||||
// 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) {
|
||||
root := t.TempDir()
|
||||
fake := newFakeProvider(t, 0)
|
||||
d := startDaemon(t, root, fake.srv.URL, map[string]string{"TALK_MAX_TEXT": "40"})
|
||||
lang := strings.Repeat("viel zu lange Antwort ", 12)
|
||||
long := strings.Repeat("far too long an answer ", 12)
|
||||
|
||||
code, m := speak(t, d, `{"text":"`+lang+`"}`)
|
||||
code, m := speak(t, d, `{"text":"`+long+`"}`)
|
||||
if code != 202 || m["id"] == "" || m["truncated"] != true {
|
||||
t.Fatalf("speak: %d %v, want 202 mit id und truncated=true", code, m)
|
||||
t.Fatalf("speak: %d %v, want 202 with id and truncated=true", code, m)
|
||||
}
|
||||
hits := fake.waitCompleted(t, 1)
|
||||
if n := len([]rune(hits[0].Text)); n > 40 || !strings.HasPrefix(hits[0].Text, "viel zu lange Antwort") {
|
||||
t.Errorf("Provider bekam %q (%d Zeichen), want ≤ 40 Zeichen vom Anfang", hits[0].Text, n)
|
||||
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)
|
||||
}
|
||||
|
||||
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||
res := runTalk(t, env, "say", lang)
|
||||
if res.code != 0 || !strings.Contains(res.stderr, "gekürzt") {
|
||||
t.Errorf("talk say (lang): Exit %d, stderr %q — will Kürzungs-Hinweis", res.code, res.stderr)
|
||||
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)
|
||||
}
|
||||
fake.waitCompleted(t, 2)
|
||||
|
||||
res = runTalk(t, env, "say", "kurz genug")
|
||||
if strings.Contains(res.stderr, "gekürzt") {
|
||||
t.Errorf("kurzer Text mit Kürzungs-Hinweis: %q", res.stderr)
|
||||
res = runTalk(t, env, "say", "short enough")
|
||||
if strings.Contains(res.stderr, "truncated") {
|
||||
t.Errorf("short text with truncation note: %q", res.stderr)
|
||||
}
|
||||
fake.waitCompleted(t, 3)
|
||||
|
||||
res = runTalk(t, env, "status")
|
||||
if !strings.Contains(res.stdout, "limits: max. 40 Zeichen") {
|
||||
t.Errorf("talk status ohne Limits-Zeile: %q", res.stdout)
|
||||
if !strings.Contains(res.stdout, "limits: max 40 characters") {
|
||||
t.Errorf("talk status without limits line: %q", res.stdout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaxAgeVerwirftGeparkte: Rate-Limit 1 s, max_age 1,5 s, drei normal-
|
||||
// Ansagen direkt hintereinander: die erste spielt sofort, die zweite parkt
|
||||
// 1 s (frisch genug), die dritte wäre erst nach 2 s fällig — veraltet,
|
||||
// verworfen, nie beim Provider.
|
||||
func TestMaxAgeVerwirftGeparkte(t *testing.T) {
|
||||
// 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) {
|
||||
root := t.TempDir()
|
||||
fake := newFakeProvider(t, 0)
|
||||
d := startDaemon(t, root, fake.srv.URL, map[string]string{
|
||||
@@ -60,19 +60,19 @@ func TestMaxAgeVerwirftGeparkte(t *testing.T) {
|
||||
"TALK_MAX_AGE": "1500ms",
|
||||
})
|
||||
|
||||
for _, text := range []string{"eins", "zwei", "drei"} {
|
||||
for _, text := range []string{"one", "two", "three"} {
|
||||
code, m := speak(t, d, `{"text":"`+text+`"}`)
|
||||
if code != 202 || m["id"] == "" {
|
||||
t.Fatalf("speak %s: %d %v — beim Eintritt darf nichts verfallen", text, code, m)
|
||||
t.Fatalf("speak %s: %d %v — nothing may expire on admission", text, code, m)
|
||||
}
|
||||
}
|
||||
hits := fake.waitCompleted(t, 2)
|
||||
if hits[0].Text != "eins" || hits[1].Text != "zwei" {
|
||||
t.Errorf("gesprochen = %q, %q; want eins, zwei", hits[0].Text, hits[1].Text)
|
||||
if hits[0].Text != "one" || hits[1].Text != "two" {
|
||||
t.Errorf("spoken = %q, %q; want one, two", hits[0].Text, hits[1].Text)
|
||||
}
|
||||
fake.assertNoFurtherArrivals(t, 1500*time.Millisecond)
|
||||
waitStatusIdle(t, d, 3*time.Second)
|
||||
if !strings.Contains(d.stderr.String(), "max_age") {
|
||||
t.Errorf("Daemon-Protokoll nennt den Verfall nicht:\n%s", d.stderr.String())
|
||||
t.Errorf("daemon log does not mention the expiry:\n%s", d.stderr.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user