Add integration tests for migration categories and execution
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
- Implemented MigrationCategoryTests to validate migration categorization for startup, release, seed, and data migrations. - Added tests for edge cases, including null, empty, and whitespace migration names. - Created StartupMigrationHostTests to verify the behavior of the migration host with real PostgreSQL instances using Testcontainers. - Included tests for migration execution, schema creation, and handling of pending release migrations. - Added SQL migration files for testing: creating a test table, adding a column, a release migration, and seeding data.
This commit is contained in:
683
docs/router/26-Step.md
Normal file
683
docs/router/26-Step.md
Normal file
@@ -0,0 +1,683 @@
|
||||
# Step 26: End-to-End Testing
|
||||
|
||||
**Phase 7: Testing & Documentation**
|
||||
**Estimated Complexity:** High
|
||||
**Dependencies:** All implementation steps
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
End-to-end testing validates the complete request flow from HTTP client through the gateway, transport layer, microservice, and back. Tests cover all handlers, authentication, rate limiting, streaming, and failure scenarios.
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
1. Validate complete request/response flow
|
||||
2. Test all route handlers
|
||||
3. Verify authentication and authorization
|
||||
4. Test rate limiting behavior
|
||||
5. Validate streaming and large payloads
|
||||
6. Test failure scenarios and resilience
|
||||
|
||||
---
|
||||
|
||||
## Test Infrastructure
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end test fixture providing gateway and microservice hosts.
|
||||
/// </summary>
|
||||
public sealed class EndToEndTestFixture : IAsyncLifetime
|
||||
{
|
||||
private IHost? _gatewayHost;
|
||||
private IHost? _microserviceHost;
|
||||
private InMemoryTransportHub? _transportHub;
|
||||
|
||||
public HttpClient GatewayClient { get; private set; } = null!;
|
||||
public string GatewayBaseUrl { get; private set; } = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Shared transport hub for InMemory testing
|
||||
_transportHub = new InMemoryTransportHub(
|
||||
NullLoggerFactory.Instance.CreateLogger<InMemoryTransportHub>());
|
||||
|
||||
// Start gateway
|
||||
_gatewayHost = await CreateGatewayHostAsync();
|
||||
await _gatewayHost.StartAsync();
|
||||
|
||||
GatewayBaseUrl = "http://localhost:5000";
|
||||
GatewayClient = new HttpClient { BaseAddress = new Uri(GatewayBaseUrl) };
|
||||
|
||||
// Start test microservice
|
||||
_microserviceHost = await CreateMicroserviceHostAsync();
|
||||
await _microserviceHost.StartAsync();
|
||||
|
||||
// Wait for connection
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
private async Task<IHost> CreateGatewayHostAsync()
|
||||
{
|
||||
return Host.CreateDefaultBuilder()
|
||||
.ConfigureWebHostDefaults(web =>
|
||||
{
|
||||
web.UseUrls("http://localhost:5000");
|
||||
web.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddSingleton(_transportHub!);
|
||||
services.AddStellaGateway(context.Configuration);
|
||||
services.AddInMemoryTransport();
|
||||
|
||||
// Use in-memory rate limiter
|
||||
services.AddSingleton<IRateLimiter, InMemoryRateLimiter>();
|
||||
|
||||
// Mock Authority
|
||||
services.AddSingleton<IAuthorityClient, MockAuthorityClient>();
|
||||
});
|
||||
|
||||
web.Configure(app =>
|
||||
{
|
||||
app.UseRouting();
|
||||
app.UseStellaGateway();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapStellaRoutes();
|
||||
});
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
private async Task<IHost> CreateMicroserviceHostAsync()
|
||||
{
|
||||
var host = StellaMicroserviceBuilder
|
||||
.Create("test-service")
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(_transportHub!);
|
||||
services.AddScoped<TestEndpointHandler>();
|
||||
})
|
||||
.ConfigureTransport(t => t.Default = "InMemory")
|
||||
.ConfigureEndpoints(e =>
|
||||
{
|
||||
e.AutoDiscover = true;
|
||||
e.BasePath = "/api";
|
||||
})
|
||||
.Build();
|
||||
|
||||
return (IHost)host;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
GatewayClient.Dispose();
|
||||
|
||||
if (_microserviceHost != null)
|
||||
{
|
||||
await _microserviceHost.StopAsync();
|
||||
_microserviceHost.Dispose();
|
||||
}
|
||||
|
||||
if (_gatewayHost != null)
|
||||
{
|
||||
await _gatewayHost.StopAsync();
|
||||
_gatewayHost.Dispose();
|
||||
}
|
||||
|
||||
_transportHub?.Dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Endpoint Handler
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
[StellaEndpoint(BasePath = "/test")]
|
||||
public class TestEndpointHandler : EndpointHandler
|
||||
{
|
||||
[StellaGet("echo")]
|
||||
public ResponsePayload Echo()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
method = Context.Method,
|
||||
path = Context.Path,
|
||||
query = Context.Query.ToDictionary(q => q.Key, q => q.Value.ToString()),
|
||||
headers = Context.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()),
|
||||
claims = Context.Claims
|
||||
});
|
||||
}
|
||||
|
||||
[StellaPost("echo")]
|
||||
public async Task<ResponsePayload> EchoBody()
|
||||
{
|
||||
var body = Context.ReadBodyAsString();
|
||||
return Ok(new { body });
|
||||
}
|
||||
|
||||
[StellaGet("items/{id}")]
|
||||
public ResponsePayload GetItem([FromPath] string id)
|
||||
{
|
||||
return Ok(new { id });
|
||||
}
|
||||
|
||||
[StellaGet("slow")]
|
||||
public async Task<ResponsePayload> SlowEndpoint(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(5000, cancellationToken);
|
||||
return Ok(new { completed = true });
|
||||
}
|
||||
|
||||
[StellaGet("error")]
|
||||
public ResponsePayload ThrowError()
|
||||
{
|
||||
throw new InvalidOperationException("Test error");
|
||||
}
|
||||
|
||||
[StellaGet("status/{code}")]
|
||||
public ResponsePayload ReturnStatus([FromPath] int code)
|
||||
{
|
||||
return Response().WithStatus(code).WithJson(new { statusCode = code }).Build();
|
||||
}
|
||||
|
||||
[StellaGet("protected")]
|
||||
[StellaAuth(RequiredClaims = new[] { "admin" })]
|
||||
public ResponsePayload ProtectedEndpoint()
|
||||
{
|
||||
return Ok(new { message = "Access granted" });
|
||||
}
|
||||
|
||||
[StellaPost("upload")]
|
||||
public ResponsePayload HandleUpload()
|
||||
{
|
||||
var size = Context.ContentLength ?? Context.RawBody?.Length ?? 0;
|
||||
return Ok(new { bytesReceived = size });
|
||||
}
|
||||
|
||||
[StellaGet("stream")]
|
||||
public ResponsePayload StreamResponse()
|
||||
{
|
||||
var data = new byte[1024 * 1024]; // 1MB
|
||||
Random.Shared.NextBytes(data);
|
||||
return Response()
|
||||
.WithBytes(data, "application/octet-stream")
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Basic Request/Response Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class BasicRequestResponseTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public BasicRequestResponseTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_Echo_ReturnsRequestDetails()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/echo");
|
||||
var content = await response.Content.ReadFromJsonAsync<EchoResponse>();
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Equal("GET", content?.Method);
|
||||
Assert.Equal("/api/test/echo", content?.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Post_Echo_ReturnsBody()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
var body = new StringContent("{\"test\": true}", Encoding.UTF8, "application/json");
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync("/api/test/echo", body);
|
||||
var content = await response.Content.ReadFromJsonAsync<EchoBodyResponse>();
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Contains("test", content?.Body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithPathParameter_ExtractsParameter()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/items/12345");
|
||||
var content = await response.Content.ReadFromJsonAsync<ItemResponse>();
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Equal("12345", content?.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_NonExistentPath_Returns404()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/nonexistent");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
private record EchoResponse(
|
||||
string Method,
|
||||
string Path,
|
||||
Dictionary<string, string> Query,
|
||||
Dictionary<string, string> Claims);
|
||||
|
||||
private record EchoBodyResponse(string Body);
|
||||
private record ItemResponse(string Id);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class AuthenticationTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public AuthenticationTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Protected_WithoutToken_Returns401()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/protected");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Protected_WithValidToken_Returns200()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
var token = CreateTestToken(new Dictionary<string, string> { ["admin"] = "true" });
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/protected");
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Protected_WithInvalidToken_Returns401()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token");
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/protected");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Protected_WithMissingClaim_Returns403()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
var token = CreateTestToken(new Dictionary<string, string> { ["user"] = "true" }); // No admin claim
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/protected");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||
}
|
||||
|
||||
private string CreateTestToken(Dictionary<string, string> claims)
|
||||
{
|
||||
// Create a test JWT (would use test key in real implementation)
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("test-key-for-testing-only-12345"));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claimsList = claims.Select(c => new Claim(c.Key, c.Value)).ToList();
|
||||
claimsList.Add(new Claim("sub", "test-user"));
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: "test",
|
||||
audience: "test",
|
||||
claims: claimsList,
|
||||
expires: DateTime.UtcNow.AddHours(1),
|
||||
signingCredentials: creds);
|
||||
|
||||
return handler.WriteToken(token);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class RateLimitingTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public RateLimitingTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RateLimit_ExceedingLimit_Returns429()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
var tasks = new List<Task<HttpResponseMessage>>();
|
||||
|
||||
// Act - Send 100 requests quickly
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
tasks.Add(client.GetAsync("/api/test/echo"));
|
||||
}
|
||||
|
||||
var responses = await Task.WhenAll(tasks);
|
||||
|
||||
// Assert - Some should be rate limited
|
||||
var rateLimited = responses.Count(r => r.StatusCode == HttpStatusCode.TooManyRequests);
|
||||
Assert.True(rateLimited > 0, "Expected some requests to be rate limited");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RateLimit_Headers_ArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/echo");
|
||||
|
||||
// Assert
|
||||
Assert.True(response.Headers.Contains("X-RateLimit-Limit"));
|
||||
Assert.True(response.Headers.Contains("X-RateLimit-Remaining"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RateLimit_PerUser_IsolatesUsers()
|
||||
{
|
||||
// Arrange
|
||||
var client1 = new HttpClient { BaseAddress = new Uri(_fixture.GatewayBaseUrl) };
|
||||
var client2 = new HttpClient { BaseAddress = new Uri(_fixture.GatewayBaseUrl) };
|
||||
|
||||
client1.DefaultRequestHeaders.Add("X-API-Key", "user1-key");
|
||||
client2.DefaultRequestHeaders.Add("X-API-Key", "user2-key");
|
||||
|
||||
// Act - Exhaust rate limit for user1
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
await client1.GetAsync("/api/test/echo");
|
||||
}
|
||||
|
||||
// User2 should still have quota
|
||||
var response = await client2.GetAsync("/api/test/echo");
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timeout and Cancellation Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class TimeoutAndCancellationTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public TimeoutAndCancellationTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Slow_Request_TimesOut()
|
||||
{
|
||||
// Arrange
|
||||
var client = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(_fixture.GatewayBaseUrl),
|
||||
Timeout = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(
|
||||
() => client.GetAsync("/api/test/slow"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cancelled_Request_PropagatesCancellation()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
var task = client.GetAsync("/api/test/slow", cts.Token);
|
||||
await Task.Delay(100);
|
||||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(() => task);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming and Large Payload Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class StreamingTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public StreamingTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LargeUpload_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
var data = new byte[1024 * 1024]; // 1MB
|
||||
Random.Shared.NextBytes(data);
|
||||
var content = new ByteArrayContent(data);
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync("/api/test/upload", content);
|
||||
var result = await response.Content.ReadFromJsonAsync<UploadResponse>();
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Equal(data.Length, result?.BytesReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LargeDownload_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/stream");
|
||||
var data = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
Assert.Equal(1024 * 1024, data.Length);
|
||||
}
|
||||
|
||||
private record UploadResponse(long BytesReceived);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Tests
|
||||
|
||||
```csharp
|
||||
namespace StellaOps.Router.Tests;
|
||||
|
||||
public class ErrorHandlingTests : IClassFixture<EndToEndTestFixture>
|
||||
{
|
||||
private readonly EndToEndTestFixture _fixture;
|
||||
|
||||
public ErrorHandlingTests(EndToEndTestFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handler_Exception_Returns500()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/error");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Custom_StatusCode_IsPreserved()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/test/status/418");
|
||||
|
||||
// Assert
|
||||
Assert.Equal((HttpStatusCode)418, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Error_Response_HasCorrectFormat()
|
||||
{
|
||||
// Arrange
|
||||
var client = _fixture.GatewayClient;
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api/nonexistent");
|
||||
var content = await response.Content.ReadFromJsonAsync<ErrorResponse>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(content?.Error);
|
||||
}
|
||||
|
||||
private record ErrorResponse(string Error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## YAML Configuration
|
||||
|
||||
```yaml
|
||||
# Test configuration
|
||||
Router:
|
||||
Transports:
|
||||
- Type: InMemory
|
||||
Enabled: true
|
||||
|
||||
RateLimiting:
|
||||
Enabled: true
|
||||
DefaultTier: free
|
||||
Tiers:
|
||||
free:
|
||||
RequestsPerMinute: 60
|
||||
authenticated:
|
||||
RequestsPerMinute: 600
|
||||
|
||||
Authentication:
|
||||
Enabled: true
|
||||
AllowAnonymous: false
|
||||
TestMode: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. `StellaOps.Router.Tests/EndToEndTestFixture.cs`
|
||||
2. `StellaOps.Router.Tests/TestEndpointHandler.cs`
|
||||
3. `StellaOps.Router.Tests/BasicRequestResponseTests.cs`
|
||||
4. `StellaOps.Router.Tests/AuthenticationTests.cs`
|
||||
5. `StellaOps.Router.Tests/RateLimitingTests.cs`
|
||||
6. `StellaOps.Router.Tests/TimeoutAndCancellationTests.cs`
|
||||
7. `StellaOps.Router.Tests/StreamingTests.cs`
|
||||
8. `StellaOps.Router.Tests/ErrorHandlingTests.cs`
|
||||
9. Mock implementations for Authority, Rate Limiter
|
||||
10. CI integration configuration
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to [Step 27: Reference Example & Migration Skeleton](27-Step.md) to create example implementations.
|
||||
Reference in New Issue
Block a user