package config import ( "os" "path/filepath" "testing" "time" ) // validConfig returns a minimal valid TOML config string. func validConfig() string { return ` [server] listen_addr = "0.0.0.0:8443" tls_cert = "/etc/mcias/server.crt" tls_key = "/etc/mcias/server.key" [database] path = "/var/lib/mcias/mcias.db" [tokens] issuer = "https://auth.example.com" default_expiry = "720h" admin_expiry = "8h" service_expiry = "8760h" [argon2] time = 3 memory = 65536 threads = 4 [master_key] passphrase_env = "MCIAS_MASTER_PASSPHRASE" ` } func writeTempConfig(t *testing.T, content string) string { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "mcias.toml") if err := os.WriteFile(path, []byte(content), 0600); err != nil { t.Fatalf("write temp config: %v", err) } return path } func TestLoadValidConfig(t *testing.T) { path := writeTempConfig(t, validConfig()) cfg, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Server.ListenAddr != "0.0.0.0:8443" { t.Errorf("ListenAddr = %q, want %q", cfg.Server.ListenAddr, "0.0.0.0:8443") } if cfg.Tokens.Issuer != "https://auth.example.com" { t.Errorf("Issuer = %q, want %q", cfg.Tokens.Issuer, "https://auth.example.com") } if cfg.DefaultExpiry() != 720*time.Hour { t.Errorf("DefaultExpiry = %v, want %v", cfg.DefaultExpiry(), 720*time.Hour) } if cfg.AdminExpiry() != 8*time.Hour { t.Errorf("AdminExpiry = %v, want %v", cfg.AdminExpiry(), 8*time.Hour) } if cfg.ServiceExpiry() != 8760*time.Hour { t.Errorf("ServiceExpiry = %v, want %v", cfg.ServiceExpiry(), 8760*time.Hour) } if cfg.Argon2.Time != 3 { t.Errorf("Argon2.Time = %d, want 3", cfg.Argon2.Time) } if cfg.Argon2.Memory != 65536 { t.Errorf("Argon2.Memory = %d, want 65536", cfg.Argon2.Memory) } if cfg.MasterKey.PassphraseEnv != "MCIAS_MASTER_PASSPHRASE" { t.Errorf("MasterKey.PassphraseEnv = %q", cfg.MasterKey.PassphraseEnv) } } func TestLoadMissingFile(t *testing.T) { _, err := Load("/nonexistent/path/mcias.toml") if err == nil { t.Error("expected error for missing file, got nil") } } func TestLoadInvalidTOML(t *testing.T) { path := writeTempConfig(t, "this is not valid TOML {{{{") _, err := Load(path) if err == nil { t.Error("expected error for invalid TOML, got nil") } } func TestValidateMissingListenAddr(t *testing.T) { path := writeTempConfig(t, ` [server] tls_cert = "/etc/mcias/server.crt" tls_key = "/etc/mcias/server.key" [database] path = "/var/lib/mcias/mcias.db" [tokens] issuer = "https://auth.example.com" default_expiry = "720h" admin_expiry = "8h" service_expiry = "8760h" [argon2] time = 3 memory = 65536 threads = 4 [master_key] passphrase_env = "MCIAS_MASTER_PASSPHRASE" `) _, err := Load(path) if err == nil { t.Error("expected error for missing listen_addr, got nil") } } func TestValidateArgon2TooWeak(t *testing.T) { tests := []struct { name string time uint32 memory uint32 }{ {"time too low", 1, 65536}, {"memory too low", 3, 32768}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { content := validConfig() // Override argon2 section path := writeTempConfig(t, content) cfg, err := Load(path) if err != nil { t.Fatalf("baseline load failed: %v", err) } // Manually set unsafe params and re-validate cfg.Argon2.Time = tc.time cfg.Argon2.Memory = tc.memory if err := cfg.validate(); err == nil { t.Errorf("expected validation error for time=%d memory=%d, got nil", tc.time, tc.memory) } }) } } func TestValidateMasterKeyBothSet(t *testing.T) { path := writeTempConfig(t, ` [server] listen_addr = "0.0.0.0:8443" tls_cert = "/etc/mcias/server.crt" tls_key = "/etc/mcias/server.key" [database] path = "/var/lib/mcias/mcias.db" [tokens] issuer = "https://auth.example.com" default_expiry = "720h" admin_expiry = "8h" service_expiry = "8760h" [argon2] time = 3 memory = 65536 threads = 4 [master_key] passphrase_env = "MCIAS_MASTER_PASSPHRASE" keyfile = "/etc/mcias/master.key" `) _, err := Load(path) if err == nil { t.Error("expected error when both passphrase_env and keyfile are set, got nil") } } func TestValidateMasterKeyNoneSet(t *testing.T) { path := writeTempConfig(t, ` [server] listen_addr = "0.0.0.0:8443" tls_cert = "/etc/mcias/server.crt" tls_key = "/etc/mcias/server.key" [database] path = "/var/lib/mcias/mcias.db" [tokens] issuer = "https://auth.example.com" default_expiry = "720h" admin_expiry = "8h" service_expiry = "8760h" [argon2] time = 3 memory = 65536 threads = 4 [master_key] `) _, err := Load(path) if err == nil { t.Error("expected error when neither passphrase_env nor keyfile is set, got nil") } } func TestDurationParsing(t *testing.T) { var d duration if err := d.UnmarshalText([]byte("1h30m")); err != nil { t.Fatalf("unexpected error: %v", err) } if d.Duration != 90*time.Minute { t.Errorf("Duration = %v, want %v", d.Duration, 90*time.Minute) } if err := d.UnmarshalText([]byte("not-a-duration")); err == nil { t.Error("expected error for invalid duration, got nil") } }