diff --git a/k_phone/test/filter_proxy_test.dart b/k_phone/test/filter_proxy_test.dart index 503d839..3642a8d 100644 --- a/k_phone/test/filter_proxy_test.dart +++ b/k_phone/test/filter_proxy_test.dart @@ -38,26 +38,29 @@ Future<({HttpServer server, Completer completer})> _mockHttp() asyn } // Mock for Component 2's /auth/get-token endpoint. -// Responds to every request, completing [tokenReq] on the first one. -// When [ok] is false, returns 401 with an error payload. -Future<({HttpServer server, Completer tokenReq})> _mockTokenServer({ +// Reads and captures the full request body; completes [tokenReq] on the first call. +// When [ok] is false, returns 401. +Future<({HttpServer server, Completer<({HttpRequest req, String rawBody})> tokenReq})> + _mockTokenServer({ String token = 'test-bearer-token', bool ok = true, }) async { final server = await HttpServer.bind('127.0.0.1', 0); - final c = Completer(); + final c = Completer<({HttpRequest req, String rawBody})>(); server.listen((req) async { - await req.drain(); - if (!c.isCompleted) c.complete(req); - final body = ok + final bb = BytesBuilder(copy: false); + await for (final chunk in req) bb.add(chunk); + final rawBody = utf8.decode(bb.takeBytes()); + if (!c.isCompleted) c.complete((req: req, rawBody: rawBody)); + final resp = ok ? '{"ok":true,"token":"$token","expires_in":300}' - : '{"ok":false,"error":"no active session","login_required":true}'; + : '{"ok":false,"error":"card not available","login_required":true}'; req.response ..statusCode = ok ? 200 : 401 ..headers.set('content-type', 'application/json') - ..headers.set('content-length', '${body.length}') + ..headers.set('content-length', '${resp.length}') ..headers.set('connection', 'close') - ..write(body); + ..write(resp); await req.response.close(); }); return (server: server, tokenReq: c); @@ -196,7 +199,7 @@ void main() { group('HTTP routing', () { late FilterProxy proxy; late HttpServer comp2; - late Completer comp2TokenReq; + late Completer<({HttpRequest req, String rawBody})> comp2TokenReq; late HttpServer endpoint; late Completer endpointReq; late HttpServer directServer; @@ -242,11 +245,43 @@ void main() { 'GET http://127.0.0.1:${endpoint.port}/api HTTP/1.1\r\n' 'Host: 127.0.0.1:${endpoint.port}\r\n\r\n', ); - final req = await comp2TokenReq.future.timeout(_kTimeout); + final (:req, rawBody: _) = await comp2TokenReq.future.timeout(_kTimeout); expect(req.method, 'POST'); expect(req.uri.path, '/auth/get-token'); }); + test('gated host: /auth/get-token body carries url, method, nonce', () async { + await _round( + proxy.port, + 'GET http://127.0.0.1:${endpoint.port}/api?x=1 HTTP/1.1\r\n' + 'Host: 127.0.0.1:${endpoint.port}\r\n\r\n', + ); + final (:req, :rawBody) = await comp2TokenReq.future.timeout(_kTimeout); + expect(req.uri.path, '/auth/get-token'); + final body = jsonDecode(rawBody) as Map; + expect(body['url'], contains('/api?x=1')); + expect(body['method'], 'GET'); + expect(body['nonce'], isA()); + expect((body['nonce'] as String).length, greaterThan(8)); + }); + + test('gated POST: /auth/get-token body carries method POST', () async { + const postBody = '{"key":"val"}'; + await _round( + proxy.port, + 'POST http://127.0.0.1:${endpoint.port}/submit HTTP/1.1\r\n' + 'Host: 127.0.0.1:${endpoint.port}\r\n' + 'Content-Type: application/json\r\n' + 'Content-Length: ${postBody.length}\r\n\r\n' + '$postBody', + ); + final (:req, :rawBody) = await comp2TokenReq.future.timeout(_kTimeout); + expect(req.uri.path, '/auth/get-token'); + final body = jsonDecode(rawBody) as Map; + expect(body['method'], 'POST'); + expect(body['url'], contains('/submit')); + }); + test('gated host: request goes directly to endpoint with Bearer token', () async { final response = await _round( proxy.port,