mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 17:43:21 +01:00
feat(config): register all defaults in code, shrink example config
Only the database password is truly mandatory to get started, but config.example.json was 267 lines with 90+ options. Newcomers faced a wall of settings with no indication of what matters. Add registerDefaults() with all sane defaults via Viper so a minimal config (just DB credentials) produces a fully working server. Gameplay multipliers default to 1.0 instead of Go's zero value 0.0, which previously zeroed out all quest rewards for minimal configs. Uses dot-notation defaults for GameplayOptions/DebugOptions so users can override individual fields without losing other defaults. - Shrink config.example.json from 267 to 10 lines - Rename full original to config.reference.json as documentation - Simplify wizard buildDefaultConfig() from ~220 to ~12 lines - Fix latent bug: SaveDumps default used wrong key DevModeOptions - Add tests: minimal config, backward compat, single-field override - Update release workflow, README, CONTRIBUTING, docker/README
This commit is contained in:
@@ -2,8 +2,11 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// TestLoadConfigNoFile tests LoadConfig when config file doesn't exist
|
||||
@@ -497,3 +500,191 @@ func BenchmarkConfigCreation(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeMinimalConfig writes a minimal config.json to dir and returns its path.
|
||||
func writeMinimalConfig(t *testing.T, dir, content string) {
|
||||
t.Helper()
|
||||
if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(content), 0644); err != nil {
|
||||
t.Fatalf("writing config.json: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMinimalConfigDefaults verifies that a minimal config.json produces a fully
|
||||
// populated Config with sane defaults (multipliers not zero, entrance entries present, etc).
|
||||
func TestMinimalConfigDefaults(t *testing.T) {
|
||||
viper.Reset()
|
||||
dir := t.TempDir()
|
||||
origDir, _ := os.Getwd()
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
writeMinimalConfig(t, dir, `{
|
||||
"Database": { "Password": "test" }
|
||||
}`)
|
||||
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
|
||||
// Multipliers must be 1.0 (not Go's zero value 0.0)
|
||||
multipliers := map[string]float32{
|
||||
"HRPMultiplier": cfg.GameplayOptions.HRPMultiplier,
|
||||
"SRPMultiplier": cfg.GameplayOptions.SRPMultiplier,
|
||||
"GRPMultiplier": cfg.GameplayOptions.GRPMultiplier,
|
||||
"ZennyMultiplier": cfg.GameplayOptions.ZennyMultiplier,
|
||||
"MaterialMultiplier": cfg.GameplayOptions.MaterialMultiplier,
|
||||
"GCPMultiplier": cfg.GameplayOptions.GCPMultiplier,
|
||||
"GMaterialMultiplier": cfg.GameplayOptions.GMaterialMultiplier,
|
||||
}
|
||||
for name, val := range multipliers {
|
||||
if val != 1.0 {
|
||||
t.Errorf("%s = %v, want 1.0", name, val)
|
||||
}
|
||||
}
|
||||
|
||||
// Entrance entries should be present
|
||||
if len(cfg.Entrance.Entries) != 6 {
|
||||
t.Errorf("Entrance.Entries = %d, want 6", len(cfg.Entrance.Entries))
|
||||
}
|
||||
|
||||
// Commands should be present
|
||||
if len(cfg.Commands) != 12 {
|
||||
t.Errorf("Commands = %d, want 12", len(cfg.Commands))
|
||||
}
|
||||
|
||||
// Courses should be present
|
||||
if len(cfg.Courses) != 11 {
|
||||
t.Errorf("Courses = %d, want 11", len(cfg.Courses))
|
||||
}
|
||||
|
||||
// Standard ports
|
||||
if cfg.Sign.Port != 53312 {
|
||||
t.Errorf("Sign.Port = %d, want 53312", cfg.Sign.Port)
|
||||
}
|
||||
if cfg.API.Port != 8080 {
|
||||
t.Errorf("API.Port = %d, want 8080", cfg.API.Port)
|
||||
}
|
||||
if cfg.Entrance.Port != 53310 {
|
||||
t.Errorf("Entrance.Port = %d, want 53310", cfg.Entrance.Port)
|
||||
}
|
||||
|
||||
// Servers enabled by default
|
||||
if !cfg.Sign.Enabled {
|
||||
t.Error("Sign.Enabled should be true")
|
||||
}
|
||||
if !cfg.API.Enabled {
|
||||
t.Error("API.Enabled should be true")
|
||||
}
|
||||
if !cfg.Channel.Enabled {
|
||||
t.Error("Channel.Enabled should be true")
|
||||
}
|
||||
if !cfg.Entrance.Enabled {
|
||||
t.Error("Entrance.Enabled should be true")
|
||||
}
|
||||
|
||||
// Database defaults
|
||||
if cfg.Database.Host != "localhost" {
|
||||
t.Errorf("Database.Host = %q, want localhost", cfg.Database.Host)
|
||||
}
|
||||
if cfg.Database.Port != 5432 {
|
||||
t.Errorf("Database.Port = %d, want 5432", cfg.Database.Port)
|
||||
}
|
||||
|
||||
// ClientMode defaults to ZZ
|
||||
if cfg.RealClientMode != ZZ {
|
||||
t.Errorf("RealClientMode = %v, want ZZ", cfg.RealClientMode)
|
||||
}
|
||||
|
||||
// BinPath default
|
||||
if cfg.BinPath != "bin" {
|
||||
t.Errorf("BinPath = %q, want bin", cfg.BinPath)
|
||||
}
|
||||
|
||||
// Gameplay limits
|
||||
if cfg.GameplayOptions.MaximumNP != 100000 {
|
||||
t.Errorf("MaximumNP = %d, want 100000", cfg.GameplayOptions.MaximumNP)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFullConfigBackwardCompat verifies that existing full configs still load correctly.
|
||||
func TestFullConfigBackwardCompat(t *testing.T) {
|
||||
viper.Reset()
|
||||
dir := t.TempDir()
|
||||
origDir, _ := os.Getwd()
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the reference config (the full original config.example.json).
|
||||
// Look in the project root (one level up from config/).
|
||||
refPath := filepath.Join(origDir, "..", "config.reference.json")
|
||||
refData, err := os.ReadFile(refPath)
|
||||
if err != nil {
|
||||
t.Skipf("config.reference.json not found at %s, skipping backward compat test", refPath)
|
||||
}
|
||||
writeMinimalConfig(t, dir, string(refData))
|
||||
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() with full config error: %v", err)
|
||||
}
|
||||
|
||||
// Spot-check values from the reference config
|
||||
if cfg.GameplayOptions.HRPMultiplier != 1.0 {
|
||||
t.Errorf("HRPMultiplier = %v, want 1.0", cfg.GameplayOptions.HRPMultiplier)
|
||||
}
|
||||
if cfg.Sign.Port != 53312 {
|
||||
t.Errorf("Sign.Port = %d, want 53312", cfg.Sign.Port)
|
||||
}
|
||||
if len(cfg.Entrance.Entries) != 6 {
|
||||
t.Errorf("Entrance.Entries = %d, want 6", len(cfg.Entrance.Entries))
|
||||
}
|
||||
if len(cfg.Commands) != 12 {
|
||||
t.Errorf("Commands = %d, want 12", len(cfg.Commands))
|
||||
}
|
||||
if cfg.GameplayOptions.MaximumNP != 100000 {
|
||||
t.Errorf("MaximumNP = %d, want 100000", cfg.GameplayOptions.MaximumNP)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSingleFieldOverride verifies that overriding one field in a dot-notation
|
||||
// section doesn't clobber other fields' defaults.
|
||||
func TestSingleFieldOverride(t *testing.T) {
|
||||
viper.Reset()
|
||||
dir := t.TempDir()
|
||||
origDir, _ := os.Getwd()
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
writeMinimalConfig(t, dir, `{
|
||||
"Database": { "Password": "test" },
|
||||
"GameplayOptions": { "HRPMultiplier": 2.0 }
|
||||
}`)
|
||||
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
|
||||
// Overridden field
|
||||
if cfg.GameplayOptions.HRPMultiplier != 2.0 {
|
||||
t.Errorf("HRPMultiplier = %v, want 2.0", cfg.GameplayOptions.HRPMultiplier)
|
||||
}
|
||||
|
||||
// Other multipliers should retain defaults
|
||||
if cfg.GameplayOptions.SRPMultiplier != 1.0 {
|
||||
t.Errorf("SRPMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.SRPMultiplier)
|
||||
}
|
||||
if cfg.GameplayOptions.ZennyMultiplier != 1.0 {
|
||||
t.Errorf("ZennyMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.ZennyMultiplier)
|
||||
}
|
||||
if cfg.GameplayOptions.GCPMultiplier != 1.0 {
|
||||
t.Errorf("GCPMultiplier = %v, want 1.0 (should retain default)", cfg.GameplayOptions.GCPMultiplier)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user