@j3r3mcdev/oast-server 1.1.12 → 1.1.14

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 (106) hide show
  1. package/dist/listeners/api/api.routes.js +31 -5
  2. package/package.json +4 -1
  3. package/.env.example +0 -0
  4. package/.github/workflows/ci.yml +0 -29
  5. package/.github/workflows/publish.yml +0 -31
  6. package/image.png +0 -0
  7. package/jest.config.js +0 -14
  8. package/sadmin list shadows +0 -9
  9. package/src/api/controllers/__tests__/tasks.controller.test.ts +0 -74
  10. package/src/api/controllers/events.controller.ts +0 -10
  11. package/src/api/controllers/health.controller.ts +0 -7
  12. package/src/api/controllers/index.ts +0 -0
  13. package/src/api/controllers/tasks.controller.ts +0 -41
  14. package/src/api/dto/__tests__/create-task.dto.test.ts +0 -41
  15. package/src/api/dto/__tests__/filter-tasks.dto.test.ts +0 -35
  16. package/src/api/dto/create-task.dto.ts +0 -33
  17. package/src/api/dto/filter-tasks.dto.ts +0 -33
  18. package/src/api/services/__tests__/events.service.test.ts +0 -41
  19. package/src/api/services/__tests__/tasks.service.test.ts +0 -41
  20. package/src/api/services/events.service.ts +0 -17
  21. package/src/api/services/tasks.service.ts +0 -79
  22. package/src/api/sse/events.stream.ts +0 -90
  23. package/src/bootstrap.ts +0 -89
  24. package/src/config/constants.ts +0 -0
  25. package/src/config/env.ts +0 -0
  26. package/src/core/__tests__/core-router.test.ts +0 -30
  27. package/src/core/__tests__/core-server.test.ts +0 -44
  28. package/src/core/__tests__/event.normalizer.test.ts +0 -56
  29. package/src/core/__tests__/event.router.test.ts +0 -89
  30. package/src/core/__tests__/logger.test.ts +0 -32
  31. package/src/core/__tests__/storage-manager.test.ts +0 -74
  32. package/src/core/event.normalizer.ts +0 -167
  33. package/src/core/event.router.ts +0 -13
  34. package/src/core/http/__tests__/adapter-node.test.ts +0 -52
  35. package/src/core/http/__tests__/body-parser-multipart.test.ts +0 -41
  36. package/src/core/http/__tests__/body-parser-raw.test.ts +0 -28
  37. package/src/core/http/__tests__/body-parser-text.test.ts +0 -28
  38. package/src/core/http/__tests__/compile-path.test.ts +0 -39
  39. package/src/core/http/__tests__/middleware-pipeline.test.ts +0 -51
  40. package/src/core/http/__tests__/request.test.ts +0 -34
  41. package/src/core/http/__tests__/response.test.ts +0 -35
  42. package/src/core/http/__tests__/router-match.test.ts +0 -171
  43. package/src/core/http/adapter-node.ts +0 -51
  44. package/src/core/http/buildRequest.ts +0 -18
  45. package/src/core/http/compile-path.ts +0 -32
  46. package/src/core/http/errors.ts +0 -37
  47. package/src/core/http/http-server.ts +0 -52
  48. package/src/core/http/index.ts +0 -0
  49. package/src/core/http/main.ts +0 -0
  50. package/src/core/http/middleware.ts +0 -160
  51. package/src/core/http/request.ts +0 -55
  52. package/src/core/http/response.ts +0 -93
  53. package/src/core/http/router.ts +0 -138
  54. package/src/core/http/utils.ts +0 -0
  55. package/src/core/id-generator.ts +0 -8
  56. package/src/core/logger.ts +0 -113
  57. package/src/core/router.ts +0 -44
  58. package/src/core/server.ts +0 -85
  59. package/src/core/storage.ts +0 -64
  60. package/src/index.ts +0 -14
  61. package/src/listeners/api/__tests__/api.controller.test.ts +0 -116
  62. package/src/listeners/api/__tests__/api.extractor.test.ts +0 -46
  63. package/src/listeners/api/__tests__/api.listener.test.ts +0 -82
  64. package/src/listeners/api/__tests__/api.routes.test.ts +0 -155
  65. package/src/listeners/api/__tests__/api.sse.test.ts +0 -105
  66. package/src/listeners/api/api.controllers.ts +0 -67
  67. package/src/listeners/api/api.extractor.ts +0 -43
  68. package/src/listeners/api/api.listener.ts +0 -50
  69. package/src/listeners/api/api.routes.ts +0 -76
  70. package/src/listeners/api/api.sse.ts +0 -38
  71. package/src/listeners/dns/__tests__/dns.test.ts +0 -118
  72. package/src/listeners/dns/dns.extractor.ts +0 -14
  73. package/src/listeners/dns/dns.listener.ts +0 -61
  74. package/src/listeners/http/__tests__/http.extractor.test.ts +0 -59
  75. package/src/listeners/http/__tests__/http.listener.test.ts +0 -133
  76. package/src/listeners/http/http.extractor.ts +0 -15
  77. package/src/listeners/http/http.listener.ts +0 -110
  78. package/src/listeners/listener.interface.ts +0 -4
  79. package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +0 -69
  80. package/src/listeners/smtp/__tests__/smtp.listener.test.ts +0 -150
  81. package/src/listeners/smtp/smtp.extractor.ts +0 -18
  82. package/src/listeners/smtp/smtp.listener.ts +0 -78
  83. package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +0 -41
  84. package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +0 -87
  85. package/src/listeners/ssrf/ssrf.extractor.ts +0 -14
  86. package/src/listeners/ssrf/ssrf.listener.ts +0 -37
  87. package/src/listeners/tcp/tcp.extractor.ts +0 -16
  88. package/src/listeners/tcp/tcp.listener.ts +0 -61
  89. package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +0 -35
  90. package/src/listeners/webhook/__tests__/webhook.listener.test.ts +0 -122
  91. package/src/listeners/webhook/webhook.extractor.ts +0 -12
  92. package/src/listeners/webhook/webhook.listener.ts +0 -58
  93. package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +0 -33
  94. package/src/listeners/websocket/__tests__/websocket.listener.test.ts +0 -90
  95. package/src/listeners/websocket/websocket.extractor.ts +0 -11
  96. package/src/listeners/websocket/websocket.listener.ts +0 -40
  97. package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +0 -75
  98. package/src/storage-adapters/adapters/memory.storage.ts +0 -64
  99. package/src/storage-adapters/adapters/redis.storage.ts +0 -0
  100. package/src/storage-adapters/adapters/sqlite.storage.ts +0 -0
  101. package/src/storage-adapters/storage.interface.ts +0 -26
  102. package/src/types/event.types.ts +0 -166
  103. package/src/utils/token.ts +0 -0
  104. package/src-api.txt +0 -0
  105. package/src-architecture.txt +0 -0
  106. package/tsconfig.json +0 -20
@@ -2,11 +2,41 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleApiRequest = handleApiRequest;
4
4
  const api_controllers_1 = require("./api.controllers");
5
+ const event_normalizer_1 = require("../../core/event.normalizer");
5
6
  async function handleApiRequest(req, res, storage, sse, logger) {
6
7
  try {
7
8
  const url = new URL(req.url ?? "", `http://${req.headers.host}`);
8
9
  const path = url.pathname;
9
10
  const method = req.method ?? "GET";
11
+ //
12
+ // ---------------------------
13
+ // POST / (API EVENT)
14
+ // ---------------------------
15
+ //
16
+ if (method === "POST" && path === "/") {
17
+ let body = "";
18
+ req.on("data", (chunk) => (body += chunk));
19
+ req.on("end", async () => {
20
+ try {
21
+ const parsed = JSON.parse(body);
22
+ const normalized = event_normalizer_1.EventNormalizer.normalizeApi({
23
+ ip: req.socket.remoteAddress ?? "",
24
+ body: parsed,
25
+ raw: parsed,
26
+ });
27
+ await storage.save(normalized);
28
+ res.writeHead(200, { "Content-Type": "application/json" });
29
+ res.end(JSON.stringify({ success: true }));
30
+ }
31
+ catch (err) {
32
+ logger.error("API POST error", err);
33
+ res.writeHead(400, { "Content-Type": "application/json" });
34
+ res.end(JSON.stringify({ success: false, error: err.message }));
35
+ }
36
+ });
37
+ return;
38
+ }
39
+ //
10
40
  // ---------------------------
11
41
  // SSE STREAM
12
42
  // ---------------------------
@@ -14,32 +44,28 @@ async function handleApiRequest(req, res, storage, sse, logger) {
14
44
  await sse.handle(res);
15
45
  return;
16
46
  }
47
+ //
17
48
  // ---------------------------
18
49
  // ROUTES REST
19
50
  // ---------------------------
20
- // GET /events
21
51
  if (method === "GET" && path === "/events") {
22
52
  await api_controllers_1.ApiController.listEvents(url, res, storage);
23
53
  return;
24
54
  }
25
- // GET /events/:id
26
55
  if (method === "GET" && path.startsWith("/events/")) {
27
56
  const id = path.split("/")[2];
28
57
  await api_controllers_1.ApiController.getEvent(id, res, storage);
29
58
  return;
30
59
  }
31
- // DELETE /events
32
60
  if (method === "DELETE" && path === "/events") {
33
61
  await api_controllers_1.ApiController.deleteAll(res, storage);
34
62
  return;
35
63
  }
36
- // DELETE /events/:id
37
64
  if (method === "DELETE" && path.startsWith("/events/")) {
38
65
  const id = path.split("/")[2];
39
66
  await api_controllers_1.ApiController.deleteOne(id, res, storage);
40
67
  return;
41
68
  }
42
- // GET /stats
43
69
  if (method === "GET" && path === "/stats") {
44
70
  await api_controllers_1.ApiController.stats(res, storage);
45
71
  return;
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@j3r3mcdev/oast-server",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Modular OAST callback server for security auditing",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/**/*"
9
+ ],
7
10
  "scripts": {
8
11
  "dev": "ts-node-dev --respawn --transpile-only src/core/server.ts",
9
12
  "build": "tsc -p tsconfig.json",
package/.env.example DELETED
File without changes
@@ -1,29 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: ["main"]
6
- pull_request:
7
- branches: ["main"]
8
-
9
- jobs:
10
- build-and-test:
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - name: Checkout repository
15
- uses: actions/checkout@v4
16
-
17
- - name: Setup Node
18
- uses: actions/setup-node@v4
19
- with:
20
- node-version: 20
21
-
22
- - name: Install dependencies
23
- run: npm install
24
-
25
- - name: Run tests
26
- run: npm test
27
-
28
- - name: Build project
29
- run: npm run build
@@ -1,31 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*.*.*"
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: Checkout repository
14
- uses: actions/checkout@v4
15
-
16
- - name: Setup Node
17
- uses: actions/setup-node@v4
18
- with:
19
- node-version: 20
20
- registry-url: "https://registry.npmjs.org"
21
-
22
- - name: Install dependencies
23
- run: npm ci
24
-
25
- - name: Build project
26
- run: npm run build
27
-
28
- - name: Publish package
29
- env:
30
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31
- run: npm publish --access public
package/image.png DELETED
Binary file
package/jest.config.js DELETED
@@ -1,14 +0,0 @@
1
- const { createDefaultPreset } = require("ts-jest");
2
-
3
- const tsJestTransformCfg = createDefaultPreset().transform;
4
-
5
- /** @type {import("jest").Config} **/
6
- module.exports = {
7
- testEnvironment: "node",
8
- transform: {
9
- ...tsJestTransformCfg,
10
- },
11
-
12
- // 🔥 Empêche Jest d'exécuter les tests compilés dans dist/
13
- testPathIgnorePatterns: ["/dist/"],
14
- };
@@ -1,9 +0,0 @@
1
- 8faa343 (HEAD -> feat/core-event, origin/main, main) HEAD@{0}: checkout: moving from main to feat/core-event
2
- 8faa343 (HEAD -> feat/core-event, origin/main, main) HEAD@{1}: commit: ADD FULL architecture
3
- 8f8f441 HEAD@{2}: commit: ADD CI/CD
4
- 0c987a0 HEAD@{3}: commit: ADD CI/CD
5
- 8aad148 HEAD@{4}: commit: ADD CI/CD
6
- e1f2959 HEAD@{5}: commit: ADD CI/CD
7
- 271e3b1 HEAD@{6}: commit: ADD Architecture & package.json & tsconfig.json
8
- b840cd3 HEAD@{7}: Branch: renamed refs/heads/master to refs/heads/main
9
- b840cd3 HEAD@{9}: commit (initial): Initial project structure
@@ -1,74 +0,0 @@
1
- import { describe, it, expect, jest, beforeEach } from "@jest/globals";
2
- import { TasksController } from "../tasks.controller";
3
- import { TasksService } from "../../services/tasks.service";
4
-
5
- describe("TasksController", () => {
6
- let service: jest.Mocked<TasksService>;
7
- let controller: TasksController;
8
-
9
- beforeEach(() => {
10
- service = {
11
- create: jest.fn() as jest.MockedFunction<TasksService["create"]>,
12
- get: jest.fn() as jest.MockedFunction<TasksService["get"]>,
13
- list: jest.fn() as jest.MockedFunction<TasksService["list"]>,
14
- cancel: jest.fn() as jest.MockedFunction<TasksService["cancel"]>,
15
- } as unknown as jest.Mocked<TasksService>;
16
-
17
- controller = new TasksController(service);
18
- });
19
-
20
- it("crée une tâche", async () => {
21
- const fakeTask = {
22
- id: "123",
23
- type: "x",
24
- payload: {},
25
- priority: "normal",
26
- metadata: {},
27
- status: "pending",
28
- createdAt: Date.now(),
29
- updatedAt: Date.now(),
30
- } as const;
31
-
32
- service.create.mockReturnValue(fakeTask);
33
-
34
- const req = { body: { type: "x", payload: {} } };
35
-
36
- const result = await controller.create({ req, res: {}, params: {} });
37
-
38
- expect(service.create).toHaveBeenCalled();
39
- expect(result).toEqual({
40
- status: 201,
41
- body: fakeTask,
42
- });
43
- });
44
-
45
- it("retourne 404 si tâche absente", async () => {
46
- service.get.mockReturnValue(undefined);
47
-
48
- const result = await controller.getOne({
49
- req: {},
50
- res: {},
51
- params: { id: "123" },
52
- });
53
-
54
- expect(result).toEqual({
55
- status: 404,
56
- body: { error: "Task not found" },
57
- });
58
- });
59
-
60
- it("annule une tâche", async () => {
61
- service.cancel.mockReturnValue(true);
62
-
63
- const result = await controller.cancel({
64
- req: {},
65
- res: {},
66
- params: { id: "123" },
67
- });
68
-
69
- expect(result).toEqual({
70
- status: 200,
71
- body: { cancelled: true },
72
- });
73
- });
74
- });
@@ -1,10 +0,0 @@
1
- import { EventsService } from "../services/events.service";
2
-
3
- export class EventsController {
4
- constructor(private readonly events: EventsService) {}
5
-
6
- stream = async ({ res, query }: any) => {
7
- const channels = query.channels?.split(",") ?? [];
8
- return this.events.connect(res, channels);
9
- };
10
- }
@@ -1,7 +0,0 @@
1
- import { Request, Response } from "express";
2
-
3
- export class HealthController {
4
- check = (req: Request, res: Response) => {
5
- res.json({ status: "ok", timestamp: Date.now() });
6
- };
7
- }
File without changes
@@ -1,41 +0,0 @@
1
- import { TasksService } from "../services/tasks.service";
2
- import { CreateTaskDtoValidator } from "../dto/create-task.dto";
3
- import { FilterTasksDtoValidator } from "../dto/filter-tasks.dto";
4
-
5
- export interface HandlerContext {
6
- params: Record<string, string>;
7
- req: any;
8
- res: any;
9
- }
10
-
11
- export class TasksController {
12
- constructor(private readonly tasks: TasksService) {}
13
-
14
- list = async ({ req }: HandlerContext) => {
15
- const dto = FilterTasksDtoValidator.validate(req.query);
16
- const result = await this.tasks.list(dto);
17
- return { status: 200, body: result };
18
- };
19
-
20
- create = async ({ req }: HandlerContext) => {
21
- const dto = CreateTaskDtoValidator.validate(req.body);
22
- const task = await this.tasks.create(dto);
23
- return { status: 201, body: task };
24
- };
25
-
26
- getOne = async ({ params }: HandlerContext) => {
27
- const task = await this.tasks.get(params.id);
28
- if (!task) {
29
- return { status: 404, body: { error: "Task not found" } };
30
- }
31
- return { status: 200, body: task };
32
- };
33
-
34
- cancel = async ({ params }: HandlerContext) => {
35
- const ok = await this.tasks.cancel(params.id);
36
- if (!ok) {
37
- return { status: 404, body: { error: "Task not found" } };
38
- }
39
- return { status: 200, body: { cancelled: true } };
40
- };
41
- }
@@ -1,41 +0,0 @@
1
- import { describe, it, expect } from "@jest/globals";
2
- import { CreateTaskDtoValidator } from "../create-task.dto";
3
-
4
- describe("CreateTaskDtoValidator", () => {
5
- it("valide un DTO correct", () => {
6
- const dto = CreateTaskDtoValidator.validate({
7
- type: "crawl:api",
8
- payload: { url: "https://example.com" },
9
- priority: "high",
10
- });
11
-
12
- expect(dto).toEqual({
13
- type: "crawl:api",
14
- payload: { url: "https://example.com" },
15
- priority: "high",
16
- metadata: {},
17
- });
18
- });
19
-
20
- it("applique les valeurs par défaut", () => {
21
- const dto = CreateTaskDtoValidator.validate({
22
- type: "ping",
23
- payload: {},
24
- });
25
-
26
- expect(dto.priority).toBe("normal");
27
- expect(dto.metadata).toEqual({});
28
- });
29
-
30
- it("rejette un type invalide", () => {
31
- expect(() => CreateTaskDtoValidator.validate({ payload: {} })).toThrow(
32
- "Invalid type",
33
- );
34
- });
35
-
36
- it("rejette un payload invalide", () => {
37
- expect(() =>
38
- CreateTaskDtoValidator.validate({ type: "x", payload: null }),
39
- ).toThrow("Invalid payload");
40
- });
41
- });
@@ -1,35 +0,0 @@
1
- import { describe, it, expect } from "@jest/globals";
2
- import { FilterTasksDtoValidator } from "../filter-tasks.dto";
3
-
4
- describe("FilterTasksDtoValidator", () => {
5
- it("valide un query correct", () => {
6
- const dto = FilterTasksDtoValidator.validate({
7
- status: "running",
8
- limit: "10",
9
- type: "crawl:api",
10
- });
11
-
12
- expect(dto).toEqual({
13
- status: "running",
14
- limit: 10,
15
- type: "crawl:api",
16
- });
17
- });
18
-
19
- it("rejette un status invalide", () => {
20
- expect(() =>
21
- FilterTasksDtoValidator.validate({ status: "unknown" }),
22
- ).toThrow("Invalid status");
23
- });
24
-
25
- it("rejette un limit invalide", () => {
26
- expect(() => FilterTasksDtoValidator.validate({ limit: "-5" })).toThrow(
27
- "Invalid limit",
28
- );
29
- });
30
-
31
- it("accepte un query vide", () => {
32
- const dto = FilterTasksDtoValidator.validate({});
33
- expect(dto).toEqual({});
34
- });
35
- });
@@ -1,33 +0,0 @@
1
- export interface CreateTaskDto {
2
- type: string;
3
- payload: Record<string, any>;
4
- priority?: "low" | "normal" | "high";
5
- metadata?: Record<string, any>;
6
- }
7
-
8
- export class CreateTaskDtoValidator {
9
- static validate(input: any): CreateTaskDto {
10
- if (!input || typeof input !== "object") {
11
- throw new Error("Invalid body: expected object");
12
- }
13
-
14
- if (!input.type || typeof input.type !== "string") {
15
- throw new Error("Invalid type: expected non-empty string");
16
- }
17
-
18
- if (!input.payload || typeof input.payload !== "object") {
19
- throw new Error("Invalid payload: expected object");
20
- }
21
-
22
- if (input.priority && !["low", "normal", "high"].includes(input.priority)) {
23
- throw new Error("Invalid priority: must be low, normal or high");
24
- }
25
-
26
- return {
27
- type: input.type,
28
- payload: input.payload,
29
- priority: input.priority ?? "normal",
30
- metadata: input.metadata ?? {},
31
- };
32
- }
33
- }
@@ -1,33 +0,0 @@
1
- export interface FilterTasksDto {
2
- status?: "pending" | "running" | "completed" | "failed";
3
- limit?: number;
4
- type?: string;
5
- }
6
-
7
- export class FilterTasksDtoValidator {
8
- static validate(query: any): FilterTasksDto {
9
- const dto: FilterTasksDto = {};
10
-
11
- if (query.status) {
12
- const allowed = ["pending", "running", "completed", "failed"];
13
- if (!allowed.includes(query.status)) {
14
- throw new Error("Invalid status");
15
- }
16
- dto.status = query.status;
17
- }
18
-
19
- if (query.limit) {
20
- const n = Number(query.limit);
21
- if (isNaN(n) || n <= 0) {
22
- throw new Error("Invalid limit");
23
- }
24
- dto.limit = n;
25
- }
26
-
27
- if (query.type) {
28
- dto.type = String(query.type);
29
- }
30
-
31
- return dto;
32
- }
33
- }
@@ -1,41 +0,0 @@
1
- import { describe, it, expect, jest } from "@jest/globals";
2
- import { TasksService } from "../tasks.service";
3
- import { EventsService } from "../events.service";
4
- import { EventStreamManager } from "../../sse/events.stream";
5
-
6
- describe("TasksService", () => {
7
- it("émet un événement lors de la création d'une tâche", () => {
8
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
9
- const events = new EventsService(stream);
10
-
11
- const spy = jest.spyOn(stream, "broadcast");
12
-
13
- const service = new TasksService(events);
14
-
15
- const task = service.create({ type: "x", payload: {} });
16
-
17
- expect(spy).toHaveBeenCalledWith(
18
- "tasks",
19
- "task.created",
20
- expect.objectContaining({ id: task.id }),
21
- );
22
- });
23
-
24
- it("émet un événement lors de l'annulation d'une tâche", () => {
25
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
26
- const events = new EventsService(stream);
27
-
28
- const spy = jest.spyOn(stream, "broadcast");
29
-
30
- const service = new TasksService(events);
31
-
32
- const task = service.create({ type: "x", payload: {} });
33
- service.cancel(task.id);
34
-
35
- expect(spy).toHaveBeenCalledWith(
36
- "tasks",
37
- "task.cancelled",
38
- expect.objectContaining({ id: task.id }),
39
- );
40
- });
41
- });
@@ -1,41 +0,0 @@
1
- import { describe, it, expect, jest } from "@jest/globals";
2
- import { TasksService } from "../tasks.service";
3
- import { EventsService } from "../events.service";
4
- import { EventStreamManager } from "../../sse/events.stream";
5
-
6
- describe("TasksService", () => {
7
- it("émet un événement lors de la création d'une tâche", () => {
8
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
9
- const events = new EventsService(stream);
10
-
11
- const spy = jest.spyOn(stream, "broadcast");
12
-
13
- const service = new TasksService(events);
14
-
15
- const task = service.create({ type: "x", payload: {} });
16
-
17
- expect(spy).toHaveBeenCalledWith(
18
- "tasks",
19
- "task.created",
20
- expect.objectContaining({ id: task.id }),
21
- );
22
- });
23
-
24
- it("émet un événement lors de l'annulation d'une tâche", () => {
25
- const stream = new EventStreamManager({ heartbeatInterval: 999999 });
26
- const events = new EventsService(stream);
27
-
28
- const spy = jest.spyOn(stream, "broadcast");
29
-
30
- const service = new TasksService(events);
31
-
32
- const task = service.create({ type: "x", payload: {} });
33
- service.cancel(task.id);
34
-
35
- expect(spy).toHaveBeenCalledWith(
36
- "tasks",
37
- "task.cancelled",
38
- expect.objectContaining({ id: task.id }),
39
- );
40
- });
41
- });
@@ -1,17 +0,0 @@
1
- import { EventStreamManager } from "../sse/events.stream";
2
-
3
- export class EventsService {
4
- constructor(private stream: EventStreamManager) {}
5
-
6
- connect(res: any, channels: string[] = []) {
7
- return this.stream.addClient(res, channels);
8
- }
9
-
10
- emit(channel: string, event: string, data: any) {
11
- this.stream.broadcast(channel, event, data);
12
- }
13
-
14
- emitAll(event: string, data: any) {
15
- this.stream.broadcastAll(event, data);
16
- }
17
- }
@@ -1,79 +0,0 @@
1
- import { CreateTaskDto } from "../dto/create-task.dto";
2
- import { FilterTasksDto } from "../dto/filter-tasks.dto";
3
- import { EventsService } from "./events.service";
4
-
5
- export interface Task {
6
- id: string;
7
- type: string;
8
- payload: Record<string, any>;
9
- priority: "low" | "normal" | "high";
10
- metadata: Record<string, any>;
11
- status: "pending" | "running" | "completed" | "failed";
12
- createdAt: number;
13
- updatedAt: number;
14
- }
15
-
16
- export class TasksService {
17
- private tasks = new Map<string, Task>();
18
- private events: EventsService;
19
-
20
- constructor(events: EventsService) {
21
- this.events = events;
22
- }
23
-
24
- create(dto: CreateTaskDto): Task {
25
- const id = crypto.randomUUID();
26
- const now = Date.now();
27
-
28
- const task: Task = {
29
- id,
30
- type: dto.type,
31
- payload: dto.payload,
32
- priority: dto.priority ?? "normal",
33
- metadata: dto.metadata ?? {},
34
- status: "pending",
35
- createdAt: now,
36
- updatedAt: now,
37
- };
38
-
39
- this.tasks.set(id, task);
40
-
41
- this.events.emit("tasks", "task.created", task);
42
-
43
- return task;
44
- }
45
-
46
- get(id: string): Task | undefined {
47
- return this.tasks.get(id);
48
- }
49
-
50
- list(filters: FilterTasksDto): Task[] {
51
- let results = Array.from(this.tasks.values());
52
-
53
- if (filters.status) {
54
- results = results.filter((t) => t.status === filters.status);
55
- }
56
-
57
- if (filters.type) {
58
- results = results.filter((t) => t.type === filters.type);
59
- }
60
-
61
- if (filters.limit) {
62
- results = results.slice(0, filters.limit);
63
- }
64
-
65
- return results;
66
- }
67
-
68
- cancel(id: string): boolean {
69
- const task = this.tasks.get(id);
70
- if (!task) return false;
71
-
72
- task.status = "failed";
73
- task.updatedAt = Date.now();
74
-
75
- this.events.emit("tasks", "task.cancelled", task);
76
-
77
- return true;
78
- }
79
- }