Development Guide
Guide for building, testing, and running the Virtufin WebSocketManager locally.
Table of Contents
- Prerequisites
- Project Structure
- Building
- Running Locally
- Testing
- Debugging
- Generating Protobuf Files
- Troubleshooting
Prerequisites
Required Software
| Software | Version | Purpose |
|---|---|---|
| .NET SDK | 10.0+ | Runtime and build |
| Dapr CLI | 1.18+ | Local development sidecar |
| Docker | Latest | Redis/Valkey for state + pub/sub |
| buf | 1.40+ | Protobuf code generation (optional) |
Install .NET SDK
# macOS
brew install dotnet
# Linux (Ubuntu)
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install dotnet-sdk-10.0
# Windows
winget install Microsoft.DotNet.SDK.10
Install Dapr CLI
# macOS
brew install dapr/tap/dapr
# Linux
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | bash
# Initialize Dapr (downloads placement, redis-placement, and zipkin containers)
dapr init
Project Structure
src/
├── Virtufin.WebSocketManager/ # Main application
│ ├── Configuration/ # Options + PortConstants
│ ├── Services/ # gRPC service + Dapr + store
│ ├── HostedServices/ # ConnectionReclaimer, Sweeper
│ ├── Models/ # DTOs + domain types
│ └── Program.cs
├── Virtufin.WebSocketManager.Protos/ # Proto definitions
│ ├── proto/websocketmanager.proto
│ └── Generated/ # Buf-generated stubs
└── Virtufin.WebSocketManager.Client/ # .NET client NuGet library
tests/
├── Virtufin.WebSocketManager.Tests/ # Service-level tests
├── Virtufin.WebSocketManager.Client.Tests/# Client library tests
├── python/ # Python client tests
└── typescript/ # TypeScript client tests
Building
# Build the entire solution
dotnet build Virtufin.WebSocketManager.slnx
# Build a specific project
dotnet build src/Virtufin.WebSocketManager/Virtufin.WebSocketManager.csproj
The build output is placed in src/*/bin/Debug/net10.0/.
Running Locally
1. Start Dapr and Redis
dapr init # one-time
docker compose up -d redis
2. Run the WebSocketManager
Clone the upstream virtufin/deploy-local repo (or copy its components/ directory to any path of your choice), then run with --resources-path pointing at that components/ directory:
cd src/Virtufin.WebSocketManager
DAPR_COMPONENTS_PATH=/path/to/deploy-local/components # set to your path
dapr run --app-id websocketmanager \
--app-protocol grpc \
--app-port 5002 \
--dapr-http-port 3500 \
--resources-path "$DAPR_COMPONENTS_PATH" \
-- dotnet run
For convenience, if you cloned deploy-local as a sibling of
virtufin-websocketmanager/, the path becomes
../deploy-local/components.
The service exposes:
- gRPC on
:5002(with reflection enabled) - HTTP/REST + Swagger on
:5001
3. Verify
# Health check
curl http://localhost:5001/health
# Swagger UI
open http://localhost:5001/swagger
# gRPC reflection service list
grpcurl -plaintext localhost:5002 list
Testing
Run All Tests
dotnet test Virtufin.WebSocketManager.slnx
Run Tests with Coverage
dotnet test Virtufin.WebSocketManager.slnx \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
Run Specific Tests
# By class name
dotnet test --filter "FullyQualifiedName~DaprConnectionRepositoryTests"
# By trait/category
dotnet test --filter "Category=Integration"
Test Projects
| Test Project | Description |
|---|---|
Virtufin.WebSocketManager.Tests |
Service-level tests (gRPC service, Dapr repository, hosted services) |
Virtufin.WebSocketManager.Client.Tests |
Client library tests (hand-written + generated) |
tests/python/ |
Python client smoke tests |
tests/typescript/ |
TypeScript client smoke tests |
Writing Tests
- Place unit tests next to the class they test in
*.Tests/ - Mock external dependencies (Dapr, gRPC channels) using
Moqor hand-rolled fakes - Use
[Fact](xUnit) for single-case tests,[Theory]with[InlineData]for parameterized tests
Debugging
Visual Studio Code
.vscode/launch.json includes a debug profile that runs the WebSocketManager under Dapr:
# In VS Code
code .
# Press F5 → "Launch WebSocketManager (Dapr)" profile
JetBrains Rider
Rider auto-detects the launchSettings profile. Right-click Virtufin.WebSocketManager → "Run/Debug" → choose the "Dapr" launch profile.
Common Debug Scenarios
- Connection leaks: set a breakpoint in
ConnectionReclaimerHostedService.ExecuteAsyncand inspect theconnectionslist - gRPC serialization errors: enable
EnableDetailedErrorsinProgram.cs(gated onIsDevelopment()) - Dapr state not persisting: verify the Dapr sidecar logs (
docker logs dapr_redis-dapr_1) and check the state store component config
Generating Protobuf Files
Proto files live in src/Virtufin.WebSocketManager.Protos/proto/. To regenerate stubs after editing a .proto file:
cd src/Virtufin.WebSocketManager.Protos/proto
buf generate
This regenerates:
- C# stubs →
src/Virtufin.WebSocketManager/Generated/ - Python stubs →
src/python/virtufin/websocketmanager/websocketmanager_pb2*.py - TypeScript stubs →
src/typescript/src/generated/websocketmanager_pb.js
Generated files are excluded from git.
Regenerate Client Tests
After regenerating stubs, also regenerate the cross-language client tests:
# The repos are NOT a monorepo, so the script is fetched
# directly from the virtufin-common Gitea repo at runtime.
python3 <(curl -L https://git.haenerconsulting.com/virtufin/virtufin-common/raw/branch/master/scripts/generate-client-tests.py) \
src/Virtufin.WebSocketManager.Protos/proto/websocketmanager.proto \
--prefix W
Troubleshooting
"Dapr sidecar not responding"
Check that Dapr is initialized and the placement service is running:
dapr status
docker ps | grep dapr
If dapr_redis-placement_1 is not running, restart Dapr: dapr init --slim.
"Connection not found" errors
This typically means the owning instance died and the connection was reclaimed by another instance. Check the reclaim logs:
docker logs virtufin-websocketmanager 2>&1 | grep -i reclaim
"gRPC reflection service not available"
Verify reflection is enabled in Program.cs (builder.Services.AddGrpcReflection()) and that you're connecting to the gRPC port (5002), not the HTTP port (5001).
Port conflicts
If 5001/5002 are in use, override via env vars:
HttpPort=5001 GrpcPort=5002 dapr run --app-port 5002 -- dotnet run
Or command-line switches:
dotnet run -- --http-port 6001 --grpc-port 6002