Files
gochat/backend/internal/config/config_test.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

239 lines
7.6 KiB
Go

package config
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestValidate_ValidConfig(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{
Host: "localhost",
Port: 5432,
User: "gochat",
Password: "secret",
Name: "gochat_db",
},
Redis: RedisConfig{
URL: "redis://localhost:6379",
},
JWT: JWTConfig{
Secret: "test-secret-key-min-32-chars!!",
ExpiryHours: 24,
RefreshExpiryHours: 168,
},
Log: LogConfig{Level: "info", Format: "json"},
Captain: CaptainConfig{Enabled: false},
Worker: WorkerConfig{Concurrency: 4},
OAuth: OAuthConfig{},
RateLimit: RateLimitConfig{Enabled: true, RequestsPerMinute: 100, WindowSeconds: 60},
Search: SearchConfig{Engine: "meilisearch", Host: "http://localhost:7700", IndexPrefix: "gochat_", TimeoutSeconds: 5},
}
err := Validate(cfg)
assert.NoError(t, err)
}
func TestValidate_InvalidPort(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 0, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid server port")
}
func TestValidate_InvalidMode(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "invalid"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid server mode")
}
func TestValidate_MissingDBHost(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "database host is required")
}
func TestValidate_MissingDBName(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "database name is required")
}
func TestValidate_MissingRedisURL(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "redis url is required")
}
func TestValidate_InvalidRedisURL(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "not-a-valid-url://::"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid redis URL")
}
func TestValidate_JWTSecretInProduction(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "release"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "change-me-in-production"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "JWT secret must be changed in production")
}
func TestValidate_InvalidLogLevel(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
Log: LogConfig{Level: "invalid"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid log level")
}
func TestValidate_InvalidWorkerConcurrency(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
Log: LogConfig{Level: "info"},
Worker: WorkerConfig{Concurrency: 0},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "worker concurrency")
}
func TestValidate_SearchMeilisearchRequiresValidHost(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
Log: LogConfig{Level: "info"},
Worker: WorkerConfig{Concurrency: 1},
RateLimit: RateLimitConfig{RequestsPerMinute: 100, WindowSeconds: 60},
Search: SearchConfig{Engine: "meilisearch", Host: "not a url", TimeoutSeconds: 5},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid search.host")
}
func TestValidate_SearchDBFallbackAllowed(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "debug"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
Log: LogConfig{Level: "info"},
Worker: WorkerConfig{Concurrency: 1},
RateLimit: RateLimitConfig{RequestsPerMinute: 100, WindowSeconds: 60},
Search: SearchConfig{Engine: "db"},
}
err := Validate(cfg)
assert.NoError(t, err)
}
func TestValidate_SearchDBFallbackRejectedInRelease(t *testing.T) {
cfg := &Config{
Server: ServerConfig{Host: "localhost", Port: 8080, Mode: "release"},
Database: DatabaseConfig{Host: "localhost", Name: "db", User: "user"},
Redis: RedisConfig{URL: "redis://localhost:6379"},
JWT: JWTConfig{Secret: "test-secret-key-min-32-chars!!"},
Log: LogConfig{Level: "info"},
Worker: WorkerConfig{Concurrency: 1},
RateLimit: RateLimitConfig{RequestsPerMinute: 100, WindowSeconds: 60},
Search: SearchConfig{Engine: "db"},
}
err := Validate(cfg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "release mode requires meilisearch")
}
func TestDatabaseConfig_DSN(t *testing.T) {
cfg := DatabaseConfig{
Host: "localhost",
Port: 5432,
User: "gochat",
Password: "secret",
DBName: "gochat_db",
SSLMode: "disable",
}
dsn := cfg.DSN()
assert.Contains(t, dsn, "host=localhost")
assert.Contains(t, dsn, "port=5432")
assert.Contains(t, dsn, "user=gochat")
assert.Contains(t, dsn, "dbname=gochat_db")
assert.Contains(t, dsn, "sslmode=disable")
}
func TestJWTConfig_ExpiryDuration(t *testing.T) {
cfg := JWTConfig{ExpiryHours: 24}
dur := cfg.ExpiryDuration()
assert.Equal(t, 24*time.Hour, dur)
}
func TestServerConfig_Address(t *testing.T) {
cfg := ServerConfig{Host: "0.0.0.0", Port: 3000}
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
assert.Equal(t, "0.0.0.0:3000", addr)
}