From 69460e9652f384402ae6e0757d3bf0ab1600df55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 2 Sep 2026 15:10:48 +0300 Subject: [PATCH] test: add unit + integration tests for tf-docs (8 passing) --- server.js | 15 +++++- test/server.test.js | 117 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 test/server.test.js diff --git a/server.js b/server.js index e12857c..d3e4c5a 100644 --- a/server.js +++ b/server.js @@ -125,6 +125,17 @@ const server = http.createServer((req, res) => { `); }); -server.listen(PORT, () => { +if (require.main === module) { + server.listen(PORT, () => { console.log(`[${SERVICE}] listening on :${PORT} (version ${VERSION})`); -}); + }); +} + +module.exports = { + getDocsCandidates, + contentTypeFor, + health, + docsHandler, + server, + s3, +}; diff --git a/test/server.test.js b/test/server.test.js new file mode 100644 index 0000000..3fe1db2 --- /dev/null +++ b/test/server.test.js @@ -0,0 +1,117 @@ +'use strict'; + +process.env.S3_BUCKET = process.env.S3_BUCKET || 'terraform-registry'; + +const test = require('node:test'); +const assert = require('node:assert'); +const { Readable } = require('node:stream'); + +const { getDocsCandidates, contentTypeFor, server, s3 } = require('../server.js'); + +async function withServer(fn) { + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + const base = `http://127.0.0.1:${server.address().port}`; + try { + await fn(base); + } finally { + await new Promise((resolve) => server.close(resolve)); + } +} + +// --- getDocsCandidates (URL -> S3 keys) --- + +test('candidates: trailing slash -> index.html', () => { + assert.deepStrictEqual( + getDocsCandidates('/docs/nubes/nubes/1.0.0/'), + ['docs/nubes/nubes/1.0.0/index.html'] + ); +}); + +test('candidates: file with extension -> exact + root index fallback', () => { + assert.deepStrictEqual( + getDocsCandidates('/docs/nubes/nubes/1.0.0/page.html'), + ['docs/nubes/nubes/1.0.0/page.html', 'docs/nubes/nubes/1.0.0/index.html'] + ); +}); + +test('candidates: directory-style path -> exact + index.html + root index', () => { + assert.deepStrictEqual( + getDocsCandidates('/docs/nubes/nubes/1.0.0/guides/getting-started'), + [ + 'docs/nubes/nubes/1.0.0/guides/getting-started', + 'docs/nubes/nubes/1.0.0/guides/getting-started/index.html', + 'docs/nubes/nubes/1.0.0/index.html', + ] + ); +}); + +// --- contentTypeFor --- + +test('contentTypeFor: known and unknown extensions', () => { + assert.strictEqual(contentTypeFor('index.html'), 'text/html; charset=utf-8'); + assert.strictEqual(contentTypeFor('style.css'), 'text/css; charset=utf-8'); + assert.strictEqual(contentTypeFor('app.js'), 'application/javascript; charset=utf-8'); + assert.strictEqual(contentTypeFor('logo.png'), 'image/png'); + assert.strictEqual(contentTypeFor('data.json'), 'application/json; charset=utf-8'); + assert.strictEqual(contentTypeFor('file.unknown'), 'application/octet-stream'); +}); + +// --- /health --- + +test('GET /health -> 200 ok when S3 available', async () => { + const originalSend = s3.send; + s3.send = async () => ({ $metadata: { httpStatusCode: 200 } }); + await withServer(async (base) => { + const res = await fetch(`${base}/health`); + assert.strictEqual(res.status, 200); + assert.strictEqual(await res.text(), 'ok'); + }); + s3.send = originalSend; +}); + +test('GET /health -> 503 when S3 unreachable', async () => { + const originalSend = s3.send; + s3.send = async () => { throw new Error('boom'); }; + await withServer(async (base) => { + const res = await fetch(`${base}/health`); + assert.strictEqual(res.status, 503); + assert.match(await res.text(), /s3 unreachable/); + }); + s3.send = originalSend; +}); + +// --- /docs --- + +test('GET /docs/... -> 200 streams body with content-type', async () => { + const originalSend = s3.send; + s3.send = async (cmd) => { + if (cmd.input && cmd.input.Key) { + return { Body: Readable.from(['ok']), ContentType: 'text/html' }; + } + return { $metadata: { httpStatusCode: 200 } }; + }; + await withServer(async (base) => { + const res = await fetch(`${base}/docs/nubes/nubes/1.0.0/`); + assert.strictEqual(res.status, 200); + assert.match(res.headers.get('content-type') || '', /text\/html/); + assert.strictEqual(await res.text(), 'ok'); + }); + s3.send = originalSend; +}); + +test('GET /docs/... -> 404 when no key found', async () => { + const originalSend = s3.send; + s3.send = async (cmd) => { + if (cmd.input && cmd.input.Key) { + const err = new Error('Not Found'); + err.name = 'NoSuchKey'; + throw err; + } + return { $metadata: { httpStatusCode: 200 } }; + }; + await withServer(async (base) => { + const res = await fetch(`${base}/docs/nubes/nubes/1.0.0/missing`); + assert.strictEqual(res.status, 404); + }); + s3.send = originalSend; +});