114 lines
4.0 KiB
Go
114 lines
4.0 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package preferences
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/speech"
|
|
)
|
|
|
|
func TestInitialCatalogRoundTrip(t *testing.T) {
|
|
for _, size := range []struct{ count, nameBytes int }{{1, 100}, {6000, 100}, {4, 250000}} {
|
|
count := size.count
|
|
catalog := speech.Catalog{
|
|
"espeak-ng": make([]speech.Voice, count),
|
|
"other": {{Name: "Other voice", Language: "es"}},
|
|
"empty": nil,
|
|
}
|
|
for index := range catalog["espeak-ng"] {
|
|
catalog["espeak-ng"][index] = speech.Voice{Name: strings.Repeat("v", size.nameBytes), Language: "de"}
|
|
}
|
|
// The last entry must survive even when it lies beyond the old cutoff.
|
|
catalog["espeak-ng"][count-1] = speech.Voice{Name: "English voice", Language: "en"}
|
|
initial := Response{Catalog: catalog, Message: "Preferences ready.", Application: "Example", CanSave: true, Accepted: true}
|
|
var wire bytes.Buffer
|
|
encoder := json.NewEncoder(&wire)
|
|
if err := encoder.Encode(Response{ChooseApplication: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := EncodeInitial(encoder, initial); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, line := range bytes.Split(bytes.TrimSpace(wire.Bytes()), []byte{'\n'}) {
|
|
if len(line)+1 > initialMessageLimit {
|
|
t.Fatalf("protocol line has %d bytes", len(line)+1)
|
|
}
|
|
}
|
|
// Reopening after the chooser uses the same response reader. Subsequent
|
|
// present requests must not be absorbed into the assembled catalog.
|
|
if err := encoder.Encode(Response{Present: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
updates := make(chan Response, 3)
|
|
errorsIn := make(chan error, 1)
|
|
read_responses(t.Context(), &wire, updates, errorsIn)
|
|
if err := <-errorsIn; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := <-updates; !got.ChooseApplication {
|
|
t.Fatal("application chooser response was mixed into the catalog")
|
|
}
|
|
if got := <-updates; !reflect.DeepEqual(got, initial) {
|
|
t.Fatalf("catalog or session metadata changed for %d voices", count)
|
|
}
|
|
if got := <-updates; !got.Present || got.Catalog != nil {
|
|
t.Fatal("present response was mixed into the initial catalog")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitialCatalogRejectsOversizedEntry(t *testing.T) {
|
|
for _, initial := range []Response{
|
|
{Message: strings.Repeat("x", initialMessageLimit)},
|
|
{Catalog: speech.Catalog{"espeak-ng": {{Name: strings.Repeat("x", initialMessageLimit)}}}},
|
|
} {
|
|
var wire bytes.Buffer
|
|
if err := EncodeInitial(json.NewEncoder(&wire), initial); err == nil {
|
|
t.Fatal("oversized settings or voice accepted")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitialCatalogIncompleteAndCancellation(t *testing.T) {
|
|
updates := make(chan Response, 1)
|
|
errorsIn := make(chan error, 1)
|
|
read_responses(t.Context(), strings.NewReader(`{"CatalogPending":true}`+"\n"), updates, errorsIn)
|
|
if err := <-errorsIn; !errors.Is(err, io.ErrUnexpectedEOF) || len(updates) != 0 {
|
|
t.Fatalf("incomplete catalog reached the UI: %v", err)
|
|
}
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
cancel()
|
|
read_responses(ctx, strings.NewReader("{}\n"), make(chan Response), errorsIn)
|
|
}
|
|
|
|
func TestDefaultVoicesFiltersESpeakAndDefaultAlias(t *testing.T) {
|
|
t.Setenv("LC_ALL", "en_US.UTF-8")
|
|
voices := []speech.Voice{{Name: "German", Language: "de"}, {Name: "English", Language: "en-US"}}
|
|
session := &uiSession{catalog: speech.Catalog{"": voices, "espeak-ng": voices, "other": voices}}
|
|
for _, module := range []string{"", "espeak-ng"} {
|
|
if got := session.default_voices(module); !reflect.DeepEqual(got, voices[1:]) {
|
|
t.Fatalf("module %q: %v", module, got)
|
|
}
|
|
}
|
|
if got := session.default_voices("other"); !reflect.DeepEqual(got, voices) {
|
|
t.Fatal("other synthesizer was filtered")
|
|
}
|
|
if !reflect.DeepEqual(session.catalog["espeak-ng"], voices) {
|
|
t.Fatal("language selection lost the full catalog")
|
|
}
|
|
session.catalog[""] = voices[:1]
|
|
if got := session.default_voices(""); !reflect.DeepEqual(got, voices[:1]) {
|
|
t.Fatal("unidentified default synthesizer was filtered")
|
|
}
|
|
}
|