Skip to content

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

  1. Install Bruno.
  2. 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.
  3. 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

requestId must be a valid GUID. Requests with a non-GUID requestId (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 the Authorization: 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 OK with 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 type400 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.