dotnet-cqrs/PHASE1-TESTING-GUIDE.md

15 KiB

Phase 1 Testing & Validation Guide

This guide provides comprehensive testing procedures for validating all Phase 1 event streaming functionality.

Table of Contents

  1. Prerequisites
  2. Starting the Sample Application
  3. Test 1: Workflow Start Semantics
  4. Test 2: Event Consumer (Broadcast Mode)
  5. Test 3: Ephemeral Streams
  6. Test 4: gRPC Streaming
  7. Test 5: Existing Features (Regression)
  8. Expected Results Summary

Prerequisites

Required Tools:

Optional Tools:

  • Postman or similar REST client
  • gRPC UI or BloomRPC for visual gRPC testing

Starting the Sample Application

cd Svrnty.Sample
dotnet run

Expected Output:

=== Svrnty CQRS Sample with Event Streaming ===

gRPC (HTTP/2): http://localhost:6000
  - CommandService, QueryService, DynamicQueryService
  - EventService (bidirectional streaming)

HTTP API (HTTP/1.1): http://localhost:6001
  - Commands: POST /api/command/*
  - Queries: GET/POST /api/query/*
  - Swagger UI: http://localhost:6001/swagger

Event Streams Configured:
  - UserWorkflow stream (ephemeral, at-least-once)
  - InvitationWorkflow stream (ephemeral, at-least-once)

Subscriptions Active:
  - user-analytics (broadcast mode)
  - invitation-processor (exclusive mode)

info: EventConsumerBackgroundService[0]
      Event consumer starting...
info: EventConsumerBackgroundService[0]
      Subscribing to 'user-analytics' subscription (broadcast mode)...

Verify: Application starts without errors and background consumer logs appear.


Test 1: Workflow Start Semantics

Objective: Verify that commands create workflow instances with correlation IDs.

Test 1.1: Add User Command (HTTP)

curl -X POST http://localhost:6001/api/command/addUser \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "john@example.com"
  }'

Expected Response:

5432

(Returns the generated user ID)

Expected Console Output:

info: EventConsumerBackgroundService[0]
      [ANALYTICS] Received event: UserAddedEvent (EventId: <guid>, CorrelationId: <workflow-id>, OccurredAt: <timestamp>)
info: EventConsumerBackgroundService[0]
      [ANALYTICS] User added: UserId=5432, Name=John Doe

Verify:

  • Command returns user ID
  • EventConsumerBackgroundService logs show event received
  • CorrelationId is present and is a GUID (workflow ID)

Test 1.2: Invite User Command (Multi-Step Workflow)

Step 1: Send Invitation

curl -X POST http://localhost:6001/api/command/inviteUser \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "inviterName": "Admin"
  }'

Expected Response:

"<invitation-id-guid>"

Expected Console Output:

info: EventConsumerBackgroundService[0]
      [ANALYTICS] Received event: UserInvitedEvent (EventId: <guid>, CorrelationId: <workflow-id>, ...)
info: EventConsumerBackgroundService[0]
      [ANALYTICS] User invited: InvitationId=<invitation-id>, Email=jane@example.com, Inviter=Admin

Step 2: Accept Invitation

curl -X POST http://localhost:6001/api/command/acceptInvite \
  -H "Content-Type: application/json" \
  -d '{
    "invitationId": "<invitation-id-from-step-1>",
    "email": "jane@example.com",
    "name": "Jane Doe"
  }'

Expected Response:

7891

(Returns the generated user ID)

Expected Console Output:

info: EventConsumerBackgroundService[0]
      [ANALYTICS] Received event: UserInviteAcceptedEvent (EventId: <guid>, CorrelationId: <new-workflow-id>, ...)
info: EventConsumerBackgroundService[0]
      [ANALYTICS] Invitation accepted: InvitationId=<invitation-id>, UserId=7891, Name=Jane Doe

Verify:

  • Both commands complete successfully
  • Events are emitted for both steps
  • Each command creates its own workflow instance (Phase 1 behavior)
  • Different CorrelationIds for invite vs accept (Phase 1 limitation - Phase 2 will support continuation)

Test 2: Event Consumer (Broadcast Mode)

Objective: Verify that broadcast subscription delivers events to all consumers.

Test 2.1: Multiple Events

Execute multiple commands and observe that EventConsumerBackgroundService receives all events:

# Add multiple users
for i in {1..5}; do
  curl -X POST http://localhost:6001/api/command/addUser \
    -H "Content-Type: application/json" \
    -d "{\"name\": \"User $i\", \"email\": \"user$i@example.com\"}"
  sleep 1
done

Expected Console Output:

info: EventConsumerBackgroundService[0]
      [ANALYTICS] Received event: UserAddedEvent (EventId: <guid-1>, ...)
info: EventConsumerBackgroundService[0]
      [ANALYTICS] User added: UserId=<id-1>, Name=User 1
info: EventConsumerBackgroundService[0]
      [ANALYTICS] Received event: UserAddedEvent (EventId: <guid-2>, ...)
info: EventConsumerBackgroundService[0]
      [ANALYTICS] User added: UserId=<id-2>, Name=User 2
...

Verify:

  • All 5 events are received by the consumer
  • Events appear in order
  • No events are missed (broadcast mode guarantees all consumers get all events)

Test 3: Ephemeral Streams

Objective: Verify ephemeral stream behavior (message queue semantics).

Test 3.1: Event Visibility Timeout

Ephemeral streams use visibility timeouts. Events that aren't acknowledged within the timeout are automatically requeued.

Current Behavior (Phase 1.4):

  • EventSubscriptionClient automatically acknowledges events after processing
  • Visibility timeout is set to 30 seconds by default
  • Events are deleted after acknowledgment (ephemeral semantics)

Manual Test:

  1. Send a command to generate an event
  2. Observe that the event is delivered to the consumer
  3. Event is automatically acknowledged and removed from the stream
curl -X POST http://localhost:6001/api/command/addUser \
  -H "Content-Type: application/json" \
  -d '{"name": "Test User", "email": "test@example.com"}'

Verify:

  • Event is delivered once to the consumer
  • No duplicate deliveries (event is removed after acknowledgment)
  • If you stop and restart the app, previous events are gone (ephemeral semantics)

Test 3.2: Application Restart (Ephemeral Behavior)

  1. Send several commands to generate events
  2. Stop the application (Ctrl+C)
  3. Restart the application
  4. Observe that previous events are NOT replayed (ephemeral streams don't persist data)
# While app is running
curl -X POST http://localhost:6001/api/command/addUser \
  -H "Content-Type: application/json" \
  -d '{"name": "Before Restart", "email": "before@example.com"}'

# Stop app (Ctrl+C)
# Restart app
# No events from before restart are delivered

Verify:

  • After restart, no historical events are delivered
  • Only new events (after restart) are received
  • This confirms ephemeral stream behavior (data is not persisted)

Test 4: gRPC Streaming

Objective: Verify gRPC EventService bidirectional streaming.

Test 4.1: List gRPC Services

grpcurl -plaintext localhost:6000 list

Expected Output:

grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection
svrnty.cqrs.CommandService
svrnty.cqrs.DynamicQueryService
svrnty.cqrs.QueryService
svrnty.cqrs.events.EventService

Verify: svrnty.cqrs.events.EventService is listed

Test 4.2: Inspect EventService

grpcurl -plaintext localhost:6000 describe svrnty.cqrs.events.EventService

Expected Output:

svrnty.cqrs.events.EventService is a service:
service EventService {
  rpc Subscribe ( stream .svrnty.cqrs.events.SubscriptionRequest ) returns ( stream .svrnty.cqrs.events.EventMessage );
}

Verify: Subscribe method is available with bidirectional streaming

Test 4.3: Subscribe to Events via gRPC

This test requires a separate terminal window for the gRPC client:

Terminal 1: Start gRPC subscription (leave this running)

grpcurl -plaintext -d @ localhost:6000 svrnty.cqrs.events.EventService.Subscribe <<EOF
{
  "subscribe": {
    "subscription_id": "test-grpc-sub",
    "correlation_id": "test-correlation",
    "delivery_mode": "DELIVERY_MODE_IMMEDIATE"
  }
}
EOF

Terminal 2: Send commands to generate events

curl -X POST http://localhost:6001/api/command/addUser \
  -H "Content-Type: application/json" \
  -d '{"name": "gRPC Test User", "email": "grpc@example.com"}'

Expected Output in Terminal 1:

{
  "event": {
    "subscriptionId": "test-grpc-sub",
    "correlationId": "<workflow-id>",
    "eventType": "UserAddedEvent",
    "eventId": "<event-id>",
    "sequence": 1,
    "occurredAt": "2025-12-09T...",
    "placeholder": {
      "data": "Event data placeholder"
    }
  }
}

Verify:

  • gRPC client receives event in real-time
  • Event contains correct metadata (eventType, correlationId, etc.)
  • Phase 1 uses placeholder for event data (Phase 2 will add full event payloads)

Test 4.4: gRPC with Event Type Filtering

grpcurl -plaintext -d @ localhost:6000 svrnty.cqrs.events.EventService.Subscribe <<EOF
{
  "subscribe": {
    "subscription_id": "filtered-sub",
    "correlation_id": "test-correlation",
    "delivery_mode": "DELIVERY_MODE_IMMEDIATE",
    "event_types": ["UserInvitedEvent", "UserInviteAcceptedEvent"]
  }
}
EOF

Send various commands and observe that only UserInvitedEvent and UserInviteAcceptedEvent are delivered.

Verify:

  • Only events matching the filter are delivered
  • UserAddedEvent and other types are not delivered

Test 5: Existing Features (Regression)

Objective: Verify that existing CQRS functionality still works correctly.

Test 5.1: HTTP Query Endpoint

curl -X GET "http://localhost:6001/api/query/fetchUser?userId=1234"

Expected Response:

{
  "userId": 1234,
  "name": "John Doe",
  "email": "john@example.com"
}

Verify: Query endpoints work correctly

Test 5.2: gRPC Command Service

grpcurl -plaintext -d '{
  "addUserCommand": {
    "name": "gRPC User",
    "email": "grpc-user@example.com"
  }
}' localhost:6000 svrnty.cqrs.CommandService.Execute

Expected Response:

{
  "addUserCommandResponse": {
    "result": 6789
  }
}

Verify: gRPC commands still work correctly

Test 5.3: FluentValidation

# Send invalid command (missing required fields)
curl -X POST http://localhost:6001/api/command/addUser \
  -H "Content-Type: application/json" \
  -d '{}'

Expected Response: HTTP 400 with validation errors

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Name": ["'Name' must not be empty."],
    "Email": ["'Email' must not be empty."]
  }
}

Verify: Validation still works correctly

Test 5.4: Swagger UI

Navigate to: http://localhost:6001/swagger

Verify:

  • Swagger UI loads correctly
  • All command and query endpoints are listed
  • Can test endpoints interactively

Expected Results Summary

Phase 1.1 - Workflow Abstraction

  • Commands create workflow instances with unique IDs
  • Events are emitted with workflow ID as correlation ID
  • Multi-step workflows work (invite → accept/decline)

Phase 1.2 - Stream Configuration

  • Streams can be configured with fluent API
  • Ephemeral stream type is supported
  • At-least-once delivery semantics work

Phase 1.3 - In-Memory Storage

  • InMemoryEventStreamStore handles ephemeral operations
  • Events are enqueued and dequeued correctly
  • Consumer registry tracks active consumers

Phase 1.4 - Subscription System

  • IEventSubscriptionClient provides async enumerable interface
  • Broadcast mode delivers events to all consumers
  • Exclusive mode would distribute load (demonstrated by single consumer)
  • Automatic acknowledgment works
  • Visibility timeout enforcement works

Phase 1.7 - gRPC Streaming

  • EventService is available via gRPC
  • Bidirectional streaming works
  • Event type filtering works
  • Acknowledge/Nack commands are accepted (logged)
  • GrpcEventDeliveryProvider is registered and operational

Phase 1.8 - Sample Project

  • Sample project demonstrates all features
  • EventConsumerBackgroundService shows event consumption
  • Documentation provides usage examples
  • Application starts and runs correctly

Regression Tests

  • Existing HTTP endpoints work
  • Existing gRPC endpoints work
  • FluentValidation works
  • Swagger UI works
  • Dynamic queries work

Known Limitations (Phase 1)

These are expected limitations that will be addressed in future phases:

  1. No Workflow Continuation: Each command creates a new workflow instance. Multi-step workflows (invite → accept) have different correlation IDs.

    • Phase 2 Fix: Workflow continuation API will allow referencing existing workflow instances
  2. Placeholder Event Data: gRPC events use placeholder data instead of actual event payloads.

    • Phase 2 Fix: Source generator will add strongly-typed event messages to proto file
  3. Polling-Based Delivery: EventSubscriptionClient uses polling (100ms intervals) instead of true push.

    • Phase 2 Fix: Channel-based push delivery will eliminate polling
  4. No Persistent Streams: Only ephemeral streams are supported. Data is lost on restart.

    • Phase 2 Fix: Persistent streams with event sourcing capabilities
  5. Manual Ack/Nack Not Functional: Acknowledge and Nack commands are logged but don't affect delivery.

    • Phase 2 Fix: Full manual acknowledgment support with retry logic

Troubleshooting

Issue: "Address already in use" errors

Solution: Kill any processes using ports 6000 or 6001:

# macOS/Linux
lsof -ti:6000 | xargs kill -9
lsof -ti:6001 | xargs kill -9

# Windows
netstat -ano | findstr :6000
taskkill /PID <pid> /F

Issue: Event consumer not logging events

Check:

  1. Application started successfully
  2. EventConsumerBackgroundService is registered in Program.cs
  3. Subscription "user-analytics" matches configured subscription ID
  4. Check application logs for errors

Issue: grpcurl not found

Solution:

# macOS
brew install grpcurl

# Linux
wget https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz
tar -xvf grpcurl_1.8.9_linux_x86_64.tar.gz
sudo mv grpcurl /usr/local/bin/

# Windows
choco install grpcurl

Next Steps

After completing Phase 1 testing:

  1. Phase 2: Persistent Streams & Event Sourcing

    • Add EventStoreDB or similar persistent storage
    • Implement stream replay capabilities
    • Add snapshot support
  2. Phase 3: Advanced Features

    • Consumer groups (Kafka-style partitioning)
    • Dead letter queues
    • Retry policies
    • Circuit breakers
  3. Production Readiness

    • Add comprehensive unit tests
    • Add integration tests
    • Performance benchmarking
    • Monitoring and observability