package oidc_test import ( "errors" "keycape/internal/domain" "keycape/internal/server/oidc" "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestOptionalMFAFollowsEnrollment(t *testing.T) { for _, tc := range []struct { name string enrolled bool lookupErr error acr []string want int challenge bool }{ {name: "unenrolled despite global requirement", want: http.StatusFound}, {name: "enrolled", enrolled: true, want: http.StatusOK, challenge: true}, {name: "lookup unavailable", lookupErr: errors.New("provider unavailable"), want: http.StatusInternalServerError}, {name: "explicit step up", acr: []string{"aal2"}, lookupErr: errors.New("must not query enrollment"), want: http.StatusOK, challenge: true}, } { t.Run(tc.name, func(t *testing.T) { sessions := oidc.NewSessionStore() h := &oidc.AuthorizeHandler{ ClientConfig: map[string]*domain.Client{"test-client": {ClientID: "test-client", MFAOptional: true}}, Auth: &mockAuthProvider{callbackResult: &domain.AuthResult{Username: "alice"}}, MFA: &mockMFAProvider{required: true, requiredErr: errors.New("global policy must not be queried"), enrolled: tc.enrolled, enrolledErr: tc.lookupErr}, Sessions: sessions, Emitter: &captureEmitter{}, } h.PendingStates().Store("optional", &oidc.PendingState{ClientID: "test-client", RedirectURI: "https://app.example/callback", State: "optional", ACRValues: tc.acr, ExpiresAt: time.Now().Add(time.Minute)}) rec := httptest.NewRecorder() h.ServeHTTPCallback(rec, httptest.NewRequest(http.MethodGet, "/authorize/callback?code=x&state=optional", nil)) if rec.Code != tc.want { t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String()) } if strings.Contains(rec.Body.String(), "KeyCape MFA") != tc.challenge { t.Fatal("unexpected MFA challenge state") } if tc.want != http.StatusFound && strings.Contains(rec.Header().Get("Location"), "code=") { t.Fatal("issued authorization code before MFA") } }) } }