// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini package main import ( "os" "os/exec" "strings" "testing" ) // TestLoadConfigDefaults exercises the success path in-process: all required // vars set, defaults left unset, and asserts the documented defaults. func TestLoadConfigDefaults(t *testing.T) { t.Setenv("PDNS_API_KEY", "k") t.Setenv("MYSQL_DSN", "user:pass@tcp(127.0.0.1:3306)/db") t.Setenv("PUID", "1000") t.Setenv("PGID", "1000") os.Unsetenv("LISTEN_ADDR") os.Unsetenv("TABLE_PREFIX") os.Unsetenv("DEFAULT_TTL") os.Unsetenv("PDNS_SERVER_ID") os.Unsetenv("LOG_LEVEL") cfg := LoadConfig() if cfg.ListenAddr != ":3000" { t.Errorf("ListenAddr = %q, want :3000", cfg.ListenAddr) } if cfg.TablePrefix != "coredns_" { t.Errorf("TablePrefix = %q, want coredns_", cfg.TablePrefix) } if cfg.DefaultTTL != 120 { t.Errorf("DefaultTTL = %d, want 120", cfg.DefaultTTL) } if cfg.PDNSServerID != "localhost" { t.Errorf("PDNSServerID = %q, want localhost", cfg.PDNSServerID) } if cfg.LogLevel != "info" { t.Errorf("LogLevel = %q, want info", cfg.LogLevel) } } // TestLoadConfigFatal exercises the os.Exit(1) paths of LoadConfig via a // re-exec subprocess, since LoadConfig calls os.Exit directly and cannot be // asserted in-process. func TestLoadConfigFatal(t *testing.T) { baseEnv := map[string]string{ "PDNS_API_KEY": "k", "MYSQL_DSN": "user:pass@tcp(127.0.0.1:3306)/db", "PUID": "1000", "PGID": "1000", } tests := []struct { name string mutate map[string]string // overrides/additions to baseEnv; "" value means unset wantInErr string }{ {"missing PDNS_API_KEY", map[string]string{"PDNS_API_KEY": ""}, "PDNS_API_KEY"}, {"missing MYSQL_DSN", map[string]string{"MYSQL_DSN": ""}, "MYSQL_DSN"}, {"missing PUID", map[string]string{"PUID": ""}, "PUID"}, {"missing PGID", map[string]string{"PGID": ""}, "PGID"}, {"PUID zero rejected", map[string]string{"PUID": "0"}, "root"}, {"PGID zero rejected", map[string]string{"PGID": "0"}, "root"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { env := map[string]string{} for k, v := range baseEnv { env[k] = v } for k, v := range tt.mutate { env[k] = v } cmd := exec.Command(os.Args[0], "-test.run=TestConfigFatalHelper") cmd.Env = []string{"GO_CONFIG_CRASH=1"} for k, v := range env { if v != "" { cmd.Env = append(cmd.Env, k+"="+v) } } out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected non-zero exit, got success. output: %s", out) } exitErr, ok := err.(*exec.ExitError) if !ok { t.Fatalf("expected *exec.ExitError, got %T: %v", err, err) } if exitErr.ExitCode() == 0 { t.Fatalf("expected non-zero exit code") } if !strings.Contains(string(out), tt.wantInErr) { t.Fatalf("stderr output = %q, want substring %q", out, tt.wantInErr) } }) } } // TestConfigFatalHelper is not a real test; it's invoked as a subprocess by // TestLoadConfigFatal via -test.run, guarded by GO_CONFIG_CRASH so it never // runs (and never touches the DB) under a normal `go test` invocation. func TestConfigFatalHelper(t *testing.T) { if os.Getenv("GO_CONFIG_CRASH") == "" { t.Skip("only runs as a re-exec subprocess") } LoadConfig() }