Skip to content

Development Guide

Guide for building, testing, and running the Virtufin WebSocketManager locally.

Table of Contents


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 Moq or 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.ExecuteAsync and inspect the connections list
  • gRPC serialization errors: enable EnableDetailedErrors in Program.cs (gated on IsDevelopment())
  • 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# stubssrc/Virtufin.WebSocketManager/Generated/
  • Python stubssrc/python/virtufin/websocketmanager/websocketmanager_pb2*.py
  • TypeScript stubssrc/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