// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package tentaclewire import ( "encoding/json" "os/exec" "path/filepath" "strings" "testing" ) func TestRemoteSpeechSDKValidation(t *testing.T) { cases := []struct { data string valid bool }{ {`{"kind":"speak","parts":[{"text":" hello\t世界 ","characters":true,"language":"en_US"},{"end":true}]}`, true}, {`{"kind":"cancel"}`, true}, {`{"kind":"pause","paused":false}`, true}, {`{"kind":"pause","paused":true}`, true}, {`{"kind":"speak","priority":2,"parts":[{"text":"x"}]}`, true}, {`{"kind":"speak","priority":3,"parts":[{"text":"x"}]}`, false}, {`{"kind":"speak","priority":true,"parts":[{"text":"x"}]}`, false}, {`{"kind":"speak","parts":[]}`, false}, {`{"kind":"speak","parts":[{}]}`, false}, {`{"kind":"speak","parts":[{"text":null}]}`, false}, {`{"kind":"speak","parts":[{"text":"x","end":true}]}`, false}, {`{"kind":"speak","parts":[{"text":"x","language":"../en"}]}`, false}, {`{"kind":"speak","parts":[{"text":"x","voice":"peer path"}]}`, false}, {`{"kind":"speak","parts":[{"text":"x"}],"paused":false}`, false}, {`{"kind":"cancel","parts":null}`, false}, {`{"kind":"cancel","parts":[{"text":"x"}]}`, false}, {`{"kind":"cancel","paused":false}`, false}, {`{"kind":"pause"}`, false}, {`{"kind":"pause","paused":null}`, false}, {`{"kind":"pause","paused":0}`, false}, {`{"kind":"wave","path":"anything"}`, false}, {`{"kind":"cancel","priority":null}`, false}, {`{"kind":"cancel","extra":1}`, false}, {`{"kind":"speak","parts":[{"text":"` + strings.Repeat("x", MaxRemoteSpeechBytes) + `"}]}`, false}, {`{"kind":"speak","parts":[{"text":"` + strings.Repeat("<", 1100) + `"}]}`, false}, {`{"kind":"speak","parts":[{"text":"` + strings.Repeat("\u2028", 1100) + `"}]}`, false}, } var pythonCases []any for _, item := range cases { var speech RemoteSpeech valid := DecodeStrict([]byte(item.data), &speech) == nil && ValidRemoteSpeech(speech) if valid != item.valid { t.Fatalf("Go validation: %.100s: got %v", item.data, valid) } var value any if err := json.Unmarshal([]byte(item.data), &value); err != nil { t.Fatal(err) } pythonCases = append(pythonCases, map[string]any{"speech": value, "valid": item.valid}) } path, err := filepath.Abs("../../sdk/python") if err != nil { t.Fatal(err) } script := `import json, sys sys.path.insert(0, sys.argv[1]) from cthulhu_tentacle.session import valid_remote_speech for case in json.load(sys.stdin): assert valid_remote_speech(case['speech']) == case['valid'], str(case)[:150] ` data, _ := json.Marshal(pythonCases) command := exec.Command("python3", "-B", "-c", script, path) command.Stdin = strings.NewReader(string(data)) if output, err := command.CombinedOutput(); err != nil { t.Fatalf("Python validation: %v: %s", err, output) } } func TestRemoteSpeechEncodedBoundary(t *testing.T) { value := RemoteSpeech{Kind: "speak", Parts: []RemoteSpeechPart{{Text: "x"}}} data, _ := json.Marshal(value) value.Parts[0].Text = strings.Repeat("x", MaxRemoteSpeechBytes-len(data)+1) if !ValidRemoteSpeech(value) { t.Fatal("rejected exact bound") } value.Parts[0].Text += "x" if ValidRemoteSpeech(value) { t.Fatal("accepted above bound") } value.Parts[0].Text = string([]byte{0xff}) if ValidRemoteSpeech(value) { t.Fatal("accepted invalid UTF-8") } } func TestRemoteSpeechPresentationSDKValidation(t *testing.T) { base := `"lease":"` + strings.Repeat("a", 32) + `","epoch":1,"sequence":3,"speech":{"kind":"speak","parts":[{"text":"x"}]}` cases := []struct { fields string valid bool }{ {"", true}, {`,"utterance":3`, true}, {`,"utterance":1,"final":true`, true}, {`,"utterance":0,"final":false`, true}, {`,"utterance":4`, false}, {`,"utterance":-1`, false}, {`,"utterance":true`, false}, {`,"utterance":null`, false}, {`,"utterance":18446744073709551616`, false}, {`,"final":true`, false}, {`,"utterance":1,"final":1`, false}, {`,"utterance":1,"final":null`, false}, {`,"extra":1`, false}, } var pythonCases []any for _, item := range cases { data := []byte(`{` + base + item.fields + `}`) var request RemotePresentation if valid := DecodeStrict(data, &request) == nil && ValidRemotePresentation(request); valid != item.valid { t.Fatalf("Go presentation validation: %s: got %v", item.fields, valid) } // Use RawMessage when encoding the cases for Python, preserving uint64 // boundaries rather than passing them through float64. pythonCases = append(pythonCases, map[string]any{"request": json.RawMessage(data), "valid": item.valid}) } for _, kind := range []string{"cancel", "pause"} { request := RemotePresentation{RemoteOperation: RemoteOperation{Lease: strings.Repeat("a", 32), Epoch: 1, Sequence: 1}, Utterance: 1, Speech: RemoteSpeech{Kind: kind}} if kind == "pause" { paused := true request.Speech.Paused = &paused } if ValidRemotePresentation(request) { t.Fatal("grouped non-speak effect") } pythonCases = append(pythonCases, map[string]any{"request": request, "valid": false}) } path, err := filepath.Abs("../../sdk/python") if err != nil { t.Fatal(err) } script := `import json, sys sys.path.insert(0, sys.argv[1]) from cthulhu_tentacle.session import valid_remote_presentation for case in json.load(sys.stdin): assert valid_remote_presentation(case['request']) == case['valid'], str(case)[:180] ` data, _ := json.Marshal(pythonCases) command := exec.Command("python3", "-B", "-c", script, path) command.Stdin = strings.NewReader(string(data)) if output, err := command.CombinedOutput(); err != nil { t.Fatalf("Python presentation validation: %v: %s", err, output) } }