📋 Task Description
Implement comprehensive API integration tests covering all REST endpoints, authentication, authorization, validation, error handling, and API contracts. Tests should run against real infrastructure (MongoDB, Redis, RabbitMQ) using Docker containers.
🎯 Objectives
- Create integration test infrastructure with TestContainers
- Implement tests for all Process API endpoints
- Implement tests for all Policy API endpoints
- Test authentication and authorization flows
- Test request validation and error responses
- Test API contract compliance (OpenAPI spec)
- Verify HTTP status codes and headers
- Test pagination and filtering
- Test idempotency guarantees
- Add test data builders and fixtures
- Configure test logging and diagnostics
- Document integration testing approach
📦 Deliverables
1. Setup Integration Test Infrastructure
Update tests/StarGate.IntegrationTests/StarGate.IntegrationTests.csproj:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageReference Include="Testcontainers" Version="3.7.0" />
<PackageReference Include="Testcontainers.MongoDb" Version="3.7.0" />
<PackageReference Include="Testcontainers.Redis" Version="3.7.0" />
<PackageReference Include="Testcontainers.RabbitMq" Version="3.7.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" />
</ItemGroup>
2. Create Web Application Factory
Create tests/StarGate.IntegrationTests/Infrastructure/StarGateWebApplicationFactory.cs:
namespace StarGate.IntegrationTests.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Testcontainers.MongoDb;
using Testcontainers.RabbitMq;
using Testcontainers.Redis;
/// <summary>
/// Custom web application factory for integration tests.
/// </summary>
public class StarGateWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MongoDbContainer _mongoContainer = new MongoDbBuilder()
.WithImage("mongo:7.0")
.WithPortBinding(27017, true)
.Build();
private readonly RedisContainer _redisContainer = new RedisBuilder()
.WithImage("redis:7.2")
.WithPortBinding(6379, true)
.Build();
private readonly RabbitMqContainer _rabbitMqContainer = new RabbitMqBuilder()
.WithImage("rabbitmq:3.13-management")
.WithPortBinding(5672, true)
.WithPortBinding(15672, true)
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, config) =>
{
// Override configuration with test container connection strings
var testConfig = new Dictionary<string, string>
{
["MongoDB:ConnectionString"] = _mongoContainer.GetConnectionString(),
["MongoDB:DatabaseName"] = "stargate-test",
["Redis:ConnectionString"] = _redisContainer.GetConnectionString(),
["RabbitMQ:HostName"] = _rabbitMqContainer.Hostname,
["RabbitMQ:Port"] = _rabbitMqContainer.GetMappedPublicPort(5672).ToString(),
["RabbitMQ:UserName"] = "guest",
["RabbitMQ:Password"] = "guest",
// Reduce timeouts for faster tests
["Resilience:Retry:MaxRetryAttempts"] = "2",
["Resilience:CircuitBreaker:BreakDurationSeconds"] = "5",
["Resilience:Timeout:DatabaseTimeoutSeconds"] = "5"
};
config.AddInMemoryCollection(testConfig!);
});
builder.ConfigureServices(services =>
{
// Additional test service configuration if needed
});
}
public async Task InitializeAsync()
{
await _mongoContainer.StartAsync();
await _redisContainer.StartAsync();
await _rabbitMqContainer.StartAsync();
}
public new async Task DisposeAsync()
{
await _mongoContainer.DisposeAsync();
await _redisContainer.DisposeAsync();
await _rabbitMqContainer.DisposeAsync();
await base.DisposeAsync();
}
}
3. Create Test Helpers and Builders
Create tests/StarGate.IntegrationTests/Helpers/HttpClientExtensions.cs:
namespace StarGate.IntegrationTests.Helpers;
using System.Net.Http.Json;
using System.Text.Json;
public static class HttpClientExtensions
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
public static async Task<T?> GetJsonAsync<T>(this HttpClient client, string requestUri)
{
var response = await client.GetAsync(requestUri);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>(JsonOptions);
}
public static async Task<HttpResponseMessage> PostJsonAsync<T>(
this HttpClient client,
string requestUri,
T content)
{
return await client.PostAsJsonAsync(requestUri, content, JsonOptions);
}
public static async Task<TResponse?> PostAndGetJsonAsync<TRequest, TResponse>(
this HttpClient client,
string requestUri,
TRequest content)
{
var response = await client.PostAsJsonAsync(requestUri, content, JsonOptions);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TResponse>(JsonOptions);
}
}
Create tests/StarGate.IntegrationTests/Builders/CreateProcessRequestBuilder.cs:
namespace StarGate.IntegrationTests.Builders;
public class CreateProcessRequestBuilder
{
private string _clientId = "test-client";
private string _processType = "test-process";
private string _clientProcessId = Guid.NewGuid().ToString();
private Dictionary<string, string> _metadata = new();
public CreateProcessRequestBuilder WithClientId(string clientId)
{
_clientId = clientId;
return this;
}
public CreateProcessRequestBuilder WithProcessType(string processType)
{
_processType = processType;
return this;
}
public CreateProcessRequestBuilder WithClientProcessId(string clientProcessId)
{
_clientProcessId = clientProcessId;
return this;
}
public CreateProcessRequestBuilder WithMetadata(string key, string value)
{
_metadata[key] = value;
return this;
}
public CreateProcessRequestBuilder WithMetadata(Dictionary<string, string> metadata)
{
_metadata = metadata;
return this;
}
public object Build()
{
return new
{
clientId = _clientId,
processType = _processType,
clientProcessId = _clientProcessId,
metadata = _metadata
};
}
}
4. Create Process API Integration Tests
Create tests/StarGate.IntegrationTests/Api/ProcessEndpointsTests.cs:
namespace StarGate.IntegrationTests.Api;
using FluentAssertions;
using StarGate.IntegrationTests.Builders;
using StarGate.IntegrationTests.Helpers;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using Xunit;
public class ProcessEndpointsTests : IClassFixture<StarGateWebApplicationFactory>
{
private readonly HttpClient _client;
public ProcessEndpointsTests(StarGateWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task CreateProcess_Should_ReturnCreated_WithValidRequest()
{
// Arrange
var request = new CreateProcessRequestBuilder()
.WithClientId("test-client")
.WithProcessType("order")
.WithMetadata("orderId", "12345")
.Build();
// Act
var response = await _client.PostJsonAsync("/api/processes", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
response.Headers.Location.Should().NotBeNull();
var content = await response.Content.ReadAsStringAsync();
content.Should().Contain("processId");
}
[Fact]
public async Task CreateProcess_Should_ReturnBadRequest_WhenClientIdMissing()
{
// Arrange
var request = new
{
processType = "order",
clientProcessId = "test-123"
};
// Act
var response = await _client.PostJsonAsync("/api/processes", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task CreateProcess_Should_BeIdempotent_WithSameClientProcessId()
{
// Arrange
var clientProcessId = Guid.NewGuid().ToString();
var request = new CreateProcessRequestBuilder()
.WithClientProcessId(clientProcessId)
.Build();
// Act
var response1 = await _client.PostJsonAsync("/api/processes", request);
var response2 = await _client.PostJsonAsync("/api/processes", request);
// Assert
response1.StatusCode.Should().Be(HttpStatusCode.Created);
response2.StatusCode.Should().Be(HttpStatusCode.OK); // Returns existing
var location1 = response1.Headers.Location?.ToString();
var location2 = response2.Headers.Location?.ToString();
location1.Should().Be(location2);
}
[Fact]
public async Task GetProcess_Should_ReturnProcess_WhenExists()
{
// Arrange
var request = new CreateProcessRequestBuilder().Build();
var createResponse = await _client.PostJsonAsync("/api/processes", request);
var processId = ExtractProcessIdFromLocation(createResponse.Headers.Location!);
// Act
var response = await _client.GetAsync($"/api/processes/{processId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var process = await response.Content.ReadFromJsonAsync<ProcessResponse>();
process.Should().NotBeNull();
process!.ProcessId.Should().Be(processId);
}
[Fact]
public async Task GetProcess_Should_ReturnNotFound_WhenNotExists()
{
// Arrange
var nonExistentId = Guid.NewGuid();
// Act
var response = await _client.GetAsync($"/api/processes/{nonExistentId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task GetProcessByClientId_Should_ReturnProcess_WhenExists()
{
// Arrange
var clientId = "test-client-" + Guid.NewGuid();
var clientProcessId = "client-process-" + Guid.NewGuid();
var request = new CreateProcessRequestBuilder()
.WithClientId(clientId)
.WithClientProcessId(clientProcessId)
.Build();
await _client.PostJsonAsync("/api/processes", request);
// Act
var response = await _client.GetAsync(
$"/api/processes/by-client/{clientId}/{clientProcessId}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var process = await response.Content.ReadFromJsonAsync<ProcessResponse>();
process.Should().NotBeNull();
process!.ClientId.Should().Be(clientId);
process.ClientProcessId.Should().Be(clientProcessId);
}
[Fact]
public async Task ListProcesses_Should_ReturnPaginatedResults()
{
// Arrange - Create multiple processes
var clientId = "test-client-" + Guid.NewGuid();
for (int i = 0; i < 5; i++)
{
var request = new CreateProcessRequestBuilder()
.WithClientId(clientId)
.Build();
await _client.PostJsonAsync("/api/processes", request);
}
// Act
var response = await _client.GetAsync(
$"/api/processes?clientId={clientId}&pageSize=3");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<PagedResponse<ProcessResponse>>();
result.Should().NotBeNull();
result!.Items.Should().HaveCount(3);
result.HasNextPage.Should().BeTrue();
}
private static Guid ExtractProcessIdFromLocation(Uri location)
{
var segments = location.Segments;
var idString = segments[^1];
return Guid.Parse(idString);
}
}
public class ProcessResponse
{
public Guid ProcessId { get; set; }
public string ClientId { get; set; } = string.Empty;
public string ProcessType { get; set; } = string.Empty;
public string ClientProcessId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public Dictionary<string, string>? Metadata { get; set; }
}
public class PagedResponse<T>
{
public List<T> Items { get; set; } = new();
public int TotalCount { get; set; }
public bool HasNextPage { get; set; }
public string? NextCursor { get; set; }
}
5. Create Policy API Integration Tests
Create tests/StarGate.IntegrationTests/Api/PolicyEndpointsTests.cs:
namespace StarGate.IntegrationTests.Api;
using FluentAssertions;
using StarGate.IntegrationTests.Helpers;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using Xunit;
public class PolicyEndpointsTests : IClassFixture<StarGateWebApplicationFactory>
{
private readonly HttpClient _client;
public PolicyEndpointsTests(StarGateWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task CreateProcessTypePolicy_Should_ReturnCreated_WithValidRequest()
{
// Arrange
var request = new
{
processType = "test-type-" + Guid.NewGuid(),
maxRetries = 3,
timeoutSeconds = 30,
maxConcurrentProcesses = 10,
retentionDays = 30
};
// Act
var response = await _client.PostJsonAsync("/api/policies/process-types", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
[Fact]
public async Task CreateProcessTypePolicy_Should_ReturnBadRequest_WhenMaxRetriesInvalid()
{
// Arrange
var request = new
{
processType = "test-type",
maxRetries = -1, // Invalid
timeoutSeconds = 30
};
// Act
var response = await _client.PostJsonAsync("/api/policies/process-types", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task GetProcessTypePolicy_Should_ReturnPolicy_WhenExists()
{
// Arrange
var processType = "test-type-" + Guid.NewGuid();
var createRequest = new
{
processType,
maxRetries = 5,
timeoutSeconds = 60
};
await _client.PostJsonAsync("/api/policies/process-types", createRequest);
// Act
var response = await _client.GetAsync($"/api/policies/process-types/{processType}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var policy = await response.Content.ReadFromJsonAsync<PolicyResponse>();
policy.Should().NotBeNull();
policy!.ProcessType.Should().Be(processType);
policy.MaxRetries.Should().Be(5);
}
[Fact]
public async Task CreateClientPolicyOverride_Should_OverrideProcessTypeDefaults()
{
// Arrange
var processType = "order";
var clientId = "premium-client";
// Create process type policy
var typePolicy = new { processType, maxRetries = 3, timeoutSeconds = 30 };
await _client.PostJsonAsync("/api/policies/process-types", typePolicy);
// Create client override
var clientOverride = new
{
clientId,
processType,
maxRetries = 10, // Override
timeoutSeconds = 120 // Override
};
// Act
var response = await _client.PostJsonAsync("/api/policies/client-overrides", clientOverride);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
// Verify override is retrieved
var getResponse = await _client.GetAsync(
$"/api/policies/client-overrides/{clientId}/{processType}");
var policy = await getResponse.Content.ReadFromJsonAsync<PolicyResponse>();
policy!.MaxRetries.Should().Be(10);
}
}
public class PolicyResponse
{
public string ProcessType { get; set; } = string.Empty;
public string? ClientId { get; set; }
public int MaxRetries { get; set; }
public int TimeoutSeconds { get; set; }
public int? MaxConcurrentProcesses { get; set; }
public int? RetentionDays { get; set; }
}
6. Create Authentication Tests
Create tests/StarGate.IntegrationTests/Api/AuthenticationTests.cs:
namespace StarGate.IntegrationTests.Api;
using FluentAssertions;
using StarGate.IntegrationTests.Infrastructure;
using System.Net;
using System.Net.Http.Headers;
using Xunit;
public class AuthenticationTests : IClassFixture<StarGateWebApplicationFactory>
{
private readonly HttpClient _client;
public AuthenticationTests(StarGateWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Request_Should_ReturnUnauthorized_WithoutAuthorizationHeader()
{
// Act
var response = await _client.GetAsync("/api/processes");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task Request_Should_ReturnUnauthorized_WithInvalidToken()
{
// Arrange
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "invalid-token");
// Act
var response = await _client.GetAsync("/api/processes");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task Request_Should_Succeed_WithValidToken()
{
// Arrange
var token = await GetValidTokenAsync();
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
// Act
var response = await _client.GetAsync("/api/processes");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
private async Task<string> GetValidTokenAsync()
{
// Generate or retrieve a valid test token
// Implementation depends on auth strategy
return await Task.FromResult("test-valid-token");
}
}
7. Configure Test Coverage
Create coverlet.runsettings:
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage">
<Configuration>
<Format>opencover,cobertura</Format>
<Include>[StarGate.*]*</Include>
<Exclude>[StarGate.*.Tests]*,[*]*.Program</Exclude>
<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated</ExcludeByAttribute>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Update .github/workflows/ci.yml:
- name: Run Tests with Coverage
run: |
dotnet test --no-build --verbosity normal \
--collect:"XPlat Code Coverage" \
--settings coverlet.runsettings \
--results-directory ./coverage
- name: Generate Coverage Report
run: |
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
-reports:./coverage/**/coverage.cobertura.xml \
-targetdir:./coverage/report \
-reporttypes:Html;Badges
- name: Check Coverage Threshold
run: |
dotnet test --no-build \
/p:Threshold=80 \
/p:ThresholdType=line \
/p:ThresholdStat=total
✅ Acceptance Criteria
📝 Testing Instructions
# Run all integration tests
dotnet test tests/StarGate.IntegrationTests
# Run with coverage
dotnet test tests/StarGate.IntegrationTests \
--collect:"XPlat Code Coverage" \
--settings coverlet.runsettings
# Generate coverage report
reportgenerator \
-reports:./coverage/**/coverage.cobertura.xml \
-targetdir:./coverage/report \
-reporttypes:Html
# Open coverage report
open ./coverage/report/index.html
# Run specific test class
dotnet test --filter "FullyQualifiedName~ProcessEndpointsTests"
# Run tests with detailed output
dotnet test tests/StarGate.IntegrationTests --verbosity detailed
# Verify containers start correctly
docker ps # Should show MongoDB, Redis, RabbitMQ containers
# Check test logs
cat tests/StarGate.IntegrationTests/bin/Debug/net8.0/logs/test-*.log
📚 References
🏷️ Labels
phase-9 testing sprint-9.1 integration-tests api-tests
⏱️ Estimated Effort
12-16 hours
🔗 Dependencies
- Phase 5: API Gateway (endpoints must exist)
- Phase 6: ProcessService (business logic must work)
- Phase 8: Resilience policies (for timeout/retry tests)
🔗 Related Issues
Part of Phase 9: Testing & Quality - Sprint 9.1: Integration Tests
📌 Important Notes
TestContainers Benefits
Why TestContainers:
- Real infrastructure (not mocks)
- Isolated test environment
- Reproducible tests
- Easy CI/CD integration
- Automatic cleanup
Container Startup:
Test Run Start
↓
Start MongoDB container (~3s)
↓
Start Redis container (~2s)
↓
Start RabbitMQ container (~5s)
↓
Run Tests
↓
Stop & Remove Containers
Test Organization
By Feature:
ProcessEndpointsTests
├── CreateProcess tests
├── GetProcess tests
├── ListProcesses tests
└── Idempotency tests
PolicyEndpointsTests
├── ProcessTypePolicy tests
├── ClientOverride tests
└── Policy resolution tests
Test Naming Convention
[Fact]
public async Task MethodName_Should_ExpectedBehavior_When_Condition()
{
// Arrange
// Act
// Assert
}
Examples:
CreateProcess_Should_ReturnCreated_WithValidRequest
GetProcess_Should_ReturnNotFound_WhenNotExists
ListProcesses_Should_ReturnPaginatedResults
Builder Pattern for Test Data
Benefits:
- Readable test setup
- Reusable across tests
- Fluent interface
- Reduces duplication
Example:
var request = new CreateProcessRequestBuilder()
.WithClientId("test-client")
.WithProcessType("order")
.WithMetadata("orderId", "12345")
.Build();
Coverage Targets
Overall Target: >80%
By Project:
- StarGate.Core: >90% (domain logic)
- StarGate.Infrastructure: >85% (repos, services)
- StarGate.Server: >75% (endpoints, workers)
Exclusions:
- Program.cs (bootstrapping)
- Generated code
- Test projects
Performance Considerations
Container Startup:
- MongoDB: ~3 seconds
- Redis: ~2 seconds
- RabbitMQ: ~5 seconds
- Total: ~10 seconds overhead per test run
Optimization:
- Use ClassFixture (containers shared per test class)
- Parallel test execution where possible
- Fast test configuration (shorter timeouts)
CI/CD Integration
GitHub Actions:
- name: Run Integration Tests
run: dotnet test tests/StarGate.IntegrationTests
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/**/coverage.cobertura.xml
Coverage Badge:

Common Test Scenarios
Happy Path:
- Valid requests succeed
- Data persisted correctly
- Correct status codes returned
Validation:
- Missing required fields → 400
- Invalid data format → 400
- Constraint violations → 400
Error Handling:
- Not found → 404
- Unauthorized → 401
- Forbidden → 403
- Internal error → 500
Edge Cases:
- Empty collections
- Boundary values
- Special characters
- Very long strings
Idempotency Testing
Critical for Process Creation:
// First call: Creates process
POST /api/processes { clientProcessId: "ABC" } → 201 Created
// Second call: Returns existing
POST /api/processes { clientProcessId: "ABC" } → 200 OK
// Verify same processId returned
Test Data Isolation
Strategies:
- Unique IDs per test (Guid.NewGuid())
- Database cleanup between tests
- Separate test database
- Container recreation per class
Debugging Tests
View Container Logs:
docker logs <container-id>
Attach Debugger:
- Set breakpoint in test
- Run test in debug mode
- Containers remain running
Manual Container Inspection:
# MongoDB
docker exec -it <mongo-container> mongosh
# Redis
docker exec -it <redis-container> redis-cli
# RabbitMQ
open http://localhost:15672 # Management UI
📋 Task Description
Implement comprehensive API integration tests covering all REST endpoints, authentication, authorization, validation, error handling, and API contracts. Tests should run against real infrastructure (MongoDB, Redis, RabbitMQ) using Docker containers.
🎯 Objectives
📦 Deliverables
1. Setup Integration Test Infrastructure
Update
tests/StarGate.IntegrationTests/StarGate.IntegrationTests.csproj:2. Create Web Application Factory
Create
tests/StarGate.IntegrationTests/Infrastructure/StarGateWebApplicationFactory.cs:3. Create Test Helpers and Builders
Create
tests/StarGate.IntegrationTests/Helpers/HttpClientExtensions.cs:Create
tests/StarGate.IntegrationTests/Builders/CreateProcessRequestBuilder.cs:4. Create Process API Integration Tests
Create
tests/StarGate.IntegrationTests/Api/ProcessEndpointsTests.cs:5. Create Policy API Integration Tests
Create
tests/StarGate.IntegrationTests/Api/PolicyEndpointsTests.cs:6. Create Authentication Tests
Create
tests/StarGate.IntegrationTests/Api/AuthenticationTests.cs:7. Configure Test Coverage
Create
coverlet.runsettings:Update
.github/workflows/ci.yml:✅ Acceptance Criteria
📝 Testing Instructions
📚 References
🏷️ Labels
phase-9testingsprint-9.1integration-testsapi-tests⏱️ Estimated Effort
12-16 hours
🔗 Dependencies
🔗 Related Issues
Part of Phase 9: Testing & Quality - Sprint 9.1: Integration Tests
📌 Important Notes
TestContainers Benefits
Why TestContainers:
Container Startup:
Test Organization
By Feature:
Test Naming Convention
Examples:
CreateProcess_Should_ReturnCreated_WithValidRequestGetProcess_Should_ReturnNotFound_WhenNotExistsListProcesses_Should_ReturnPaginatedResultsBuilder Pattern for Test Data
Benefits:
Example:
Coverage Targets
Overall Target: >80%
By Project:
Exclusions:
Performance Considerations
Container Startup:
Optimization:
CI/CD Integration
GitHub Actions:
Coverage Badge:
Common Test Scenarios
Happy Path:
Validation:
Error Handling:
Edge Cases:
Idempotency Testing
Critical for Process Creation:
Test Data Isolation
Strategies:
Debugging Tests
View Container Logs:
Attach Debugger:
Manual Container Inspection: