summaryrefslogtreecommitdiff
path: root/internal/config/initializer_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/config/initializer_test.go')
-rw-r--r--internal/config/initializer_test.go260
1 files changed, 260 insertions, 0 deletions
diff --git a/internal/config/initializer_test.go b/internal/config/initializer_test.go
new file mode 100644
index 0000000..12adf57
--- /dev/null
+++ b/internal/config/initializer_test.go
@@ -0,0 +1,260 @@
+package config
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestParseConfigLoadsDefaultXDGConfig(t *testing.T) {
+ home := t.TempDir()
+ t.Setenv("HOME", home)
+
+ configPath := filepath.Join(home, ".config", "dtail", "dtail.conf")
+ if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
+ t.Fatalf("mkdir failed: %v", err)
+ }
+ writeTestConfig(t, configPath, `{"Common":{"LogLevel":"debug"}}`)
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+
+ if err := in.parseConfig(&Args{}); err != nil {
+ t.Fatalf("parseConfig failed: %v", err)
+ }
+ if in.Common.LogLevel != "debug" {
+ t.Fatalf("expected log level debug, got %q", in.Common.LogLevel)
+ }
+}
+
+// TestParseConfigFirstWins verifies that when both candidate config files
+// exist, the XDG path (~/.config/dtail/dtail.conf) takes precedence and the
+// second file (~/.dtail.conf) is ignored entirely — no silent merging.
+func TestParseConfigFirstWins(t *testing.T) {
+ home := t.TempDir()
+ t.Setenv("HOME", home)
+
+ xdgPath := filepath.Join(home, ".config", "dtail", "dtail.conf")
+ if err := os.MkdirAll(filepath.Dir(xdgPath), 0o755); err != nil {
+ t.Fatalf("mkdir failed: %v", err)
+ }
+ writeTestConfig(t, xdgPath, `{"Common":{"LogLevel":"warn"}}`)
+
+ homePath := filepath.Join(home, ".dtail.conf")
+ // The second file would override LogLevel to "error" if merging occurred.
+ writeTestConfig(t, homePath, `{"Common":{"LogLevel":"error"}}`)
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+
+ if err := in.parseConfig(&Args{}); err != nil {
+ t.Fatalf("parseConfig failed: %v", err)
+ }
+ // First-wins: the XDG config must have set the level; the home config
+ // must have been skipped, so "error" must NOT appear.
+ if in.Common.LogLevel != "warn" {
+ t.Fatalf("expected log level warn (first file wins), got %q", in.Common.LogLevel)
+ }
+}
+
+// TestParseConfigFallsBackToHomeConfig verifies that when only the legacy
+// ~/.dtail.conf exists it is loaded as the effective configuration.
+func TestParseConfigFallsBackToHomeConfig(t *testing.T) {
+ home := t.TempDir()
+ t.Setenv("HOME", home)
+
+ // Only create the fallback file; the XDG directory does not exist.
+ homePath := filepath.Join(home, ".dtail.conf")
+ writeTestConfig(t, homePath, `{"Common":{"LogLevel":"error"}}`)
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+
+ if err := in.parseConfig(&Args{}); err != nil {
+ t.Fatalf("parseConfig failed: %v", err)
+ }
+ if in.Common.LogLevel != "error" {
+ t.Fatalf("expected log level error from fallback config, got %q", in.Common.LogLevel)
+ }
+}
+
+// TestParseConfigNoConfigFile verifies that parseConfig succeeds without
+// error when neither candidate config file is present.
+func TestParseConfigNoConfigFile(t *testing.T) {
+ home := t.TempDir()
+ t.Setenv("HOME", home)
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+
+ // No config files created; must return nil, not an error.
+ if err := in.parseConfig(&Args{}); err != nil {
+ t.Fatalf("expected no error when no config file exists, got: %v", err)
+ }
+}
+
+// TestResolveSSHKeyPath verifies the three-level precedence used when
+// resolving the effective SSH private key path.
+func TestResolveSSHKeyPath(t *testing.T) {
+ tests := []struct {
+ name string
+ cli string
+ authKey string
+ legacy string
+ expected string
+ }{
+ {
+ name: "cli flag wins over both env vars",
+ cli: "/cli/key",
+ authKey: "/auth/key",
+ legacy: "/legacy/key",
+ expected: "/cli/key",
+ },
+ {
+ name: "DTAIL_AUTH_KEY_PATH wins over legacy when cli is empty",
+ cli: "",
+ authKey: "/auth/key",
+ legacy: "/legacy/key",
+ expected: "/auth/key",
+ },
+ {
+ name: "DTAIL_SSH_PRIVATE_KEYFILE_PATH used when auth key env is also empty",
+ cli: "",
+ authKey: "",
+ legacy: "/legacy/key",
+ expected: "/legacy/key",
+ },
+ {
+ name: "all empty returns empty string",
+ cli: "",
+ authKey: "",
+ legacy: "",
+ expected: "",
+ },
+ {
+ name: "cli wins when only cli is set",
+ cli: "/cli/key",
+ authKey: "",
+ legacy: "",
+ expected: "/cli/key",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ got := resolveSSHKeyPath(tc.cli, tc.authKey, tc.legacy)
+ if got != tc.expected {
+ t.Fatalf("resolveSSHKeyPath(%q, %q, %q) = %q; want %q",
+ tc.cli, tc.authKey, tc.legacy, got, tc.expected)
+ }
+ })
+ }
+}
+
+// TestProcessEnvVarsAuthKeyPathTakesPrecedence is a negative/regression test
+// that confirms the bug described in task k6 is fixed: when both
+// DTAIL_AUTH_KEY_PATH and DTAIL_SSH_PRIVATE_KEYFILE_PATH are set,
+// DTAIL_AUTH_KEY_PATH must win.
+func TestProcessEnvVarsAuthKeyPathTakesPrecedence(t *testing.T) {
+ t.Setenv("DTAIL_AUTH_KEY_PATH", "/env/auth/key")
+ t.Setenv("DTAIL_SSH_PRIVATE_KEYFILE_PATH", "/env/legacy/key")
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+ args := &Args{}
+ in.processEnvVars(args)
+
+ if args.SSHPrivateKeyFilePath != "/env/auth/key" {
+ t.Fatalf("expected DTAIL_AUTH_KEY_PATH to win, got %q", args.SSHPrivateKeyFilePath)
+ }
+}
+
+// TestProcessEnvVarsLegacyFallback verifies that DTAIL_SSH_PRIVATE_KEYFILE_PATH
+// is still applied when DTAIL_AUTH_KEY_PATH is not set.
+func TestProcessEnvVarsLegacyFallback(t *testing.T) {
+ t.Setenv("DTAIL_AUTH_KEY_PATH", "")
+ t.Setenv("DTAIL_SSH_PRIVATE_KEYFILE_PATH", "/env/legacy/key")
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+ args := &Args{}
+ in.processEnvVars(args)
+
+ if args.SSHPrivateKeyFilePath != "/env/legacy/key" {
+ t.Fatalf("expected legacy env var to be used, got %q", args.SSHPrivateKeyFilePath)
+ }
+}
+
+// TestProcessEnvVarsCLIFlagNotOverridden verifies that an explicit CLI flag
+// value is not overridden by either environment variable.
+func TestProcessEnvVarsCLIFlagNotOverridden(t *testing.T) {
+ t.Setenv("DTAIL_AUTH_KEY_PATH", "/env/auth/key")
+ t.Setenv("DTAIL_SSH_PRIVATE_KEYFILE_PATH", "/env/legacy/key")
+
+ in := initializer{
+ Common: newDefaultCommonConfig(),
+ Server: newDefaultServerConfig(),
+ Client: newDefaultClientConfig(),
+ }
+ args := &Args{SSHPrivateKeyFilePath: "/cli/explicit/key"}
+ in.processEnvVars(args)
+
+ if args.SSHPrivateKeyFilePath != "/cli/explicit/key" {
+ t.Fatalf("expected CLI flag to be preserved, got %q", args.SSHPrivateKeyFilePath)
+ }
+}
+
+// TestDefaultAuthKeyPathNoLiteralTilde is a regression test for the bug
+// described in task l6: when neither os.UserHomeDir() nor the HOME environment
+// variable can be resolved, defaultAuthKeyPath must return "" rather than the
+// literal string "~/.ssh/id_rsa" which the SSH library cannot expand.
+func TestDefaultAuthKeyPathNoLiteralTilde(t *testing.T) {
+ // Unset HOME so that os.UserHomeDir() fails and the fallback env var is
+ // also empty. t.Setenv restores the original value after the test.
+ t.Setenv("HOME", "")
+
+ got := defaultAuthKeyPath()
+ if got == "~/.ssh/id_rsa" {
+ t.Fatal("defaultAuthKeyPath returned literal '~/.ssh/id_rsa' when HOME is empty; expected \"\"")
+ }
+ if got != "" {
+ t.Fatalf("defaultAuthKeyPath returned %q when HOME is empty; expected \"\"", got)
+ }
+}
+
+// TestDefaultAuthKeyPathWithHome verifies that when HOME is set, the returned
+// path is an absolute path constructed with filepath.Join (no literal '~').
+func TestDefaultAuthKeyPathWithHome(t *testing.T) {
+ t.Setenv("HOME", "/home/testuser")
+
+ got := defaultAuthKeyPath()
+ want := "/home/testuser/.ssh/id_rsa"
+ if got != want {
+ t.Fatalf("defaultAuthKeyPath() = %q; want %q", got, want)
+ }
+}
+
+func writeTestConfig(t *testing.T, path, body string) {
+ t.Helper()
+ if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
+ t.Fatalf("write config failed: %v", err)
+ }
+}