package handler import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/service" "encoding/json" "net/http" "net/http/httptest" "net/url" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" ) // --- Helper --- func newLinkedRpTestApp(h *AuthHandler) *fiber.App { app := fiber.New() app.Get("/api/v1/user/rp/linked", h.ListLinkedRps) return app } // --- Tests --- func TestListLinkedRps_PriorityAndAggregation(t *testing.T) { transport := roundTripFunc(func(r *http.Request) (*http.Response, error) { switch r.URL.Host { case "kratos.test": if r.URL.Path == "/sessions/whoami" { if r.Header.Get("X-Session-Token") == "" && r.Header.Get("Cookie") == "" { return httpResponse(r, http.StatusUnauthorized, "unauthorized"), nil } return httpJSONAny(r, http.StatusOK, map[string]interface{}{ "identity": map[string]interface{}{ "id": "user-123", "traits": map[string]interface{}{ "email": "user@test.com", }, }, }), nil } case "hydra.test": if r.URL.Path == "/oauth2/auth/sessions/consent" { return httpJSONAny(r, http.StatusOK, []map[string]interface{}{ { "client": map[string]interface{}{ "client_id": "devfront", "client_name": "DevFront", "redirect_uris": []string{ "https://active.example.com/callback", }, }, "grant_scope": []string{"openid", "profile"}, "handled_at": time.Now().Format(time.RFC3339), }, }), nil } if r.URL.Path == "/admin/clients/client-audit" { return httpJSONAny(r, http.StatusOK, map[string]interface{}{ "client_id": "client-audit", "client_name": "Audit App", }), nil } if r.URL.Path == "/admin/clients/client-consent" { return httpJSONAny(r, http.StatusOK, map[string]interface{}{ "client_id": "client-consent", "client_name": "Consent App", }), nil } } return httpResponse(r, http.StatusNotFound, "not found"), nil }) client := &http.Client{Transport: transport} origDefault := http.DefaultClient http.DefaultClient = client defer func() { http.DefaultClient = origDefault }() auditRepo := &mockAuditRepo{ logs: []domain.AuditLog{ { UserID: "user-123", EventType: "consent.granted", Timestamp: time.Now().Add(-10 * time.Hour), Details: `{"client_id":"client-audit", "scopes":["audit_scope"]}`, }, }, } consentRepo := &mockConsentRepo{ consents: []domain.ClientConsent{ { Subject: "user-123", ClientID: "client-consent", GrantedScopes: []string{"consent_scope"}, UpdatedAt: time.Now().Add(-2 * time.Hour), }, }, } h := &AuthHandler{ Hydra: &service.HydraAdminService{ AdminURL: "http://hydra.test", HTTPClient: client, }, AuditRepo: auditRepo, ConsentRepo: consentRepo, KratosAdmin: new(MockKratosAdminService), } t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test") t.Setenv("KRATOS_ADMIN_URL", "http://kratos.test") t.Setenv("HYDRA_PUBLIC_URL", "https://sso.example.com/oidc") t.Setenv("DEVFRONT_URL", "http://localhost:5174") app := newLinkedRpTestApp(h) req := httptest.NewRequest(http.MethodGet, "/api/v1/user/rp/linked", nil) req.Header.Set("Cookie", "ory_kratos_session=valid") resp, err := app.Test(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) var res struct { Items []struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` Scopes []string `json:"scopes"` InitURL string `json:"init_url"` } `json:"items"` } json.NewDecoder(resp.Body).Decode(&res) assert.Equal(t, 3, len(res.Items)) statusMap := make(map[string]string) for _, item := range res.Items { statusMap[item.ID] = item.Status } assert.Equal(t, "active", statusMap["devfront"]) assert.Equal(t, "inactive", statusMap["client-consent"]) assert.Equal(t, "inactive", statusMap["client-audit"]) var activeInitURL string for _, item := range res.Items { if item.ID == "devfront" { activeInitURL = item.InitURL break } } parsedInitURL, err := url.Parse(activeInitURL) assert.NoError(t, err) assert.Equal(t, "http", parsedInitURL.Scheme) assert.Equal(t, "localhost:5174", parsedInitURL.Host) assert.Equal(t, "/login", parsedInitURL.Path) assert.Equal(t, "1", parsedInitURL.Query().Get("auto")) assert.Equal(t, "/clients", parsedInitURL.Query().Get("returnTo")) }