@darthcav/ts-http-server 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,7 +112,7 @@ public/ # Documentation output (generated)
112
112
  [Apache-2.0](LICENSE)
113
113
 
114
114
  [node-version]: https://img.shields.io/badge/node-%3E%3D25-orange.svg?style=flat-square
115
- [version-image]: https://img.shields.io/badge/version-0.1.0-blue.svg?style=flat-square
115
+ [version-image]: https://img.shields.io/badge/version-0.2.0-blue.svg?style=flat-square
116
116
  [ci-badge]: https://github.com/darthcav/ts-http-server/actions/workflows/tests.yml/badge.svg
117
117
  [coverage-badge]: https://img.shields.io/badge/coverage-check%20CI-yellow.svg?style=flat-square
118
118
  [pages-url]: https://darthcav.github.io/ts-http-server/
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=defaultPlugins.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultPlugins.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/defaultPlugins.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { equal, ok } from "node:assert/strict";
2
+ import { cwd } from "node:process";
3
+ import { suite, test } from "node:test";
4
+ import defaultPlugins from "../defaults/defaultPlugins.js";
5
+ suite("defaultPlugins", () => {
6
+ const locals = {
7
+ pkg: { name: "ts-http-server", version: "0.0.0", description: "Test" },
8
+ };
9
+ test("returns all plugins using default baseDir", () => {
10
+ const plugins = defaultPlugins({ locals });
11
+ equal(plugins.size, 7);
12
+ ok(plugins.has("@fastify/accepts"));
13
+ ok(plugins.has("@fastify/view"));
14
+ ok(plugins.has("@fastify/static"));
15
+ });
16
+ test("returns all plugins using explicit baseDir", () => {
17
+ const plugins = defaultPlugins({ locals, baseDir: cwd() });
18
+ equal(plugins.size, 7);
19
+ ok(plugins.has("@fastify/view"));
20
+ ok(plugins.has("@fastify/static"));
21
+ });
22
+ });
23
+ //# sourceMappingURL=defaultPlugins.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultPlugins.test.js","sourceRoot":"","sources":["../../src/__tests__/defaultPlugins.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,cAAc,MAAM,+BAA+B,CAAA;AAE1D,KAAK,CAAC,gBAAgB,EAAE,GAAG,EAAE;IACzB,MAAM,MAAM,GAAG;QACX,GAAG,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE;KACzE,CAAA;IAED,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,OAAO,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QAC1C,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACtB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAA;QACnC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAA;QAChC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,OAAO,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;QAC1D,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACtB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAA;QAChC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;AACN,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=defaultRoutes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultRoutes.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/defaultRoutes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,181 @@
1
+ import { equal, match, ok } from "node:assert/strict";
2
+ import { request as httpRequest } from "node:http";
3
+ import { after, before, suite, test } from "node:test";
4
+ import { setTimeout } from "node:timers/promises";
5
+ import defaultPlugins from "../defaults/defaultPlugins.js";
6
+ import defaultRoutes from "../defaults/defaultRoutes.js";
7
+ import launcher from "../launcher.js";
8
+ // ---------------------------------------------------------------------------
9
+ // HTTP server suite
10
+ // ---------------------------------------------------------------------------
11
+ suite("defaultRoutes [HTTP]", () => {
12
+ // ---------------------------------------------------------------------------
13
+ // Minimal test logger (no real I/O)
14
+ // ---------------------------------------------------------------------------
15
+ const noop = () => { };
16
+ const testLogger = {
17
+ category: ["test"],
18
+ info: noop,
19
+ error: noop,
20
+ warn: noop,
21
+ debug: noop,
22
+ getChild: () => testLogger,
23
+ };
24
+ // ---------------------------------------------------------------------------
25
+ // Error route for 500 testing
26
+ // ---------------------------------------------------------------------------
27
+ function errorRoute() {
28
+ const routes = new Map();
29
+ routes.set("ERROR_ROUTE", {
30
+ method: "GET",
31
+ url: "/error",
32
+ handler: async () => {
33
+ throw new Error("test server error");
34
+ },
35
+ });
36
+ return routes;
37
+ }
38
+ // ---------------------------------------------------------------------------
39
+ // Full plugin + route setup (EJS views, static files, accepts negotiation)
40
+ // ---------------------------------------------------------------------------
41
+ const locals = {
42
+ pkg: { name: "ts-http-server", version: "0.0.0", description: "Test" },
43
+ };
44
+ const plugins = defaultPlugins({ locals });
45
+ const routes = new Map([...defaultRoutes(), ...errorRoute()]);
46
+ const port = 19002;
47
+ const base = `http://localhost:${port}`;
48
+ let server;
49
+ before(async () => {
50
+ server = launcher({
51
+ logger: testLogger,
52
+ locals: { ...locals, port },
53
+ plugins,
54
+ routes,
55
+ opts: { disableRequestLogging: true },
56
+ });
57
+ await setTimeout(1000);
58
+ });
59
+ after(async () => {
60
+ await setTimeout(500);
61
+ await server.close();
62
+ });
63
+ test("GET / → 200 text/html", async () => {
64
+ const res = await fetch(`${base}/`);
65
+ const body = await res.text();
66
+ equal(res.status, 200);
67
+ equal(res.statusText, "OK");
68
+ match(res.headers.get("content-type") ?? "", /text\/html/);
69
+ ok(body.includes("<!doctype html>"));
70
+ });
71
+ test("HEAD / → 200 text/html", async () => {
72
+ const res = await fetch(`${base}/`, { method: "HEAD" });
73
+ equal(res.status, 200);
74
+ equal(res.statusText, "OK");
75
+ match(res.headers.get("content-type") ?? "", /text\/html/);
76
+ });
77
+ test("GET / with application/json → 406 Not Acceptable (json)", async () => {
78
+ const res = await fetch(`${base}/`, {
79
+ headers: { accept: "application/json" },
80
+ });
81
+ const body = (await res.json());
82
+ equal(res.status, 406);
83
+ equal(res.statusText, "Not Acceptable");
84
+ match(res.headers.get("content-type") ?? "", /application\/json/);
85
+ equal(body.statusCode, 406);
86
+ equal(body.error, "Not Acceptable");
87
+ });
88
+ test("POST / → 405 Method Not Allowed (html)", async () => {
89
+ const res = await fetch(`${base}/`, { method: "POST" });
90
+ const body = await res.text();
91
+ equal(res.status, 405);
92
+ equal(res.statusText, "Method Not Allowed");
93
+ equal(res.headers.get("allow"), "GET, HEAD");
94
+ match(res.headers.get("content-type") ?? "", /text\/html/);
95
+ ok(body.includes("405"));
96
+ });
97
+ test("POST / with application/json → 405 Method Not Allowed (json)", async () => {
98
+ const res = await fetch(`${base}/`, {
99
+ method: "POST",
100
+ headers: { accept: "application/json" },
101
+ });
102
+ const body = (await res.json());
103
+ equal(res.status, 405);
104
+ equal(res.statusText, "Method Not Allowed");
105
+ equal(res.headers.get("allow"), "GET, HEAD");
106
+ match(res.headers.get("content-type") ?? "", /application\/json/);
107
+ equal(body.statusCode, 405);
108
+ equal(body.error, "Method Not Allowed");
109
+ });
110
+ test("GET /missing → 404 Not Found (html)", async () => {
111
+ const res = await fetch(`${base}/missing`);
112
+ const body = await res.text();
113
+ equal(res.status, 404);
114
+ equal(res.statusText, "Not Found");
115
+ match(res.headers.get("content-type") ?? "", /text\/html/);
116
+ ok(body.includes("404"));
117
+ });
118
+ test("GET /missing with application/json → 404 Not Found (json)", async () => {
119
+ const res = await fetch(`${base}/missing`, {
120
+ headers: { accept: "application/json" },
121
+ });
122
+ const body = (await res.json());
123
+ equal(res.status, 404);
124
+ equal(res.statusText, "Not Found");
125
+ match(res.headers.get("content-type") ?? "", /application\/json/);
126
+ equal(body.statusCode, 404);
127
+ equal(body.error, "Not Found");
128
+ });
129
+ test("GET /error → 500 Internal Server Error (html)", async () => {
130
+ const res = await fetch(`${base}/error`);
131
+ const body = await res.text();
132
+ equal(res.status, 500);
133
+ equal(res.statusText, "Internal Server Error");
134
+ match(res.headers.get("content-type") ?? "", /text\/html/);
135
+ ok(body.includes("500"));
136
+ });
137
+ test("GET /error with application/json → 500 Internal Server Error (json)", async () => {
138
+ const res = await fetch(`${base}/error`, {
139
+ headers: { accept: "application/json" },
140
+ });
141
+ const body = await res.json();
142
+ equal(res.status, 500);
143
+ equal(res.statusText, "Internal Server Error");
144
+ match(res.headers.get("content-type") ?? "", /application\/json/);
145
+ ok(body);
146
+ });
147
+ test("GET / with Referer header → 200 text/html", async () => {
148
+ const res = await fetch(`${base}/`, {
149
+ headers: { accept: "text/html", referer: `${base}/` },
150
+ });
151
+ equal(res.status, 200);
152
+ match(res.headers.get("content-type") ?? "", /text\/html/);
153
+ });
154
+ test("GET / without User-Agent → 200 (covers onResponse user-agent fallback)", async () => {
155
+ const status = await new Promise((resolve, reject) => {
156
+ const req = httpRequest({
157
+ hostname: "localhost",
158
+ port,
159
+ path: "/",
160
+ method: "GET",
161
+ headers: { accept: "text/html" },
162
+ }, (res) => {
163
+ res.resume();
164
+ resolve(res.statusCode ?? 0);
165
+ });
166
+ req.on("error", reject);
167
+ req.end();
168
+ });
169
+ equal(status, 200);
170
+ });
171
+ test("GET /missing with text/plain → 404 plain text (Boom)", async () => {
172
+ const res = await fetch(`${base}/missing`, {
173
+ headers: { accept: "text/plain" },
174
+ });
175
+ const body = await res.text();
176
+ equal(res.status, 404);
177
+ match(res.headers.get("content-type") ?? "", /text\/plain/);
178
+ ok(body.includes("Not Found"));
179
+ });
180
+ });
181
+ //# sourceMappingURL=defaultRoutes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultRoutes.test.js","sourceRoot":"","sources":["../../src/__tests__/defaultRoutes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=launcher.decorators.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/launcher.decorators.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { equal, ok } from "node:assert/strict";
2
+ import { after, before, suite, test } from "node:test";
3
+ import { setTimeout } from "node:timers/promises";
4
+ import FastifyAccepts from "@fastify/accepts";
5
+ import launcher from "../launcher.js";
6
+ // ---------------------------------------------------------------------------
7
+ // Suite covering decorators map and done callback
8
+ // ---------------------------------------------------------------------------
9
+ suite("launcher [HTTP] with decorators and done callback", () => {
10
+ // ---------------------------------------------------------------------------
11
+ // Minimal test logger (no real I/O)
12
+ // ---------------------------------------------------------------------------
13
+ const noop = () => { };
14
+ const testLogger = {
15
+ category: ["test"],
16
+ info: noop,
17
+ error: noop,
18
+ warn: noop,
19
+ debug: noop,
20
+ getChild: () => testLogger,
21
+ };
22
+ // ---------------------------------------------------------------------------
23
+ // Minimal plugins and routes (no EJS/static required)
24
+ // ---------------------------------------------------------------------------
25
+ const plugins = new Map([
26
+ ["@fastify/accepts", { plugin: FastifyAccepts }],
27
+ ]);
28
+ const routes = new Map([
29
+ [
30
+ "JSON_OK",
31
+ {
32
+ method: "GET",
33
+ url: "/",
34
+ handler: async (_request, reply) => reply.send({ ok: true }),
35
+ },
36
+ ],
37
+ ]);
38
+ const port = 19003;
39
+ const base = `http://localhost:${port}`;
40
+ let server;
41
+ let doneWasCalled = false;
42
+ before(async () => {
43
+ server = launcher({
44
+ logger: testLogger,
45
+ locals: { port },
46
+ plugins,
47
+ routes,
48
+ decorators: new Map([["myDecorator", true]]),
49
+ opts: { disableRequestLogging: true },
50
+ done: () => {
51
+ doneWasCalled = true;
52
+ },
53
+ });
54
+ await setTimeout(500);
55
+ });
56
+ after(async () => {
57
+ await setTimeout(200);
58
+ await server.close();
59
+ });
60
+ test("done callback is invoked on successful listen", () => {
61
+ ok(doneWasCalled);
62
+ });
63
+ test("GET / → 200 JSON with decorators registered", async () => {
64
+ const res = await fetch(`${base}/`);
65
+ const body = (await res.json());
66
+ equal(res.status, 200);
67
+ equal(body.ok, true);
68
+ });
69
+ });
70
+ //# sourceMappingURL=launcher.decorators.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/launcher.decorators.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAG7C,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAGrC,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,KAAK,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC5D,8EAA8E;IAC9E,oCAAoC;IACpC,8EAA8E;IAC9E,MAAM,IAAI,GAAG,GAAS,EAAE,GAAE,CAAC,CAAA;IAC3B,MAAM,UAAU,GAAG;QACf,QAAQ,EAAE,CAAC,MAAM,CAAC;QAClB,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;KACR,CAAA;IAEtB,8EAA8E;IAC9E,sDAAsD;IACtD,8EAA8E;IAC9E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAoB;QACvC,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;KACnD,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAuB;QACzC;YACI,SAAS;YACT;gBACI,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,GAAG;gBACR,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;aAC/D;SACJ;KACJ,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,KAAK,CAAA;IAClB,MAAM,IAAI,GAAG,oBAAoB,IAAI,EAAE,CAAA;IACvC,IAAI,MAAuB,CAAA;IAC3B,IAAI,aAAa,GAAG,KAAK,CAAA;IAEzB,MAAM,CAAC,KAAK,IAAI,EAAE;QACd,MAAM,GAAG,QAAQ,CAAC;YACd,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,EAAE,IAAI,EAAE;YAChB,OAAO;YACP,MAAM;YACN,UAAU,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5C,IAAI,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE;YACrC,IAAI,EAAE,GAAG,EAAE;gBACP,aAAa,GAAG,IAAI,CAAA;YACxB,CAAC;SACJ,CAAC,CAAA;QACF,MAAM,UAAU,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,KAAK,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,UAAU,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;IACxB,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,EAAE,CAAC,aAAa,CAAC,CAAA;IACrB,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,CAAA;QACnC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoB,CAAA;QAClD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACtB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IACxB,CAAC,CAAC,CAAA;AACN,CAAC,CAAC,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { equal, ok } from "node:assert/strict";
2
- import { after, before, describe, it } from "node:test";
2
+ import { after, before, suite, test } from "node:test";
3
3
  import { setTimeout } from "node:timers/promises";
4
4
  import FastifyAccepts from "@fastify/accepts";
5
5
  import launcher from "../launcher.js";
@@ -55,7 +55,7 @@ const routes = new Map([
55
55
  // ---------------------------------------------------------------------------
56
56
  // HTTP server suite
57
57
  // ---------------------------------------------------------------------------
58
- describe("launcher [HTTP]", () => {
58
+ suite("launcher [HTTP]", () => {
59
59
  const port = 19001;
60
60
  const base = `http://localhost:${port}`;
61
61
  let server;
@@ -73,18 +73,18 @@ describe("launcher [HTTP]", () => {
73
73
  await setTimeout(200);
74
74
  await server.close();
75
75
  });
76
- it("GET / → 200 JSON", async () => {
76
+ test("GET / → 200 JSON", async () => {
77
77
  const res = await fetch(`${base}/`);
78
78
  equal(res.status, 200);
79
79
  const body = (await res.json());
80
80
  equal(body.ok, true);
81
81
  });
82
- it("POST / → 405", async () => {
82
+ test("POST / → 405", async () => {
83
83
  const res = await fetch(`${base}/`, { method: "POST" });
84
84
  equal(res.status, 405);
85
85
  equal(res.headers.get("allow"), "GET, HEAD");
86
86
  });
87
- it("GET /missing → 404 JSON", async () => {
87
+ test("GET /missing → 404 JSON", async () => {
88
88
  const res = await fetch(`${base}/missing`, {
89
89
  headers: { accept: "application/json" },
90
90
  });
@@ -94,19 +94,19 @@ describe("launcher [HTTP]", () => {
94
94
  ok(body.statusCode);
95
95
  equal(body.statusCode, 404);
96
96
  });
97
- it("GET /missing → 404 plain text", async () => {
97
+ test("GET /missing → 404 plain text", async () => {
98
98
  const res = await fetch(`${base}/missing`, {
99
99
  headers: { accept: "text/plain" },
100
100
  });
101
101
  equal(res.status, 404);
102
102
  });
103
- it("GET /error → 500 JSON", async () => {
103
+ test("GET /error → 500 JSON", async () => {
104
104
  const res = await fetch(`${base}/error`, {
105
105
  headers: { accept: "application/json" },
106
106
  });
107
107
  equal(res.status, 500);
108
108
  });
109
- it("GET /error → 500 plain text", async () => {
109
+ test("GET /error → 500 plain text", async () => {
110
110
  const res = await fetch(`${base}/error`, {
111
111
  headers: { accept: "text/plain" },
112
112
  });
@@ -1 +1 @@
1
- {"version":3,"file":"launcher.test.js","sourceRoot":"","sources":["../../src/__tests__/launcher.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"launcher.test.js","sourceRoot":"","sources":["../../src/__tests__/launcher.test.ts"],"names":[],"mappings":"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"}
@@ -11,8 +11,8 @@ import type { FastifyReply, FastifyRequest } from "fastify";
11
11
  * fastify.addHook("onResponse", onResponse)
12
12
  * ```
13
13
  *
14
- * Uses `reply.log.info` so each log record is automatically correlated with
15
- * the request ID assigned by Fastify.
14
+ * Uses `reply.log.info` for 2xx/3xx and `reply.log.error` for 4xx/5xx,
15
+ * so each log record is automatically correlated with the request ID assigned by Fastify.
16
16
  */
17
17
  export default function onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void>;
18
18
  //# sourceMappingURL=onResponse.d.ts.map
@@ -10,8 +10,8 @@
10
10
  * fastify.addHook("onResponse", onResponse)
11
11
  * ```
12
12
  *
13
- * Uses `reply.log.info` so each log record is automatically correlated with
14
- * the request ID assigned by Fastify.
13
+ * Uses `reply.log.info` for 2xx/3xx and `reply.log.error` for 4xx/5xx,
14
+ * so each log record is automatically correlated with the request ID assigned by Fastify.
15
15
  */
16
16
  export default async function onResponse(request, reply) {
17
17
  const contentLength = reply.getHeader("content-length");
@@ -3,7 +3,7 @@ import type { FastifyReply, FastifyRequest } from "fastify";
3
3
  * Fastify `preHandler` hook that logs incoming request details:
4
4
  *
5
5
  * ```
6
- * Incoming request: {method} {url} HTTP/{httpVersion} from {ip}
6
+ * Incoming request [{id}]: {method} {url} HTTP/{httpVersion} from {ip}
7
7
  * ```
8
8
  *
9
9
  * Intended to be registered via:
@@ -2,7 +2,7 @@
2
2
  * Fastify `preHandler` hook that logs incoming request details:
3
3
  *
4
4
  * ```
5
- * Incoming request: {method} {url} HTTP/{httpVersion} from {ip}
5
+ * Incoming request [{id}]: {method} {url} HTTP/{httpVersion} from {ip}
6
6
  * ```
7
7
  *
8
8
  * Intended to be registered via:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@darthcav/ts-http-server",
3
3
  "description": "A TypeScript HTTP server for Node.js >= 25",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "author": {
6
6
  "name": "darthcav"
7
7
  },
@@ -0,0 +1,25 @@
1
+ import { equal, ok } from "node:assert/strict"
2
+ import { cwd } from "node:process"
3
+ import { suite, test } from "node:test"
4
+ import defaultPlugins from "../defaults/defaultPlugins.ts"
5
+
6
+ suite("defaultPlugins", () => {
7
+ const locals = {
8
+ pkg: { name: "ts-http-server", version: "0.0.0", description: "Test" },
9
+ }
10
+
11
+ test("returns all plugins using default baseDir", () => {
12
+ const plugins = defaultPlugins({ locals })
13
+ equal(plugins.size, 7)
14
+ ok(plugins.has("@fastify/accepts"))
15
+ ok(plugins.has("@fastify/view"))
16
+ ok(plugins.has("@fastify/static"))
17
+ })
18
+
19
+ test("returns all plugins using explicit baseDir", () => {
20
+ const plugins = defaultPlugins({ locals, baseDir: cwd() })
21
+ equal(plugins.size, 7)
22
+ ok(plugins.has("@fastify/view"))
23
+ ok(plugins.has("@fastify/static"))
24
+ })
25
+ })
@@ -0,0 +1,204 @@
1
+ import { equal, match, ok } from "node:assert/strict"
2
+ import { request as httpRequest } from "node:http"
3
+ import { after, before, suite, test } from "node:test"
4
+ import { setTimeout } from "node:timers/promises"
5
+ import type { Logger } from "@logtape/logtape"
6
+ import type { FastifyInstance, RouteOptions } from "fastify"
7
+ import defaultPlugins from "../defaults/defaultPlugins.ts"
8
+ import defaultRoutes from "../defaults/defaultRoutes.ts"
9
+ import launcher from "../launcher.ts"
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // HTTP server suite
13
+ // ---------------------------------------------------------------------------
14
+
15
+ suite("defaultRoutes [HTTP]", () => {
16
+ // ---------------------------------------------------------------------------
17
+ // Minimal test logger (no real I/O)
18
+ // ---------------------------------------------------------------------------
19
+ const noop = (): void => {}
20
+ const testLogger = {
21
+ category: ["test"],
22
+ info: noop,
23
+ error: noop,
24
+ warn: noop,
25
+ debug: noop,
26
+ getChild: () => testLogger,
27
+ } as unknown as Logger
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Error route for 500 testing
31
+ // ---------------------------------------------------------------------------
32
+ function errorRoute(): Map<string, RouteOptions> {
33
+ const routes = new Map<string, RouteOptions>()
34
+ routes.set("ERROR_ROUTE", {
35
+ method: "GET",
36
+ url: "/error",
37
+ handler: async () => {
38
+ throw new Error("test server error")
39
+ },
40
+ })
41
+ return routes
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Full plugin + route setup (EJS views, static files, accepts negotiation)
46
+ // ---------------------------------------------------------------------------
47
+ const locals = {
48
+ pkg: { name: "ts-http-server", version: "0.0.0", description: "Test" },
49
+ }
50
+ const plugins = defaultPlugins({ locals })
51
+ const routes = new Map([...defaultRoutes(), ...errorRoute()])
52
+
53
+ const port = 19002
54
+ const base = `http://localhost:${port}`
55
+ let server: FastifyInstance
56
+
57
+ before(async () => {
58
+ server = launcher({
59
+ logger: testLogger,
60
+ locals: { ...locals, port },
61
+ plugins,
62
+ routes,
63
+ opts: { disableRequestLogging: true },
64
+ })
65
+ await setTimeout(1000)
66
+ })
67
+
68
+ after(async () => {
69
+ await setTimeout(500)
70
+ await server.close()
71
+ })
72
+
73
+ test("GET / → 200 text/html", async () => {
74
+ const res = await fetch(`${base}/`)
75
+ const body = await res.text()
76
+ equal(res.status, 200)
77
+ equal(res.statusText, "OK")
78
+ match(res.headers.get("content-type") ?? "", /text\/html/)
79
+ ok(body.includes("<!doctype html>"))
80
+ })
81
+
82
+ test("HEAD / → 200 text/html", async () => {
83
+ const res = await fetch(`${base}/`, { method: "HEAD" })
84
+ equal(res.status, 200)
85
+ equal(res.statusText, "OK")
86
+ match(res.headers.get("content-type") ?? "", /text\/html/)
87
+ })
88
+
89
+ test("GET / with application/json → 406 Not Acceptable (json)", async () => {
90
+ const res = await fetch(`${base}/`, {
91
+ headers: { accept: "application/json" },
92
+ })
93
+ const body = (await res.json()) as { statusCode: number; error: string }
94
+ equal(res.status, 406)
95
+ equal(res.statusText, "Not Acceptable")
96
+ match(res.headers.get("content-type") ?? "", /application\/json/)
97
+ equal(body.statusCode, 406)
98
+ equal(body.error, "Not Acceptable")
99
+ })
100
+
101
+ test("POST / → 405 Method Not Allowed (html)", async () => {
102
+ const res = await fetch(`${base}/`, { method: "POST" })
103
+ const body = await res.text()
104
+ equal(res.status, 405)
105
+ equal(res.statusText, "Method Not Allowed")
106
+ equal(res.headers.get("allow"), "GET, HEAD")
107
+ match(res.headers.get("content-type") ?? "", /text\/html/)
108
+ ok(body.includes("405"))
109
+ })
110
+
111
+ test("POST / with application/json → 405 Method Not Allowed (json)", async () => {
112
+ const res = await fetch(`${base}/`, {
113
+ method: "POST",
114
+ headers: { accept: "application/json" },
115
+ })
116
+ const body = (await res.json()) as { statusCode: number; error: string }
117
+ equal(res.status, 405)
118
+ equal(res.statusText, "Method Not Allowed")
119
+ equal(res.headers.get("allow"), "GET, HEAD")
120
+ match(res.headers.get("content-type") ?? "", /application\/json/)
121
+ equal(body.statusCode, 405)
122
+ equal(body.error, "Method Not Allowed")
123
+ })
124
+
125
+ test("GET /missing → 404 Not Found (html)", async () => {
126
+ const res = await fetch(`${base}/missing`)
127
+ const body = await res.text()
128
+ equal(res.status, 404)
129
+ equal(res.statusText, "Not Found")
130
+ match(res.headers.get("content-type") ?? "", /text\/html/)
131
+ ok(body.includes("404"))
132
+ })
133
+
134
+ test("GET /missing with application/json → 404 Not Found (json)", async () => {
135
+ const res = await fetch(`${base}/missing`, {
136
+ headers: { accept: "application/json" },
137
+ })
138
+ const body = (await res.json()) as { statusCode: number; error: string }
139
+ equal(res.status, 404)
140
+ equal(res.statusText, "Not Found")
141
+ match(res.headers.get("content-type") ?? "", /application\/json/)
142
+ equal(body.statusCode, 404)
143
+ equal(body.error, "Not Found")
144
+ })
145
+
146
+ test("GET /error → 500 Internal Server Error (html)", async () => {
147
+ const res = await fetch(`${base}/error`)
148
+ const body = await res.text()
149
+ equal(res.status, 500)
150
+ equal(res.statusText, "Internal Server Error")
151
+ match(res.headers.get("content-type") ?? "", /text\/html/)
152
+ ok(body.includes("500"))
153
+ })
154
+
155
+ test("GET /error with application/json → 500 Internal Server Error (json)", async () => {
156
+ const res = await fetch(`${base}/error`, {
157
+ headers: { accept: "application/json" },
158
+ })
159
+ const body = await res.json()
160
+ equal(res.status, 500)
161
+ equal(res.statusText, "Internal Server Error")
162
+ match(res.headers.get("content-type") ?? "", /application\/json/)
163
+ ok(body)
164
+ })
165
+
166
+ test("GET / with Referer header → 200 text/html", async () => {
167
+ const res = await fetch(`${base}/`, {
168
+ headers: { accept: "text/html", referer: `${base}/` },
169
+ })
170
+ equal(res.status, 200)
171
+ match(res.headers.get("content-type") ?? "", /text\/html/)
172
+ })
173
+
174
+ test("GET / without User-Agent → 200 (covers onResponse user-agent fallback)", async () => {
175
+ const status = await new Promise<number>((resolve, reject) => {
176
+ const req = httpRequest(
177
+ {
178
+ hostname: "localhost",
179
+ port,
180
+ path: "/",
181
+ method: "GET",
182
+ headers: { accept: "text/html" },
183
+ },
184
+ (res) => {
185
+ res.resume()
186
+ resolve(res.statusCode ?? 0)
187
+ },
188
+ )
189
+ req.on("error", reject)
190
+ req.end()
191
+ })
192
+ equal(status, 200)
193
+ })
194
+
195
+ test("GET /missing with text/plain → 404 plain text (Boom)", async () => {
196
+ const res = await fetch(`${base}/missing`, {
197
+ headers: { accept: "text/plain" },
198
+ })
199
+ const body = await res.text()
200
+ equal(res.status, 404)
201
+ match(res.headers.get("content-type") ?? "", /text\/plain/)
202
+ ok(body.includes("Not Found"))
203
+ })
204
+ })
@@ -0,0 +1,80 @@
1
+ import { equal, ok } from "node:assert/strict"
2
+ import { after, before, suite, test } from "node:test"
3
+ import { setTimeout } from "node:timers/promises"
4
+ import FastifyAccepts from "@fastify/accepts"
5
+ import type { Logger } from "@logtape/logtape"
6
+ import type { FastifyInstance, RouteOptions } from "fastify"
7
+ import launcher from "../launcher.ts"
8
+ import type { FSTPlugin } from "../types.ts"
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Suite covering decorators map and done callback
12
+ // ---------------------------------------------------------------------------
13
+
14
+ suite("launcher [HTTP] with decorators and done callback", () => {
15
+ // ---------------------------------------------------------------------------
16
+ // Minimal test logger (no real I/O)
17
+ // ---------------------------------------------------------------------------
18
+ const noop = (): void => {}
19
+ const testLogger = {
20
+ category: ["test"],
21
+ info: noop,
22
+ error: noop,
23
+ warn: noop,
24
+ debug: noop,
25
+ getChild: () => testLogger,
26
+ } as unknown as Logger
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Minimal plugins and routes (no EJS/static required)
30
+ // ---------------------------------------------------------------------------
31
+ const plugins = new Map<string, FSTPlugin>([
32
+ ["@fastify/accepts", { plugin: FastifyAccepts }],
33
+ ])
34
+ const routes = new Map<string, RouteOptions>([
35
+ [
36
+ "JSON_OK",
37
+ {
38
+ method: "GET",
39
+ url: "/",
40
+ handler: async (_request, reply) => reply.send({ ok: true }),
41
+ },
42
+ ],
43
+ ])
44
+
45
+ const port = 19003
46
+ const base = `http://localhost:${port}`
47
+ let server: FastifyInstance
48
+ let doneWasCalled = false
49
+
50
+ before(async () => {
51
+ server = launcher({
52
+ logger: testLogger,
53
+ locals: { port },
54
+ plugins,
55
+ routes,
56
+ decorators: new Map([["myDecorator", true]]),
57
+ opts: { disableRequestLogging: true },
58
+ done: () => {
59
+ doneWasCalled = true
60
+ },
61
+ })
62
+ await setTimeout(500)
63
+ })
64
+
65
+ after(async () => {
66
+ await setTimeout(200)
67
+ await server.close()
68
+ })
69
+
70
+ test("done callback is invoked on successful listen", () => {
71
+ ok(doneWasCalled)
72
+ })
73
+
74
+ test("GET / → 200 JSON with decorators registered", async () => {
75
+ const res = await fetch(`${base}/`)
76
+ const body = (await res.json()) as { ok: boolean }
77
+ equal(res.status, 200)
78
+ equal(body.ok, true)
79
+ })
80
+ })
@@ -1,5 +1,5 @@
1
1
  import { equal, ok } from "node:assert/strict"
2
- import { after, before, describe, it } from "node:test"
2
+ import { after, before, suite, test } from "node:test"
3
3
  import { setTimeout } from "node:timers/promises"
4
4
  import FastifyAccepts from "@fastify/accepts"
5
5
  import type { Logger } from "@logtape/logtape"
@@ -65,7 +65,7 @@ const routes = new Map<string, RouteOptions>([
65
65
  // HTTP server suite
66
66
  // ---------------------------------------------------------------------------
67
67
 
68
- describe("launcher [HTTP]", () => {
68
+ suite("launcher [HTTP]", () => {
69
69
  const port = 19001
70
70
  const base = `http://localhost:${port}`
71
71
  let server: import("fastify").FastifyInstance
@@ -86,20 +86,20 @@ describe("launcher [HTTP]", () => {
86
86
  await server.close()
87
87
  })
88
88
 
89
- it("GET / → 200 JSON", async () => {
89
+ test("GET / → 200 JSON", async () => {
90
90
  const res = await fetch(`${base}/`)
91
91
  equal(res.status, 200)
92
92
  const body = (await res.json()) as { ok: boolean }
93
93
  equal(body.ok, true)
94
94
  })
95
95
 
96
- it("POST / → 405", async () => {
96
+ test("POST / → 405", async () => {
97
97
  const res = await fetch(`${base}/`, { method: "POST" })
98
98
  equal(res.status, 405)
99
99
  equal(res.headers.get("allow"), "GET, HEAD")
100
100
  })
101
101
 
102
- it("GET /missing → 404 JSON", async () => {
102
+ test("GET /missing → 404 JSON", async () => {
103
103
  const res = await fetch(`${base}/missing`, {
104
104
  headers: { accept: "application/json" },
105
105
  })
@@ -113,21 +113,21 @@ describe("launcher [HTTP]", () => {
113
113
  equal(body.statusCode, 404)
114
114
  })
115
115
 
116
- it("GET /missing → 404 plain text", async () => {
116
+ test("GET /missing → 404 plain text", async () => {
117
117
  const res = await fetch(`${base}/missing`, {
118
118
  headers: { accept: "text/plain" },
119
119
  })
120
120
  equal(res.status, 404)
121
121
  })
122
122
 
123
- it("GET /error → 500 JSON", async () => {
123
+ test("GET /error → 500 JSON", async () => {
124
124
  const res = await fetch(`${base}/error`, {
125
125
  headers: { accept: "application/json" },
126
126
  })
127
127
  equal(res.status, 500)
128
128
  })
129
129
 
130
- it("GET /error → 500 plain text", async () => {
130
+ test("GET /error → 500 plain text", async () => {
131
131
  const res = await fetch(`${base}/error`, {
132
132
  headers: { accept: "text/plain" },
133
133
  })
@@ -12,8 +12,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
12
12
  * fastify.addHook("onResponse", onResponse)
13
13
  * ```
14
14
  *
15
- * Uses `reply.log.info` so each log record is automatically correlated with
16
- * the request ID assigned by Fastify.
15
+ * Uses `reply.log.info` for 2xx/3xx and `reply.log.error` for 4xx/5xx,
16
+ * so each log record is automatically correlated with the request ID assigned by Fastify.
17
17
  */
18
18
  export default async function onResponse(
19
19
  request: FastifyRequest,
@@ -4,7 +4,7 @@ import type { FastifyReply, FastifyRequest } from "fastify"
4
4
  * Fastify `preHandler` hook that logs incoming request details:
5
5
  *
6
6
  * ```
7
- * Incoming request: {method} {url} HTTP/{httpVersion} from {ip}
7
+ * Incoming request [{id}]: {method} {url} HTTP/{httpVersion} from {ip}
8
8
  * ```
9
9
  *
10
10
  * Intended to be registered via: