Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions test/unit/ports/HttpServerPort.test.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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<void> {
if (server !== null) {
const activeServer = server;
server = null;
await new Promise<void>((resolve, reject) => {
activeServer.close((err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
}

async function listenOnLoopback(activeServer: HttpServerHandle): Promise<void> {
await new Promise<void>((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', () => {
Expand All @@ -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');
});
});
Loading