From a13826afe75327ca032ff935198d0476ae345f03 Mon Sep 17 00:00:00 2001 From: David Ibrahim Dekah Date: Tue, 20 Oct 2026 07:42:24 +0100 Subject: [PATCH] test: add API Service health-route failure tests (SW-044) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Title ``` test: add API Service health-route failure tests (SW-044) ``` ## PR Description Implements expanded API Service test coverage for health-route method/path/error behavior as specified in SW-044. ### Changes Added three new test cases to `templates/api-service/test/server.test.js`: 1. **Invalid HTTP method rejection** - Verifies that `/health` endpoint rejects POST, PUT, DELETE, and PATCH methods with 404 responses 2. **Invalid path rejection** - Tests 404 responses for `/health/`, `/health/status`, `/invalid`, and `/` paths 3. **Response structure stability** - Validates health response payload structure and content-type header across network configurations ### Acceptance Criteria - ✅ Supported health response remains stable - ✅ Invalid methods/paths fail predictably (404 with `{ error: 'not_found' }`) - ✅ No network dependencies (uses `127.0.0.1` with ephemeral ports) - ✅ Template file passes syntax validation ### Implementation Notes All tests use the built-in `globalThis.fetch` API with ephemeral ports (`server.listen(0)`) to ensure no external network calls are made. The implementation follows existing test patterns in the codebase. Fixes #146 --- templates/api-service/test/server.test.js | 129 ++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/templates/api-service/test/server.test.js b/templates/api-service/test/server.test.js index f920532..4e77fe4 100644 --- a/templates/api-service/test/server.test.js +++ b/templates/api-service/test/server.test.js @@ -67,3 +67,132 @@ test('HTTP server exposes health and Stellar network routes', async () => { }); } }); + +test('health route rejects invalid HTTP methods', async () => { + const server = createApiServer({ STELLAR_NETWORK: 'TESTNET' }); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + + try { + const address = server.address(); + assert.notEqual(address, null); + assert.equal(typeof address, 'object'); + + const baseUrl = `http://127.0.0.1:${address.port}`; + + // Test POST to /health + const postResponse = await globalThis.fetch(`${baseUrl}/health`, { + method: 'POST', + }); + assert.equal(postResponse.status, 404); + const postPayload = await postResponse.json(); + assert.deepEqual(postPayload, { error: 'not_found' }); + + // Test PUT to /health + const putResponse = await globalThis.fetch(`${baseUrl}/health`, { + method: 'PUT', + }); + assert.equal(putResponse.status, 404); + const putPayload = await putResponse.json(); + assert.deepEqual(putPayload, { error: 'not_found' }); + + // Test DELETE to /health + const deleteResponse = await globalThis.fetch(`${baseUrl}/health`, { + method: 'DELETE', + }); + assert.equal(deleteResponse.status, 404); + const deletePayload = await deleteResponse.json(); + assert.deepEqual(deletePayload, { error: 'not_found' }); + + // Test PATCH to /health + const patchResponse = await globalThis.fetch(`${baseUrl}/health`, { + method: 'PATCH', + }); + assert.equal(patchResponse.status, 404); + const patchPayload = await patchResponse.json(); + assert.deepEqual(patchPayload, { error: 'not_found' }); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +}); + +test('health route rejects invalid paths', async () => { + const server = createApiServer({ STELLAR_NETWORK: 'TESTNET' }); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + + try { + const address = server.address(); + assert.notEqual(address, null); + assert.equal(typeof address, 'object'); + + const baseUrl = `http://127.0.0.1:${address.port}`; + + // Test /health with trailing slash + const trailingSlashResponse = await globalThis.fetch( + `${baseUrl}/health/`, + ); + assert.equal(trailingSlashResponse.status, 404); + const trailingSlashPayload = await trailingSlashResponse.json(); + assert.deepEqual(trailingSlashPayload, { error: 'not_found' }); + + // Test /health with extra path segments + const extraPathResponse = await globalThis.fetch( + `${baseUrl}/health/status`, + ); + assert.equal(extraPathResponse.status, 404); + const extraPathPayload = await extraPathResponse.json(); + assert.deepEqual(extraPathPayload, { error: 'not_found' }); + + // Test completely invalid path + const invalidPathResponse = await globalThis.fetch(`${baseUrl}/invalid`); + assert.equal(invalidPathResponse.status, 404); + const invalidPathPayload = await invalidPathResponse.json(); + assert.deepEqual(invalidPathPayload, { error: 'not_found' }); + + // Test root path + const rootResponse = await globalThis.fetch(`${baseUrl}/`); + assert.equal(rootResponse.status, 404); + const rootPayload = await rootResponse.json(); + assert.deepEqual(rootPayload, { error: 'not_found' }); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +}); + +test('health route response structure remains stable', async () => { + const server = createApiServer({ STELLAR_NETWORK: 'MAINNET' }); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + + try { + const address = server.address(); + assert.notEqual(address, null); + assert.equal(typeof address, 'object'); + + const baseUrl = `http://127.0.0.1:${address.port}`; + const health = await globalThis.fetch(`${baseUrl}/health`); + const healthPayload = await health.json(); + + // Verify response structure + assert.equal(typeof healthPayload, 'object'); + assert.equal(Object.keys(healthPayload).length, 3); + assert.equal(healthPayload.status, 'ok'); + assert.equal(healthPayload.app, '{{projectName}}'); + assert.equal(healthPayload.network, 'MAINNET'); + + // Verify content-type header + assert.equal( + health.headers.get('content-type'), + 'application/json', + ); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +});