Testing CQRS Command System¶
This document provides examples for testing the command endpoint (POST /api/command) using existing commands in the system. Requests are intended to be run with Bruno; each example shows the request followed immediately by its expected response.
Command Endpoint Overview¶
- URL:
POST /api/command - Authentication: Required (Clerk JWT or Basic Auth)
- Content-Type:
application/json
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 command endpoint requires authentication. Use a seeded service account with Basic Auth.
Do not hardcode credentials in this document or commit them. Request the 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.
Available Commands for Testing¶
requestIdmust be a valid GUID. Requests with a non-GUIDrequestId(e.g.test-create-user-001) will fail. Use a fresh GUID for each request. (See Non-GUID requestId below.)
1. CreateUser Command¶
Test basic user creation functionality.
- Request:
POST <base-url>/api/command - Auth: Basic Auth (service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440101",
"commandType": "CreateUser",
"expectedVersion": 0,
"command": {
"aggregateRootId": "550e8400-e29b-41d4-a716-446655440001",
"email": "testuser@example.com",
"firstName": "Test",
"lastName": "User",
"userName": "testuser",
"emailVerified": true,
"status": "Active",
"roles": ["Member"],
"userType": "Human"
}
}
- Expected response (
200 OK):
{
"requestId": "550e8400-e29b-41d4-a716-446655440101",
"success": true,
"eventCount": 1,
"commandType": "CreateUser"
}
2. CreateCompetition Command¶
Test competition creation.
- Request:
POST <base-url>/api/command - Auth: Basic Auth (service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440102",
"commandType": "CreateCompetition",
"expectedVersion": 0,
"command": {
"aggregateRootId": "550e8400-e29b-41d4-a716-446655440002",
"sport": "RugbyLeague",
"name": "Test Competition",
"shortName": "Test Comp",
"displayName": "Test Competition",
"slug": "test-comp",
"code": "TC",
"dataSource": "LuckBox"
}
}
- Expected response (
200 OK):
{
"requestId": "550e8400-e29b-41d4-a716-446655440102",
"success": true,
"eventCount": 1,
"commandType": "CreateCompetition"
}
3. CreateTeam Command¶
Test team creation.
- Request:
POST <base-url>/api/command - Auth: Basic Auth (service account)
- Headers:
Content-Type: application/json - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440103",
"commandType": "CreateTeam",
"expectedVersion": 0,
"command": {
"aggregateRootId": "550e8400-e29b-41d4-a716-446655440003",
"sport": "RugbyLeague",
"teamName": "Test Team",
"shortName": "TT",
"displayName": "Test Team",
"slug": "test-team",
"code": "TT",
"dataSource": "LuckBox"
}
}
- Expected response (
200 OK):
{
"requestId": "550e8400-e29b-41d4-a716-446655440103",
"success": true,
"eventCount": 1,
"commandType": "CreateTeam"
}
Authentication Testing¶
Using Service Account Basic Auth¶
This is the default for the examples above — Basic Auth with a seeded service account.
Using Clerk JWT¶
For requests authenticated with a Clerk JWT, set the request auth to Bearer and supply the token instead of Basic Auth.
Do not hardcode the token in this document or commit it. Obtain a Clerk JWT for a test user (e.g. from the frontend session or your Clerk dashboard) and store it as a secret environment variable in Bruno (referred to as
<clerk-jwt>below).
- Request:
POST <base-url>/api/command - Auth: Bearer Token —
<clerk-jwt> - Headers:
Content-Type: application/json(Bruno adds theAuthorization: Bearer <clerk-jwt>header from the auth setting) - Body:
{
"requestId": "550e8400-e29b-41d4-a716-446655440104",
"commandType": "CreateUser",
"expectedVersion": 0,
"command": {
"aggregateRootId": "550e8400-e29b-41d4-a716-446655440004",
"email": "clerkuser@example.com",
"firstName": "Clerk",
"lastName": "User",
"userName": "clerkuser",
"emailVerified": true,
"status": "Active",
"roles": ["Member"],
"userType": "Human"
}
}
- Expected response (
200 OKwith a valid token — same shape as CreateUser):
{
"requestId": "550e8400-e29b-41d4-a716-446655440104",
"success": true,
"eventCount": 1,
"commandType": "CreateUser"
}
A missing or invalid/expired token returns 401 Unauthorized.
Negative Test Cases¶
Missing Authentication¶
Send any command with no auth header.
- Expected response:
401 Unauthorized(empty body)
Non-GUID requestId¶
Send a command whose requestId is not a GUID (e.g. "test-create-user-001").
- Expected response (
400 Bad Request):
{
"statusCode": 400,
"message": "One or more errors occurred!",
"errors": {
"requestId": ["The JSON value is not in a supported Guid format."]
}
}
Existing Aggregate (e.g. User Already Exists)¶
Re-running a create command with an aggregateRootId/email that already exists returns 200 OK with success: false:
{
"requestId": "550e8400-e29b-41d4-a716-446655440101",
"success": false,
"eventCount": 0,
"errorMessage": "User already exists",
"errorCode": "INVALID_COMMAND",
"commandType": "CreateUser"
}
On a fresh database the create succeeds (success: true); to re-create, use a fresh aggregateRootId and email.
Other Cases to Check¶
- Invalid command type →
400 Bad Request - Invalid command data → validation errors in the response
- Duplicate request ID → handled idempotently
- Version conflict (wrong
expectedVersion) → optimistic-concurrency error
Validation Points¶
- Commands execute successfully with proper authentication
- Events are generated and stored in the event store
- Command validation works correctly
- Authorization rules are enforced based on user context
- Proper error handling for invalid inputs
- Response format matches expected structure
This approach tests the command system using real domain commands without adding test-specific code to the core system.