feat: Add functional tests for OrderService and update InventoryService command and idempotency logic.

This commit is contained in:
Ho Ngoc Hai
2026-01-18 00:19:46 +07:00
parent 844e40f818
commit 811ddd1e19
384 changed files with 6939 additions and 2793 deletions

View File

@@ -0,0 +1,528 @@
---
name: development-lifecycle
description: Development lifecycle workflow - Code → Build → Test → Fix → Deploy. Use for understanding complete development process, debugging build errors, fixing test failures, and continuous improvement cycles in GoodGo microservices.
compatibility: ".NET 10+, Docker 24+, docker-compose 2.x"
metadata:
author: Velik Ho
version: "1.0"
---
# Development Lifecycle / Quy Trình Phát Triển
Complete iterative development workflow from code to deployment with focus on troubleshooting and debugging.
## When to Use This Skill / Khi Nào Sử Dụng
Use this skill when:
- Starting development on a new feature / Bắt đầu phát triển feature mới
- Debugging build or test failures / Debug lỗi build hoặc test
- Understanding the complete dev cycle / Hiểu quy trình phát triển hoàn chỉnh
- Setting up local development environment / Setup môi trường local
- Troubleshooting deployment issues / Khắc phục sự cố deployment
## Core Concepts / Khái Niệm Cốt Lõi
### Development Cycle / Chu Kỳ Phát Triển
```mermaid
graph LR
A[① CODE<br/>Write Features] --> B[② BUILD<br/>Compile & Package]
B --> C[③ TEST<br/>Run Tests]
C --> D{Pass?}
D -->|No| E[④ FIX<br/>Debug & Resolve]
E --> B
D -->|Yes| F[⑤ DEPLOY<br/>Local/Staging/Prod]
F --> G{Issues?}
G -->|Yes| E
G -->|No| H[✓ Done]
```
### Philosophy / Triết Lý
1. **Fail Fast** - Phát hiện lỗi sớm nhất có thể
2. **Iterative** - Cải tiến liên tục qua từng cycle
3. **Automated** - Tự động hóa các bước lặp lại
4. **Observable** - Luôn có visibility vào system state
## Phase 1: CODE ✍️
**Goal**: Viết code sạch, maintainable theo architecture chuẩn
### Workflow
**Use existing skills** cho coding phase:
- [dotnet-microservice-workflow](../dotnet-microservice-workflow/SKILL.md) - 4-layer architecture
- [api-design](../api-design/SKILL.md) - RESTful endpoints
- [cqrs-mediatr](../cqrs-mediatr/SKILL.md) - Commands/Queries
- [domain-driven-design](../domain-driven-design/SKILL.md) - Domain modeling
### Best Practices
```csharp
// ✅ GOOD: Clear intention, follows patterns
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
private readonly IOrderRepository _repository;
public async Task<OrderResult> Handle(CreateOrderCommand cmd, CancellationToken ct)
{
var order = new Order(cmd.UserId, cmd.ShippingAddress);
foreach (var item in cmd.Items)
order.AddItem(item.ProductId, item.Quantity, item.Price);
await _repository.AddAsync(order, ct);
await _repository.UnitOfWork.SaveChangesAsync(ct);
return new OrderResult(order.Id);
}
}
// ❌ BAD: Mixed concerns, no separation
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var order = new Order { UserId = request.UserId };
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return Ok(order.Id);
}
```
---
## Phase 2: BUILD 🔨
**Goal**: Compile code và tạo artifacts (DLLs, Docker images)
### Local .NET Build
```bash
# Restore NuGet packages
dotnet restore
# Build solution
dotnet build
# Build specific project
dotnet build src/MyService.API/
# Release build
dotnet build -c Release
# Verbose output (for debugging)
dotnet build -v detailed
```
### Docker Build
```bash
# Build service image
docker build -t my-service:latest -f services/my-service-net/Dockerfile .
# Build via docker-compose
docker-compose -f deployments/local/docker-compose.yml build my-service-net
# No cache (clean build)
docker-compose -f deployments/local/docker-compose.yml build --no-cache my-service-net
# Parallel build multiple services
docker-compose -f deployments/local/docker-compose.yml build --parallel
```
### Common Build Errors
| Error Code | Cause | Solution |
|------------|-------|----------|
| **CS0246** | Missing type/namespace | Run `dotnet restore`, add package reference |
| **CS0103** | Name does not exist | Add `using` statement |
| **CS1061** | Missing member | Check property/method name, NuGet version |
| **NU1101** | Unable to find package | Check package name, NuGet source |
| **Docker context** | Wrong build context | Verify `-f` and context path |
**See detailed solutions**: [references/build-errors.md](references/build-errors.md)
---
## Phase 3: TEST 🧪
**Goal**: Validate correctness through automated tests
### Test Pyramid
```
┌──────────────┐
│ E2E Tests │ ← ~5% - Slow, brittle
│ (UI/API) │
├──────────────┤
│ Integration │ ← ~15% - Medium speed
│ Tests │ Database, HTTP
├──────────────┤
│ Unit Tests │ ← ~80% - Fast, isolated
│ (Handlers, │ Pure logic
│ Domain) │
└──────────────┘
```
### Test Commands
```bash
# Run all tests
dotnet test
# Run specific test project
dotnet test tests/MyService.UnitTests/
# Run with filter
dotnet test --filter Category=Unit
dotnet test --filter FullyQualifiedName~CreateOrderHandler
# Watch mode (TDD)
dotnet watch test --project tests/MyService.UnitTests/
# Coverage report
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=opencover
# Detailed output
dotnet test --logger "console;verbosity=detailed"
```
### Test Structure
**Use testing skill**: [dotnet-senior-tester](../dotnet-senior-tester/SKILL.md)
```csharp
// ✅ GOOD: Arrange-Act-Assert, clear mocking
public class CreateOrderCommandHandlerTests
{
[Fact]
public async Task Handle_ValidCommand_CreatesOrder()
{
// Arrange
var repository = Substitute.For<IOrderRepository>();
var unitOfWork = Substitute.For<IUnitOfWork>();
repository.UnitOfWork.Returns(unitOfWork);
var handler = new CreateOrderCommandHandler(repository);
var command = new CreateOrderCommand(UserId: Guid.NewGuid(), /* ... */);
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
await repository.Received(1).AddAsync(Arg.Any<Order>(), Arg.Any<CancellationToken>());
await unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
Assert.NotEqual(Guid.Empty, result.OrderId);
}
}
```
**See test failure patterns**: [references/test-failures.md](references/test-failures.md)
---
## Phase 4: FIX 🔧
**Goal**: Debug và resolve issues nhanh chóng
### Debugging Workflow
```mermaid
graph TD
A[Error Detected] --> B{Error Type?}
B -->|Build| C[Check Build Logs]
B -->|Test| D[Check Test Output]
B -->|Runtime| E[Check App Logs]
C --> F[Identify Root Cause]
D --> F
E --> F
F --> G[Apply Fix]
G --> H[Verify Fix]
H --> I{Fixed?}
I -->|No| F
I -->|Yes| J[Document Solution]
```
### Debugging Tools
**Build Errors:**
```bash
# Verbose build output
dotnet build -v detailed
# Clean and rebuild
dotnet clean && dotnet build
# Check references
dotnet list package
```
**Test Failures:**
```bash
# Run single test with details
dotnet test --filter TestMethodName --logger "console;verbosity=detailed"
# Debug test in VS Code
# Set breakpoint, F5 to debug
```
**Runtime Issues:**
```bash
# Container logs
docker logs -f my-service-net
docker logs --tail 100 my-service-net
# Follow logs
docker-compose -f deployments/local/docker-compose.yml logs -f my-service-net
# Exec into container
docker exec -it my-service-net sh
# Check health endpoint
curl http://localhost/api/v1/my-service/health
```
### Common Fix Patterns
| Issue | Pattern | Example |
|-------|---------|---------|
| **NullReferenceException** | Check mock setup | `repository.Setup(x => x.Get()).Returns(entity)` |
| **Async deadlock** | Use `ConfigureAwait(false)` | `await task.ConfigureAwait(false);` |
| **Database timeout** | Check connection string, indexes | Add index on frequently queried columns |
| **Container won't start** | Check environment vars, ports | Verify `docker-compose.yml` config |
**See detailed guide**: [references/debugging-guide.md](references/debugging-guide.md)
---
## Phase 5: DEPLOY 🚀
**Goal**: Deploy và verify application in target environment
### Local Deployment
```bash
# Start service with docker-compose
docker-compose -f deployments/local/docker-compose.yml up -d my-service-net
# Check status
docker-compose -f deployments/local/docker-compose.yml ps
# Health check
curl http://localhost/api/v1/my-service/health
# Swagger UI
open http://localhost/api/v1/my-service/swagger
```
### Health Checks
Every service MUST have `/health` endpoint:
```csharp
// Program.cs
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, name: "database")
.AddRedis(redisConnection, name: "redis");
app.MapHealthChecks("/health");
```
### Verification Checklist
- [ ] Service starts without errors
- [ ] `/health` returns 200 OK
- [ ] Database migrations applied
- [ ] Swagger UI accessible
- [ ] Sample API call works
- [ ] Logs show no errors
### Staging/Production
**Use deployment skill**: [deployment-kubernetes](../deployment-kubernetes/SKILL.md)
```bash
# kubectl commands for K8s
kubectl apply -f deployments/kubernetes/my-service.yaml
kubectl rollout status deployment/my-service
kubectl get pods -l app=my-service
```
---
## Development Patterns / Các Mẫu Phát Triển
### Pattern 1: Test-Driven Development (TDD)
```
1. ❌ RED - Write failing test
2. ✅ GREEN - Write minimal code to pass
3. 🔧 REFACTOR - Improve code quality
4. Repeat
```
**Benefits:**
- Better design (testable code)
- High confidence in changes
- Living documentation
### Pattern 2: Incremental Development
```bash
# Small commits with working state
git commit -m "feat: add CreateOrder command"
git commit -m "test: add CreateOrderHandler tests"
git commit -m "fix: validate order items quantity"
```
### Pattern 3: Hot Reload Development
```bash
# .NET hot reload (faster iteration)
dotnet watch run --project src/MyService.API
# Docker with volume mount (for config changes)
docker-compose up --build
```
---
## Helper Scripts / Scripts Hỗ Trợ
### Check Environment
```bash
# Verify development environment
./scripts/check-env.sh
```
### Quick Build
```bash
# Build single service quickly
./scripts/quick-build.sh my-service-net
```
### Debug Build
```bash
# Debug build errors with verbose output
./scripts/debug-build.sh my-service-net
```
### Health Check
```bash
# Check all services health
./scripts/health-check.sh
```
---
## Common Mistakes / Lỗi Thường Gặp
### 1. Skip Tests
**Problem**: Bugs go to production
**Solution**: Write tests first (TDD) or immediately after feature
```bash
# ❌ BAD: Push without testing
git push origin main
# ✅ GOOD: Always test first
dotnet test && git push origin main
```
### 2. Ignore Build Warnings
**Problem**: Tech debt accumulates, future breaks
**Solution**: Treat warnings as errors
```xml
<!-- MyService.API.csproj -->
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
```
### 3. No Logging
**Problem**: Hard to debug production issues
**Solution**: Add structured logging
```csharp
// ✅ GOOD: Structured logging
_logger.LogInformation("Creating order for user {UserId} with {ItemCount} items",
command.UserId, command.Items.Count);
```
### 4. Skip Health Checks
**Problem**: Can't verify deployment success
**Solution**: Always add `/health` endpoint
```csharp
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
```
---
## Quick Reference / Tham Chiếu Nhanh
### Development Commands
| Task | Command |
|------|---------|
| **Restore packages** | `dotnet restore` |
| **Build** | `dotnet build` |
| **Run tests** | `dotnet test` |
| **Run locally** | `dotnet run --project src/MyService.API` |
| **Watch mode** | `dotnet watch run` |
| **Hot reload tests** | `dotnet watch test` |
### Docker Commands
| Task | Command |
|------|---------|
| **Build image** | `docker-compose build my-service-net` |
| **Start service** | `docker-compose up -d my-service-net` |
| **View logs** | `docker-compose logs -f my-service-net` |
| **Restart** | `docker-compose restart my-service-net` |
| **Stop** | `docker-compose down` |
| **Rebuild & start** | `docker-compose up -d --build my-service-net` |
### Debugging Commands
| Task | Command |
|------|---------|
| **Verbose build** | `dotnet build -v detailed` |
| **Clean build** | `dotnet clean && dotnet build` |
| **Test with logs** | `dotnet test --logger console` |
| **Container logs** | `docker logs -f my-service-net` |
| **Exec into container** | `docker exec -it my-service-net sh` |
---
## Resources / Tài Nguyên
### Related Skills
- [dotnet-microservice-workflow](../dotnet-microservice-workflow/SKILL.md) - 4-phase architecture workflow
- [dotnet-senior-tester](../dotnet-senior-tester/SKILL.md) - Comprehensive testing
- [docker-traefik](../docker-traefik/SKILL.md) - Docker patterns
- [error-handling-patterns](../error-handling-patterns/SKILL.md) - Exception handling
- [project-rules](../project-rules/SKILL.md) - Coding standards
### Helper Resources
- [Build Errors Catalog](references/build-errors.md) - Common build errors and solutions
- [Test Failures Guide](references/test-failures.md) - Test debugging patterns
- [Debugging Guide](references/debugging-guide.md) - Advanced debugging techniques
### External Resources
- [.NET CLI Reference](https://learn.microsoft.com/en-us/dotnet/core/tools/)
- [Docker Compose Reference](https://docs.docker.com/compose/compose-file/)
- [xUnit Documentation](https://xunit.net/)

View File

@@ -0,0 +1,298 @@
# Common Build Errors Catalog
Quick reference for troubleshooting .NET and Docker build errors in GoodGo microservices.
## .NET Build Errors
### CS0246: Type or namespace not found
**Error:**
```
error CS0246: The type or namespace name 'X' could not be found
```
**Common Causes:**
1. Missing package reference
2. Incorrect namespace
3. Package not restored
**Solutions:**
```bash
# 1. Restore packages
dotnet restore
# 2. Add missing package
dotnet add package <PackageName>
# 3. Check using statements
# Add: using YourNamespace;
# 4. Clean and rebuild
dotnet clean && dotnet build
```
---
### CS0103: Name does not exist in current context
**Error:**
```
error CS0103: The name 'X' does not exist in the current context
```
**Common Causes:**
1. Missing `using` directive
2. Typo in variable/method name
3. Wrong scope
**Solutions:**
```csharp
// Add using directive
using System.Linq;
using Microsoft.EntityFrameworkCore;
// Check variable is in scope
public void Method()
{
var item = new Item();
// item is only available within Method()
}
```
---
### CS1061: Does not contain a definition
**Error:**
```
error CS1061: 'Type' does not contain a definition for 'Member'
```
**Common Causes:**
1. Typo in property/method name
2. Wrong NuGet package version
3. Extension method not imported
**Solutions:**
```csharp
// 1. Check spelling
order.Items // not order.Item
// 2. Import extension methods
using System.Linq; // for .Where(), .Select(), etc.
// 3. Update package version
dotnet list package --outdated
dotnet add package Microsoft.EntityFrameworkCore --version 10.0.0
```
---
### NU1101: Unable to find package
**Error:**
```
error NU1101: Unable to find package 'X'. No packages exist with this id
```
**Solutions:**
```bash
# 1. Check package name (spelling)
dotnet add package Swashbuckle.AspNetCore # not Swashbuckle.AspNet
# 2. Check NuGet sources
dotnet nuget list source
# 3. Clear NuGet cache
dotnet nuget locals all --clear
# 4. Restore with verbose
dotnet restore -v detailed
```
---
### MSB3277: Version conflicts
**Error:**
```
warning MSB3277: Found conflicts between different versions of "X"
```
**Solutions:**
```xml
<!-- Directory.Build.props or .csproj -->
<ItemGroup>
<!-- Force specific version across all projects -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
</ItemGroup>
```
```bash
# Or update all references
dotnet add package Microsoft.EntityFrameworkCore --version 10.0.2
```
---
## Docker Build Errors
### Cannot connect to Docker daemon
**Error:**
```
Cannot connect to the Docker daemon at unix:///var/run/docker.sock
```
**Solutions:**
```bash
# 1. Start Docker Desktop (macOS/Windows)
open -a Docker
# 2. Check Docker status
docker info
# 3. Restart Docker daemon (Linux)
sudo systemctl restart docker
```
---
### Context error: Dockerfile not found
**Error:**
```
ERROR: failed to solve: failed to read dockerfile: failed to resolve path
```
**Common Causes:**
- Wrong build context
- Dockerfile in wrong location
**Solutions:**
```bash
# Correct: Build from repo root with context
docker build -t my-service -f services/my-service-net/Dockerfile .
# ❌ Wrong: Building from service directory
cd services/my-service-net
docker build -t my-service .
# ✅ Correct: Specify context
docker build -t my-service -f Dockerfile ../..
```
---
### COPY failed: file not found
**Error:**
```
ERROR: failed to copy files: file not found
```
**Common Cause:** Wrong COPY path in Dockerfile
**Solutions:**
```dockerfile
# ❌ Wrong: Path relative to Dockerfile location
COPY src/ /app/
# ✅ Correct: Path relative to build context (repo root)
COPY services/my-service-net/src/ /app/
```
---
### Docker build cache issues
**Problem:** Old files being used despite changes
**Solutions:**
```bash
# Build without cache
docker build --no-cache -t my-service .
# Or via docker-compose
docker-compose build --no-cache my-service-net
# Clear all build cache
docker builder prune -a
```
---
## Entity Framework Errors
### No migrations found
**Error:**
```
No migrations were found
```
**Solutions:**
```bash
# Create initial migration
dotnet ef migrations add InitialCreate --project src/MyService.Infrastructure
# Verify migrations folder exists
ls src/MyService.Infrastructure/Migrations/
```
---
### Connection string error
**Error:**
```
A connection string could not be constructed
```
**Solutions:**
```bash
# 1. Add connection string to appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=mydb;..."
}
}
# 2. Set environment variable
export ConnectionStrings__DefaultConnection="Host=..."
# 3. Verify in Program.cs
builder.Services.AddDbContext<MyContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
```
---
## Quick Troubleshooting Checklist
When build fails:
- [ ] Run `dotnet restore`
- [ ] Check for typos
- [ ] Verify `using` statements
- [ ] Check package versions (`dotnet list package`)
- [ ] Clean build: `dotnet clean && dotnet build`
- [ ] Check build logs: `dotnet build -v detailed`
- [ ] Clear NuGet cache: `dotnet nuget locals all --clear`
When Docker build fails:
- [ ] Check Docker daemon is running
- [ ] Verify build context path
- [ ] Check Dockerfile COPY paths
- [ ] Try `--no-cache` build
- [ ] Check `.dockerignore` file
- [ ] Verify base image exists
---
## Related Resources
- [Test Failures Guide](test-failures.md)
- [Debugging Guide](debugging-guide.md)
- Main Skill: [development-lifecycle](../SKILL.md)

View File

@@ -0,0 +1,462 @@
# Debugging Guide
Advanced debugging techniques for .NET microservices development.
## Visual Studio Code Debugging
### Setup launch.json
```json
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Debug",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/MyService.API/bin/Debug/net10.0/MyService.API.dll",
"args": [],
"cwd": "${workspaceFolder}/src/MyService.API",
"console": "internalConsole",
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
{
"name": ".NET Attach",
"type": "coreclr",
"request": "attach"
}
]
}
```
### Debug Tests
```json
{
"name": ".NET Test Debug",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "dotnet",
"args": [
"test",
"--filter",
"FullyQualifiedName~MyTestMethod"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole"
}
```
**Usage:**
1. Set breakpoint in test or code
2. F5 to start debugging
3. Step through with F10 (over) / F11 (into)
---
## Remote Debugging Docker Containers
### Enable remote debugging in Dockerfile
```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS debug
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet build --no-restore
# Install debugger
RUN apt-get update && apt-get install -y procps
EXPOSE 8080
EXPOSE 4024
ENTRYPOINT ["dotnet", "run", "--no-build", "--project", "src/MyService.API"]
```
### Docker Compose debug config
```yaml
services:
my-service-debug:
build:
context: ../..
dockerfile: services/my-service-net/Dockerfile
target: debug
environment:
- ASPNETCORE_ENVIRONMENT=Development
- DOTNET_USE_POLLING_FILE_WATCHER=true
ports:
- "8080:8080"
- "4024:4024" # Debugger port
volumes:
- ./src:/src:ro
```
### Attach to running container
```bash
# 1. Start container with debug enabled
docker-compose up my-service-debug
# 2. Find process ID
docker exec my-service-debug ps aux | grep dotnet
# 3. In VS Code, use "Attach to Process" configuration
# Select the dotnet process from Docker container
```
---
## Logging Strategies
### Structured Logging with Serilog
```csharp
// Program.cs
builder.Host.UseSerilog((context, config) =>
{
config
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithProperty("Service", "MyService")
.WriteTo.Console(new JsonFormatter())
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day);
});
// Usage in code
_logger.LogInformation(
"Creating order for {UserId} with {ItemCount} items and total {Total:C}",
command.UserId,
command.Items.Count,
order.Total
);
```
**Benefits:**
- Searchable structured data
- Context preservation
- Easy filtering
---
### Log Scopes
```csharp
using (_logger.BeginScope("OrderId: {OrderId}", orderId))
{
_logger.LogInformation("Processing order");
_logger.LogInformation("Validating items");
_logger.LogInformation("Calculating total");
}
// All logs include OrderId automatically
```
---
## Debugging Tools
### dotnet-dump (Memory dumps)
```bash
# Install tool
dotnet tool install -g dotnet-dump
# Create dump of running process
dotnet-dump collect -p <pid>
# Analyze dump
dotnet-dump analyze <dump-file>
# Inside analyzer
> clrstack # View call stack
> dumpheap -stat # Heap statistics
> eeheap -gc # GC heap info
```
---
### dotnet-trace (Performance tracing)
```bash
# Install tool
dotnet tool install -g dotnet-trace
# Collect trace
dotnet-trace collect -p <pid> --duration 00:00:30
# Convert to speedscope format
dotnet-trace convert trace.nettrace --format speedscope
# View in browser
open https://www.speedscope.app/
```
---
### dotnet-counters (Live metrics)
```bash
# Install tool
dotnet tool install -g dotnet-counters
# Monitor live
dotnet-counters monitor -p <pid>
# Custom metrics
dotnet-counters monitor \
-p <pid> \
--counters System.Runtime,Microsoft.AspNetCore.Hosting
```
---
## Database Debugging
### EF Core Query Logging
```csharp
// Enable sensitive data logging (Development only!)
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseNpgsql(connectionString);
if (builder.Environment.IsDevelopment())
{
options
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
});
```
**Output:**
```
Executed DbCommand (23ms) [Parameters=[@p0='123'], CommandType='Text']
SELECT * FROM orders WHERE id = @p0
```
---
### SQL Profiling
```bash
# PostgreSQL: Enable query logging
# In postgresql.conf or docker-compose
POSTGRES_EXTRA_FLAGS="-c log_statement=all"
# View logs
docker-compose logs -f postgres | grep "statement:"
```
---
## HTTP Debugging
### Fiddler / Postman for API testing
**Setup authentication:**
```bash
# Get JWT token
curl -X POST http://localhost/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test123"}'
# Use token in requests
curl http://localhost/api/v1/orders \
-H "Authorization: Bearer <token>"
```
---
### Request/Response logging middleware
```csharp
// Custom middleware
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public async Task InvokeAsync(HttpContext context)
{
// Log request
context.Request.EnableBuffering();
var requestBody = await ReadBodyAsync(context.Request.Body);
_logger.LogInformation(
"HTTP {Method} {Path} Body: {Body}",
context.Request.Method,
context.Request.Path,
requestBody
);
// Capture response
var originalBody = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
await _next(context);
// Log response
responseBody.Seek(0, SeekOrigin.Begin);
var response = await new StreamReader(responseBody).ReadToEndAsync();
_logger.LogInformation(
"HTTP {StatusCode} Response: {Response}",
context.Response.StatusCode,
response
);
// Copy to original stream
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBody);
}
}
```
---
## Common Debugging Scenarios
### Find source of exception
```bash
# Enable first-chance exceptions in VS Code
# .vscode/launch.json
"exceptionOptions": {
"breakpoints": {
"raised": true,
"userUnhandled": false
}
}
```
---
### Memory leak investigation
```bash
# 1. Take baseline dump
dotnet-dump collect -p <pid> -o baseline.dump
# 2. Exercise application
# ... use the app normally ...
# 3. Take second dump
dotnet-dump collect -p <pid> -o leaked.dump
# 4. Compare
dotnet-dump analyze leaked.dump
> dumpheap -stat
# Look for growing object counts
```
---
### Slow request diagnosis
**Add request timing:**
```csharp
app.Use(async (context, next) =>
{
var sw = Stopwatch.StartNew();
await next();
sw.Stop();
if (sw.ElapsedMilliseconds > 1000)
{
_logger.LogWarning(
"Slow request: {Method} {Path} took {Elapsed}ms",
context.Request.Method,
context.Request.Path,
sw.ElapsedMilliseconds
);
}
});
```
---
## Performance Profiling
### BenchmarkDotNet
```csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class OrderBenchmarks
{
private Order _order;
[GlobalSetup]
public void Setup()
{
_order = new Order(/* ... */);
}
[Benchmark]
public void AddItem()
{
_order.AddItem(Guid.NewGuid(), 1, 10.0m);
}
}
// Run benchmarks
BenchmarkRunner.Run<OrderBenchmarks>();
```
---
## Quick Reference
### VS Code Shortcuts
| Action | Shortcut |
|--------|----------|
| Start debugging | F5 |
| Step over | F10 |
| Step into | F11 |
| Step out | Shift+F11 |
| Continue | F5 |
| Toggle breakpoint | F9 |
| Add watch | Right-click → Add to Watch |
### Useful Commands
```bash
# View running processes
ps aux | grep dotnet
# Find process by port
lsof -i :8080
# Kill process
kill -9 <pid>
# View environment variables
docker exec my-service env
# Execute command in container
docker exec -it my-service sh
# Follow logs with filter
docker logs -f my-service | grep ERROR
```
---
## Related Resources
- [Build Errors Catalog](build-errors.md)
- [Test Failures Guide](test-failures.md)
- Main Skill: [development-lifecycle](../SKILL.md)
- [.NET Debugging Documentation](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/)

View File

@@ -0,0 +1,354 @@
# Test Failures Guide
Common test failure patterns and how to fix them in .NET microservices.
## Mock Setup Issues
### NullReferenceException in tests
**Symptom:**
```
System.NullReferenceException: Object reference not set to an instance of an object
```
**Common Cause:** Mock not properly configured
**Solution:**
```csharp
// ❌ BAD: Mock returns null by default
var repository = Substitute.For<IOrderRepository>();
var order = await repository.GetByIdAsync(orderId); // returns null!
order.Status; // NullReferenceException
// ✅ GOOD: Configure mock to return data
var repository = Substitute.For<IOrderRepository>();
var expectedOrder = new Order(/* ... */);
repository.GetByIdAsync(orderId).Returns(expectedOrder);
var order = await repository.GetByIdAsync(orderId);
Assert.NotNull(order);
```
---
### UnitOfWork mock setup
**Problem:** `SaveChangesAsync` not properly mocked
**Solution:**
```csharp
// ✅ Complete mock setup
var repository = Substitute.For<IOrderRepository>();
var unitOfWork = Substitute.For<IUnitOfWork>();
// Important: Link repository to unitOfWork
repository.UnitOfWork.Returns(unitOfWork);
// Configure SaveChangesAsync behavior
unitOfWork.SaveChangesAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult(1));
// Now handler can use it
var handler = new CreateOrderHandler(repository);
await handler.Handle(command, CancellationToken.None);
// Verify SaveChangesAsync was called
await unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
```
---
## Async/Await Issues
### Test hangs or times out
**Common Causes:**
1. Missing `await`
2. Deadlock from `.Result` or `.Wait()`
3. Infinite loop
**Solutions:**
```csharp
// ❌ BAD: Forgetting await
[Fact]
public async Task TestMethod()
{
var result = handler.Handle(command, ct); // Missing await!
Assert.NotNull(result); // Wrong - this is Task, not result
}
// ✅ GOOD: Proper await
[Fact]
public async Task TestMethod()
{
var result = await handler.Handle(command, ct);
Assert.NotNull(result);
}
// ❌ BAD: Blocking async code
var result = repository.GetByIdAsync(id).Result; // Deadlock risk!
// ✅ GOOD: Await properly
var result = await repository.GetByIdAsync(id);
```
---
## Assertion Failures
### Expected vs Actual mismatch
**Symptom:**
```
Assert.Equal() Failure
Expected: 5
Actual: 0
```
**Debugging:**
```csharp
// ❌ Unclear what went wrong
Assert.Equal(5, order.Items.Count);
// ✅ Better: Add message
Assert.Equal(5, order.Items.Count,
$"Expected 5 items but got {order.Items.Count}");
// ✅ Best: Use specific assertions
Assert.NotEmpty(order.Items);
Assert.Equal(5, order.Items.Count);
Assert.All(order.Items, item => Assert.NotNull(item.ProductId));
```
---
### Collection comparison failures
**Problem:** Comparing collections incorrectly
**Solutions:**
```csharp
// ❌ BAD: Reference comparison
var expected = new List<int> { 1, 2, 3 };
var actual = service.GetNumbers();
Assert.Equal(expected, actual); // May fail even if content same
// ✅ GOOD: Value comparison
Assert.Equal(expected.Count, actual.Count);
Assert.All(expected, item => Assert.Contains(item, actual));
// ✅ Or use FluentAssertions
actual.Should().BeEquivalentTo(expected);
```
---
## Database Test Issues
### Test fails due to database state
**Problem:** Tests interfere with each other
**Solutions:**
```csharp
// ✅ Use in-memory database per test
public class OrderRepositoryTests : IDisposable
{
private readonly DbContextOptions<OrderContext> _options;
private readonly OrderContext _context;
public OrderRepositoryTests()
{
_options = new DbContextOptionsBuilder<OrderContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new OrderContext(_options);
}
public void Dispose()
{
_context.Database.EnsureDeleted();
_context.Dispose();
}
}
// ✅ Or use Testcontainers for real database
public class OrderRepositoryIntegrationTests : IAsyncLifetime
{
private PostgreSqlContainer _container;
public async Task InitializeAsync()
{
_container = new PostgreSqlBuilder().Build();
await _container.StartAsync();
}
public async Task DisposeAsync()
{
await _container.DisposeAsync();
}
}
```
---
## Integration Test Issues
### TestServer authentication failures
**Problem:** Requests return 401 Unauthorized
**Solutions:**
```csharp
// ✅ Setup test authentication
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove real auth
services.RemoveAll<IAuthenticationService>();
// Add test auth
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"Test", options => { });
});
}
}
// Usage in test
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Test-User-Id", userId.ToString());
```
---
### Port already in use
**Error:**
```
Address already in use: bind
```
**Solutions:**
```csharp
// ✅ Let TestServer choose random port
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.UseUrls(); // Empty = random port
});
// ✅ Or use different port per test class
builder.UseUrls("http://localhost:0"); // 0 = random port
```
---
## Flaky Tests
### Tests pass/fail intermittently
**Common Causes:**
1. Race conditions
2. Time-dependent logic
3. Shared state
4. External dependencies
**Solutions:**
```csharp
// ❌ BAD: Time-dependent test
[Fact]
public void TestCreatedDate()
{
var order = new Order();
Assert.Equal(DateTime.UtcNow, order.CreatedAt); // Flaky!
}
// ✅ GOOD: Test with tolerance
[Fact]
public void TestCreatedDate()
{
var before = DateTime.UtcNow;
var order = new Order();
var after = DateTime.UtcNow;
Assert.InRange(order.CreatedAt, before, after);
}
// ✅ Or inject time provider
public class Order
{
public DateTime CreatedAt { get; }
public Order(ITimeProvider timeProvider)
{
CreatedAt = timeProvider.UtcNow;
}
}
// In test: mock time
var timeProvider = Substitute.For<ITimeProvider>();
timeProvider.UtcNow.Returns(new DateTime(2024, 1, 1));
```
---
## Test Coverage Issues
### Low coverage on critical code
**Problem:** Important logic not tested
**Solutions:**
```bash
# Generate coverage report
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura
# View HTML report
reportgenerator -reports:coverage.cobertura.xml -targetdir:coveragereport
open coveragereport/index.html
# Or use coverlet
dotnet add package coverlet.collector
dotnet test --collect:"XPlat Code Coverage"
```
**Focus on:**
- Domain logic (business rules)
- Command/Query handlers
- Critical paths (checkout, payment, etc.)
---
## Quick Troubleshooting Checklist
When tests fail:
- [ ] Check mock setup (returns correct values?)
- [ ] Verify async/await (no missing `await`?)
- [ ] Check assertions (expected vs actual clear?)
- [ ] Isolate test (run alone, not in suite)
- [ ] Check test output logs
- [ ] Add debug logging
- [ ] Use debugger with breakpoints
For integration tests:
- [ ] Check TestServer configuration
- [ ] Verify authentication setup
- [ ] Check database state (clean between tests?)
- [ ] Review test ordering (independent?)
- [ ] Check external dependencies (mocked?)
---
## Related Resources
- [Build Errors Catalog](build-errors.md)
- [Debugging Guide](debugging-guide.md)
- [dotnet-senior-tester skill](../../dotnet-senior-tester/SKILL.md)
- Main Skill: [development-lifecycle](../SKILL.md)

View File

@@ -0,0 +1,120 @@
#!/bin/bash
# EN: Check development environment for GoodGo microservices
# VI: Kiểm tra môi trường phát triển cho GoodGo microservices
set -e
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Development Environment Check"
echo " Kiểm tra Môi Trường Phát Triển"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
check_command() {
local cmd=$1
local required_version=$2
if command -v $cmd &> /dev/null; then
local version=$($cmd --version 2>&1 | head -n 1)
echo -e "${GREEN}${NC} $cmd: $version"
return 0
else
echo -e "${RED}${NC} $cmd: Not installed"
if [ ! -z "$required_version" ]; then
echo -e " ${YELLOW}Required: $required_version${NC}"
fi
return 1
fi
}
check_dotnet() {
if command -v dotnet &> /dev/null; then
local version=$(dotnet --version)
echo -e "${GREEN}${NC} .NET SDK: $version"
# Check if version >= 8.0
local major=$(echo $version | cut -d. -f1)
if [ "$major" -lt 8 ]; then
echo -e " ${YELLOW}Warning: .NET 8.0+ recommended${NC}"
fi
return 0
else
echo -e "${RED}${NC} .NET SDK: Not installed"
echo -e " ${YELLOW}Required: .NET 8.0+${NC}"
return 1
fi
}
check_docker() {
if command -v docker &> /dev/null; then
local version=$(docker --version | cut -d' ' -f3 | tr -d ',')
echo -e "${GREEN}${NC} Docker: $version"
# Check if Docker daemon is running
if docker ps &> /dev/null; then
echo -e " ${GREEN}Docker daemon is running${NC}"
else
echo -e " ${RED}Docker daemon is not running${NC}"
return 1
fi
return 0
else
echo -e "${RED}${NC} Docker: Not installed"
return 1
fi
}
check_docker_compose() {
if command -v docker-compose &> /dev/null; then
local version=$(docker-compose --version | cut -d' ' -f4 | tr -d ',')
echo -e "${GREEN}${NC} docker-compose: $version"
return 0
elif docker compose version &> /dev/null; then
local version=$(docker compose version | cut -d' ' -f4 | tr -d 'v')
echo -e "${GREEN}${NC} docker compose: $version"
return 0
else
echo -e "${RED}${NC} docker-compose: Not installed"
return 1
fi
}
# Main checks
echo "Core Tools:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
ERRORS=0
check_dotnet || ((ERRORS++))
check_docker || ((ERRORS++))
check_docker_compose || ((ERRORS++))
echo ""
echo "Optional Tools:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
check_command "git" || true
check_command "curl" || true
check_command "jq" || true
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ $ERRORS -eq 0 ]; then
echo -e "${GREEN}✓ All core tools are installed!${NC}"
echo ""
exit 0
else
echo -e "${RED}$ERRORS core tool(s) missing or not working${NC}"
echo ""
echo "Installation guides:"
echo " .NET SDK: https://dotnet.microsoft.com/download"
echo " Docker: https://docs.docker.com/get-docker/"
echo ""
exit 1
fi

View File

@@ -0,0 +1,85 @@
#!/bin/bash
# EN: Debug build errors with verbose output
# VI: Debug lỗi build với output chi tiết
set -e
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
if [ $# -eq 0 ]; then
echo -e "${RED}Error: Service name required${NC}"
echo "Usage: $0 <service-name>"
echo "Example: $0 my-service-net"
exit 1
fi
SERVICE_NAME=$1
SERVICE_DIR="services/${SERVICE_NAME}"
LOG_FILE="build-debug-$(date +%Y%m%d-%H%M%S).log"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Debug Build: ${SERVICE_NAME}${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Check if service exists
if [ ! -d "$SERVICE_DIR" ]; then
echo -e "${RED}✗ Service not found: $SERVICE_DIR${NC}"
exit 1
fi
cd "$SERVICE_DIR"
# Find solution file
SOLUTION_FILE=$(find . -maxdepth 1 -name "*.slnx" -o -name "*.sln" | head -n 1)
if [ -z "$SOLUTION_FILE" ]; then
echo -e "${RED}✗ No solution file found${NC}"
exit 1
fi
echo -e "${YELLOW}Step 1: Cleaning previous build artifacts...${NC}"
dotnet clean "$SOLUTION_FILE"
echo ""
echo -e "${YELLOW}Step 2: Restoring packages with detailed output...${NC}"
dotnet restore "$SOLUTION_FILE" -v detailed 2>&1 | tee "$LOG_FILE"
echo ""
echo -e "${YELLOW}Step 3: Building with detailed verbosity...${NC}"
echo -e "${YELLOW}(This will take longer but show all information)${NC}"
echo ""
# Build with detailed output
dotnet build "$SOLUTION_FILE" --no-restore -v detailed 2>&1 | tee -a "$LOG_FILE"
BUILD_STATUS=$?
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if [ $BUILD_STATUS -eq 0 ]; then
echo -e "${GREEN}✓ Build completed successfully!${NC}"
echo ""
echo "Log saved to: $LOG_FILE"
else
echo -e "${RED}✗ Build failed${NC}"
echo ""
echo "Full debug log saved to: $LOG_FILE"
echo ""
echo -e "${YELLOW}Common issues to check:${NC}"
echo " 1. Missing package references (dotnet add package)"
echo " 2. Using statements (add 'using' directive)"
echo " 3. NuGet package version conflicts"
echo " 4. Target framework mismatch"
echo ""
echo "Grep for errors:"
echo " cat $LOG_FILE | grep -i error"
echo " cat $LOG_FILE | grep -i CS[0-9]"
exit 1
fi

View File

@@ -0,0 +1,90 @@
#!/bin/bash
# EN: Check health of all running services
# VI: Kiểm tra health của tất cả services đang chạy
set -e
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Service Health Check${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Default base URL
BASE_URL="${BASE_URL:-http://localhost}"
TIMEOUT=5
check_health() {
local service=$1
local path=$2
local url="${BASE_URL}${path}"
echo -n "Checking $service... "
if response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $TIMEOUT "$url" 2>/dev/null); then
if [ "$response" = "200" ]; then
echo -e "${GREEN}✓ Healthy (200)${NC}"
return 0
else
echo -e "${YELLOW}⚠ Response: $response${NC}"
return 1
fi
else
echo -e "${RED}✗ Not reachable${NC}"
return 1
fi
}
# Track results
TOTAL=0
HEALTHY=0
UNHEALTHY=0
# Service list (customize based on your services)
SERVICES=(
"IAM Service:/api/v1/iam/health"
"Storage Service:/api/v1/storage/health"
"Catalog Service:/api/v1/catalog/health"
"Order Service:/api/v1/order/health"
"Inventory Service:/api/v1/inventory/health"
"F&B Engine:/api/v1/fnb-engine/health"
"Booking Service:/api/v1/booking/health"
)
for SERVICE_INFO in "${SERVICES[@]}"; do
IFS=':' read -r NAME PATH <<< "$SERVICE_INFO"
((TOTAL++))
if check_health "$NAME" "$PATH"; then
((HEALTHY++))
else
((UNHEALTHY++))
fi
done
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo "Summary:"
echo " Total services: $TOTAL"
echo -e " Healthy: ${GREEN}$HEALTHY${NC}"
echo -e " Unhealthy: ${RED}$UNHEALTHY${NC}"
echo ""
if [ $UNHEALTHY -eq 0 ]; then
echo -e "${GREEN}✓ All services are healthy!${NC}"
exit 0
else
echo -e "${YELLOW}⚠ Some services are not healthy${NC}"
echo ""
echo "Troubleshooting:"
echo " • Check if services are running: docker-compose ps"
echo " • View logs: docker-compose logs -f <service-name>"
echo " • Restart service: docker-compose restart <service-name>"
exit 1
fi

View File

@@ -0,0 +1,58 @@
#!/bin/bash
# EN: Quick build script for a single service
# VI: Script build nhanh cho một service
set -e
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
if [ $# -eq 0 ]; then
echo -e "${RED}Error: Service name required${NC}"
echo "Usage: $0 <service-name>"
echo "Example: $0 my-service-net"
exit 1
fi
SERVICE_NAME=$1
SERVICE_DIR="services/${SERVICE_NAME}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} Quick Build: ${SERVICE_NAME}${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Check if service exists
if [ ! -d "$SERVICE_DIR" ]; then
echo -e "${RED}✗ Service not found: $SERVICE_DIR${NC}"
exit 1
fi
cd "$SERVICE_DIR"
# Find solution file
SOLUTION_FILE=$(find . -maxdepth 1 -name "*.slnx" -o -name "*.sln" | head -n 1)
if [ -z "$SOLUTION_FILE" ]; then
echo -e "${RED}✗ No solution file found${NC}"
exit 1
fi
echo -e "${YELLOW}📦 Restoring packages...${NC}"
dotnet restore "$SOLUTION_FILE"
echo ""
echo -e "${YELLOW}🔨 Building solution...${NC}"
dotnet build "$SOLUTION_FILE" --no-restore
echo ""
echo -e "${GREEN}✓ Build completed successfully!${NC}"
echo ""
echo "Next steps:"
echo " • Run tests: dotnet test"
echo " • Run locally: dotnet run --project src/*/API/"
echo " • Build Docker: docker-compose -f deployments/local/docker-compose.yml build ${SERVICE_NAME}"