forked from baron/baron-sso
df 클라이언트 상세 조회 테스트 추가
This commit is contained in:
@@ -118,6 +118,24 @@ func (m *mockConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID s
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// --- Mock Secret Repository ---
|
||||
|
||||
type mockSecretRepo struct {
|
||||
secrets map[string]string
|
||||
}
|
||||
|
||||
func (m *mockSecretRepo) Upsert(ctx context.Context, clientID, secret string) error {
|
||||
if m.secrets == nil { m.secrets = make(map[string]string) }
|
||||
m.secrets[clientID] = secret
|
||||
return nil
|
||||
}
|
||||
func (m *mockSecretRepo) GetByID(ctx context.Context, clientID string) (string, error) {
|
||||
return m.secrets[clientID], nil
|
||||
}
|
||||
func (m *mockSecretRepo) Delete(ctx context.Context, clientID string) error {
|
||||
delete(m.secrets, clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- HTTP Mock Helpers ---
|
||||
|
||||
|
||||
142
backend/internal/handler/dev_handler_test.go
Normal file
142
backend/internal/handler/dev_handler_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/service"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestListClients_Success(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path == "/clients" {
|
||||
return httpJSONAny(r, http.StatusOK, []map[string]interface{}{
|
||||
{"client_id": "client-1", "client_name": "App One", "metadata": map[string]interface{}{"status": "active"}},
|
||||
{"client_id": "client-2", "client_name": "App Two", "metadata": map[string]interface{}{"status": "inactive"}},
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusNotFound, map[string]string{"error":"not found"}), nil
|
||||
})
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
}
|
||||
app := fiber.New()
|
||||
app.Get("/api/v1/dev/clients", h.ListClients)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var res struct {
|
||||
Items []clientSummary `json:"items"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&res)
|
||||
assert.Equal(t, 2, len(res.Items))
|
||||
assert.Equal(t, "client-1", res.Items[0].ID)
|
||||
assert.Equal(t, "App One", res.Items[0].Name)
|
||||
}
|
||||
|
||||
func TestGetClient_Success(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path == "/clients/client-123" {
|
||||
return httpJSONAny(r, http.StatusOK, map[string]interface{}{
|
||||
"client_id": "client-123",
|
||||
"client_name": "Test App",
|
||||
"metadata": map[string]interface{}{"status": "active"},
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusNotFound, map[string]string{"error":"not found"}), nil
|
||||
})
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
PublicURL: "http://hydra-public.test", // PublicURL 추가
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
}
|
||||
app := fiber.New()
|
||||
app.Get("/api/v1/dev/clients/:id", h.GetClient)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/client-123", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var res clientDetailResponse
|
||||
json.NewDecoder(resp.Body).Decode(&res)
|
||||
assert.Equal(t, "client-123", res.Client.ID)
|
||||
assert.Equal(t, "Test App", res.Client.Name)
|
||||
assert.Equal(t, "http://hydra-public.test/oauth2/auth", res.Endpoints.Authorization)
|
||||
}
|
||||
|
||||
func TestGetClient_NotFound(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return httpJSONAny(r, http.StatusNotFound, map[string]string{"error":"not found"}), nil
|
||||
})
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
}
|
||||
app := fiber.New()
|
||||
app.Get("/api/v1/dev/clients/:id", h.GetClient)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/non-existent", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestCreateClient_Success(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.Method == http.MethodPost && r.URL.Path == "/clients" {
|
||||
return httpJSONAny(r, http.StatusCreated, map[string]interface{}{
|
||||
"client_id": "new-client-123",
|
||||
"client_name": "New App",
|
||||
"client_secret": "secret-123",
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusInternalServerError, map[string]string{"error":"hydra error"}), nil
|
||||
})
|
||||
|
||||
secretRepo := &mockSecretRepo{secrets: make(map[string]string)}
|
||||
redisRepo := &mockRedisRepo{data: make(map[string]string)}
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
SecretRepo: secretRepo,
|
||||
Redis: redisRepo,
|
||||
}
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/dev/clients", h.CreateClient)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"client_name": "New App",
|
||||
"type": "confidential",
|
||||
"redirectUris": []string{"http://localhost/cb"},
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
secret, _ := secretRepo.GetByID(nil, "new-client-123")
|
||||
assert.Equal(t, "secret-123", secret)
|
||||
}
|
||||
45
docs/frontend_hydra_testing_guide.md
Normal file
45
docs/frontend_hydra_testing_guide.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Frontend 기능과 백엔드 테스트 매핑 가이드
|
||||
|
||||
이 문서는 `devfront`와 `userfront`의 Hydra 관련 기능이 백엔드의 어떤 API를 호출하고, 해당 API가 어떤 테스트 코드로 검증되는지 설명합니다. 모든 기능은 백엔드에 이미 구현되어 있으며, '테스트' 열은 해당 기능을 검증하는 자동화 테스트의 존재 여부를 나타냅니다.
|
||||
|
||||
## 1. `devfront` (개발자/관리자 포털)
|
||||
|
||||
`devfront`는 OAuth2 클라이언트(RP)를 생성하고 관리하는 데 사용됩니다.
|
||||
|
||||
| `devfront` 기능 | 백엔드 API | 검증 테스트 파일 | 테스트 상태 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **클라이언트 목록 조회** | `GET /api/v1/dev/clients` | `dev_handler_test.go` | `TestListClients_Success` |
|
||||
| **클라이언트 생성** | `POST /api/v1/dev/clients` | `dev_handler_test.go` | `TestCreateClient_Success` |
|
||||
| **클라이언트 상세 조회** | `GET /api/v1/dev/clients/:id` | `dev_handler_test.go` | `TestGetClient_Success`, `TestGetClient_NotFound` |
|
||||
| **클라이언트 정보 수정** | `PUT /api/v1/dev/clients/:id` | - | (테스트 미작성) |
|
||||
| **클라이언트 상태 변경** | `PATCH /api/v1/dev/clients/:id/status`| - | (테스트 미작성) |
|
||||
| **클라이언트 삭제** | `DELETE /api/v1/dev/clients/:id` | - | (테스트 미작성) |
|
||||
| **시크릿 재발급** | `POST /api/v1/dev/clients/:id/rotate-secret`| - | (테스트 미작성) |
|
||||
| **동의한 사용자 목록 조회**| `GET /api/v1/dev/consents` | - | (테스트 미작성) |
|
||||
| **사용자 동의 철회** | `DELETE /api/v1/dev/consents` | - | (테스트 미작성) |
|
||||
|
||||
*참고: `dev_handler.go` 내의 기능들은 백엔드에 구현되어 있으나, 이번 커버리지 90% 달성 목표(핵심 인증 로직 중심)에서 관리자 기능으로 분류되어 우선순위가 조정되었습니다.*
|
||||
|
||||
---
|
||||
|
||||
## 2. `userfront` (사용자 포털)
|
||||
|
||||
`userfront`는 최종 사용자가 애플리케이션(RP)의 정보 접근 요청을 승인하거나 거부하는 OIDC 동의 화면 및 연동 관리를 처리합니다.
|
||||
|
||||
### 2.1. OIDC 동의 (Consent) 및 연동 관리
|
||||
| `userfront` 기능 | 백엔드 API | 검증 테스트 파일 | 테스트 상태 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **동의 정보 조회** | `GET /api/v1/auth/consent` | `auth_handler_consent_test.go` | `TestGetConsentRequest_Normal` |
|
||||
| **동의 승인** | `POST /api/v1/auth/consent/accept` | `auth_handler_consent_test.go` | `TestAcceptConsentRequest_Normal` |
|
||||
| **동의 거부** | `POST /api/v1/auth/consent/reject` | - | (테스트 미작성) |
|
||||
| **연동된 앱 목록 조회** | `GET /api/v1/user/rp/linked` | `auth_handler_linked_test.go` | `TestListLinkedRps_PriorityAndAggregation` |
|
||||
| **연동 해제 (Revoke)** | `DELETE /api/v1/user/rp/linked/:id`| `auth_handler_client_test.go` | `TestRevokeLinkedRp_Success` |
|
||||
| **연동 이력 조회** | `GET /api/v1/user/rp/history` | `auth_handler_client_test.go` | `TestListRpHistory_Aggregation` |
|
||||
|
||||
### 2.2. 인증 플로우 (Login Flows)
|
||||
| `userfront` 기능 | 백엔드 API | 검증 테스트 파일 | 테스트 상태 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **QR 로그인 초기화** | `POST /api/v1/auth/qr/init` | `auth_handler_qr_test.go` | `TestQRLoginFlow_Success` |
|
||||
| **QR 로그인 승인 (Scan)** | `POST /api/v1/auth/qr/approve` | `auth_handler_qr_test.go` | `TestScanQRLogin_Success` |
|
||||
| **매직 링크 초기화** | `POST /api/v1/auth/enchanted-link/init`| `auth_handler_link_test.go` | `TestEnchantedLinkFlow_Email_Success` |
|
||||
| **매직 링크 검증** | `POST /api/v1/auth/magic-link/verify` | `auth_handler_link_test.go` | `TestEnchantedLinkFlow_Email_Success` |
|
||||
Reference in New Issue
Block a user