package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "codeberg.org/SquidSE/ClusterCanvas/service/internal/settings" ) type securityResponseTest struct { Security settings.SecuritySettings `json:"security"` } func TestSecurityGetReturnsDefaultsWhenMissing(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) request := httptest.NewRequest(http.MethodGet, "/api/v1/security", nil) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, recorder.Code) } var payload securityResponseTest if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } want := settings.DefaultSecuritySettings() if payload.Security != want { t.Fatalf("got %#v, want %#v", payload.Security, want) } } func TestSecurityPutRejectsIdleTimeoutZero(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) body := []byte(`{ "security": { "idle_timeout_minutes": 0, "session_lifetime_hours": 24, "reauth_sensitive_actions": false, "reauth_grace_minutes": 15 } }`) request := httptest.NewRequest(http.MethodPut, "/api/v1/security", bytes.NewReader(body)) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, recorder.Code) } } func TestSecurityPutRejectsIdleTimeoutAboveMax(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) body := []byte(`{ "security": { "idle_timeout_minutes": 241, "session_lifetime_hours": 24, "reauth_sensitive_actions": false, "reauth_grace_minutes": 15 } }`) request := httptest.NewRequest(http.MethodPut, "/api/v1/security", bytes.NewReader(body)) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, recorder.Code) } } func TestSecurityPutRejectsSessionLifetimeZero(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) body := []byte(`{ "security": { "idle_timeout_minutes": 30, "session_lifetime_hours": 0, "reauth_sensitive_actions": false, "reauth_grace_minutes": 15 } }`) request := httptest.NewRequest(http.MethodPut, "/api/v1/security", bytes.NewReader(body)) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, recorder.Code) } } func TestSecurityPutRoundTrip(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) body := []byte(`{ "security": { "idle_timeout_minutes": 45, "session_lifetime_hours": 12, "reauth_sensitive_actions": true, "reauth_grace_minutes": 0, "totp_enabled": true } }`) putReq := httptest.NewRequest(http.MethodPut, "/api/v1/security", bytes.NewReader(body)) putRec := httptest.NewRecorder() router.ServeHTTP(putRec, putReq) if putRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, putRec.Code) } getReq := httptest.NewRequest(http.MethodGet, "/api/v1/security", nil) getRec := httptest.NewRecorder() router.ServeHTTP(getRec, getReq) if getRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, getRec.Code) } var payload securityResponseTest if err := json.NewDecoder(getRec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } want := settings.SecuritySettings{ IdleTimeoutMinutes: 45, SessionLifetimeHours: 12, ReauthSensitiveActions: true, ReauthGraceMinutes: 0, TotpEnabled: true, } if payload.Security != want { t.Fatalf("got %#v, want %#v", payload.Security, want) } }