Files
key-cape/src/internal/config/config_test.go
tegwick 06d20c3379
Some checks failed
Build and Publish Container Image / build-and-push (push) Has been cancelled
Load LLDAP organizational unit config
2026-05-25 00:28:33 +02:00

342 lines
9.2 KiB
Go

package config_test
import (
"os"
"path/filepath"
"testing"
"keycape/internal/config"
)
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// writeTempFile creates a temporary file with the given content and returns its path.
func writeTempFile(t *testing.T, content string) string {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "keycape-test-*")
if err != nil {
t.Fatalf("create temp file: %v", err)
}
if _, err := f.WriteString(content); err != nil {
t.Fatalf("write temp file: %v", err)
}
f.Close()
return f.Name()
}
// validConfig returns a minimal valid Config for use in tests.
func validConfig(keyPath string) *config.Config {
return &config.Config{
Issuer: "https://auth.example.com",
Port: 8080,
TokenLifetime: "15m",
PrivateKeyPEM: keyPath,
Environment: "dev",
Clients: []config.ClientConfig{
{
ClientID: "test-app",
DisplayName: "Test App",
RedirectURIs: []string{"https://app.example.com/callback"},
ClientType: "public",
},
},
}
}
// ---------------------------------------------------------------------------
// Load tests
// ---------------------------------------------------------------------------
func TestLoad_ValidYAML(t *testing.T) {
keyPath := writeTempFile(t, "placeholder-key")
yaml := `
issuer: "https://auth.example.com"
port: 8080
tokenLifetime: "15m"
privateKeyPem: "` + keyPath + `"
environment: "dev"
clients:
- clientId: "demo"
displayName: "Demo"
redirectUris:
- "https://demo.example.com/cb"
clientType: "public"
`
cfgPath := writeTempFile(t, yaml)
cfg, err := config.Load(cfgPath)
if err != nil {
t.Fatalf("Load: unexpected error: %v", err)
}
if cfg.Issuer != "https://auth.example.com" {
t.Errorf("Issuer: want %q, got %q", "https://auth.example.com", cfg.Issuer)
}
if cfg.Port != 8080 {
t.Errorf("Port: want 8080, got %d", cfg.Port)
}
if len(cfg.Clients) != 1 {
t.Errorf("Clients: want 1, got %d", len(cfg.Clients))
}
}
func TestLoad_AutheliaSplitURLs(t *testing.T) {
keyPath := writeTempFile(t, "placeholder-key")
yaml := `
issuer: "https://kc.example.com"
port: 8080
tokenLifetime: "15m"
privateKeyPem: "` + keyPath + `"
environment: "dev"
authelia:
baseURL: "http://authelia.sso.svc.cluster.local:9091"
browserBaseURL: "https://auth.example.com"
tokenBaseURL: "http://authelia.sso.svc.cluster.local:9091"
clientId: "keycape"
clientSecret: "secret"
redirectURI: "https://kc.example.com/authorize/callback"
clients:
- clientId: "netkingdom-bootstrap-console"
displayName: "NetKingdom Bootstrap Console"
redirectUris:
- "http://127.0.0.1:8876/oidc/callback"
- "http://localhost:8876/oidc/callback"
clientType: "public"
`
cfgPath := writeTempFile(t, yaml)
cfg, err := config.Load(cfgPath)
if err != nil {
t.Fatalf("Load: unexpected error: %v", err)
}
if cfg.Authelia.BaseURL != "http://authelia.sso.svc.cluster.local:9091" {
t.Errorf("Authelia.BaseURL: got %q", cfg.Authelia.BaseURL)
}
if cfg.Authelia.BrowserBaseURL != "https://auth.example.com" {
t.Errorf("Authelia.BrowserBaseURL: got %q", cfg.Authelia.BrowserBaseURL)
}
if cfg.Authelia.TokenBaseURL != "http://authelia.sso.svc.cluster.local:9091" {
t.Errorf("Authelia.TokenBaseURL: got %q", cfg.Authelia.TokenBaseURL)
}
if len(cfg.Clients) != 1 || cfg.Clients[0].ClientID != "netkingdom-bootstrap-console" {
t.Fatalf("bootstrap client not loaded: %+v", cfg.Clients)
}
if got := cfg.Clients[0].RedirectURIs; len(got) != 2 || got[0] != "http://127.0.0.1:8876/oidc/callback" {
t.Errorf("bootstrap redirect URIs not loaded: %+v", got)
}
}
func TestLoad_PrivacyIDEARequireForAll(t *testing.T) {
keyPath := writeTempFile(t, "placeholder-key")
yaml := `
issuer: "https://kc.example.com"
port: 8080
tokenLifetime: "15m"
privateKeyPem: "` + keyPath + `"
environment: "dev"
privacyidea:
baseURL: "http://privacyidea.mfa.svc.cluster.local:8080"
adminToken: "service-token"
realm: "coulomb"
requireForAll: true
clients:
- clientId: "netkingdom-bootstrap-console"
displayName: "NetKingdom Bootstrap Console"
redirectUris:
- "http://127.0.0.1:8876/oidc/callback"
clientType: "public"
`
cfgPath := writeTempFile(t, yaml)
cfg, err := config.Load(cfgPath)
if err != nil {
t.Fatalf("Load: unexpected error: %v", err)
}
if cfg.PrivacyIDEA.Realm != "coulomb" {
t.Errorf("PrivacyIDEA.Realm: got %q", cfg.PrivacyIDEA.Realm)
}
if !cfg.PrivacyIDEA.RequireForAll {
t.Error("PrivacyIDEA.RequireForAll should load from YAML")
}
}
func TestLoad_LLDAPOrganisationalUnits(t *testing.T) {
keyPath := writeTempFile(t, "placeholder-key")
yaml := `
issuer: "https://kc.example.com"
port: 8080
tokenLifetime: "15m"
privateKeyPem: "` + keyPath + `"
environment: "dev"
lldap:
url: "ldap://lldap.sso.svc.cluster.local:3890"
bindDN: "uid=admin,ou=people,dc=netkingdom,dc=local"
bindPW: "secret"
baseDN: "dc=netkingdom,dc=local"
userOU: "ou=people"
groupOU: "ou=groups"
clients:
- clientId: "netkingdom-bootstrap-console"
displayName: "NetKingdom Bootstrap Console"
redirectUris:
- "http://127.0.0.1:8876/oidc/callback"
clientType: "public"
`
cfgPath := writeTempFile(t, yaml)
cfg, err := config.Load(cfgPath)
if err != nil {
t.Fatalf("Load: unexpected error: %v", err)
}
if cfg.LLDAP.UserOU != "ou=people" {
t.Errorf("LLDAP.UserOU: got %q", cfg.LLDAP.UserOU)
}
if cfg.LLDAP.GroupOU != "ou=groups" {
t.Errorf("LLDAP.GroupOU: got %q", cfg.LLDAP.GroupOU)
}
}
func TestLoad_FileNotFound(t *testing.T) {
_, err := config.Load(filepath.Join(t.TempDir(), "nonexistent.yaml"))
if err == nil {
t.Error("Load: expected error for missing file, got nil")
}
}
func TestLoad_InvalidYAML(t *testing.T) {
bad := writeTempFile(t, "not: valid: yaml: [[[")
_, err := config.Load(bad)
if err == nil {
t.Error("Load: expected error for invalid YAML, got nil")
}
}
// ---------------------------------------------------------------------------
// Validate tests
// ---------------------------------------------------------------------------
func TestValidate_ValidConfig(t *testing.T) {
keyPath := writeTempFile(t, "key")
errs := config.ValidateConfig(validConfig(keyPath))
if len(errs) != 0 {
t.Errorf("ValidateConfig: expected no errors, got %v", errs)
}
}
func TestValidate_MissingIssuer(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Issuer = ""
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "issuer") {
t.Errorf("expected issuer error, got %v", errs)
}
}
func TestValidate_InvalidIssuerURL(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Issuer = "not a url"
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "issuer") {
t.Errorf("expected issuer URL error, got %v", errs)
}
}
func TestValidate_PortZero(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Port = 0
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "port") {
t.Errorf("expected port error, got %v", errs)
}
}
func TestValidate_PortTooHigh(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Port = 70000
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "port") {
t.Errorf("expected port error, got %v", errs)
}
}
func TestValidate_NoClients(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Clients = nil
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "client") {
t.Errorf("expected client error, got %v", errs)
}
}
func TestValidate_ClientMissingRedirectURI(t *testing.T) {
keyPath := writeTempFile(t, "key")
cfg := validConfig(keyPath)
cfg.Clients[0].RedirectURIs = nil
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "redirect") {
t.Errorf("expected redirect_uri error, got %v", errs)
}
}
func TestValidate_MissingPrivateKeyPEM(t *testing.T) {
cfg := validConfig("")
cfg.PrivateKeyPEM = ""
errs := config.ValidateConfig(cfg)
if !containsErr(errs, "privateKeyPem") {
t.Errorf("expected privateKeyPem error, got %v", errs)
}
}
// ---------------------------------------------------------------------------
// Env var loading test
// ---------------------------------------------------------------------------
func TestLoad_FromEnvVar(t *testing.T) {
keyPath := writeTempFile(t, "key")
yaml := `
issuer: "https://auth.example.com"
port: 9090
tokenLifetime: "30m"
privateKeyPem: "` + keyPath + `"
environment: "dev"
clients:
- clientId: "env-app"
displayName: "Env App"
redirectUris:
- "https://env.example.com/cb"
clientType: "public"
`
cfgPath := writeTempFile(t, yaml)
t.Setenv("KEYCAPE_CONFIG", cfgPath)
// Load with empty path triggers env var lookup.
cfg, err := config.Load("")
if err != nil {
t.Fatalf("Load with env var: %v", err)
}
if cfg.Port != 9090 {
t.Errorf("Port: want 9090, got %d", cfg.Port)
}
}
// ---------------------------------------------------------------------------
// Helper
// ---------------------------------------------------------------------------
func containsErr(errs []string, substring string) bool {
for _, e := range errs {
for i := 0; i <= len(e)-len(substring); i++ {
if e[i:i+len(substring)] == substring {
return true
}
}
}
return false
}