test(04-04): close translation lookup and catalog boundary coverage
- Cover nested groups, malformed paths, invalid pipes, and compass locale - Name plugin, file, and key on activation catalog failures - Prove hello activation reads the translator through towel.WithLocale Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
"git.golem15.com/golem15/summercms/phrasebook"
|
"git.golem15.com/golem15/summercms/phrasebook"
|
||||||
"git.golem15.com/golem15/summercms/postcard"
|
"git.golem15.com/golem15/summercms/postcard"
|
||||||
"git.golem15.com/golem15/summercms/surf"
|
"git.golem15.com/golem15/summercms/surf"
|
||||||
|
"git.golem15.com/golem15/summercms/towel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGreeterHelloPrintsLayeredConfig(t *testing.T) {
|
func TestGreeterHelloPrintsLayeredConfig(t *testing.T) {
|
||||||
@@ -122,6 +124,13 @@ func TestHelloPluginResolvesLang(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("translator not published")
|
t.Fatal("translator not published")
|
||||||
}
|
}
|
||||||
|
ctx := towel.WithLocale(context.Background(), "pl")
|
||||||
|
if got := tr.Get(ctx, "golem15.hello::lang.greeting", nil); got != "Cześć" {
|
||||||
|
t.Fatalf("towel locale = %q", got)
|
||||||
|
}
|
||||||
|
if got := tr.Get(context.Background(), "golem15.hello::lang.greeting", nil); got != "Hello" {
|
||||||
|
t.Fatalf("app locale default = %q", got)
|
||||||
|
}
|
||||||
if got := tr.GetIn("en", "golem15.hello::lang.labels.pluginName", nil); got != "Hello plugin" {
|
if got := tr.GetIn("en", "golem15.hello::lang.labels.pluginName", nil); got != "Hello plugin" {
|
||||||
t.Fatalf("en = %q", got)
|
t.Fatalf("en = %q", got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,6 +457,26 @@ func TestActivateMalformedLangFailsWithPluginContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActivateMalformedLangLeafNamesFileAndKey(t *testing.T) {
|
||||||
|
p := &langPlugin{
|
||||||
|
id: "golem15.hello",
|
||||||
|
fsys: fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("labels:\n count: 1\n")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app := backpack.New(&compass.Config{})
|
||||||
|
_, err := activate([]Plugin{p}, app, []string{"golem15.hello"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected non-string leaf error")
|
||||||
|
}
|
||||||
|
msg := err.Error()
|
||||||
|
for _, want := range []string{"golem15.hello", "lang/en/lang.yaml", "labels.count", "non-string leaf"} {
|
||||||
|
if !strings.Contains(msg, want) {
|
||||||
|
t.Fatalf("error %q missing %s", msg, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type mailPlugin struct {
|
type mailPlugin struct {
|
||||||
id string
|
id string
|
||||||
fsys fs.FS
|
fsys fs.FS
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
@@ -249,3 +251,95 @@ func TestLocaleFallbackSmoke(t *testing.T) {
|
|||||||
t.Fatalf("production lookup must be silent, got %q", buf.String())
|
t.Fatalf("production lookup must be silent, got %q", buf.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCatalogBoundaries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("invalid yaml names plugin and file", func(t *testing.T) {
|
||||||
|
err := NewCatalog().Load("golem15.hello", fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("greeting: [\n")},
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "lang/en/lang.yaml") {
|
||||||
|
t.Fatalf("parse error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("nested group file flattens under group name", func(t *testing.T) {
|
||||||
|
cat := NewCatalog()
|
||||||
|
err := cat.Load("golem15.hello", fstest.MapFS{
|
||||||
|
"lang/en/validation.yaml": {Data: []byte("fields:\n name: Name is required\n")},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tr := NewTranslator(cat, Options{})
|
||||||
|
if got := tr.GetIn("en", "golem15.hello::validation.fields.name", nil); got != "Name is required" {
|
||||||
|
t.Fatalf("nested group = %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("path extra segments and wrong suffix are malformed", func(t *testing.T) {
|
||||||
|
for _, file := range []string{"lang/en/nested/lang.yaml", "lang/en/lang.yml", "i18n/en/lang.yaml"} {
|
||||||
|
err := NewCatalog().Load("golem15.hello", fstest.MapFS{
|
||||||
|
file: {Data: []byte("greeting: Hi\n")},
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "malformed path") {
|
||||||
|
t.Fatalf("%s error = %v", file, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("inverted bracket range", func(t *testing.T) {
|
||||||
|
err := NewCatalog().Load("golem15.hello", fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("items: \"[5,2] nope|{1} one\"\n")},
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "lang/en/lang.yaml") || !strings.Contains(err.Error(), "malformed pipe") {
|
||||||
|
t.Fatalf("inverted range error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("empty pipe form", func(t *testing.T) {
|
||||||
|
err := NewCatalog().Load("golem15.hello", fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("items: \"one||other\"\n")},
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "malformed pipe") {
|
||||||
|
t.Fatalf("empty form error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("get substitutes name case variants", func(t *testing.T) {
|
||||||
|
cat := NewCatalog()
|
||||||
|
if err := cat.Load("golem15.hello", fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("helloName: \"hi :name :Name :NAME\"\n")},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tr := NewTranslator(cat, Options{})
|
||||||
|
if got := tr.GetIn("en", "golem15.hello::lang.helloName", map[string]string{"name": "alice"}); got != "hi alice Alice ALICE" {
|
||||||
|
t.Fatalf("Get params = %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("configured locale and fallback", func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("locale: pl\nfallback_locale: en\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg, err := compass.Load(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
app := backpack.New(cfg)
|
||||||
|
p := &langPlugin{id: "golem15.hello", fsys: fstest.MapFS{
|
||||||
|
"lang/en/lang.yaml": {Data: []byte("greeting: Hello\nonlyEn: English only\n")},
|
||||||
|
"lang/pl/lang.yaml": {Data: []byte("greeting: Cześć\n")},
|
||||||
|
}}
|
||||||
|
if err := Activate(app, []*langPlugin{p}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tr, ok := app.Lookup[*Translator]()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("translator not published")
|
||||||
|
}
|
||||||
|
if got := tr.Get(context.Background(), "golem15.hello::lang.greeting", nil); got != "Cześć" {
|
||||||
|
t.Fatalf("configured app.locale = %q", got)
|
||||||
|
}
|
||||||
|
if got := tr.GetIn("pl", "golem15.hello::lang.onlyEn", nil); got != "English only" {
|
||||||
|
t.Fatalf("configured fallback = %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user