Importer System Testing¶
This document provides examples for testing the import endpoint (POST /api/import). Requests are intended to be run with Bruno; each example shows the request followed immediately by its expected response.
In the responses below,
importJobId,importResultId,requestTime, claim identifiers, andexecutionDurationvary per request.
Prerequisites¶
- Install Bruno.
- Run the application from Visual Studio: set LBS.AspireHost as the startup project and start it. When the Aspire session launches and the site appears, copy the generated URL — this is your base URL (referred to as
<base-url>below). Use it as the root for every Bruno request. - Configure authentication (see below).
Authentication Setup¶
The import endpoint requires authentication. The user importer used here (UserSeeding) generates CreateUser commands, which require the UserManager or Admin role (via the role hierarchy). Use a seeded admin/service account with Basic Auth.
Do not hardcode credentials in this document or commit them. Request the admin/service account username and password from your team lead / Engineering Lead. In Bruno, store them as secret environment variables and reference those variables from the request's Basic Auth fields, so the values never end up in committed files. Bruno builds the Basic Auth header for you — there is no need to base64-encode anything by hand.
The passwords inside the import request bodies below (e.g.
MemberPassword123!) are fabricated example data for the test users being created — they are not real credentials, so they stay inline so the examples remain self-contained. The requests in steps 4 and 5 authenticate as those just-created users, so their Basic Auth uses the same example values.
Notes¶
requestIdmust be a valid GUID. Requests with a non-GUIDrequestIdwill fail with"The JSON value is not in a supported Guid format."Use a fresh GUID for each request.- Processing is asynchronous by default. A
Normalpriority import is queued and returns aPendingjob immediately (processedSynchronously: false). Set"priority": "High"to process synchronously only when the queue is empty at request time — the response then carries the full result (success,commandsExecuted, etc.). If another import is still queued/processing, even aHighrequest is queued and returnsPending. The examples useHighso you can see results immediately; run them one at a time to keep the queue empty. - Creating a user that already exists fails. Re-running a create against an existing email/account returns an import
status: "Failed"with"User already exists"(AGGREGATE_PROCESSING_FAILED). On a fresh database the create succeeds; to re-create, use fresh emails /userNames. - Use the real importer type.
importerTypemust be a registered importer (e.g.UserSeeding). An unknown type returns400 UNKNOWN_IMPORTER_TYPE(see step 6). Query theAvailableImportersread-model to list registered importers.
Test Importer Functionality¶
1. Create Users with the UserSeeding Importer (Should Succeed)¶
Create test users with an account that has Admin/UserManager via the role hierarchy. The importData is a UserSeedingImport (enabled, skipIfUsersExist, users[]). Each user's userType is Human or ServiceAccount; service accounts can authenticate via Basic Auth using their password.
- Request:
POST <base-url>/api/import - Auth: Basic Auth (admin/service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440201",
"importerType": "UserSeeding",
"priority": "High",
"importData": {
"enabled": true,
"skipIfUsersExist": false,
"users": [
{
"email": "member@example.com",
"firstName": "Test",
"lastName": "Member",
"userName": "testmember",
"password": "MemberPassword123!",
"userType": "ServiceAccount",
"roles": ["Member"],
"emailVerified": true
},
{
"email": "service@example.com",
"firstName": "Test",
"lastName": "Service",
"userName": "testservice",
"password": "ServicePassword123!",
"userType": "ServiceAccount",
"roles": ["ServiceAccount", "Member"],
"emailVerified": true
}
]
}
}
- Expected response (
200 OK, processed synchronously):
{
"requestId": "550e8400-e29b-41d4-a716-446655440201",
"importJobId": "<job-id>",
"importResultId": "<result-id>",
"status": "Completed",
"priority": "High",
"importerType": "UserSeeding",
"message": "Import completed successfully",
"processedSynchronously": true,
"success": true,
"commandsExecuted": 2,
"eventsGenerated": 2,
"aggregatesProcessed": 2,
"executionDuration": "00:00:00.21"
}
2. Queue an Import Asynchronously (Default Priority)¶
Omit priority (or set it to Normal) to queue the import instead of processing it inline. The response returns immediately with a Pending job; processing happens in the background.
- Request:
POST <base-url>/api/import - Auth: Basic Auth (admin/service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440202",
"importerType": "UserSeeding",
"importData": {
"enabled": true,
"skipIfUsersExist": false,
"users": [
{
"email": "queued@example.com",
"firstName": "Queued",
"lastName": "User",
"userName": "queueduser",
"userType": "Human",
"roles": ["Member"],
"emailVerified": true
}
]
}
}
- Expected response (
200 OK, queued):
{
"requestId": "550e8400-e29b-41d4-a716-446655440202",
"importJobId": "<job-id>",
"status": "Pending",
"priority": "Normal",
"importerType": "UserSeeding",
"message": "Import queued for processing with Normal priority",
"processedSynchronously": false
}
3. Test Without Authentication (Should Fail)¶
Should fail with 401 at the endpoint (authentication is required before the import is accepted).
- Request:
POST <base-url>/api/import - Auth: None
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440203",
"importerType": "UserSeeding",
"importData": {
"enabled": true,
"skipIfUsersExist": false,
"users": [
{
"email": "unauthorized@example.com",
"userName": "unauthorized",
"userType": "Human",
"roles": ["Member"]
}
]
}
}
- Expected response:
401 Unauthorized(empty body)
4. Test with Non-Admin User (Should Fail During Processing)¶
Authentication succeeds and the import is accepted, but the CreateUser command requires the UserManager or Admin role. A user with only Member is rejected during processing. With "priority": "High" and an empty queue the failure is returned synchronously (status: "Failed", errorCode: "AGGREGATE_PROCESSING_FAILED", shown below); otherwise — Normal priority, or High while another import is still queued/processing — the request returns Pending (like step 2) and the failure is visible via the import job result instead.
This request authenticates as the member account created in step 1, so its Basic Auth uses that user's example credentials (member@example.com / MemberPassword123!), not the seeded secret.
- Request:
POST <base-url>/api/import - Auth: Basic Auth —
member@example.com/MemberPassword123!(example user from step 1) - Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440204",
"importerType": "UserSeeding",
"priority": "High",
"importData": {
"enabled": true,
"skipIfUsersExist": false,
"users": [
{
"email": "shouldfail@example.com",
"userName": "shouldfail",
"userType": "Human",
"roles": ["Member"]
}
]
}
}
- Expected response (
200 OKwith aFailedstatus — the command-level role check rejects the user during processing):
{
"requestId": "550e8400-e29b-41d4-a716-446655440204",
"importJobId": "<job-id>",
"importResultId": "<result-id>",
"status": "Failed",
"priority": "High",
"importerType": "UserSeeding",
"message": "Import failed: Import failed at aggregate <id>: User 'member@example.com' (<id>) lacks required roles to execute CreateUserCommand. Required roles: [UserManager, Admin]. User roles: [Member].",
"processedSynchronously": true,
"success": false,
"commandsExecuted": 1,
"eventsGenerated": 0,
"aggregatesProcessed": 1,
"executionDuration": "00:00:00.09",
"errorMessage": "Import failed at aggregate <id>: User 'member@example.com' (<id>) lacks required roles to execute CreateUserCommand. Required roles: [UserManager, Admin]. User roles: [Member].",
"errorCode": "AGGREGATE_PROCESSING_FAILED"
}
5. Verify Created Users¶
Query for a created service account. This authenticates as the service account created in step 1, so its Basic Auth uses that user's example credentials (service@example.com / ServicePassword123!).
- Request:
POST <base-url>/api/readmodel - Auth: Basic Auth —
service@example.com/ServicePassword123!(example user from step 1) - Headers:
Content-Type: application/json - Body:
- Expected response (
200 OK):
{
"data": [
{
"name": "service@example.com",
"email": "service@example.com",
"roles": ["ServiceAccount", "Member"],
"claims": {
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "<user-id>",
"account_type": "ServiceAccount",
"internal_user_id": "<user-id>",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "ServiceAccount, Member"
},
"authenticationType": "Basic",
"requestTime": "2026-06-18T05:08:49.86Z",
"effectiveRoles": ["ServiceAccount", "Member", "Public"]
}
],
"totalCount": 1,
"skip": 0
}
6. Test Unknown Importer Type (Should Fail)¶
An unregistered importerType is rejected with 400 UNKNOWN_IMPORTER_TYPE.
- Request:
POST <base-url>/api/import - Auth: Basic Auth (admin/service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440206",
"importerType": "TestUser",
"importData": {
"enabled": true,
"users": []
}
}
- Expected response (
400 Bad Request):
{
"requestId": "550e8400-e29b-41d4-a716-446655440206",
"importJobId": "00000000-0000-0000-0000-000000000000",
"status": "Failed",
"priority": "Normal",
"importerType": "TestUser",
"message": "Unknown importer type: TestUser",
"processedSynchronously": false,
"errorMessage": "Unknown importer type: TestUser",
"errorCode": "UNKNOWN_IMPORTER_TYPE"
}
Key Features Being Tested¶
- Importer Registration - Dynamic importer resolution via
importerType; unknown types are rejected withUNKNOWN_IMPORTER_TYPE - User Context -
IRequireUserContextprovides authentication context to the importer - Role-Based Authorization - The generated
CreateUsercommand requiresUserManagerorAdmin(with hierarchy support); enforced during processing - Command Generation - Importers translate import data into domain commands
- Async Queue with Sync Fallback - Imports queue by default;
Highpriority with an empty queue runs synchronously and returns the full result - Error Handling - Endpoint-level (
401,400 UNKNOWN_IMPORTER_TYPE) and processing-level (AGGREGATE_PROCESSING_FAILED) failures