Files
galaxy-game/mail/internal/app/runtime_test.go
T
2026-04-17 18:39:16 +02:00

185 lines
5.1 KiB
Go

package app
import (
"context"
"io"
"log/slog"
"net"
"os"
"path/filepath"
"testing"
"time"
"galaxy/mail/internal/config"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/require"
)
func TestNewRuntimeStartsWithStubMode(t *testing.T) {
t.Parallel()
redisServer := miniredis.RunT(t)
templateDir := writeStage6Templates(t)
cfg := config.DefaultConfig()
cfg.Redis.Addr = redisServer.Addr()
cfg.Templates.Dir = templateDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
runtime, err := NewRuntime(context.Background(), cfg, testLogger())
require.NoError(t, err)
require.NoError(t, runtime.Close())
}
func TestNewRuntimeRejectsInvalidRedisConfig(t *testing.T) {
t.Parallel()
templateDir := writeStage6Templates(t)
cfg := config.DefaultConfig()
cfg.Redis.Addr = "127.0.0.1"
cfg.Templates.Dir = templateDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
_, err := NewRuntime(context.Background(), cfg, testLogger())
require.Error(t, err)
require.Contains(t, err.Error(), "redis addr")
}
func TestNewRuntimeRejectsUnavailableRedis(t *testing.T) {
t.Parallel()
templateDir := writeStage6Templates(t)
cfg := config.DefaultConfig()
cfg.Redis.Addr = "127.0.0.1:6399"
cfg.Redis.OperationTimeout = 100 * time.Millisecond
cfg.Templates.Dir = templateDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
_, err := NewRuntime(context.Background(), cfg, testLogger())
require.Error(t, err)
require.Contains(t, err.Error(), "ping redis")
}
func TestNewRuntimeRejectsMissingTemplateDirectory(t *testing.T) {
t.Parallel()
redisServer := miniredis.RunT(t)
cfg := config.DefaultConfig()
cfg.Redis.Addr = redisServer.Addr()
cfg.Templates.Dir = filepath.Join(t.TempDir(), "missing")
cfg.InternalHTTP.Addr = mustFreeAddr(t)
_, err := NewRuntime(context.Background(), cfg, testLogger())
require.Error(t, err)
require.Contains(t, err.Error(), "template catalog")
}
func TestNewRuntimeRejectsMissingRequiredTemplateFile(t *testing.T) {
t.Parallel()
redisServer := miniredis.RunT(t)
rootDir := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(rootDir, "auth.login_code", "en"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "auth.login_code", "en", "subject.tmpl"), []byte("Subject"), 0o644))
cfg := config.DefaultConfig()
cfg.Redis.Addr = redisServer.Addr()
cfg.Templates.Dir = rootDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
_, err := NewRuntime(context.Background(), cfg, testLogger())
require.Error(t, err)
require.Contains(t, err.Error(), "text.tmpl")
}
func TestNewRuntimeRejectsBrokenTemplateCatalog(t *testing.T) {
t.Parallel()
redisServer := miniredis.RunT(t)
rootDir := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(rootDir, "auth.login_code", "en"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "auth.login_code", "en", "subject.tmpl"), []byte("Your login code"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "auth.login_code", "en", "text.tmpl"), []byte("Code: {{.code}}"), 0o644))
require.NoError(t, os.MkdirAll(filepath.Join(rootDir, "game.turn_ready", "en"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "game.turn_ready", "en", "subject.tmpl"), []byte("{{if .turn_number}"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "game.turn_ready", "en", "text.tmpl"), []byte("Turn ready"), 0o644))
cfg := config.DefaultConfig()
cfg.Redis.Addr = redisServer.Addr()
cfg.Templates.Dir = rootDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
_, err := NewRuntime(context.Background(), cfg, testLogger())
require.Error(t, err)
require.Contains(t, err.Error(), "template parse failed")
}
func TestRuntimeRunStopsOnContextCancellation(t *testing.T) {
t.Parallel()
redisServer := miniredis.RunT(t)
templateDir := writeStage6Templates(t)
cfg := config.DefaultConfig()
cfg.Redis.Addr = redisServer.Addr()
cfg.Templates.Dir = templateDir
cfg.InternalHTTP.Addr = mustFreeAddr(t)
cfg.ShutdownTimeout = time.Second
runtime, err := NewRuntime(context.Background(), cfg, testLogger())
require.NoError(t, err)
defer func() {
require.NoError(t, runtime.Close())
}()
runCtx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() {
done <- runtime.Run(runCtx)
}()
time.Sleep(100 * time.Millisecond)
cancel()
require.Eventually(t, func() bool {
select {
case err := <-done:
return err == nil
default:
return false
}
}, 5*time.Second, 10*time.Millisecond)
}
func writeStage6Templates(t *testing.T) string {
t.Helper()
rootDir := t.TempDir()
templateDir := filepath.Join(rootDir, "auth.login_code", "en")
require.NoError(t, os.MkdirAll(templateDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(templateDir, "subject.tmpl"), []byte("Your login code"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(templateDir, "text.tmpl"), []byte("Code: {{.code}}"), 0o644))
return rootDir
}
func testLogger() *slog.Logger {
return slog.New(slog.NewJSONHandler(io.Discard, nil))
}
func mustFreeAddr(t *testing.T) string {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer func() {
require.NoError(t, listener.Close())
}()
return listener.Addr().String()
}