forked from baron/baron-sso
585 lines
20 KiB
Go
585 lines
20 KiB
Go
package handler
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"baron-sso-backend/internal/service"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type devMockRPUserMetadataRepo struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *devMockRPUserMetadataRepo) Get(ctx context.Context, clientID, userID string) (*domain.RPUserMetadata, error) {
|
|
args := m.Called(ctx, clientID, userID)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*domain.RPUserMetadata), args.Error(1)
|
|
}
|
|
|
|
func (m *devMockRPUserMetadataRepo) Upsert(ctx context.Context, metadata *domain.RPUserMetadata) error {
|
|
args := m.Called(ctx, metadata)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func TestDevHandler_RPUserMetadataRoundTrip(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"tenant_id": "tenant-1",
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "approvalLevel",
|
|
"valueType": "text",
|
|
"value": "A",
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "activeMember",
|
|
"valueType": "boolean",
|
|
"value": "true",
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "score",
|
|
"valueType": "number",
|
|
"value": "1",
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "featureList",
|
|
"valueType": "array",
|
|
"value": `["default"]`,
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "preferences",
|
|
"valueType": "object",
|
|
"value": `{"theme":"light","density":"comfortable"}`,
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "contractDate",
|
|
"valueType": "date",
|
|
"value": "2026-06-09",
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "approvedAt",
|
|
"valueType": "datetime",
|
|
"value": "2026-06-09T09:30",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Upsert", mock.Anything, mock.MatchedBy(func(row *domain.RPUserMetadata) bool {
|
|
return row.ClientID == "client-1" &&
|
|
row.UserID == "user-1" &&
|
|
row.Metadata["approvalLevel"] == "A" &&
|
|
row.Metadata["activeMember"] == false &&
|
|
row.Metadata["score"] == float64(42) &&
|
|
assert.ObjectsAreEqual([]any{"sso", "claims"}, row.Metadata["featureList"]) &&
|
|
assert.ObjectsAreEqual(map[string]any{"theme": "dark", "density": "compact"}, row.Metadata["preferences"]) &&
|
|
rpMetadataNumberEquals(row.Metadata["contractDate"], 1781017200) &&
|
|
rpMetadataNumberEquals(row.Metadata["approvedAt"], 1780968600) &&
|
|
row.Metadata["approvalLevel_permissions"].(map[string]any)["readPermission"] == "admin_only" &&
|
|
row.Metadata["approvalLevel_permissions"].(map[string]any)["writePermission"] == "user_and_admin" &&
|
|
row.Metadata["featureList_permissions"].(map[string]any)["readPermission"] == "admin_only" &&
|
|
row.Metadata["featureList_permissions"].(map[string]any)["writePermission"] == "admin_only"
|
|
})).Return(nil).Once()
|
|
repo.On("Get", mock.Anything, "client-1", "user-1").Return(&domain.RPUserMetadata{
|
|
ClientID: "client-1",
|
|
UserID: "user-1",
|
|
Metadata: domain.JSONMap{"approvalLevel": "A"},
|
|
}, nil).Once()
|
|
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "admin", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
app.Get("/api/v1/dev/clients/:id/users/:userId/metadata", h.GetRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{
|
|
"approvalLevel": "A",
|
|
"activeMember": false,
|
|
"score": 42,
|
|
"featureList": []string{"sso", "claims"},
|
|
"preferences": map[string]any{
|
|
"theme": "dark",
|
|
"density": "compact",
|
|
},
|
|
"contractDate": float64(1781017200),
|
|
"approvedAt": float64(1780968600),
|
|
"approvalLevel_permissions": map[string]any{
|
|
"writePermission": "user_and_admin",
|
|
},
|
|
},
|
|
})
|
|
putReq := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
putReq.Header.Set("Content-Type", "application/json")
|
|
putResp, _ := app.Test(putReq, -1)
|
|
assert.Equal(t, http.StatusOK, putResp.StatusCode)
|
|
|
|
getReq := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/client-1/users/user-1/metadata", nil)
|
|
getResp, _ := app.Test(getReq, -1)
|
|
assert.Equal(t, http.StatusOK, getResp.StatusCode)
|
|
|
|
var got map[string]any
|
|
assert.NoError(t, json.NewDecoder(getResp.Body).Decode(&got))
|
|
assert.Equal(t, "client-1", got["clientId"])
|
|
assert.Equal(t, "user-1", got["userId"])
|
|
assert.Equal(t, "A", got["metadata"].(map[string]any)["approvalLevel"])
|
|
repo.AssertExpectations(t)
|
|
}
|
|
|
|
func TestDevHandler_RPUserMetadataAdminUpsertRequiresRPManage(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"tenant_id": "tenant-1",
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "approvalLevel",
|
|
"valueType": "text",
|
|
"value": "A",
|
|
"readPermission": "user_and_admin",
|
|
"writePermission": "user_and_admin",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
t.Run("tenant grant does not allow rp user metadata admin upsert", func(t *testing.T) {
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Upsert", mock.Anything, mock.AnythingOfType("*domain.RPUserMetadata")).Return(nil).Maybe()
|
|
keto := new(devMockKetoService)
|
|
keto.On("CheckPermission", mock.Anything, "User:operator-1", "RelyingParty", "client-1", "manage").Return(false, nil)
|
|
keto.On("CheckPermission", mock.Anything, "User:operator-1", "Tenant", "tenant-1", "grant_dev_permissions").Return(true, nil).Maybe()
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
Keto: keto,
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "operator-1", Role: domain.RoleUser})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"approvalLevel": "B"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
repo.AssertNotCalled(t, "Upsert", mock.Anything, mock.Anything)
|
|
keto.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("rp manage allows rp user metadata admin upsert", func(t *testing.T) {
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Upsert", mock.Anything, mock.MatchedBy(func(row *domain.RPUserMetadata) bool {
|
|
return row.ClientID == "client-1" &&
|
|
row.UserID == "user-1" &&
|
|
row.Metadata["approvalLevel"] == "B"
|
|
})).Return(nil).Once()
|
|
keto := new(devMockKetoService)
|
|
keto.On("CheckPermission", mock.Anything, "User:operator-1", "RelyingParty", "client-1", "manage").Return(true, nil)
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
Keto: keto,
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "operator-1", Role: domain.RoleUser})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"approvalLevel": "B"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
repo.AssertExpectations(t)
|
|
keto.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestDevHandler_RPUserMetadataMirrorsToKratosTraits(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"tenant_id": "tenant-1",
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "approvalLevel",
|
|
"valueType": "text",
|
|
"value": "A",
|
|
"readPermission": "user_and_admin",
|
|
"writePermission": "admin_only",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Upsert", mock.Anything, mock.AnythingOfType("*domain.RPUserMetadata")).Return(nil).Once()
|
|
kratos := new(MockKratosAdmin)
|
|
kratos.On("GetIdentity", mock.Anything, "user-1").Return(&service.KratosIdentity{
|
|
ID: "user-1",
|
|
State: "active",
|
|
Traits: map[string]any{
|
|
"email": "user@example.com",
|
|
"name": "User One",
|
|
},
|
|
}, nil).Once()
|
|
var capturedTraits map[string]any
|
|
kratos.On("UpdateIdentity", mock.Anything, "user-1", mock.Anything, "active").Run(func(args mock.Arguments) {
|
|
capturedTraits = args.Get(2).(map[string]any)
|
|
}).Return(&service.KratosIdentity{ID: "user-1", State: "active", Traits: map[string]any{}}, nil).Once()
|
|
identityWriter := service.NewIdentityWriteService(kratos, nil)
|
|
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
KratosAdmin: kratos,
|
|
IdentityWriter: identityWriter,
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "admin", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"approvalLevel": "B"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
rpClaims := capturedTraits["rp_custom_claims"].(map[string]any)
|
|
clientClaims := rpClaims["client-1"].(domain.JSONMap)
|
|
require.Equal(t, "B", clientClaims["approvalLevel"])
|
|
require.Equal(t, map[string]any{
|
|
"readPermission": "user_and_admin",
|
|
"writePermission": "admin_only",
|
|
}, clientClaims["approvalLevel_permissions"])
|
|
repo.AssertExpectations(t)
|
|
kratos.AssertExpectations(t)
|
|
}
|
|
|
|
func rpMetadataNumberEquals(value any, want int64) bool {
|
|
switch typed := value.(type) {
|
|
case int64:
|
|
return typed == want
|
|
case int:
|
|
return int64(typed) == want
|
|
case float64:
|
|
return typed == float64(want)
|
|
case float32:
|
|
return float64(typed) == float64(want)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func TestDevHandler_SelfUpdateRPUserMetadataHonorsWritePermission(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"tenant_id": "tenant-1",
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "approvalLevel",
|
|
"valueType": "text",
|
|
"value": "A",
|
|
"readPermission": "user_and_admin",
|
|
"writePermission": "user_and_admin",
|
|
},
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "internalRank",
|
|
"valueType": "text",
|
|
"value": "S",
|
|
"readPermission": "admin_only",
|
|
"writePermission": "admin_only",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
t.Run("rejects admin_only claim", func(t *testing.T) {
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Upsert", mock.Anything, mock.AnythingOfType("*domain.RPUserMetadata")).Return(nil).Maybe()
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "user-1", Role: domain.RoleUser})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/me/metadata", h.SelfUpdateRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"internalRank": "A"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/me/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
repo.AssertNotCalled(t, "Upsert", mock.Anything, mock.Anything)
|
|
})
|
|
|
|
t.Run("allows user_and_admin claim for self", func(t *testing.T) {
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
repo.On("Get", mock.Anything, "client-1", "user-1").Return(&domain.RPUserMetadata{
|
|
ClientID: "client-1",
|
|
UserID: "user-1",
|
|
Metadata: domain.JSONMap{
|
|
"internalRank": "S",
|
|
"internalRank_permissions": map[string]any{
|
|
"readPermission": "admin_only",
|
|
"writePermission": "admin_only",
|
|
},
|
|
},
|
|
}, nil).Once()
|
|
repo.On("Upsert", mock.Anything, mock.MatchedBy(func(row *domain.RPUserMetadata) bool {
|
|
return row.ClientID == "client-1" &&
|
|
row.UserID == "user-1" &&
|
|
row.Metadata["approvalLevel"] == "B" &&
|
|
row.Metadata["internalRank"] == "S"
|
|
})).Return(nil).Once()
|
|
kratos := new(MockKratosAdmin)
|
|
kratos.On("GetIdentity", mock.Anything, "user-1").Return(&service.KratosIdentity{
|
|
ID: "user-1",
|
|
State: "active",
|
|
Traits: map[string]any{
|
|
"email": "user@example.com",
|
|
},
|
|
}, nil).Once()
|
|
var capturedTraits map[string]any
|
|
kratos.On("UpdateIdentity", mock.Anything, "user-1", mock.Anything, "active").Run(func(args mock.Arguments) {
|
|
capturedTraits = args.Get(2).(map[string]any)
|
|
}).Return(&service.KratosIdentity{ID: "user-1", State: "active", Traits: map[string]any{}}, nil).Once()
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
KratosAdmin: kratos,
|
|
IdentityWriter: service.NewIdentityWriteService(kratos, nil),
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "user-1", Role: domain.RoleUser})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/me/metadata", h.SelfUpdateRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"approvalLevel": "B"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/me/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
rpClaims := capturedTraits["rp_custom_claims"].(map[string]any)
|
|
clientClaims := rpClaims["client-1"].(domain.JSONMap)
|
|
require.Equal(t, "B", clientClaims["approvalLevel"])
|
|
require.Equal(t, "S", clientClaims["internalRank"])
|
|
repo.AssertExpectations(t)
|
|
kratos.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestDevHandler_RPUserMetadataRejectsUndefinedClaimKey(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "contract_date",
|
|
"valueType": "date",
|
|
"value": "2026-06-09",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "admin", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"unknown_claim": "A"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
repo.AssertNotCalled(t, "Upsert", mock.Anything, mock.Anything)
|
|
}
|
|
|
|
func TestDevHandler_RPUserMetadataRejectsInvalidTypedClaimValue(t *testing.T) {
|
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
if r.URL.Path == "/clients/client-1" {
|
|
return httpJSONAny(r, http.StatusOK, map[string]any{
|
|
"client_id": "client-1",
|
|
"client_name": "Client One",
|
|
"metadata": map[string]any{
|
|
"id_token_claims": []map[string]any{
|
|
{
|
|
"namespace": "rp_claims",
|
|
"key": "contract_date",
|
|
"valueType": "date",
|
|
"value": "2026-06-09",
|
|
},
|
|
},
|
|
},
|
|
}), nil
|
|
}
|
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
|
})
|
|
|
|
repo := new(devMockRPUserMetadataRepo)
|
|
h := &DevHandler{
|
|
Hydra: &service.HydraAdminService{
|
|
AdminURL: "http://hydra.test",
|
|
HTTPClient: &http.Client{Transport: transport},
|
|
},
|
|
RPUserMetadataRepo: repo,
|
|
}
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "admin", Role: domain.RoleSuperAdmin})
|
|
return c.Next()
|
|
})
|
|
app.Put("/api/v1/dev/clients/:id/users/:userId/metadata", h.UpsertRPUserMetadata)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"metadata": map[string]any{"contract_date": "2026/06/09"},
|
|
})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-1/users/user-1/metadata", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, _ := app.Test(req, -1)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
repo.AssertNotCalled(t, "Upsert", mock.Anything, mock.Anything)
|
|
}
|
|
|
|
func TestNormalizeIDTokenClaimsMetadataAcceptsUnixDateDefaults(t *testing.T) {
|
|
metadata, err := normalizeIDTokenClaimsMetadata(map[string]any{
|
|
"id_token_claims": []any{
|
|
map[string]any{
|
|
"namespace": "rp_claims",
|
|
"key": "contract_date",
|
|
"valueType": "date",
|
|
"value": float64(1781020800),
|
|
},
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
claims := metadata["id_token_claims"].([]normalizedIDTokenClaim)
|
|
require.Len(t, claims, 1)
|
|
require.Equal(t, "1781020800", claims[0].Value)
|
|
}
|