diff --git a/test/unit/ports/HttpServerPort.test.ts b/test/unit/ports/HttpServerPort.test.ts index 7cb174356..c9ca455b7 100644 --- a/test/unit/ports/HttpServerPort.test.ts +++ b/test/unit/ports/HttpServerPort.test.ts @@ -1,9 +1,10 @@ import { describe, it, expect, afterEach } from 'vitest'; -import HttpServerPort_ from '../../../src/ports/HttpServerPort.ts'; +import HttpServerPort, { + type HttpRequest, + type HttpServerHandle, +} from '../../../src/ports/HttpServerPort.ts'; import NodeHttpAdapter from '../../../src/infrastructure/adapters/NodeHttpAdapter.ts'; -const HttpServerPort = (HttpServerPort_) as any; - describe('HttpServerPort', () => { it('abstract methods are not callable on base prototype', () => { expect(HttpServerPort.prototype.createServer).toBeUndefined(); @@ -12,13 +13,38 @@ describe('HttpServerPort', () => { describe('NodeHttpAdapter', () => { const adapter = new NodeHttpAdapter(); - let server; + let server: HttpServerHandle | null = null; - afterEach(() => { - if (server) { - server.close(); + async function closeActiveServer(): Promise { + if (server !== null) { + const activeServer = server; server = null; + await new Promise((resolve, reject) => { + activeServer.close((err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); } + } + + async function listenOnLoopback(activeServer: HttpServerHandle): Promise { + await new Promise((resolve, reject) => { + activeServer.listen(0, '127.0.0.1', (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + } + + afterEach(async () => { + await closeActiveServer(); }); it('is an instance of HttpServerPort', () => { @@ -31,28 +57,40 @@ describe('NodeHttpAdapter', () => { headers: { 'content-type': 'text/plain' }, body: 'ok', })); + const activeServer = server; - await new Promise((resolve) => server.listen(0, resolve)); + await listenOnLoopback(activeServer); + + expect(activeServer.address()).not.toBeNull(); }); it('handles a basic request/response cycle', async () => { - server = adapter.createServer(async (/** @type {any} */ req) => ({ - status: 200, + server = adapter.createServer(async (req: HttpRequest) => ({ + status: 201, headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ method: req.method, url: req.url }), + body: [ + req.method, + req.url, + req.headers['content-type'], + req.body === undefined ? '' : new TextDecoder().decode(req.body), + ].join('|'), })); + const activeServer = server; - await new Promise((resolve) => { - server.listen(0, function () { - // 'this' is the underlying Node server inside the listen callback - // We need a different approach to get the port - resolve(null); - }); + await listenOnLoopback(activeServer); + + const address = activeServer.address(); + if (address === null) { + throw new Error('expected server address after listen'); + } + const response = await fetch(`http://${address.address}:${address.port}/nodes/A?verbose=1`, { + method: 'POST', + headers: { 'content-type': 'text/plain' }, + body: 'hello', }); - // The simple wrapper doesn't expose address(), so we test start/stop - // Full request/response testing would require exposing the port - expect(server.listen).toBeDefined(); - expect(server.close).toBeDefined(); + expect(response.status).toBe(201); + expect(response.headers.get('content-type')).toBe('application/json'); + expect(await response.text()).toBe('POST|/nodes/A?verbose=1|text/plain|hello'); }); });