/* 이 테스트 파일은 HydraAdminService의 기능을 검증하기 위한 유닛 테스트입니다. Hydra Admin API와의 통신을 시뮬레이션하기 위해 httptest 패키지를 사용하여 실제 네트워크 호출 없이 HTTP 핸들러를 모킹(Mocking)하는 방식으로 작성되었습니다. 주요 테스트 항목: 1. 클라이언트 관리: List, Get, Create, Update, Patch, Delete (성공 및 NotFound 케이스) 2. OIDC 인증/동의 흐름: GetConsentRequest, AcceptConsentRequest, RejectConsentRequest, GetLoginRequest, AcceptLoginRequest, RejectLoginRequest 3. 세션 관리: ListConsentSessions, RevokeConsentSessions 4. 존재하지 않는 리소스 접근 시 에러 처리 */ package service import ( "baron-sso-backend/internal/domain" "bytes" "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" ) // clientForHandler는 주어진 핸들러로 요청을 보내는 http.Client를 반환합니다. // ory_service_test.go에 정의된 것과 동일한 로직입니다. func mockHydraClient(h http.Handler) *http.Client { return &http.Client{ Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { var bodyBytes []byte if req.Body != nil { bodyBytes, _ = io.ReadAll(req.Body) } r := httptest.NewRequest(req.Method, req.URL.String(), bytes.NewReader(bodyBytes)) r.Header = req.Header.Clone() w := httptest.NewRecorder() h.ServeHTTP(w, r) return w.Result(), nil }), } } func TestHydraAdminService_ListClients(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || !strings.Contains(r.URL.Path, "/clients") { t.Errorf("unexpected request: %s %s", r.Method, r.URL.String()) return } clients := []domain.HydraClient{ {ClientID: "client-1", ClientName: "Client One"}, {ClientID: "client-2", ClientName: "Client Two"}, } json.NewEncoder(w).Encode(clients) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } clients, err := svc.ListClients(context.Background(), 10, 0) if err != nil { t.Fatalf("ListClients failed: %v", err) } if len(clients) != 2 { t.Errorf("expected 2 clients, got %d", len(clients)) } } func TestHydraAdminService_GetClient_Success(t *testing.T) { targetID := "test-client" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { client := domain.HydraClient{ ClientID: targetID, ClientName: "Test Client", } json.NewEncoder(w).Encode(client) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } client, err := svc.GetClient(context.Background(), targetID) if err != nil { t.Fatalf("GetClient failed: %v", err) } if client.ClientID != targetID { t.Errorf("expected %s, got %s", targetID, client.ClientID) } } func TestHydraAdminService_GetClient_NotFound(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } _, err := svc.GetClient(context.Background(), "non-existent") if err != ErrHydraNotFound { t.Errorf("expected ErrHydraNotFound, got %v", err) } } func TestHydraAdminService_PatchClientStatus(t *testing.T) { targetID := "test-client" status := "inactive" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPatch { t.Errorf("expected PATCH, got %s", r.Method) } var patch []map[string]interface{} json.NewDecoder(r.Body).Decode(&patch) if patch[0]["value"] != status { t.Errorf("expected status %s, got %v", status, patch[0]["value"]) } client := domain.HydraClient{ClientID: targetID} json.NewEncoder(w).Encode(client) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } _, err := svc.PatchClientStatus(context.Background(), targetID, status) if err != nil { t.Fatalf("PatchClientStatus failed: %v", err) } } func TestHydraAdminService_CreateClient(t *testing.T) { newClient := domain.HydraClient{ClientID: "new-client"} handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(newClient) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } created, err := svc.CreateClient(context.Background(), newClient) if err != nil { t.Fatalf("CreateClient failed: %v", err) } if created.ClientID != newClient.ClientID { t.Errorf("expected %s, got %s", newClient.ClientID, created.ClientID) } } func TestHydraAdminService_UpdateClient(t *testing.T) { targetID := "test-client" updatedClient := domain.HydraClient{ClientID: targetID, ClientName: "Updated"} handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { t.Errorf("expected PUT, got %s", r.Method) } json.NewEncoder(w).Encode(updatedClient) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } res, err := svc.UpdateClient(context.Background(), targetID, updatedClient) if err != nil { t.Fatalf("UpdateClient failed: %v", err) } if res.ClientName != "Updated" { t.Errorf("expected Updated, got %s", res.ClientName) } } func TestHydraAdminService_DeleteClient(t *testing.T) { targetID := "test-client" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { t.Errorf("expected DELETE, got %s", r.Method) } w.WriteHeader(http.StatusNoContent) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } err := svc.DeleteClient(context.Background(), targetID) if err != nil { t.Fatalf("DeleteClient failed: %v", err) } } func TestHydraAdminService_ListConsentSessions(t *testing.T) { subject := "user-123" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("subject") != subject { t.Errorf("expected subject %s, got %s", subject, r.URL.Query().Get("subject")) } sessions := []domain.HydraConsentSession{{Subject: subject}} json.NewEncoder(w).Encode(sessions) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } res, err := svc.ListConsentSessions(context.Background(), subject, "") if err != nil { t.Fatalf("ListConsentSessions failed: %v", err) } if len(res) != 1 { t.Errorf("expected 1 session, got %d", len(res)) } } func TestHydraAdminService_RevokeConsentSessions(t *testing.T) { subject := "user-123" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { t.Errorf("expected DELETE, got %s", r.Method) } w.WriteHeader(http.StatusNoContent) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } err := svc.RevokeConsentSessions(context.Background(), subject, "client-1") if err != nil { t.Fatalf("RevokeConsentSessions failed: %v", err) } } func TestHydraAdminService_GetConsentRequest(t *testing.T) { challenge := "consent-challenge" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("consent_challenge") != challenge { t.Errorf("expected challenge %s, got %s", challenge, r.URL.Query().Get("consent_challenge")) } resp := domain.HydraConsentRequest{Challenge: challenge} json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } req, err := svc.GetConsentRequest(context.Background(), challenge) if err != nil { t.Fatalf("GetConsentRequest failed: %v", err) } if req.Challenge != challenge { t.Errorf("expected %s, got %s", challenge, req.Challenge) } } func TestHydraAdminService_RejectConsentRequest(t *testing.T) { challenge := "consent-challenge" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { t.Errorf("expected PUT, got %s", r.Method) } resp := struct { RedirectTo string `json:"redirect_to"` }{ RedirectTo: "http://redirect", } json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } resp, err := svc.RejectConsentRequest(context.Background(), challenge) if err != nil { t.Fatalf("RejectConsentRequest failed: %v", err) } if resp.RedirectTo != "http://redirect" { t.Errorf("expected http://redirect, got %s", resp.RedirectTo) } } func TestHydraAdminService_RejectLoginRequest(t *testing.T) { challenge := "login-challenge" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := struct { RedirectTo string `json:"redirect_to"` }{ RedirectTo: "http://redirect", } json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } resp, err := svc.RejectLoginRequest(context.Background(), challenge, "error", "desc") if err != nil { t.Fatalf("RejectLoginRequest failed: %v", err) } if resp.RedirectTo != "http://redirect" { t.Errorf("expected http://redirect, got %s", resp.RedirectTo) } } func TestHydraAdminService_GetLoginRequest(t *testing.T) { challenge := "login-challenge" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := domain.HydraLoginRequest{Challenge: challenge} json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } req, err := svc.GetLoginRequest(context.Background(), challenge) if err != nil { t.Fatalf("GetLoginRequest failed: %v", err) } if req.Challenge != challenge { t.Errorf("expected %s, got %s", challenge, req.Challenge) } } func TestHydraAdminService_AcceptConsentRequest(t *testing.T) { challenge := "consent-challenge" grantInfo := &domain.HydraConsentRequest{RequestedScope: []string{"openid"}} handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := struct { RedirectTo string `json:"redirect_to"` }{ RedirectTo: "http://callback", } json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } resp, err := svc.AcceptConsentRequest(context.Background(), challenge, grantInfo, nil) if err != nil { t.Fatalf("AcceptConsentRequest failed: %v", err) } if resp.RedirectTo != "http://callback" { t.Errorf("expected http://callback, got %s", resp.RedirectTo) } } func TestHydraAdminService_AcceptLoginRequest(t *testing.T) { challenge := "login-challenge" subject := "user@example.com" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := struct { RedirectTo string `json:"redirect_to"` }{ RedirectTo: "http://callback", } json.NewEncoder(w).Encode(resp) }) svc := &HydraAdminService{ AdminURL: "http://hydra:4445", HTTPClient: mockHydraClient(handler), } resp, err := svc.AcceptLoginRequest(context.Background(), challenge, subject) if err != nil { t.Fatalf("AcceptLoginRequest failed: %v", err) } if resp.RedirectTo != "http://callback" { t.Errorf("expected http://callback, got %s", resp.RedirectTo) } }