566 lines
15 KiB
Markdown
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
|