dotnet-cqrs/PHASE1-TESTING-GUIDE.md

566 lines
15 KiB
Markdown

# Phase 1 Testing & Validation Guide
This guide provides comprehensive testing procedures for validating all Phase 1 event streaming functionality.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Starting the Sample Application](#starting-the-sample-application)
3. [Test 1: Workflow Start Semantics](#test-1-workflow-start-semantics)
4. [Test 2: Event Consumer (Broadcast Mode)](#test-2-event-consumer-broadcast-mode)
5. [Test 3: Ephemeral Streams](#test-3-ephemeral-streams)
6. [Test 4: gRPC Streaming](#test-4-grpc-streaming)
7. [Test 5: Existing Features (Regression)](#test-5-existing-features-regression)
8. [Expected Results Summary](#expected-results-summary)
---
## Prerequisites
**Required Tools:**
- .NET 10 SDK
- `curl` (for HTTP testing)
- `grpcurl` (for gRPC testing) - Install: `brew install grpcurl` (macOS) or download from https://github.com/fullstorydev/grpcurl
**Optional Tools:**
- Postman or similar REST client
- gRPC UI or BloomRPC for visual gRPC testing
---
## Starting the Sample Application
```bash
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)
```bash
curl -X POST http://localhost:6001/api/command/addUser \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com"
}'
```
**Expected Response:**
```json
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**
```bash
curl -X POST http://localhost:6001/api/command/inviteUser \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"inviterName": "Admin"
}'
```
**Expected Response:**
```json
"<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**
```bash
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:**
```json
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:
```bash
# 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
```bash
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)
```bash
# 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
```bash
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
```bash
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)**
```bash
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**
```bash
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:**
```json
{
"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
```bash
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
```bash
curl -X GET "http://localhost:6001/api/query/fetchUser?userId=1234"
```
**Expected Response:**
```json
{
"userId": 1234,
"name": "John Doe",
"email": "john@example.com"
}
```
**Verify:** Query endpoints work correctly
### Test 5.2: gRPC Command Service
```bash
grpcurl -plaintext -d '{
"addUserCommand": {
"name": "gRPC User",
"email": "grpc-user@example.com"
}
}' localhost:6000 svrnty.cqrs.CommandService.Execute
```
**Expected Response:**
```json
{
"addUserCommandResponse": {
"result": 6789
}
}
```
**Verify:** gRPC commands still work correctly
### Test 5.3: FluentValidation
```bash
# 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
```json
{
"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
- [x] Commands create workflow instances with unique IDs
- [x] Events are emitted with workflow ID as correlation ID
- [x] Multi-step workflows work (invite → accept/decline)
### ✅ Phase 1.2 - Stream Configuration
- [x] Streams can be configured with fluent API
- [x] Ephemeral stream type is supported
- [x] At-least-once delivery semantics work
### ✅ Phase 1.3 - In-Memory Storage
- [x] InMemoryEventStreamStore handles ephemeral operations
- [x] Events are enqueued and dequeued correctly
- [x] Consumer registry tracks active consumers
### ✅ Phase 1.4 - Subscription System
- [x] IEventSubscriptionClient provides async enumerable interface
- [x] Broadcast mode delivers events to all consumers
- [x] Exclusive mode would distribute load (demonstrated by single consumer)
- [x] Automatic acknowledgment works
- [x] Visibility timeout enforcement works
### ✅ Phase 1.7 - gRPC Streaming
- [x] EventService is available via gRPC
- [x] Bidirectional streaming works
- [x] Event type filtering works
- [x] Acknowledge/Nack commands are accepted (logged)
- [x] GrpcEventDeliveryProvider is registered and operational
### ✅ Phase 1.8 - Sample Project
- [x] Sample project demonstrates all features
- [x] EventConsumerBackgroundService shows event consumption
- [x] Documentation provides usage examples
- [x] Application starts and runs correctly
### ✅ Regression Tests
- [x] Existing HTTP endpoints work
- [x] Existing gRPC endpoints work
- [x] FluentValidation works
- [x] Swagger UI works
- [x] 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:
```bash
# 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:**
```bash
# 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