@j3r3mcdev/oast-server 1.1.1 → 1.1.3

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.
Files changed (191) hide show
  1. package/.github/workflows/ci.yml +29 -29
  2. package/.github/workflows/publish.yml +31 -31
  3. package/README.md +192 -192
  4. package/dist/api/controllers/__tests__/tasks.controller.test.d.ts +1 -0
  5. package/dist/api/controllers/events.controller.d.ts +6 -0
  6. package/dist/api/controllers/health.controller.d.ts +4 -0
  7. package/dist/api/controllers/index.d.ts +0 -0
  8. package/dist/api/controllers/tasks.controller.d.ts +40 -0
  9. package/dist/api/dto/__tests__/create-task.dto.test.d.ts +1 -0
  10. package/dist/api/dto/__tests__/filter-tasks.dto.test.d.ts +1 -0
  11. package/dist/api/dto/create-task.dto.d.ts +9 -0
  12. package/dist/api/dto/filter-tasks.dto.d.ts +8 -0
  13. package/dist/api/services/__tests__/events.service.test.d.ts +1 -0
  14. package/dist/api/services/__tests__/tasks.service.test.d.ts +1 -0
  15. package/dist/api/services/events.service.d.ts +8 -0
  16. package/dist/api/services/tasks.service.d.ts +22 -0
  17. package/dist/api/sse/events.stream.d.ts +25 -0
  18. package/dist/bootstrap.d.ts +1 -0
  19. package/dist/bootstrap.js +76 -0
  20. package/dist/config/constants.d.ts +0 -0
  21. package/dist/config/env.d.ts +0 -0
  22. package/dist/core/__tests__/core-router.test.d.ts +1 -0
  23. package/dist/core/__tests__/core-server.test.d.ts +1 -0
  24. package/dist/core/__tests__/event.normalizer.test.d.ts +1 -0
  25. package/dist/core/__tests__/event.router.test.d.ts +1 -0
  26. package/dist/core/__tests__/logger.test.d.ts +1 -0
  27. package/dist/core/__tests__/storage-manager.test.d.ts +1 -0
  28. package/dist/core/event.normalizer.d.ts +10 -0
  29. package/dist/core/event.router.d.ts +7 -0
  30. package/dist/core/http/__tests__/adapter-node.test.d.ts +1 -0
  31. package/dist/core/http/__tests__/body-parser-multipart.test.d.ts +1 -0
  32. package/dist/core/http/__tests__/body-parser-raw.test.d.ts +1 -0
  33. package/dist/core/http/__tests__/body-parser-text.test.d.ts +1 -0
  34. package/dist/core/http/__tests__/compile-path.test.d.ts +1 -0
  35. package/dist/core/http/__tests__/middleware-pipeline.test.d.ts +1 -0
  36. package/dist/core/http/__tests__/request.test.d.ts +1 -0
  37. package/dist/core/http/__tests__/response.test.d.ts +1 -0
  38. package/dist/core/http/__tests__/router-match.test.d.ts +1 -0
  39. package/dist/core/http/adapter-node.d.ts +8 -0
  40. package/dist/core/http/buildRequest.d.ts +2 -0
  41. package/dist/core/http/compile-path.d.ts +13 -0
  42. package/dist/core/http/errors.d.ts +9 -0
  43. package/dist/core/http/http-server.d.ts +7 -0
  44. package/dist/core/http/index.d.ts +0 -0
  45. package/dist/core/http/main.d.ts +0 -0
  46. package/dist/core/http/middleware.d.ts +12 -0
  47. package/dist/core/http/request.d.ts +35 -0
  48. package/dist/core/http/response.d.ts +17 -0
  49. package/dist/core/http/router.d.ts +28 -0
  50. package/dist/core/http/utils.d.ts +0 -0
  51. package/dist/core/id-generator.d.ts +3 -0
  52. package/dist/core/logger.d.ts +29 -0
  53. package/dist/core/router.d.ts +16 -0
  54. package/dist/core/server.d.ts +25 -0
  55. package/dist/core/storage.d.ts +24 -0
  56. package/dist/index.d.ts +9 -0
  57. package/dist/index.js +19 -74
  58. package/dist/listeners/api/__tests__/api.controller.test.d.ts +1 -0
  59. package/dist/listeners/api/__tests__/api.extractor.test.d.ts +1 -0
  60. package/dist/listeners/api/__tests__/api.listener.test.d.ts +1 -0
  61. package/dist/listeners/api/__tests__/api.routes.test.d.ts +1 -0
  62. package/dist/listeners/api/__tests__/api.sse.test.d.ts +1 -0
  63. package/dist/listeners/api/api.controllers.d.ts +9 -0
  64. package/dist/listeners/api/api.extractor.d.ts +5 -0
  65. package/dist/listeners/api/api.listener.d.ts +17 -0
  66. package/dist/listeners/api/api.routes.d.ts +5 -0
  67. package/dist/listeners/api/api.sse.d.ts +10 -0
  68. package/dist/listeners/dns/__tests__/dns.test.d.ts +1 -0
  69. package/dist/listeners/dns/dns.extractor.d.ts +8 -0
  70. package/dist/listeners/dns/dns.listener.d.ts +15 -0
  71. package/dist/listeners/http/__tests__/http.extractor.test.d.ts +1 -0
  72. package/dist/listeners/http/__tests__/http.listener.test.d.ts +1 -0
  73. package/dist/listeners/http/http.extractor.d.ts +11 -0
  74. package/dist/listeners/http/http.listener.d.ts +17 -0
  75. package/dist/listeners/listener.interface.d.ts +4 -0
  76. package/dist/listeners/smtp/__tests__/smtp.extractor.test.d.ts +1 -0
  77. package/dist/listeners/smtp/__tests__/smtp.listener.test.d.ts +1 -0
  78. package/dist/listeners/smtp/smtp.extractor.d.ts +12 -0
  79. package/dist/listeners/smtp/smtp.listener.d.ts +13 -0
  80. package/dist/listeners/ssrf/__tests__/ssrf.extractor.test.d.ts +1 -0
  81. package/dist/listeners/ssrf/__tests__/ssrf.listener.test.d.ts +1 -0
  82. package/dist/listeners/ssrf/__tests__/ssrf.listener.test.js +4 -16
  83. package/dist/listeners/ssrf/ssrf.extractor.d.ts +10 -0
  84. package/dist/listeners/ssrf/ssrf.listener.d.ts +8 -0
  85. package/dist/listeners/tcp/tcp.extractor.d.ts +4 -0
  86. package/dist/listeners/tcp/tcp.listener.d.ts +18 -0
  87. package/dist/listeners/webhook/__tests__/webhook.extractor.test.d.ts +1 -0
  88. package/dist/listeners/webhook/__tests__/webhook.listener.test.d.ts +1 -0
  89. package/dist/listeners/webhook/webhook.extractor.d.ts +8 -0
  90. package/dist/listeners/webhook/webhook.listener.d.ts +8 -0
  91. package/dist/listeners/websocket/__tests__/websocket.extractor.test.d.ts +1 -0
  92. package/dist/listeners/websocket/__tests__/websocket.listener.test.d.ts +1 -0
  93. package/dist/listeners/websocket/websocket.extractor.d.ts +10 -0
  94. package/dist/listeners/websocket/websocket.listener.d.ts +8 -0
  95. package/dist/storage-adapters/adapters/__tests__/memory.storage.test.d.ts +1 -0
  96. package/dist/storage-adapters/adapters/memory.storage.d.ts +21 -0
  97. package/dist/storage-adapters/adapters/redis.storage.d.ts +0 -0
  98. package/dist/storage-adapters/adapters/sqlite.storage.d.ts +0 -0
  99. package/dist/storage-adapters/storage.interface.d.ts +18 -0
  100. package/dist/types/event.types.d.ts +118 -0
  101. package/dist/utils/token.d.ts +0 -0
  102. package/jest.config.js +14 -14
  103. package/package.json +45 -45
  104. package/sadmin list shadows +9 -9
  105. package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
  106. package/src/api/controllers/events.controller.ts +10 -10
  107. package/src/api/controllers/health.controller.ts +7 -7
  108. package/src/api/controllers/tasks.controller.ts +41 -41
  109. package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
  110. package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
  111. package/src/api/dto/create-task.dto.ts +33 -33
  112. package/src/api/dto/filter-tasks.dto.ts +33 -33
  113. package/src/api/services/__tests__/events.service.test.ts +41 -41
  114. package/src/api/services/__tests__/tasks.service.test.ts +41 -41
  115. package/src/api/services/events.service.ts +17 -17
  116. package/src/api/services/tasks.service.ts +79 -79
  117. package/src/api/sse/events.stream.ts +90 -90
  118. package/src/bootstrap.ts +89 -0
  119. package/src/core/__tests__/core-router.test.ts +30 -30
  120. package/src/core/__tests__/core-server.test.ts +44 -44
  121. package/src/core/__tests__/event.normalizer.test.ts +56 -56
  122. package/src/core/__tests__/event.router.test.ts +89 -89
  123. package/src/core/__tests__/logger.test.ts +32 -32
  124. package/src/core/__tests__/storage-manager.test.ts +74 -74
  125. package/src/core/event.normalizer.ts +147 -147
  126. package/src/core/event.router.ts +13 -13
  127. package/src/core/http/__tests__/adapter-node.test.ts +52 -52
  128. package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
  129. package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
  130. package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
  131. package/src/core/http/__tests__/compile-path.test.ts +39 -39
  132. package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
  133. package/src/core/http/__tests__/request.test.ts +34 -34
  134. package/src/core/http/__tests__/response.test.ts +35 -35
  135. package/src/core/http/__tests__/router-match.test.ts +171 -171
  136. package/src/core/http/adapter-node.ts +51 -51
  137. package/src/core/http/buildRequest.ts +18 -18
  138. package/src/core/http/compile-path.ts +32 -32
  139. package/src/core/http/errors.ts +37 -37
  140. package/src/core/http/http-server.ts +52 -52
  141. package/src/core/http/middleware.ts +160 -160
  142. package/src/core/http/request.ts +55 -55
  143. package/src/core/http/response.ts +93 -93
  144. package/src/core/http/router.ts +138 -138
  145. package/src/core/id-generator.ts +8 -8
  146. package/src/core/logger.ts +113 -113
  147. package/src/core/router.ts +44 -44
  148. package/src/core/server.ts +85 -85
  149. package/src/core/storage.ts +64 -64
  150. package/src/index.ts +10 -89
  151. package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
  152. package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
  153. package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
  154. package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
  155. package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
  156. package/src/listeners/api/api.controllers.ts +67 -67
  157. package/src/listeners/api/api.extractor.ts +43 -43
  158. package/src/listeners/api/api.listener.ts +50 -50
  159. package/src/listeners/api/api.routes.ts +76 -76
  160. package/src/listeners/api/api.sse.ts +38 -38
  161. package/src/listeners/dns/__tests__/dns.test.ts +118 -118
  162. package/src/listeners/dns/dns.extractor.ts +14 -14
  163. package/src/listeners/dns/dns.listener.ts +61 -61
  164. package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
  165. package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
  166. package/src/listeners/http/http.extractor.ts +15 -15
  167. package/src/listeners/http/http.listener.ts +110 -110
  168. package/src/listeners/listener.interface.ts +4 -4
  169. package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
  170. package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
  171. package/src/listeners/smtp/smtp.extractor.ts +18 -18
  172. package/src/listeners/smtp/smtp.listener.ts +60 -60
  173. package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
  174. package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
  175. package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
  176. package/src/listeners/ssrf/ssrf.listener.ts +37 -37
  177. package/src/listeners/tcp/tcp.extractor.ts +16 -16
  178. package/src/listeners/tcp/tcp.listener.ts +61 -61
  179. package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
  180. package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
  181. package/src/listeners/webhook/webhook.extractor.ts +12 -12
  182. package/src/listeners/webhook/webhook.listener.ts +58 -58
  183. package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
  184. package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
  185. package/src/listeners/websocket/websocket.extractor.ts +11 -11
  186. package/src/listeners/websocket/websocket.listener.ts +40 -40
  187. package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
  188. package/src/storage-adapters/adapters/memory.storage.ts +64 -64
  189. package/src/storage-adapters/storage.interface.ts +26 -26
  190. package/src/types/event.types.ts +147 -147
  191. package/tsconfig.json +21 -21
@@ -1,46 +1,46 @@
1
- import { ApiExtractor } from "../api.extractor";
2
- import { IncomingMessage } from "http";
3
- import { Socket } from "net";
4
- import { describe, it, expect } from "@jest/globals";
5
-
6
- function mockReq(url: string, method = "GET", body?: any): IncomingMessage {
7
- const socket = new Socket();
8
- const req = new IncomingMessage(socket);
9
-
10
- req.url = url;
11
- req.method = method;
12
- req.headers = { host: "localhost" };
13
-
14
- if (body !== undefined) {
15
- const json = JSON.stringify(body);
16
- process.nextTick(() => {
17
- req.emit("data", json);
18
- req.emit("end");
19
- });
20
- } else {
21
- process.nextTick(() => req.emit("end"));
22
- }
23
-
24
- return req;
25
- }
26
-
27
- describe("ApiExtractor", () => {
28
- it("extrait un RawEvent GET", async () => {
29
- const req = mockReq("/events?page=2&type=http");
30
-
31
- const event = await ApiExtractor.extract(req);
32
-
33
- expect(event.method).toBe("GET");
34
- expect(event.path).toBe("/events");
35
- expect(event.query).toEqual({ page: "2", type: "http" });
36
- });
37
-
38
- it("extrait un RawEvent POST avec body JSON", async () => {
39
- const req = mockReq("/events", "POST", { hello: "world" });
40
-
41
- const event = await ApiExtractor.extract(req);
42
-
43
- expect(event.method).toBe("POST");
44
- expect(event.body).toEqual({ hello: "world" });
45
- });
46
- });
1
+ import { ApiExtractor } from "../api.extractor";
2
+ import { IncomingMessage } from "http";
3
+ import { Socket } from "net";
4
+ import { describe, it, expect } from "@jest/globals";
5
+
6
+ function mockReq(url: string, method = "GET", body?: any): IncomingMessage {
7
+ const socket = new Socket();
8
+ const req = new IncomingMessage(socket);
9
+
10
+ req.url = url;
11
+ req.method = method;
12
+ req.headers = { host: "localhost" };
13
+
14
+ if (body !== undefined) {
15
+ const json = JSON.stringify(body);
16
+ process.nextTick(() => {
17
+ req.emit("data", json);
18
+ req.emit("end");
19
+ });
20
+ } else {
21
+ process.nextTick(() => req.emit("end"));
22
+ }
23
+
24
+ return req;
25
+ }
26
+
27
+ describe("ApiExtractor", () => {
28
+ it("extrait un RawEvent GET", async () => {
29
+ const req = mockReq("/events?page=2&type=http");
30
+
31
+ const event = await ApiExtractor.extract(req);
32
+
33
+ expect(event.method).toBe("GET");
34
+ expect(event.path).toBe("/events");
35
+ expect(event.query).toEqual({ page: "2", type: "http" });
36
+ });
37
+
38
+ it("extrait un RawEvent POST avec body JSON", async () => {
39
+ const req = mockReq("/events", "POST", { hello: "world" });
40
+
41
+ const event = await ApiExtractor.extract(req);
42
+
43
+ expect(event.method).toBe("POST");
44
+ expect(event.body).toEqual({ hello: "world" });
45
+ });
46
+ });
@@ -1,82 +1,82 @@
1
- import http from "http";
2
- import { ApiListener } from "../api.listener";
3
- import { StorageManager } from "../../../core/storage";
4
- import { Logger } from "../../../core/logger";
5
- import { describe, test, beforeEach, afterEach, expect } from "@jest/globals";
6
- import { ApiController } from "../api.controllers";
7
-
8
- describe("ApiListener", () => {
9
- let storage: StorageManager;
10
- let api: ApiListener;
11
-
12
- beforeEach(async () => {
13
- storage = new StorageManager();
14
-
15
- api = new ApiListener(storage, {
16
- port: 9999,
17
- logger: new Logger({ context: "ApiTest" }),
18
- });
19
-
20
- await api.start();
21
- });
22
-
23
- afterEach(async () => {
24
- await api.stop();
25
- });
26
-
27
- test("GET /events returns empty list", async () => {
28
- const res = await fetch("http://localhost:9999/events");
29
- const json = await res.json();
30
-
31
- expect(json.success).toBe(true);
32
- expect(json.events).toEqual([]);
33
- });
34
-
35
- test("GET /stats returns correct structure", async () => {
36
- const res = await fetch("http://localhost:9999/stats");
37
- const json = await res.json();
38
-
39
- expect(json.success).toBe(true);
40
- expect(json.stats.total).toBe(0);
41
- expect(json.stats.byType).toEqual({});
42
- });
43
-
44
- test("GET /events/:id returns 404 when not found", async () => {
45
- const res = await fetch("http://localhost:9999/events/unknown");
46
- const json = await res.json();
47
-
48
- expect(res.status).toBe(404);
49
- expect(json.success).toBe(false);
50
- });
51
-
52
- test("DELETE /events clears all events", async () => {
53
- // On ajoute un event dans le storage
54
- await storage.save({
55
- id: "1",
56
- type: "http",
57
- timestamp: Date.now(),
58
- sourceIp: "127.0.0.1",
59
- request: {
60
- method: "GET",
61
- path: "/",
62
- headers: {},
63
- query: {},
64
- body: "",
65
- },
66
- });
67
-
68
- let events = await storage.listEvents({});
69
- expect(events.length).toBe(1);
70
-
71
- // On appelle l'API réelle
72
- const res = await fetch("http://localhost:9999/events", {
73
- method: "DELETE",
74
- });
75
-
76
- const json = await res.json();
77
- expect(json.success).toBe(true);
78
-
79
- events = await storage.listEvents({});
80
- expect(events.length).toBe(0);
81
- });
82
- });
1
+ import http from "http";
2
+ import { ApiListener } from "../api.listener";
3
+ import { StorageManager } from "../../../core/storage";
4
+ import { Logger } from "../../../core/logger";
5
+ import { describe, test, beforeEach, afterEach, expect } from "@jest/globals";
6
+ import { ApiController } from "../api.controllers";
7
+
8
+ describe("ApiListener", () => {
9
+ let storage: StorageManager;
10
+ let api: ApiListener;
11
+
12
+ beforeEach(async () => {
13
+ storage = new StorageManager();
14
+
15
+ api = new ApiListener(storage, {
16
+ port: 9999,
17
+ logger: new Logger({ context: "ApiTest" }),
18
+ });
19
+
20
+ await api.start();
21
+ });
22
+
23
+ afterEach(async () => {
24
+ await api.stop();
25
+ });
26
+
27
+ test("GET /events returns empty list", async () => {
28
+ const res = await fetch("http://localhost:9999/events");
29
+ const json = await res.json();
30
+
31
+ expect(json.success).toBe(true);
32
+ expect(json.events).toEqual([]);
33
+ });
34
+
35
+ test("GET /stats returns correct structure", async () => {
36
+ const res = await fetch("http://localhost:9999/stats");
37
+ const json = await res.json();
38
+
39
+ expect(json.success).toBe(true);
40
+ expect(json.stats.total).toBe(0);
41
+ expect(json.stats.byType).toEqual({});
42
+ });
43
+
44
+ test("GET /events/:id returns 404 when not found", async () => {
45
+ const res = await fetch("http://localhost:9999/events/unknown");
46
+ const json = await res.json();
47
+
48
+ expect(res.status).toBe(404);
49
+ expect(json.success).toBe(false);
50
+ });
51
+
52
+ test("DELETE /events clears all events", async () => {
53
+ // On ajoute un event dans le storage
54
+ await storage.save({
55
+ id: "1",
56
+ type: "http",
57
+ timestamp: Date.now(),
58
+ sourceIp: "127.0.0.1",
59
+ request: {
60
+ method: "GET",
61
+ path: "/",
62
+ headers: {},
63
+ query: {},
64
+ body: "",
65
+ },
66
+ });
67
+
68
+ let events = await storage.listEvents({});
69
+ expect(events.length).toBe(1);
70
+
71
+ // On appelle l'API réelle
72
+ const res = await fetch("http://localhost:9999/events", {
73
+ method: "DELETE",
74
+ });
75
+
76
+ const json = await res.json();
77
+ expect(json.success).toBe(true);
78
+
79
+ events = await storage.listEvents({});
80
+ expect(events.length).toBe(0);
81
+ });
82
+ });
@@ -1,155 +1,155 @@
1
- import { handleApiRequest } from "../api.routes";
2
- import { StorageManager } from "../../../core/storage";
3
- import { ApiSse } from "../api.sse";
4
- import { ApiController } from "../api.controllers";
5
- import { Logger } from "../../../core/logger";
6
- import { IncomingMessage, ServerResponse } from "http";
7
- import { Socket } from "net";
8
- import { describe, it, expect, beforeEach, jest } from "@jest/globals";
9
-
10
- // Helpers pour mocker req/res
11
- function mockReq(method: string, url: string): IncomingMessage {
12
- const socket = new Socket();
13
- const req = new IncomingMessage(socket);
14
- req.method = method;
15
- req.url = url;
16
- req.headers = { host: "localhost" };
17
- return req;
18
- }
19
-
20
- function mockRes(): ServerResponse {
21
- const socket = new Socket();
22
- const req = new IncomingMessage(socket);
23
- const res = new ServerResponse(req);
24
-
25
- jest.spyOn(res, "writeHead");
26
- jest.spyOn(res, "end");
27
-
28
- return res;
29
- }
30
-
31
- describe("handleApiRequest", () => {
32
- let storage: StorageManager;
33
- let sse: ApiSse;
34
- let logger: Logger;
35
-
36
- beforeEach(() => {
37
- storage = new StorageManager();
38
- sse = new ApiSse(new Logger({ context: "SSETest" }));
39
- logger = new Logger({ context: "ApiTest" });
40
-
41
- jest.spyOn(ApiController, "listEvents").mockResolvedValue(undefined);
42
- jest.spyOn(ApiController, "getEvent").mockResolvedValue(undefined);
43
- jest.spyOn(ApiController, "deleteAll").mockResolvedValue(undefined);
44
- jest.spyOn(ApiController, "deleteOne").mockResolvedValue(undefined);
45
- jest.spyOn(ApiController, "stats").mockResolvedValue(undefined);
46
-
47
- jest.spyOn(sse, "handle").mockReturnValue(undefined as any);
48
- });
49
-
50
- it("route GET /events → ApiController.listEvents", async () => {
51
- const req = mockReq("GET", "/events");
52
- const res = mockRes();
53
-
54
- await expect(
55
- handleApiRequest(req, res, storage, sse, logger),
56
- ).resolves.toBeUndefined();
57
-
58
- expect(ApiController.listEvents).toHaveBeenCalled();
59
- });
60
-
61
- it("route GET /events/:id → ApiController.getEvent", async () => {
62
- const req = mockReq("GET", "/events/123");
63
- const res = mockRes();
64
-
65
- await expect(
66
- handleApiRequest(req, res, storage, sse, logger),
67
- ).resolves.toBeUndefined();
68
-
69
- expect(ApiController.getEvent).toHaveBeenCalledWith("123", res, storage);
70
- });
71
-
72
- it("route DELETE /events → ApiController.deleteAll", async () => {
73
- const req = mockReq("DELETE", "/events");
74
- const res = mockRes();
75
-
76
- await expect(
77
- handleApiRequest(req, res, storage, sse, logger),
78
- ).resolves.toBeUndefined();
79
-
80
- expect(ApiController.deleteAll).toHaveBeenCalled();
81
- });
82
-
83
- it("route DELETE /events/:id → ApiController.deleteOne", async () => {
84
- const req = mockReq("DELETE", "/events/abc");
85
- const res = mockRes();
86
-
87
- await expect(
88
- handleApiRequest(req, res, storage, sse, logger),
89
- ).resolves.toBeUndefined();
90
-
91
- expect(ApiController.deleteOne).toHaveBeenCalledWith("abc", res, storage);
92
- });
93
-
94
- it("route GET /stats → ApiController.stats", async () => {
95
- const req = mockReq("GET", "/stats");
96
- const res = mockRes();
97
-
98
- await expect(
99
- handleApiRequest(req, res, storage, sse, logger),
100
- ).resolves.toBeUndefined();
101
-
102
- expect(ApiController.stats).toHaveBeenCalled();
103
- });
104
-
105
- it("route GET /events/stream → SSE", async () => {
106
- const req = mockReq("GET", "/events/stream");
107
- const res = mockRes();
108
-
109
- await expect(
110
- handleApiRequest(req, res, storage, sse, logger),
111
- ).resolves.toBeUndefined();
112
-
113
- expect(sse.handle).toHaveBeenCalledWith(res);
114
- });
115
-
116
- it("404 → renvoie Not found", async () => {
117
- const req = mockReq("GET", "/unknown");
118
- const res = mockRes();
119
-
120
- await expect(
121
- handleApiRequest(req, res, storage, sse, logger),
122
- ).resolves.toBeUndefined();
123
-
124
- expect(res.writeHead).toHaveBeenCalledWith(404, {
125
- "Content-Type": "application/json",
126
- });
127
-
128
- const raw = String((res.end as jest.Mock).mock.calls[0][0]);
129
- const body = JSON.parse(raw);
130
- expect(body.success).toBe(false);
131
- });
132
-
133
- it("500 → renvoie Internal Server Error", async () => {
134
- const req = mockReq("GET", "/events");
135
-
136
- jest
137
- .spyOn(ApiController, "listEvents")
138
- .mockRejectedValue(new Error("Boom"));
139
-
140
- const res = mockRes();
141
-
142
- await expect(
143
- handleApiRequest(req, res, storage, sse, logger),
144
- ).resolves.toBeUndefined();
145
-
146
- expect(res.writeHead).toHaveBeenCalledWith(500, {
147
- "Content-Type": "application/json",
148
- });
149
-
150
- const raw = String((res.end as jest.Mock).mock.calls[0][0]);
151
- const body = JSON.parse(raw);
152
- expect(body.success).toBe(false);
153
- expect(body.error).toBe("Boom");
154
- });
155
- });
1
+ import { handleApiRequest } from "../api.routes";
2
+ import { StorageManager } from "../../../core/storage";
3
+ import { ApiSse } from "../api.sse";
4
+ import { ApiController } from "../api.controllers";
5
+ import { Logger } from "../../../core/logger";
6
+ import { IncomingMessage, ServerResponse } from "http";
7
+ import { Socket } from "net";
8
+ import { describe, it, expect, beforeEach, jest } from "@jest/globals";
9
+
10
+ // Helpers pour mocker req/res
11
+ function mockReq(method: string, url: string): IncomingMessage {
12
+ const socket = new Socket();
13
+ const req = new IncomingMessage(socket);
14
+ req.method = method;
15
+ req.url = url;
16
+ req.headers = { host: "localhost" };
17
+ return req;
18
+ }
19
+
20
+ function mockRes(): ServerResponse {
21
+ const socket = new Socket();
22
+ const req = new IncomingMessage(socket);
23
+ const res = new ServerResponse(req);
24
+
25
+ jest.spyOn(res, "writeHead");
26
+ jest.spyOn(res, "end");
27
+
28
+ return res;
29
+ }
30
+
31
+ describe("handleApiRequest", () => {
32
+ let storage: StorageManager;
33
+ let sse: ApiSse;
34
+ let logger: Logger;
35
+
36
+ beforeEach(() => {
37
+ storage = new StorageManager();
38
+ sse = new ApiSse(new Logger({ context: "SSETest" }));
39
+ logger = new Logger({ context: "ApiTest" });
40
+
41
+ jest.spyOn(ApiController, "listEvents").mockResolvedValue(undefined);
42
+ jest.spyOn(ApiController, "getEvent").mockResolvedValue(undefined);
43
+ jest.spyOn(ApiController, "deleteAll").mockResolvedValue(undefined);
44
+ jest.spyOn(ApiController, "deleteOne").mockResolvedValue(undefined);
45
+ jest.spyOn(ApiController, "stats").mockResolvedValue(undefined);
46
+
47
+ jest.spyOn(sse, "handle").mockReturnValue(undefined as any);
48
+ });
49
+
50
+ it("route GET /events → ApiController.listEvents", async () => {
51
+ const req = mockReq("GET", "/events");
52
+ const res = mockRes();
53
+
54
+ await expect(
55
+ handleApiRequest(req, res, storage, sse, logger),
56
+ ).resolves.toBeUndefined();
57
+
58
+ expect(ApiController.listEvents).toHaveBeenCalled();
59
+ });
60
+
61
+ it("route GET /events/:id → ApiController.getEvent", async () => {
62
+ const req = mockReq("GET", "/events/123");
63
+ const res = mockRes();
64
+
65
+ await expect(
66
+ handleApiRequest(req, res, storage, sse, logger),
67
+ ).resolves.toBeUndefined();
68
+
69
+ expect(ApiController.getEvent).toHaveBeenCalledWith("123", res, storage);
70
+ });
71
+
72
+ it("route DELETE /events → ApiController.deleteAll", async () => {
73
+ const req = mockReq("DELETE", "/events");
74
+ const res = mockRes();
75
+
76
+ await expect(
77
+ handleApiRequest(req, res, storage, sse, logger),
78
+ ).resolves.toBeUndefined();
79
+
80
+ expect(ApiController.deleteAll).toHaveBeenCalled();
81
+ });
82
+
83
+ it("route DELETE /events/:id → ApiController.deleteOne", async () => {
84
+ const req = mockReq("DELETE", "/events/abc");
85
+ const res = mockRes();
86
+
87
+ await expect(
88
+ handleApiRequest(req, res, storage, sse, logger),
89
+ ).resolves.toBeUndefined();
90
+
91
+ expect(ApiController.deleteOne).toHaveBeenCalledWith("abc", res, storage);
92
+ });
93
+
94
+ it("route GET /stats → ApiController.stats", async () => {
95
+ const req = mockReq("GET", "/stats");
96
+ const res = mockRes();
97
+
98
+ await expect(
99
+ handleApiRequest(req, res, storage, sse, logger),
100
+ ).resolves.toBeUndefined();
101
+
102
+ expect(ApiController.stats).toHaveBeenCalled();
103
+ });
104
+
105
+ it("route GET /events/stream → SSE", async () => {
106
+ const req = mockReq("GET", "/events/stream");
107
+ const res = mockRes();
108
+
109
+ await expect(
110
+ handleApiRequest(req, res, storage, sse, logger),
111
+ ).resolves.toBeUndefined();
112
+
113
+ expect(sse.handle).toHaveBeenCalledWith(res);
114
+ });
115
+
116
+ it("404 → renvoie Not found", async () => {
117
+ const req = mockReq("GET", "/unknown");
118
+ const res = mockRes();
119
+
120
+ await expect(
121
+ handleApiRequest(req, res, storage, sse, logger),
122
+ ).resolves.toBeUndefined();
123
+
124
+ expect(res.writeHead).toHaveBeenCalledWith(404, {
125
+ "Content-Type": "application/json",
126
+ });
127
+
128
+ const raw = String((res.end as jest.Mock).mock.calls[0][0]);
129
+ const body = JSON.parse(raw);
130
+ expect(body.success).toBe(false);
131
+ });
132
+
133
+ it("500 → renvoie Internal Server Error", async () => {
134
+ const req = mockReq("GET", "/events");
135
+
136
+ jest
137
+ .spyOn(ApiController, "listEvents")
138
+ .mockRejectedValue(new Error("Boom"));
139
+
140
+ const res = mockRes();
141
+
142
+ await expect(
143
+ handleApiRequest(req, res, storage, sse, logger),
144
+ ).resolves.toBeUndefined();
145
+
146
+ expect(res.writeHead).toHaveBeenCalledWith(500, {
147
+ "Content-Type": "application/json",
148
+ });
149
+
150
+ const raw = String((res.end as jest.Mock).mock.calls[0][0]);
151
+ const body = JSON.parse(raw);
152
+ expect(body.success).toBe(false);
153
+ expect(body.error).toBe("Boom");
154
+ });
155
+ });