Files
galaxy-game/mail/internal/adapters/templates/catalog_test.go
T
2026-04-22 08:49:45 +02:00

205 lines
7.5 KiB
Go

package templates
import (
"errors"
"os"
"path/filepath"
"testing"
"galaxy/mail/internal/domain/common"
"github.com/stretchr/testify/require"
)
func TestNewCatalogBuildsImmutableRegistry(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "fr-fr", "subject.tmpl"), "Tour {{.turn_number}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "fr-fr", "text.tmpl"), "Bonjour {{with .player}}{{.name}}{{end}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "fr-fr", "html.tmpl"), "<p>{{.player.name}}</p>")
catalog, err := NewCatalog(rootDir)
require.NoError(t, err)
require.Equal(t, filepath.Clean(rootDir), catalog.RootDir())
locale, err := common.ParseLocale("fr-FR")
require.NoError(t, err)
resolved, err := catalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.NoError(t, err)
require.False(t, resolved.LocaleFallbackUsed())
require.Equal(t, common.Locale("fr-FR"), resolved.ResolvedLocale())
require.Equal(t, []string{"player", "player.name", "turn_number"}, resolved.RequiredVariablePaths())
subject, err := resolved.ExecuteSubject(map[string]any{
"turn_number": 54,
"player": map[string]any{
"name": "Pilot",
},
})
require.NoError(t, err)
require.Equal(t, "Tour 54", subject)
textBody, err := resolved.ExecuteText(map[string]any{
"player": map[string]any{
"name": "Pilot",
},
})
require.NoError(t, err)
require.Equal(t, "Bonjour Pilot", textBody)
htmlBody, ok, err := resolved.ExecuteHTML(map[string]any{
"player": map[string]any{
"name": "Pilot",
},
})
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, "<p>Pilot</p>", htmlBody)
}
func TestCatalogLookupFallsBackToEnglish(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "subject.tmpl"), "Turn {{.turn_number}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "text.tmpl"), "Hello {{.player.name}}")
catalog, err := NewCatalog(rootDir)
require.NoError(t, err)
locale, err := common.ParseLocale("fr-FR")
require.NoError(t, err)
resolved, err := catalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.NoError(t, err)
require.True(t, resolved.LocaleFallbackUsed())
require.Equal(t, common.Locale("en"), resolved.ResolvedLocale())
}
func TestCatalogLookupRejectsMissingEnglishFallback(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "fr-FR", "subject.tmpl"), "Tour {{.turn_number}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "fr-FR", "text.tmpl"), "Bonjour {{.player.name}}")
catalog, err := NewCatalog(rootDir)
require.NoError(t, err)
locale, err := common.ParseLocale("de-DE")
require.NoError(t, err)
_, err = catalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.Error(t, err)
require.True(t, errors.Is(err, ErrFallbackMissing))
}
func TestCatalogLookupRejectsUnknownTemplateFamily(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
catalog, err := NewCatalog(rootDir)
require.NoError(t, err)
locale, err := common.ParseLocale("en")
require.NoError(t, err)
_, err = catalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.Error(t, err)
require.True(t, errors.Is(err, ErrTemplateNotFound))
}
func TestCatalogAllowsTemplateWithoutHTML(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
catalog, err := NewCatalog(rootDir)
require.NoError(t, err)
locale, err := common.ParseLocale("en")
require.NoError(t, err)
resolved, err := catalog.Lookup(common.TemplateID("auth.login_code"), locale)
require.NoError(t, err)
htmlBody, ok, err := resolved.ExecuteHTML(map[string]any{"code": "123456"})
require.NoError(t, err)
require.False(t, ok)
require.Empty(t, htmlBody)
}
func TestCatalogVersionIsDeterministic(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "subject.tmpl"), "Turn {{.turn_number}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "text.tmpl"), "Hello {{.player.name}}")
firstCatalog, err := NewCatalog(rootDir)
require.NoError(t, err)
secondCatalog, err := NewCatalog(rootDir)
require.NoError(t, err)
locale, err := common.ParseLocale("en")
require.NoError(t, err)
firstResolved, err := firstCatalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.NoError(t, err)
secondResolved, err := secondCatalog.Lookup(common.TemplateID("game.turn.ready"), locale)
require.NoError(t, err)
require.Equal(t, firstResolved.Template().Version, secondResolved.Template().Version)
}
func TestNewCatalogRejectsMissingDirectory(t *testing.T) {
t.Parallel()
_, err := NewCatalog(filepath.Join(t.TempDir(), "missing"))
require.Error(t, err)
require.Contains(t, err.Error(), "stat root dir")
}
func TestNewCatalogRejectsMissingRequiredStartupTemplate(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "subject.tmpl"), "Turn {{.turn_number}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "text.tmpl"), "Hello {{.player.name}}")
_, err := NewCatalog(rootDir)
require.Error(t, err)
require.Contains(t, err.Error(), `required template "auth.login_code" locale "en" is missing`)
}
func TestNewCatalogRejectsBrokenTemplateParse(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "subject.tmpl"), "Your login code")
writeTemplateFile(t, rootDir, filepath.Join("auth.login_code", "en", "text.tmpl"), "Code: {{.code}}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "subject.tmpl"), "{{if .turn_number}")
writeTemplateFile(t, rootDir, filepath.Join("game.turn.ready", "en", "text.tmpl"), "Hello {{.player.name}}")
_, err := NewCatalog(rootDir)
require.Error(t, err)
require.True(t, errors.Is(err, ErrTemplateParseFailed))
}
func writeTemplateFile(t *testing.T, rootDir string, relativePath string, contents string) {
t.Helper()
absolutePath := filepath.Join(rootDir, relativePath)
require.NoError(t, os.MkdirAll(filepath.Dir(absolutePath), 0o755))
require.NoError(t, os.WriteFile(absolutePath, []byte(contents), 0o644))
}