check / check (push) Failing after 2s
DNSWATCHER_SKIP_RECORD_NOTIFICATIONS takes a comma-separated list of names from DNSWATCHER_TARGETS, read as the targets are (letter case, trailing dot, repeats). For a listed name no Record Change and no Inconsistency notification is sent; its records are still checked and saved, and its other notifications are sent. A listed name that is not a target stops startup with an error naming it. The two detections return early for a listed name. Apex domains are covered too: their own records go through the same detection. Live DNS cannot be made to disagree on purpose, so the Inconsistency test feeds records to the change detection directly, as the existing inconsistency tests do; the live test covers Record Change. Model: opus-5-5
299 lines
8.7 KiB
Go
299 lines
8.7 KiB
Go
package config_test
|
|
|
|
import (
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/config"
|
|
"sneak.berlin/go/dnswatcher/internal/globals"
|
|
"sneak.berlin/go/dnswatcher/internal/logger"
|
|
)
|
|
|
|
// newTestParams creates config.Params suitable for testing
|
|
// without requiring the fx dependency injection framework.
|
|
func newTestParams(t *testing.T) config.Params {
|
|
t.Helper()
|
|
|
|
g := &globals.Globals{
|
|
Appname: "dnswatcher",
|
|
Version: "test",
|
|
}
|
|
|
|
l, err := logger.New(nil, logger.Params{Globals: g})
|
|
require.NoError(t, err, "failed to create logger")
|
|
|
|
return config.Params{
|
|
Globals: g,
|
|
Logger: l,
|
|
}
|
|
}
|
|
|
|
// These tests exercise viper global state and MUST NOT use
|
|
// t.Parallel(). Each test resets viper for isolation.
|
|
|
|
func TestNew_DefaultValues(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com,www.example.com")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 8080, cfg.Port)
|
|
assert.False(t, cfg.Debug)
|
|
assert.Equal(t, "/var/lib/dnswatcher", cfg.DataDir)
|
|
assert.Equal(t, time.Hour, cfg.DNSInterval)
|
|
assert.Equal(t, 12*time.Hour, cfg.TLSInterval)
|
|
assert.Equal(t, 7, cfg.TLSExpiryWarning)
|
|
assert.False(t, cfg.MaintenanceMode)
|
|
assert.Empty(t, cfg.SlackWebhook)
|
|
assert.Empty(t, cfg.MattermostWebhook)
|
|
assert.Empty(t, cfg.NtfyTopic)
|
|
assert.Empty(t, cfg.SentryDSN)
|
|
assert.Empty(t, cfg.MetricsUsername)
|
|
assert.Empty(t, cfg.MetricsPassword)
|
|
assert.False(t, cfg.SendTestNotification)
|
|
assert.Empty(t, cfg.SkipRecordNotifications)
|
|
}
|
|
|
|
func TestNew_EnvironmentOverrides(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv("PORT", "9090")
|
|
t.Setenv("DNSWATCHER_DEBUG", "true")
|
|
t.Setenv("DNSWATCHER_DATA_DIR", "/tmp/test-data")
|
|
t.Setenv("DNSWATCHER_DNS_INTERVAL", "30m")
|
|
t.Setenv("DNSWATCHER_TLS_INTERVAL", "6h")
|
|
t.Setenv("DNSWATCHER_TLS_EXPIRY_WARNING", "14")
|
|
t.Setenv("DNSWATCHER_SLACK_WEBHOOK", "https://hooks.slack.com/t")
|
|
t.Setenv("DNSWATCHER_MATTERMOST_WEBHOOK", "https://mm.test/hooks/t")
|
|
t.Setenv("DNSWATCHER_NTFY_TOPIC", "https://ntfy.sh/test")
|
|
t.Setenv("DNSWATCHER_SENTRY_DSN", "https://sentry.test/1")
|
|
t.Setenv("DNSWATCHER_MAINTENANCE_MODE", "true")
|
|
t.Setenv("DNSWATCHER_METRICS_USERNAME", "admin")
|
|
t.Setenv("DNSWATCHER_METRICS_PASSWORD", "secret")
|
|
t.Setenv("DNSWATCHER_SEND_TEST_NOTIFICATION", "true")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 9090, cfg.Port)
|
|
assert.True(t, cfg.Debug)
|
|
assert.Equal(t, "/tmp/test-data", cfg.DataDir)
|
|
assert.Equal(t, 30*time.Minute, cfg.DNSInterval)
|
|
assert.Equal(t, 6*time.Hour, cfg.TLSInterval)
|
|
assert.Equal(t, 14, cfg.TLSExpiryWarning)
|
|
assert.Equal(t, "https://hooks.slack.com/t", cfg.SlackWebhook)
|
|
assert.Equal(t, "https://mm.test/hooks/t", cfg.MattermostWebhook)
|
|
assert.Equal(t, "https://ntfy.sh/test", cfg.NtfyTopic)
|
|
assert.Equal(t, "https://sentry.test/1", cfg.SentryDSN)
|
|
assert.True(t, cfg.MaintenanceMode)
|
|
assert.Equal(t, "admin", cfg.MetricsUsername)
|
|
assert.Equal(t, "secret", cfg.MetricsPassword)
|
|
assert.True(t, cfg.SendTestNotification)
|
|
}
|
|
|
|
func TestNew_NoTargetsError(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "")
|
|
|
|
_, err := config.New(nil, newTestParams(t))
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, config.ErrNoTargets)
|
|
}
|
|
|
|
func TestNew_OnlyEmptyCSVSegments(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", " , , ")
|
|
|
|
_, err := config.New(nil, newTestParams(t))
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, config.ErrNoTargets)
|
|
}
|
|
|
|
// TestNew_InvalidIntervalStopsStartup checks values that must stop startup;
|
|
// TestNew_DefaultValues and TestNew_EmptyIntervalMeansDefault check that an
|
|
// unset or empty interval means the default.
|
|
func TestNew_InvalidIntervalStopsStartup(t *testing.T) {
|
|
variables := []string{"DNSWATCHER_DNS_INTERVAL", "DNSWATCHER_TLS_INTERVAL"}
|
|
values := []string{
|
|
"banana", // not a duration
|
|
"5", // no unit
|
|
"1d", // days are not a unit time.ParseDuration knows
|
|
"0", // zero
|
|
"-1h", // negative
|
|
}
|
|
|
|
for _, variable := range variables {
|
|
for _, value := range values {
|
|
t.Run(variable+"="+value, func(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv(variable, value)
|
|
|
|
_, err := config.New(nil, newTestParams(t))
|
|
require.ErrorIs(t, err, config.ErrInvalidInterval)
|
|
require.ErrorContains(t, err, variable)
|
|
require.ErrorContains(t, err, strconv.Quote(value))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNew_EmptyIntervalMeansDefault(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv("DNSWATCHER_DNS_INTERVAL", "")
|
|
t.Setenv("DNSWATCHER_TLS_INTERVAL", "")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Hour, cfg.DNSInterval)
|
|
assert.Equal(t, 12*time.Hour, cfg.TLSInterval)
|
|
}
|
|
|
|
func TestNew_DebugEnablesDebugLogging(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv("DNSWATCHER_DEBUG", "true")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.True(t, cfg.Debug)
|
|
}
|
|
|
|
func TestNew_PortEnvNotPrefixed(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv("PORT", "3000")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3000, cfg.Port,
|
|
"PORT env should work without DNSWATCHER_ prefix")
|
|
}
|
|
|
|
func TestNew_TargetClassification(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS",
|
|
"example.com,www.example.com,api.example.com,example.org")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
|
|
// example.com and example.org are apex domains
|
|
assert.Len(t, cfg.Domains, 2)
|
|
// www.example.com and api.example.com are hostnames
|
|
assert.Len(t, cfg.Hostnames, 2)
|
|
}
|
|
|
|
func TestNew_InvalidTargetPublicSuffix(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "co.uk")
|
|
|
|
_, err := config.New(nil, newTestParams(t))
|
|
require.Error(t, err, "public suffix should be rejected")
|
|
}
|
|
|
|
func TestNew_EmptyAppnameDefaultsToDnswatcher(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
|
|
g := &globals.Globals{Appname: "", Version: "test"}
|
|
|
|
l, err := logger.New(nil, logger.Params{Globals: g})
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := config.New(
|
|
nil, config.Params{Globals: g, Logger: l},
|
|
)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 8080, cfg.Port,
|
|
"defaults should load when appname is empty")
|
|
}
|
|
|
|
func TestNew_TargetsWithWhitespace(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", " example.com , www.example.com ")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, len(cfg.Domains)+len(cfg.Hostnames),
|
|
"whitespace around targets should be trimmed")
|
|
}
|
|
|
|
func TestNew_TargetsWithTrailingComma(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com,www.example.com,")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, len(cfg.Domains)+len(cfg.Hostnames),
|
|
"trailing comma should be ignored")
|
|
}
|
|
|
|
func TestNew_SkipRecordNotifications(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.net,www.example.net,example.org")
|
|
t.Setenv("DNSWATCHER_SKIP_RECORD_NOTIFICATIONS",
|
|
" WWW.Example.net. , example.net,www.example.net")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t,
|
|
[]string{"www.example.net", "example.net"},
|
|
cfg.SkipRecordNotifications,
|
|
"names are written as targets are, each once",
|
|
)
|
|
}
|
|
|
|
func TestNew_SkipRecordNotificationsNotInTargetsStopsStartup(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.net")
|
|
t.Setenv("DNSWATCHER_SKIP_RECORD_NOTIFICATIONS",
|
|
"example.net,www.example.net")
|
|
|
|
_, err := config.New(nil, newTestParams(t))
|
|
require.ErrorIs(t, err, config.ErrNotInTargets)
|
|
require.ErrorContains(t, err, "DNSWATCHER_SKIP_RECORD_NOTIFICATIONS")
|
|
require.ErrorContains(t, err, `"www.example.net"`)
|
|
}
|
|
|
|
func TestNew_CustomDNSIntervalDuration(t *testing.T) {
|
|
viper.Reset()
|
|
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
|
t.Setenv("DNSWATCHER_DNS_INTERVAL", "5s")
|
|
|
|
cfg, err := config.New(nil, newTestParams(t))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 5*time.Second, cfg.DNSInterval)
|
|
}
|
|
|
|
func TestStatePath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
dataDir string
|
|
want string
|
|
}{
|
|
{"default", "/var/lib/dnswatcher", "/var/lib/dnswatcher/state.json"},
|
|
{"absolute", "/var/lib/dw", "/var/lib/dw/state.json"},
|
|
{"nested", "/opt/app/data", "/opt/app/data/state.json"},
|
|
{"empty", "", "/state.json"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := &config.Config{DataDir: tt.dataDir}
|
|
assert.Equal(t, tt.want, cfg.StatePath())
|
|
})
|
|
}
|
|
}
|