@j3r3mcdev/oast-server 1.1.6 → 1.1.8

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 (97) 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/core/router.d.ts +1 -0
  5. package/dist/core/router.js +3 -3
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +16 -1
  8. package/jest.config.js +14 -14
  9. package/package.json +45 -45
  10. package/sadmin list shadows +9 -9
  11. package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
  12. package/src/api/controllers/events.controller.ts +10 -10
  13. package/src/api/controllers/health.controller.ts +7 -7
  14. package/src/api/controllers/tasks.controller.ts +41 -41
  15. package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
  16. package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
  17. package/src/api/dto/create-task.dto.ts +33 -33
  18. package/src/api/dto/filter-tasks.dto.ts +33 -33
  19. package/src/api/services/__tests__/events.service.test.ts +41 -41
  20. package/src/api/services/__tests__/tasks.service.test.ts +41 -41
  21. package/src/api/services/events.service.ts +17 -17
  22. package/src/api/services/tasks.service.ts +79 -79
  23. package/src/api/sse/events.stream.ts +90 -90
  24. package/src/bootstrap.ts +89 -89
  25. package/src/core/__tests__/core-router.test.ts +30 -30
  26. package/src/core/__tests__/core-server.test.ts +44 -44
  27. package/src/core/__tests__/event.normalizer.test.ts +56 -56
  28. package/src/core/__tests__/event.router.test.ts +89 -89
  29. package/src/core/__tests__/logger.test.ts +32 -32
  30. package/src/core/__tests__/storage-manager.test.ts +74 -74
  31. package/src/core/event.normalizer.ts +147 -147
  32. package/src/core/event.router.ts +13 -13
  33. package/src/core/http/__tests__/adapter-node.test.ts +52 -52
  34. package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
  35. package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
  36. package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
  37. package/src/core/http/__tests__/compile-path.test.ts +39 -39
  38. package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
  39. package/src/core/http/__tests__/request.test.ts +34 -34
  40. package/src/core/http/__tests__/response.test.ts +35 -35
  41. package/src/core/http/__tests__/router-match.test.ts +171 -171
  42. package/src/core/http/adapter-node.ts +51 -51
  43. package/src/core/http/buildRequest.ts +18 -18
  44. package/src/core/http/compile-path.ts +32 -32
  45. package/src/core/http/errors.ts +37 -37
  46. package/src/core/http/http-server.ts +52 -52
  47. package/src/core/http/middleware.ts +160 -160
  48. package/src/core/http/request.ts +55 -55
  49. package/src/core/http/response.ts +93 -93
  50. package/src/core/http/router.ts +138 -138
  51. package/src/core/id-generator.ts +8 -8
  52. package/src/core/logger.ts +113 -113
  53. package/src/core/router.ts +44 -44
  54. package/src/core/server.ts +85 -85
  55. package/src/core/storage.ts +64 -64
  56. package/src/index.ts +14 -14
  57. package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
  58. package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
  59. package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
  60. package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
  61. package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
  62. package/src/listeners/api/api.controllers.ts +67 -67
  63. package/src/listeners/api/api.extractor.ts +43 -43
  64. package/src/listeners/api/api.listener.ts +50 -50
  65. package/src/listeners/api/api.routes.ts +76 -76
  66. package/src/listeners/api/api.sse.ts +38 -38
  67. package/src/listeners/dns/__tests__/dns.test.ts +118 -118
  68. package/src/listeners/dns/dns.extractor.ts +14 -14
  69. package/src/listeners/dns/dns.listener.ts +61 -61
  70. package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
  71. package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
  72. package/src/listeners/http/http.extractor.ts +15 -15
  73. package/src/listeners/http/http.listener.ts +110 -110
  74. package/src/listeners/listener.interface.ts +4 -4
  75. package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
  76. package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
  77. package/src/listeners/smtp/smtp.extractor.ts +18 -18
  78. package/src/listeners/smtp/smtp.listener.ts +60 -60
  79. package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
  80. package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
  81. package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
  82. package/src/listeners/ssrf/ssrf.listener.ts +37 -37
  83. package/src/listeners/tcp/tcp.extractor.ts +16 -16
  84. package/src/listeners/tcp/tcp.listener.ts +61 -61
  85. package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
  86. package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
  87. package/src/listeners/webhook/webhook.extractor.ts +12 -12
  88. package/src/listeners/webhook/webhook.listener.ts +58 -58
  89. package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
  90. package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
  91. package/src/listeners/websocket/websocket.extractor.ts +11 -11
  92. package/src/listeners/websocket/websocket.listener.ts +40 -40
  93. package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
  94. package/src/storage-adapters/adapters/memory.storage.ts +64 -64
  95. package/src/storage-adapters/storage.interface.ts +26 -26
  96. package/src/types/event.types.ts +147 -147
  97. package/tsconfig.json +20 -21
package/src/bootstrap.ts CHANGED
@@ -1,89 +1,89 @@
1
- import { Logger } from "./core/logger";
2
- import { StorageManager } from "./core/storage";
3
- import { CoreRouter } from "./core/router";
4
- import { CoreServer } from "./core/server";
5
-
6
- // Listeners
7
- import { ApiListener } from "./listeners/api/api.listener";
8
- import { HttpListener } from "./listeners/http/http.listener";
9
- import { DnsListener } from "./listeners/dns/dns.listener";
10
- import { SmtpListener } from "./listeners/smtp/smtp.listener";
11
- import { TcpListener } from "./listeners/tcp/tcp.listener";
12
-
13
- async function main() {
14
- const logger = new Logger({ context: "Bootstrap" });
15
-
16
- // Core components
17
- const storage = new StorageManager({
18
- logger: logger.withContext("Storage"),
19
- });
20
-
21
- const router = new CoreRouter(storage, {
22
- logger: logger.withContext("CoreRouter"),
23
- });
24
-
25
- // ---------------------------
26
- // LISTENERS
27
- // ---------------------------
28
-
29
- // API → (router, storage, options)
30
- const apiListener = new ApiListener(storage, {
31
- port: 3100,
32
- logger: logger.withContext("ApiListener"),
33
- });
34
-
35
- // HTTP → (router, options)
36
- const httpListener = new HttpListener(router, {
37
- port: 8080,
38
- logger: logger.withContext("HttpListener"),
39
- });
40
-
41
- // DNS → (router, options)
42
- const dnsListener = new DnsListener(router, {
43
- server: {} as any,
44
- logger: logger.withContext("DnsListener"),
45
- });
46
-
47
- const smtpListener = new SmtpListener(router, {
48
- server: {} as any,
49
- logger: logger.withContext("SmtpListener"),
50
- });
51
-
52
- // TCP → (router, storage, options)
53
- const tcpListener = new TcpListener(router, storage, {
54
- port: 9000,
55
- logger: logger.withContext("TcpListener"),
56
- });
57
-
58
- // ---------------------------
59
- // CORE SERVER
60
- // ---------------------------
61
- const server = new CoreServer({
62
- logger: logger.withContext("CoreServer"),
63
- storage,
64
- listeners: [
65
- apiListener,
66
- httpListener,
67
- dnsListener,
68
- smtpListener,
69
- tcpListener,
70
- ],
71
- });
72
-
73
- await server.start();
74
-
75
- // Graceful shutdown
76
- const shutdown = async () => {
77
- logger.warn("Received shutdown signal, stopping server...");
78
- await server.stop();
79
- process.exit(0);
80
- };
81
-
82
- process.on("SIGINT", shutdown);
83
- process.on("SIGTERM", shutdown);
84
- }
85
-
86
- main().catch((err) => {
87
- console.error("Fatal error in bootstrap:", err);
88
- process.exit(1);
89
- });
1
+ import { Logger } from "./core/logger";
2
+ import { StorageManager } from "./core/storage";
3
+ import { CoreRouter } from "./core/router";
4
+ import { CoreServer } from "./core/server";
5
+
6
+ // Listeners
7
+ import { ApiListener } from "./listeners/api/api.listener";
8
+ import { HttpListener } from "./listeners/http/http.listener";
9
+ import { DnsListener } from "./listeners/dns/dns.listener";
10
+ import { SmtpListener } from "./listeners/smtp/smtp.listener";
11
+ import { TcpListener } from "./listeners/tcp/tcp.listener";
12
+
13
+ async function main() {
14
+ const logger = new Logger({ context: "Bootstrap" });
15
+
16
+ // Core components
17
+ const storage = new StorageManager({
18
+ logger: logger.withContext("Storage"),
19
+ });
20
+
21
+ const router = new CoreRouter(storage, {
22
+ logger: logger.withContext("CoreRouter"),
23
+ });
24
+
25
+ // ---------------------------
26
+ // LISTENERS
27
+ // ---------------------------
28
+
29
+ // API → (router, storage, options)
30
+ const apiListener = new ApiListener(storage, {
31
+ port: 3100,
32
+ logger: logger.withContext("ApiListener"),
33
+ });
34
+
35
+ // HTTP → (router, options)
36
+ const httpListener = new HttpListener(router, {
37
+ port: 8080,
38
+ logger: logger.withContext("HttpListener"),
39
+ });
40
+
41
+ // DNS → (router, options)
42
+ const dnsListener = new DnsListener(router, {
43
+ server: {} as any,
44
+ logger: logger.withContext("DnsListener"),
45
+ });
46
+
47
+ const smtpListener = new SmtpListener(router, {
48
+ server: {} as any,
49
+ logger: logger.withContext("SmtpListener"),
50
+ });
51
+
52
+ // TCP → (router, storage, options)
53
+ const tcpListener = new TcpListener(router, storage, {
54
+ port: 9000,
55
+ logger: logger.withContext("TcpListener"),
56
+ });
57
+
58
+ // ---------------------------
59
+ // CORE SERVER
60
+ // ---------------------------
61
+ const server = new CoreServer({
62
+ logger: logger.withContext("CoreServer"),
63
+ storage,
64
+ listeners: [
65
+ apiListener,
66
+ httpListener,
67
+ dnsListener,
68
+ smtpListener,
69
+ tcpListener,
70
+ ],
71
+ });
72
+
73
+ await server.start();
74
+
75
+ // Graceful shutdown
76
+ const shutdown = async () => {
77
+ logger.warn("Received shutdown signal, stopping server...");
78
+ await server.stop();
79
+ process.exit(0);
80
+ };
81
+
82
+ process.on("SIGINT", shutdown);
83
+ process.on("SIGTERM", shutdown);
84
+ }
85
+
86
+ main().catch((err) => {
87
+ console.error("Fatal error in bootstrap:", err);
88
+ process.exit(1);
89
+ });
@@ -1,30 +1,30 @@
1
- import { CoreRouter, RouterHook } from "../router";
2
- import { MemoryStorage } from "../../storage-adapters/adapters/memory.storage";
3
- import { AnyNormalizedEvent } from "../../types/event.types";
4
- import { describe, it, expect, jest } from "@jest/globals";
5
-
6
- describe("CoreRouter", () => {
7
- const event: AnyNormalizedEvent = {
8
- id: "abc",
9
- type: "http",
10
- timestamp: 111,
11
- sourceIp: "1.1.1.1",
12
- request: {
13
- method: "GET",
14
- path: "/",
15
- headers: {},
16
- query: {},
17
- body: {},
18
- },
19
- };
20
-
21
- it("appelle les hooks", async () => {
22
- const hook: RouterHook = jest.fn((e: AnyNormalizedEvent) => {}); // ← FIX
23
-
24
- const router = new CoreRouter(new MemoryStorage(), { hooks: [hook] });
25
-
26
- await router.dispatch(event);
27
-
28
- expect(hook).toHaveBeenCalledWith(event);
29
- });
30
- });
1
+ import { CoreRouter, RouterHook } from "../router";
2
+ import { MemoryStorage } from "../../storage-adapters/adapters/memory.storage";
3
+ import { AnyNormalizedEvent } from "../../types/event.types";
4
+ import { describe, it, expect, jest } from "@jest/globals";
5
+
6
+ describe("CoreRouter", () => {
7
+ const event: AnyNormalizedEvent = {
8
+ id: "abc",
9
+ type: "http",
10
+ timestamp: 111,
11
+ sourceIp: "1.1.1.1",
12
+ request: {
13
+ method: "GET",
14
+ path: "/",
15
+ headers: {},
16
+ query: {},
17
+ body: {},
18
+ },
19
+ };
20
+
21
+ it("appelle les hooks", async () => {
22
+ const hook: RouterHook = jest.fn((e: AnyNormalizedEvent) => {}); // ← FIX
23
+
24
+ const router = new CoreRouter(new MemoryStorage(), { hooks: [hook] });
25
+
26
+ await router.dispatch(event);
27
+
28
+ expect(hook).toHaveBeenCalledWith(event);
29
+ });
30
+ });
@@ -1,44 +1,44 @@
1
- import { CoreServer } from "../../core/server";
2
- import { StorageManager } from "../../core/storage";
3
- import { Logger } from "../../core/logger";
4
- import { describe, it, expect } from "@jest/globals";
5
-
6
- class FakeListener {
7
- started = false;
8
- stopped = false;
9
-
10
- async start() {
11
- this.started = true;
12
- }
13
-
14
- async stop() {
15
- this.stopped = true;
16
- }
17
- }
18
-
19
- describe("CoreServer", () => {
20
- it("démarre et arrête les listeners", async () => {
21
- const listener = new FakeListener();
22
-
23
- const server = new CoreServer({
24
- storage: new StorageManager(),
25
- logger: new Logger({ enabled: false }),
26
- listeners: [listener],
27
- });
28
-
29
- await server.start();
30
- expect(listener.started).toBe(true);
31
-
32
- await server.stop();
33
- expect(listener.stopped).toBe(true);
34
- });
35
-
36
- it("expose le router", () => {
37
- const server = new CoreServer({
38
- storage: new StorageManager(),
39
- });
40
-
41
- const router = server.getRouter();
42
- expect(router).toBeDefined();
43
- });
44
- });
1
+ import { CoreServer } from "../../core/server";
2
+ import { StorageManager } from "../../core/storage";
3
+ import { Logger } from "../../core/logger";
4
+ import { describe, it, expect } from "@jest/globals";
5
+
6
+ class FakeListener {
7
+ started = false;
8
+ stopped = false;
9
+
10
+ async start() {
11
+ this.started = true;
12
+ }
13
+
14
+ async stop() {
15
+ this.stopped = true;
16
+ }
17
+ }
18
+
19
+ describe("CoreServer", () => {
20
+ it("démarre et arrête les listeners", async () => {
21
+ const listener = new FakeListener();
22
+
23
+ const server = new CoreServer({
24
+ storage: new StorageManager(),
25
+ logger: new Logger({ enabled: false }),
26
+ listeners: [listener],
27
+ });
28
+
29
+ await server.start();
30
+ expect(listener.started).toBe(true);
31
+
32
+ await server.stop();
33
+ expect(listener.stopped).toBe(true);
34
+ });
35
+
36
+ it("expose le router", () => {
37
+ const server = new CoreServer({
38
+ storage: new StorageManager(),
39
+ });
40
+
41
+ const router = server.getRouter();
42
+ expect(router).toBeDefined();
43
+ });
44
+ });
@@ -1,56 +1,56 @@
1
- import { EventNormalizer } from "../event.normalizer";
2
- import { describe, it, expect, jest } from "@jest/globals";
3
- import { IdGenerator } from "../id-generator";
4
-
5
- describe("EventNormalizer.normalizeHttp", () => {
6
- it("normalise correctement une requête HTTP", () => {
7
- // Mock du timestamp
8
- const fakeNow = 1234567890;
9
- jest.spyOn(Date, "now").mockReturnValue(fakeNow);
10
-
11
- // Mock de l'ID
12
- jest.spyOn(IdGenerator, "generate").mockReturnValue("uuid-test");
13
-
14
- const req = {
15
- ip: "9.9.9.9",
16
- method: "POST",
17
- path: "/test",
18
- headers: { "x-forwarded-for": "9.9.9.9" },
19
- query: { a: "1" },
20
- body: { hello: "world" },
21
- raw: {}, // obligatoire
22
- };
23
-
24
- const event = EventNormalizer.normalizeHttp(req);
25
-
26
- expect(event).toEqual({
27
- id: "uuid-test",
28
- type: "http",
29
- timestamp: fakeNow,
30
- sourceIp: "9.9.9.9",
31
- request: {
32
- method: "POST",
33
- path: "/test",
34
- headers: { "x-forwarded-for": "9.9.9.9" },
35
- query: { a: "1" },
36
- body: { hello: "world" },
37
- },
38
- });
39
- });
40
-
41
- it("retourne 'unknown' si aucune IP n'est trouvée", () => {
42
- const req = {
43
- ip: "",
44
- method: "GET",
45
- path: "/",
46
- headers: {},
47
- query: {},
48
- body: {},
49
- raw: {},
50
- };
51
-
52
- const event = EventNormalizer.normalizeHttp(req);
53
-
54
- expect(event.sourceIp).toBe("unknown");
55
- });
56
- });
1
+ import { EventNormalizer } from "../event.normalizer";
2
+ import { describe, it, expect, jest } from "@jest/globals";
3
+ import { IdGenerator } from "../id-generator";
4
+
5
+ describe("EventNormalizer.normalizeHttp", () => {
6
+ it("normalise correctement une requête HTTP", () => {
7
+ // Mock du timestamp
8
+ const fakeNow = 1234567890;
9
+ jest.spyOn(Date, "now").mockReturnValue(fakeNow);
10
+
11
+ // Mock de l'ID
12
+ jest.spyOn(IdGenerator, "generate").mockReturnValue("uuid-test");
13
+
14
+ const req = {
15
+ ip: "9.9.9.9",
16
+ method: "POST",
17
+ path: "/test",
18
+ headers: { "x-forwarded-for": "9.9.9.9" },
19
+ query: { a: "1" },
20
+ body: { hello: "world" },
21
+ raw: {}, // obligatoire
22
+ };
23
+
24
+ const event = EventNormalizer.normalizeHttp(req);
25
+
26
+ expect(event).toEqual({
27
+ id: "uuid-test",
28
+ type: "http",
29
+ timestamp: fakeNow,
30
+ sourceIp: "9.9.9.9",
31
+ request: {
32
+ method: "POST",
33
+ path: "/test",
34
+ headers: { "x-forwarded-for": "9.9.9.9" },
35
+ query: { a: "1" },
36
+ body: { hello: "world" },
37
+ },
38
+ });
39
+ });
40
+
41
+ it("retourne 'unknown' si aucune IP n'est trouvée", () => {
42
+ const req = {
43
+ ip: "",
44
+ method: "GET",
45
+ path: "/",
46
+ headers: {},
47
+ query: {},
48
+ body: {},
49
+ raw: {},
50
+ };
51
+
52
+ const event = EventNormalizer.normalizeHttp(req);
53
+
54
+ expect(event.sourceIp).toBe("unknown");
55
+ });
56
+ });
@@ -1,89 +1,89 @@
1
- import {
2
- AnyNormalizedEvent,
3
- NormalizedHttpEvent,
4
- } from "../../types/event.types";
5
- import {
6
- StorageListParams,
7
- Storage,
8
- } from "../../storage-adapters/storage.interface";
9
- import { EventRouter } from "../event.router";
10
- import { EventNormalizer } from "../event.normalizer";
11
- import { describe, it, expect, jest } from "@jest/globals";
12
-
13
- class MockStorage implements Storage {
14
- save = jest.fn<(event: AnyNormalizedEvent) => Promise<void>>();
15
- getEvent = jest.fn<(id: string) => Promise<AnyNormalizedEvent | null>>();
16
- listEvents =
17
- jest.fn<(params: StorageListParams) => Promise<AnyNormalizedEvent[]>>();
18
- deleteEvent = jest.fn<(id: string) => Promise<boolean>>();
19
- clearEvents = jest.fn<() => Promise<void>>();
20
- getStats =
21
- jest.fn<() => Promise<{ total: number; byType: Record<string, number> }>>();
22
- getAll = jest.fn<() => Promise<AnyNormalizedEvent[]>>();
23
- }
24
-
25
- describe("EventRouter", () => {
26
- it("enregistre un événement HTTP normalisé", async () => {
27
- const mockStorage = new MockStorage();
28
-
29
- const fakeEvent: NormalizedHttpEvent = {
30
- id: "123",
31
- type: "http",
32
- timestamp: 111,
33
- sourceIp: "1.1.1.1",
34
- request: {
35
- method: "GET",
36
- path: "/",
37
- headers: {},
38
- query: {},
39
- body: {},
40
- },
41
- };
42
-
43
- jest.spyOn(EventNormalizer, "normalizeHttp").mockReturnValue(fakeEvent);
44
-
45
- const router = new EventRouter(mockStorage);
46
-
47
- const req = {
48
- ip: "1.1.1.1",
49
- method: "GET",
50
- path: "/",
51
- headers: {},
52
- query: {},
53
- body: {},
54
- raw: {},
55
- };
56
-
57
- const result = await router.handleHttp(req);
58
-
59
- expect(EventNormalizer.normalizeHttp).toHaveBeenCalledWith(req);
60
- expect(mockStorage.save).toHaveBeenCalledWith(fakeEvent);
61
- expect(result).toEqual(fakeEvent);
62
- });
63
-
64
- it("propagate une erreur si normalizeHttp throw", async () => {
65
- const mockStorage = new MockStorage();
66
-
67
- jest.spyOn(EventNormalizer, "normalizeHttp").mockImplementation(() => {
68
- throw new Error("Boom");
69
- });
70
-
71
- const router = new EventRouter(mockStorage);
72
-
73
- await expect(router.handleHttp({} as any)).rejects.toThrow("Boom");
74
- expect(mockStorage.save).not.toHaveBeenCalled();
75
- });
76
-
77
- it("propagate une erreur si storage.save throw", async () => {
78
- const mockStorage = new MockStorage();
79
-
80
- const fakeEvent = { id: "x", type: "http" } as any;
81
-
82
- jest.spyOn(EventNormalizer, "normalizeHttp").mockReturnValue(fakeEvent);
83
- mockStorage.save.mockRejectedValue(new Error("DB error"));
84
-
85
- const router = new EventRouter(mockStorage);
86
-
87
- await expect(router.handleHttp({} as any)).rejects.toThrow("DB error");
88
- });
89
- });
1
+ import {
2
+ AnyNormalizedEvent,
3
+ NormalizedHttpEvent,
4
+ } from "../../types/event.types";
5
+ import {
6
+ StorageListParams,
7
+ Storage,
8
+ } from "../../storage-adapters/storage.interface";
9
+ import { EventRouter } from "../event.router";
10
+ import { EventNormalizer } from "../event.normalizer";
11
+ import { describe, it, expect, jest } from "@jest/globals";
12
+
13
+ class MockStorage implements Storage {
14
+ save = jest.fn<(event: AnyNormalizedEvent) => Promise<void>>();
15
+ getEvent = jest.fn<(id: string) => Promise<AnyNormalizedEvent | null>>();
16
+ listEvents =
17
+ jest.fn<(params: StorageListParams) => Promise<AnyNormalizedEvent[]>>();
18
+ deleteEvent = jest.fn<(id: string) => Promise<boolean>>();
19
+ clearEvents = jest.fn<() => Promise<void>>();
20
+ getStats =
21
+ jest.fn<() => Promise<{ total: number; byType: Record<string, number> }>>();
22
+ getAll = jest.fn<() => Promise<AnyNormalizedEvent[]>>();
23
+ }
24
+
25
+ describe("EventRouter", () => {
26
+ it("enregistre un événement HTTP normalisé", async () => {
27
+ const mockStorage = new MockStorage();
28
+
29
+ const fakeEvent: NormalizedHttpEvent = {
30
+ id: "123",
31
+ type: "http",
32
+ timestamp: 111,
33
+ sourceIp: "1.1.1.1",
34
+ request: {
35
+ method: "GET",
36
+ path: "/",
37
+ headers: {},
38
+ query: {},
39
+ body: {},
40
+ },
41
+ };
42
+
43
+ jest.spyOn(EventNormalizer, "normalizeHttp").mockReturnValue(fakeEvent);
44
+
45
+ const router = new EventRouter(mockStorage);
46
+
47
+ const req = {
48
+ ip: "1.1.1.1",
49
+ method: "GET",
50
+ path: "/",
51
+ headers: {},
52
+ query: {},
53
+ body: {},
54
+ raw: {},
55
+ };
56
+
57
+ const result = await router.handleHttp(req);
58
+
59
+ expect(EventNormalizer.normalizeHttp).toHaveBeenCalledWith(req);
60
+ expect(mockStorage.save).toHaveBeenCalledWith(fakeEvent);
61
+ expect(result).toEqual(fakeEvent);
62
+ });
63
+
64
+ it("propagate une erreur si normalizeHttp throw", async () => {
65
+ const mockStorage = new MockStorage();
66
+
67
+ jest.spyOn(EventNormalizer, "normalizeHttp").mockImplementation(() => {
68
+ throw new Error("Boom");
69
+ });
70
+
71
+ const router = new EventRouter(mockStorage);
72
+
73
+ await expect(router.handleHttp({} as any)).rejects.toThrow("Boom");
74
+ expect(mockStorage.save).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it("propagate une erreur si storage.save throw", async () => {
78
+ const mockStorage = new MockStorage();
79
+
80
+ const fakeEvent = { id: "x", type: "http" } as any;
81
+
82
+ jest.spyOn(EventNormalizer, "normalizeHttp").mockReturnValue(fakeEvent);
83
+ mockStorage.save.mockRejectedValue(new Error("DB error"));
84
+
85
+ const router = new EventRouter(mockStorage);
86
+
87
+ await expect(router.handleHttp({} as any)).rejects.toThrow("DB error");
88
+ });
89
+ });