go/pkg/i18n/mode_test.go
Snider 285a4582b0 fix(i18n): address thread-safety issues from code review
- hooks.go: Use atomic.Value for missingKeyHandler global
- loader.go: Use sync.Once for FSLoader.Languages() caching
- service.go: Use atomic.Pointer[Service] for defaultService

All globals that could race between goroutines now use proper
synchronization primitives.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 19:02:30 +00:00

161 lines
3.3 KiB
Go

package i18n
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMode_String(t *testing.T) {
tests := []struct {
mode Mode
expected string
}{
{ModeNormal, "normal"},
{ModeStrict, "strict"},
{ModeCollect, "collect"},
{Mode(99), "unknown"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.mode.String())
})
}
}
func TestMissingKey(t *testing.T) {
mk := MissingKey{
Key: "test.missing.key",
Args: map[string]any{"Name": "test"},
CallerFile: "/path/to/file.go",
CallerLine: 42,
}
assert.Equal(t, "test.missing.key", mk.Key)
assert.Equal(t, "test", mk.Args["Name"])
assert.Equal(t, "/path/to/file.go", mk.CallerFile)
assert.Equal(t, 42, mk.CallerLine)
}
func TestOnMissingKey(t *testing.T) {
// Reset handler after test
defer OnMissingKey(nil)
t.Run("sets handler", func(t *testing.T) {
var received MissingKey
OnMissingKey(func(mk MissingKey) {
received = mk
})
dispatchMissingKey("test.key", map[string]any{"foo": "bar"})
assert.Equal(t, "test.key", received.Key)
assert.Equal(t, "bar", received.Args["foo"])
})
t.Run("nil handler", func(t *testing.T) {
OnMissingKey(nil)
// Should not panic
dispatchMissingKey("test.key", nil)
})
t.Run("replaces previous handler", func(t *testing.T) {
called1 := false
called2 := false
OnMissingKey(func(mk MissingKey) {
called1 = true
})
OnMissingKey(func(mk MissingKey) {
called2 = true
})
dispatchMissingKey("test.key", nil)
assert.False(t, called1)
assert.True(t, called2)
})
}
func TestServiceMode(t *testing.T) {
// Reset default service after tests
originalService := defaultService.Load()
defer func() {
defaultService.Store(originalService)
}()
t.Run("default mode is normal", func(t *testing.T) {
defaultService.Store(nil)
defaultOnce = sync.Once{}
defaultErr = nil
svc, err := New()
require.NoError(t, err)
assert.Equal(t, ModeNormal, svc.Mode())
})
t.Run("set mode", func(t *testing.T) {
svc, err := New()
require.NoError(t, err)
svc.SetMode(ModeStrict)
assert.Equal(t, ModeStrict, svc.Mode())
svc.SetMode(ModeCollect)
assert.Equal(t, ModeCollect, svc.Mode())
svc.SetMode(ModeNormal)
assert.Equal(t, ModeNormal, svc.Mode())
})
}
func TestModeNormal_MissingKey(t *testing.T) {
svc, err := New()
require.NoError(t, err)
svc.SetMode(ModeNormal)
// Missing key should return the key itself
result := svc.T("nonexistent.key")
assert.Equal(t, "nonexistent.key", result)
}
func TestModeStrict_MissingKey(t *testing.T) {
svc, err := New()
require.NoError(t, err)
svc.SetMode(ModeStrict)
// Missing key should panic
assert.Panics(t, func() {
svc.T("nonexistent.key")
})
}
func TestModeCollect_MissingKey(t *testing.T) {
// Reset handler after test
defer OnMissingKey(nil)
svc, err := New()
require.NoError(t, err)
svc.SetMode(ModeCollect)
var received MissingKey
OnMissingKey(func(mk MissingKey) {
received = mk
})
// Missing key should dispatch action and return [key]
result := svc.T("nonexistent.key", map[string]any{"arg": "value"})
assert.Equal(t, "[nonexistent.key]", result)
assert.Equal(t, "nonexistent.key", received.Key)
assert.Equal(t, "value", received.Args["arg"])
assert.NotEmpty(t, received.CallerFile)
assert.Greater(t, received.CallerLine, 0)
}