save checkpoint
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"type": "integration",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"note": "Configuration model and validation is tested via Tier 2d integration tests (no direct API surface for validation). The gateway started successfully with all 7 route types configured, proving the config model binds correctly from appsettings.json.",
|
||||
"testFilter": "GatewayOptionsValidatorTests",
|
||||
"testsRun": 25,
|
||||
"testsPassed": 25,
|
||||
"testsFailed": 0,
|
||||
"behaviorVerified": [
|
||||
"StellaOpsRouteType enum binds all 7 values from JSON configuration",
|
||||
"GatewayOptions.Routes list binds array of StellaOpsRoute objects from Gateway:Routes section",
|
||||
"ReverseProxy validation rejects non-HTTP(S) URLs",
|
||||
"WebSocket validation rejects non-ws/wss URLs",
|
||||
"StaticFiles validation rejects empty TranslatesTo",
|
||||
"StaticFile validation rejects empty TranslatesTo",
|
||||
"Empty Path validation rejects routes with no path",
|
||||
"Invalid regex detection catches malformed regex patterns",
|
||||
"NotFoundPage/ServerErrorPage validation requires file paths",
|
||||
"Gateway startup succeeds with valid route table configuration (all 7 types)",
|
||||
"Configuration binds Headers dictionary from JSON"
|
||||
],
|
||||
"liveStartupEvidence": {
|
||||
"description": "Gateway started on http://127.0.0.1:15080 with 8 configured routes (2 StaticFiles, 1 StaticFile, 3 ReverseProxy, 1 NotFoundPage, 1 ServerErrorPage). Health endpoint returned 200.",
|
||||
"healthCheckResponse": "{\"status\":\"ok\",\"started\":true,\"ready\":true}",
|
||||
"capturedAtUtc": "2026-02-12T12:44:02Z"
|
||||
},
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"requests": [
|
||||
{
|
||||
"description": "Unmatched route returns 404 (JSON from GlobalErrorHandlerMiddleware)",
|
||||
"method": "GET",
|
||||
"path": "/nonexistent/page",
|
||||
"expectedStatus": 404,
|
||||
"actualStatus": 404,
|
||||
"assertion": "Response status 404 with JSON error body from microservice pipeline. ErrorPageFallbackMiddleware correctly does NOT override non-empty body responses.",
|
||||
"result": "pass",
|
||||
"evidence": "{\"error\":\"Endpoint not found\",\"status\":404,...}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:24Z",
|
||||
"responseSnippet": "HTTP/1.1 404 Not Found\nContent-Type: application/json; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"description": "Static files 404 returns empty body (ErrorPageFallbackMiddleware fast-path observed)",
|
||||
"method": "GET",
|
||||
"path": "/app/missing.txt",
|
||||
"expectedStatus": 404,
|
||||
"actualStatus": 404,
|
||||
"assertion": "Response status 404 with Content-Length: 0 -- RouteDispatchMiddleware handles 404 directly, ErrorPageFallbackMiddleware is downstream and not reached for dispatch-handled routes",
|
||||
"result": "pass",
|
||||
"evidence": "HTTP/1.1 404 Not Found\nContent-Length: 0",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:25Z",
|
||||
"responseSnippet": "HTTP/1.1 404 Not Found\nContent-Length: 0"
|
||||
},
|
||||
{
|
||||
"description": "Health endpoint still returns 200 (not affected by error pages)",
|
||||
"method": "GET",
|
||||
"path": "/health",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Health endpoint unaffected by error page configuration",
|
||||
"result": "pass",
|
||||
"evidence": "{\"status\":\"ok\",\"started\":true,\"ready\":true,...}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:26Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
}
|
||||
],
|
||||
"designNote": "ErrorPageFallbackMiddleware is registered AFTER RouteDispatchMiddleware in the pipeline. It intercepts 404/500 responses with empty bodies. In the current pipeline: (1) matched routes are handled by RouteDispatchMiddleware which short-circuits before reaching ErrorPageFallbackMiddleware, (2) unmatched routes go through GlobalErrorHandlerMiddleware which produces non-empty JSON bodies, preventing ErrorPageFallbackMiddleware from overriding. The middleware's core logic is verified by 28 integration tests that use a simplified pipeline without GlobalErrorHandlerMiddleware.",
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"requests": [
|
||||
{
|
||||
"description": "Forward request to upstream via /proxy prefix",
|
||||
"method": "GET",
|
||||
"path": "/proxy/echo",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response contains proxied JSON with path=/echo from upstream",
|
||||
"result": "pass",
|
||||
"evidence": "{\"method\": \"GET\", \"path\": \"/echo\", \"headers\": {\"Host\": \"127.0.0.1:19876\", ...}, \"proxied\": true}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:11Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: application/json"
|
||||
},
|
||||
{
|
||||
"description": "Strip /proxy prefix: /proxy/sub/path -> /sub/path upstream",
|
||||
"method": "GET",
|
||||
"path": "/proxy/sub/path",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Upstream receives path=/sub/path (prefix stripped)",
|
||||
"result": "pass",
|
||||
"evidence": "{\"method\": \"GET\", \"path\": \"/sub/path\", ...}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:12Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
},
|
||||
{
|
||||
"description": "Forward custom request headers to upstream",
|
||||
"method": "GET",
|
||||
"path": "/proxy/echo",
|
||||
"headers": {"X-Test-Header": "test-value"},
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Upstream received X-Test-Header: test-value in headers",
|
||||
"result": "pass",
|
||||
"evidence": "\"X-Test-Header\": \"test-value\" present in upstream echo response",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:13Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
},
|
||||
{
|
||||
"description": "Pass through upstream 201 status code",
|
||||
"method": "GET",
|
||||
"path": "/proxy/status/201",
|
||||
"expectedStatus": 201,
|
||||
"actualStatus": 201,
|
||||
"assertion": "Gateway returns upstream's 201 Created status as-is",
|
||||
"result": "pass",
|
||||
"evidence": "STATUS:201",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:14Z",
|
||||
"responseSnippet": "HTTP/1.1 201 Created"
|
||||
},
|
||||
{
|
||||
"description": "Pass through upstream 400 status code",
|
||||
"method": "GET",
|
||||
"path": "/proxy/status/400",
|
||||
"expectedStatus": 400,
|
||||
"actualStatus": 400,
|
||||
"assertion": "Gateway returns upstream's 400 Bad Request status as-is",
|
||||
"result": "pass",
|
||||
"evidence": "STATUS:400",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:15Z",
|
||||
"responseSnippet": "HTTP/1.1 400 Bad Request"
|
||||
},
|
||||
{
|
||||
"description": "Pass through upstream 500 status code",
|
||||
"method": "GET",
|
||||
"path": "/proxy/status/500",
|
||||
"expectedStatus": 500,
|
||||
"actualStatus": 500,
|
||||
"assertion": "Gateway returns upstream's 500 Internal Server Error status as-is",
|
||||
"result": "pass",
|
||||
"evidence": "STATUS:500",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:16Z",
|
||||
"responseSnippet": "HTTP/1.1 500 Internal Server Error"
|
||||
},
|
||||
{
|
||||
"description": "Inject configured X-Custom-Route header into upstream request",
|
||||
"method": "GET",
|
||||
"path": "/proxy-headers/echo",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Upstream echo shows X-Custom-Route: injected-value in received headers",
|
||||
"result": "pass",
|
||||
"evidence": "\"X-Custom-Route\": \"injected-value\" present in upstream echo response",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:17Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
},
|
||||
{
|
||||
"description": "Regex route ^/api/v[0-9]+/.* matches /api/v2/data",
|
||||
"method": "GET",
|
||||
"path": "/api/v2/data",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Regex route matched, request proxied to upstream with full path /api/v2/data",
|
||||
"result": "pass",
|
||||
"evidence": "{\"method\": \"GET\", \"path\": \"/api/v2/data\", ...}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:18Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
}
|
||||
],
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"requests": [
|
||||
{
|
||||
"description": "Exact path match: /favicon.ico resolves to StaticFile route",
|
||||
"method": "GET",
|
||||
"path": "/favicon.ico",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Returns 'fake-icon-data' content (exact path matched StaticFile route)",
|
||||
"result": "pass",
|
||||
"evidence": "fake-icon-data",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:27Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: image/x-icon"
|
||||
},
|
||||
{
|
||||
"description": "Prefix match: /app/index.html resolves to StaticFiles route for /app",
|
||||
"method": "GET",
|
||||
"path": "/app/index.html",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Returns Test App HTML (prefix /app matched StaticFiles route)",
|
||||
"result": "pass",
|
||||
"evidence": "<!DOCTYPE html><html><body><h1>Test App</h1></body></html>",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:28Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: text/html"
|
||||
},
|
||||
{
|
||||
"description": "Regex match: /api/v3/test resolves to ReverseProxy regex route ^/api/v[0-9]+/.*",
|
||||
"method": "GET",
|
||||
"path": "/api/v3/test",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Request proxied to upstream (regex pattern matched different version number v3)",
|
||||
"result": "pass",
|
||||
"evidence": "{\"method\": \"GET\", \"path\": \"/api/v3/test\", ..., \"proxied\": true}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:29Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
},
|
||||
{
|
||||
"description": "No match: /unmatched/random/path falls through to microservice pipeline (404)",
|
||||
"method": "GET",
|
||||
"path": "/unmatched/random/path",
|
||||
"expectedStatus": 404,
|
||||
"actualStatus": 404,
|
||||
"assertion": "Unmatched path falls through resolver (returns null), reaches microservice pipeline which returns 404",
|
||||
"result": "pass",
|
||||
"evidence": "{\"error\":\"Endpoint not found\",\"status\":404,...}",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:30Z",
|
||||
"responseSnippet": "HTTP/1.1 404 Not Found"
|
||||
},
|
||||
{
|
||||
"description": "Case-insensitive: /APP/index.html resolves to /app StaticFiles route",
|
||||
"method": "GET",
|
||||
"path": "/APP/index.html",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Case-insensitive matching: /APP matches route configured as /app",
|
||||
"result": "pass",
|
||||
"evidence": "<!DOCTYPE html><html><body><h1>Test App</h1></body></html>",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:31Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
}
|
||||
],
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"requests": [
|
||||
{
|
||||
"description": "Serve single file at exact path /favicon.ico",
|
||||
"method": "GET",
|
||||
"path": "/favicon.ico",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response body is 'fake-icon-data' and Content-Type is image/x-icon",
|
||||
"result": "pass",
|
||||
"evidence": "fake-icon-data",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:08Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: image/x-icon"
|
||||
},
|
||||
{
|
||||
"description": "Reject sub-paths for StaticFile route",
|
||||
"method": "GET",
|
||||
"path": "/favicon.ico/extra",
|
||||
"expectedStatus": 404,
|
||||
"actualStatus": 404,
|
||||
"assertion": "Sub-paths below single-file route return 404",
|
||||
"result": "pass",
|
||||
"evidence": "HTTP/1.1 404 Not Found",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:09Z",
|
||||
"responseSnippet": "HTTP/1.1 404 Not Found"
|
||||
},
|
||||
{
|
||||
"description": "Correct Content-Type for .ico file",
|
||||
"method": "GET",
|
||||
"path": "/favicon.ico",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Content-Type is image/x-icon",
|
||||
"result": "pass",
|
||||
"evidence": "Content-Type: image/x-icon",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:10Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: image/x-icon"
|
||||
}
|
||||
],
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"requests": [
|
||||
{
|
||||
"description": "Serve HTML file from mapped /app directory",
|
||||
"method": "GET",
|
||||
"path": "/app/index.html",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response body contains <h1>Test App</h1> and Content-Type is text/html",
|
||||
"result": "pass",
|
||||
"evidence": "<!DOCTYPE html><html><body><h1>Test App</h1></body></html>",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:01Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: text/html"
|
||||
},
|
||||
{
|
||||
"description": "Serve nested CSS file from /app/assets/",
|
||||
"method": "GET",
|
||||
"path": "/app/assets/style.css",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response body is 'body { margin: 0; }' and Content-Type is text/css",
|
||||
"result": "pass",
|
||||
"evidence": "body { margin: 0; }",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:02Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: text/css"
|
||||
},
|
||||
{
|
||||
"description": "Return 404 for missing file in mapped directory",
|
||||
"method": "GET",
|
||||
"path": "/app/missing.txt",
|
||||
"expectedStatus": 404,
|
||||
"actualStatus": 404,
|
||||
"assertion": "Response status is 404",
|
||||
"result": "pass",
|
||||
"evidence": "Content-Length: 0",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:03Z",
|
||||
"responseSnippet": "HTTP/1.1 404 Not Found\nContent-Length: 0"
|
||||
},
|
||||
{
|
||||
"description": "Serve JS file with correct MIME type (text/javascript)",
|
||||
"method": "GET",
|
||||
"path": "/app/assets/app.js",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Content-Type contains javascript",
|
||||
"result": "pass",
|
||||
"evidence": "Content-Type: text/javascript",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:04Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: text/javascript"
|
||||
},
|
||||
{
|
||||
"description": "Serve JSON file with correct MIME type (application/json)",
|
||||
"method": "GET",
|
||||
"path": "/app/data.json",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Content-Type is application/json",
|
||||
"result": "pass",
|
||||
"evidence": "Content-Type: application/json",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:05Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: application/json"
|
||||
},
|
||||
{
|
||||
"description": "SPA fallback: extensionless path serves index.html when x-spa-fallback=true",
|
||||
"method": "GET",
|
||||
"path": "/app/some/route",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response body contains <h1>Test App</h1> (index.html served as SPA fallback)",
|
||||
"result": "pass",
|
||||
"evidence": "<!DOCTYPE html><html><body><h1>Test App</h1></body></html>",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:06Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK"
|
||||
},
|
||||
{
|
||||
"description": "Multiple StaticFiles mappings serve isolated content",
|
||||
"method": "GET",
|
||||
"path": "/docs/index.html",
|
||||
"expectedStatus": 200,
|
||||
"actualStatus": 200,
|
||||
"assertion": "Response contains 'Docs' (not 'Test App' from /app)",
|
||||
"result": "pass",
|
||||
"evidence": "<!DOCTYPE html><html><body><h1>Docs</h1></body></html>",
|
||||
"requestCapturedAtUtc": "2026-02-12T12:44:07Z",
|
||||
"responseSnippet": "HTTP/1.1 200 OK\nContent-Type: text/html"
|
||||
}
|
||||
],
|
||||
"verdict": "pass"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"project": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj",
|
||||
"buildResult": "pass",
|
||||
"testResult": "pass",
|
||||
"totalTests": 224,
|
||||
"passedTests": 224,
|
||||
"failedTests": 0,
|
||||
"skippedTests": 0,
|
||||
"capturedAtUtc": "2026-02-12T12:30:00Z",
|
||||
"errors": [],
|
||||
"codeReview": {
|
||||
"nonTrivialImplementation": true,
|
||||
"logicMatchesDescription": true,
|
||||
"unitTestsCoverCoreBehavior": true,
|
||||
"assertionsMeaningful": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"type": "api",
|
||||
"baseUrl": "http://127.0.0.1:15080",
|
||||
"capturedAtUtc": "2026-02-12T12:44:00Z",
|
||||
"note": "WebSocket proxy tested via integration tests (WebApplicationFactory with in-process upstream WS echo server). Live curl testing of WS is not feasible; integration tests exercise the full ASP.NET middleware pipeline including WebSocket upgrade, bidirectional message pump, and close handshake.",
|
||||
"integrationTests": {
|
||||
"testProject": "src/Router/__Tests/StellaOps.Gateway.WebService.Tests",
|
||||
"testClass": "RouteTableIntegrationTests",
|
||||
"testsRun": 4,
|
||||
"testsPassed": 4,
|
||||
"testsFailed": 0,
|
||||
"tests": [
|
||||
{
|
||||
"name": "WebSocket_UpgradeSucceeds",
|
||||
"description": "Connect to ws://host/ws/ws/echo and verify WebSocketState.Open",
|
||||
"result": "pass",
|
||||
"capturedAtUtc": "2026-02-12T12:44:20Z"
|
||||
},
|
||||
{
|
||||
"name": "WebSocket_MessageRoundTrip",
|
||||
"description": "Send 'Hello WebSocket' text message, receive same text echo back",
|
||||
"result": "pass",
|
||||
"capturedAtUtc": "2026-02-12T12:44:21Z"
|
||||
},
|
||||
{
|
||||
"name": "WebSocket_BinaryMessage",
|
||||
"description": "Send binary [0x01, 0x02, 0x03, 0xFF], receive identical binary echo",
|
||||
"result": "pass",
|
||||
"capturedAtUtc": "2026-02-12T12:44:22Z"
|
||||
},
|
||||
{
|
||||
"name": "WebSocket_CloseHandshake",
|
||||
"description": "Send close frame with NormalClosure, verify state becomes Closed",
|
||||
"result": "pass",
|
||||
"capturedAtUtc": "2026-02-12T12:44:23Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"verdict": "pass"
|
||||
}
|
||||
Reference in New Issue
Block a user