1
0
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:
2026-06-01 15:34:08 +09:00
parent 4a1e89e421
commit 31d107ff2e
85 changed files with 2104 additions and 1149 deletions

View File

@@ -1,6 +1,7 @@
package service
import (
"baron-sso-backend/internal/domain"
"bytes"
"encoding/json"
"io"
@@ -50,19 +51,24 @@ func TestUpdateUserPassword_Success(t *testing.T) {
if got := q.Get("credentials_identifier"); got != loginID {
t.Fatalf("expected credentials_identifier=%s, got=%s", loginID, got)
}
_ = json.NewEncoder(w).Encode([]map[string]string{
{"id": identityID},
_ = json.NewEncoder(w).Encode([]map[string]any{
{
"id": identityID,
"traits": map[string]any{
"email": loginID,
},
},
})
return
}
if r.URL.Path != "/admin/identities/"+identityID {
t.Fatalf("unexpected identity lookup path: %s", r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
_ = json.NewEncoder(w).Encode(map[string]any{
"id": identityID,
"schema_id": "default",
"state": "active",
"traits": map[string]interface{}{
"traits": map[string]any{
"email": loginID,
},
})
@@ -120,17 +126,22 @@ func TestUpdateUserPassword_ServerError(t *testing.T) {
switch {
case strings.HasPrefix(r.URL.Path, "/admin/identities") && r.Method == http.MethodGet:
if r.URL.Path == "/admin/identities" {
_ = json.NewEncoder(w).Encode([]map[string]string{
{"id": "abc"},
_ = json.NewEncoder(w).Encode([]map[string]any{
{
"id": "abc",
"traits": map[string]any{
"email": "user@example.com",
},
},
})
return
}
if r.URL.Path == "/admin/identities/abc" {
_ = json.NewEncoder(w).Encode(map[string]interface{}{
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "abc",
"schema_id": "default",
"state": "active",
"traits": map[string]interface{}{
"traits": map[string]any{
"email": "user@example.com",
},
})
@@ -163,8 +174,13 @@ func TestFindIdentityID_QueryEncoding(t *testing.T) {
if values.Get("credentials_identifier") != loginID {
t.Fatalf("expected credentials_identifier=%s, got=%s", loginID, values.Get("credentials_identifier"))
}
_ = json.NewEncoder(w).Encode([]map[string]string{
{"id": "id-123"},
_ = json.NewEncoder(w).Encode([]map[string]any{
{
"id": "id-123",
"traits": map[string]any{
"email": loginID,
},
},
})
})
@@ -181,3 +197,67 @@ func TestFindIdentityID_QueryEncoding(t *testing.T) {
t.Fatalf("expected id-123, got %s", id)
}
}
func TestOryProvider_CreateUser_CustomIDSupport(t *testing.T) {
const (
email = "newuser@test.com"
name = "New User"
customUuid = "550e8400-e29b-41d4-a716-446655440000"
password = "secret123456"
kratosId = "kratos-gen-id"
)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/admin/identities" && r.Method == http.MethodGet:
// No existing identity
_ = json.NewEncoder(w).Encode([]any{})
return
case r.URL.Path == "/admin/identities/"+customUuid && r.Method == http.MethodGet:
// No identity with this ID
http.NotFound(w, r)
return
case r.URL.Path == "/admin/identities" && r.Method == http.MethodPost:
var body map[string]any
_ = json.NewDecoder(r.Body).Decode(&body)
// Verify ID is NOT in the root to avoid "unknown field id" error
if _, exists := body["id"]; exists {
t.Fatalf("payload MUST NOT contain root 'id' field for compatibility")
}
// Verify external_id and metadata_admin
if got := body["external_id"]; got != customUuid {
t.Fatalf("expected external_id %s, got %v", customUuid, got)
}
meta, ok := body["metadata_admin"].(map[string]any)
if !ok || meta["original_uuid"] != customUuid {
t.Fatalf("expected metadata_admin.original_uuid %s, got %v", customUuid, meta)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"id": kratosId,
})
return
default:
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
}
})
provider := &OryProvider{
KratosAdminURL: "http://kratos-admin.local",
HTTPClient: clientForHandler(handler),
}
id, err := provider.CreateUser(&domain.BrokerUser{
ID: customUuid,
Email: email,
Name: name,
}, password)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if id != kratosId {
t.Fatalf("expected Kratos generated ID %s, got %s", kratosId, id)
}
}