forked from baron/baron-sso
feat(user): support fixed UUID registration and enhance bulk import results
- Added support for fixed UUIDs during bulk registration (Search-first + ExternalID mapping) - Implemented idempotency and visibility restoration for soft-deleted users - Enhanced bulk upload UI to show 'New/Updated/Unchanged' status and modified fields - Added logic to reclaim identifiers (login_id) from colliding records - Added frontend E2E and backend unit tests for UUID integrity and conflict handling - Fixed i18n, formatting, and mock tests to satisfy code-check - Applied 'go fix' for 'omitzero' tags and general Go standards
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -33,7 +34,7 @@ func (r *recordingUpdateMeUserRepo) UpdateUserLoginIDs(ctx context.Context, user
|
||||
func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
token := "token-abc"
|
||||
identityID := "user-1"
|
||||
traits := map[string]interface{}{
|
||||
traits := map[string]any{
|
||||
"email": "qa@example.com",
|
||||
"name": "QA User",
|
||||
"phone_number": "+821012345678",
|
||||
@@ -51,8 +52,8 @@ func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
if r.Header.Get("X-Session-Token") != token {
|
||||
return httpResponse(r, http.StatusUnauthorized, `{"error":"invalid token"}`), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusOK, map[string]interface{}{
|
||||
"identity": map[string]interface{}{
|
||||
return httpJSONAny(r, http.StatusOK, map[string]any{
|
||||
"identity": map[string]any{
|
||||
"id": identityID,
|
||||
"traits": traits,
|
||||
},
|
||||
@@ -62,14 +63,12 @@ func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
r.URL.Path == "/admin/identities/"+identityID &&
|
||||
r.Method == http.MethodPut:
|
||||
var payload struct {
|
||||
Traits map[string]interface{} `json:"traits"`
|
||||
Traits map[string]any `json:"traits"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
return httpResponse(r, http.StatusBadRequest, `{"error":"invalid body"}`), nil
|
||||
}
|
||||
for k, v := range payload.Traits {
|
||||
traits[k] = v
|
||||
}
|
||||
maps.Copy(traits, payload.Traits)
|
||||
return httpResponse(r, http.StatusOK, `{"ok":true}`), nil
|
||||
}
|
||||
|
||||
@@ -93,7 +92,7 @@ func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
getResp1, err := app.Test(getReq1, -1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, getResp1.StatusCode)
|
||||
var profile1 map[string]interface{}
|
||||
var profile1 map[string]any
|
||||
require.NoError(t, json.NewDecoder(getResp1.Body).Decode(&profile1))
|
||||
require.Equal(t, "Old Dept", profile1["department"])
|
||||
|
||||
@@ -121,7 +120,7 @@ func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
getResp2, err := app.Test(getReq2, -1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, getResp2.StatusCode)
|
||||
var profile2 map[string]interface{}
|
||||
var profile2 map[string]any
|
||||
require.NoError(t, json.NewDecoder(getResp2.Body).Decode(&profile2))
|
||||
require.Equal(t, "New Dept", profile2["department"])
|
||||
}
|
||||
@@ -129,7 +128,7 @@ func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
|
||||
func TestUpdateMe_SyncsLocalReadModelFields(t *testing.T) {
|
||||
token := "token-sync"
|
||||
identityID := "user-sync"
|
||||
traits := map[string]interface{}{
|
||||
traits := map[string]any{
|
||||
"email": "sync@example.com",
|
||||
"name": "Old Name",
|
||||
"phone_number": "+821012345678",
|
||||
@@ -148,8 +147,8 @@ func TestUpdateMe_SyncsLocalReadModelFields(t *testing.T) {
|
||||
if r.Header.Get("X-Session-Token") != token {
|
||||
return httpResponse(r, http.StatusUnauthorized, `{"error":"invalid token"}`), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusOK, map[string]interface{}{
|
||||
"identity": map[string]interface{}{
|
||||
return httpJSONAny(r, http.StatusOK, map[string]any{
|
||||
"identity": map[string]any{
|
||||
"id": identityID,
|
||||
"traits": traits,
|
||||
},
|
||||
@@ -159,14 +158,12 @@ func TestUpdateMe_SyncsLocalReadModelFields(t *testing.T) {
|
||||
r.URL.Path == "/admin/identities/"+identityID &&
|
||||
r.Method == http.MethodPut:
|
||||
var payload struct {
|
||||
Traits map[string]interface{} `json:"traits"`
|
||||
Traits map[string]any `json:"traits"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
return httpResponse(r, http.StatusBadRequest, `{"error":"invalid body"}`), nil
|
||||
}
|
||||
for k, v := range payload.Traits {
|
||||
traits[k] = v
|
||||
}
|
||||
maps.Copy(traits, payload.Traits)
|
||||
return httpResponse(r, http.StatusOK, `{"ok":true}`), nil
|
||||
}
|
||||
|
||||
@@ -187,7 +184,7 @@ func TestUpdateMe_SyncsLocalReadModelFields(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Put("/api/v1/user/me", h.UpdateMe)
|
||||
|
||||
updateBody, _ := json.Marshal(map[string]interface{}{
|
||||
updateBody, _ := json.Marshal(map[string]any{
|
||||
"name": "New Name",
|
||||
"phone": "01087654321",
|
||||
"department": "New Dept",
|
||||
|
||||
Reference in New Issue
Block a user