@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.
- package/.github/workflows/ci.yml +29 -29
- package/.github/workflows/publish.yml +31 -31
- package/README.md +192 -192
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -1
- package/jest.config.js +14 -14
- package/package.json +45 -45
- package/sadmin list shadows +9 -9
- package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
- package/src/api/controllers/events.controller.ts +10 -10
- package/src/api/controllers/health.controller.ts +7 -7
- package/src/api/controllers/tasks.controller.ts +41 -41
- package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
- package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
- package/src/api/dto/create-task.dto.ts +33 -33
- package/src/api/dto/filter-tasks.dto.ts +33 -33
- package/src/api/services/__tests__/events.service.test.ts +41 -41
- package/src/api/services/__tests__/tasks.service.test.ts +41 -41
- package/src/api/services/events.service.ts +17 -17
- package/src/api/services/tasks.service.ts +79 -79
- package/src/api/sse/events.stream.ts +90 -90
- package/src/bootstrap.ts +89 -89
- package/src/core/__tests__/core-router.test.ts +30 -30
- package/src/core/__tests__/core-server.test.ts +44 -44
- package/src/core/__tests__/event.normalizer.test.ts +56 -56
- package/src/core/__tests__/event.router.test.ts +89 -89
- package/src/core/__tests__/logger.test.ts +32 -32
- package/src/core/__tests__/storage-manager.test.ts +74 -74
- package/src/core/event.normalizer.ts +147 -147
- package/src/core/event.router.ts +13 -13
- package/src/core/http/__tests__/adapter-node.test.ts +52 -52
- package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
- package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
- package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
- package/src/core/http/__tests__/compile-path.test.ts +39 -39
- package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
- package/src/core/http/__tests__/request.test.ts +34 -34
- package/src/core/http/__tests__/response.test.ts +35 -35
- package/src/core/http/__tests__/router-match.test.ts +171 -171
- package/src/core/http/adapter-node.ts +51 -51
- package/src/core/http/buildRequest.ts +18 -18
- package/src/core/http/compile-path.ts +32 -32
- package/src/core/http/errors.ts +37 -37
- package/src/core/http/http-server.ts +52 -52
- package/src/core/http/middleware.ts +160 -160
- package/src/core/http/request.ts +55 -55
- package/src/core/http/response.ts +93 -93
- package/src/core/http/router.ts +138 -138
- package/src/core/id-generator.ts +8 -8
- package/src/core/logger.ts +113 -113
- package/src/core/router.ts +44 -44
- package/src/core/server.ts +85 -85
- package/src/core/storage.ts +64 -64
- package/src/index.ts +14 -14
- package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
- package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
- package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
- package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
- package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
- package/src/listeners/api/api.controllers.ts +67 -67
- package/src/listeners/api/api.extractor.ts +43 -43
- package/src/listeners/api/api.listener.ts +50 -50
- package/src/listeners/api/api.routes.ts +76 -76
- package/src/listeners/api/api.sse.ts +38 -38
- package/src/listeners/dns/__tests__/dns.test.ts +118 -118
- package/src/listeners/dns/dns.extractor.ts +14 -14
- package/src/listeners/dns/dns.listener.ts +61 -61
- package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
- package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
- package/src/listeners/http/http.extractor.ts +15 -15
- package/src/listeners/http/http.listener.ts +110 -110
- package/src/listeners/listener.interface.ts +4 -4
- package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
- package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
- package/src/listeners/smtp/smtp.extractor.ts +18 -18
- package/src/listeners/smtp/smtp.listener.ts +60 -60
- package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
- package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
- package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
- package/src/listeners/ssrf/ssrf.listener.ts +37 -37
- package/src/listeners/tcp/tcp.extractor.ts +16 -16
- package/src/listeners/tcp/tcp.listener.ts +61 -61
- package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
- package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
- package/src/listeners/webhook/webhook.extractor.ts +12 -12
- package/src/listeners/webhook/webhook.listener.ts +58 -58
- package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
- package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
- package/src/listeners/websocket/websocket.extractor.ts +11 -11
- package/src/listeners/websocket/websocket.listener.ts +40 -40
- package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
- package/src/storage-adapters/adapters/memory.storage.ts +64 -64
- package/src/storage-adapters/storage.interface.ts +26 -26
- package/src/types/event.types.ts +147 -147
- package/tsconfig.json +20 -21
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@j3r3mcdev/oast-server",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Modular OAST callback server for security auditing",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "ts-node-dev --respawn --transpile-only src/core/server.ts",
|
|
9
|
-
"build": "tsc -p tsconfig.json",
|
|
10
|
-
"start": "node dist/core/server.js",
|
|
11
|
-
"clean": "rimraf dist",
|
|
12
|
-
"test": "jest --passWithNoTests"
|
|
13
|
-
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"oast",
|
|
16
|
-
"security",
|
|
17
|
-
"ssrf",
|
|
18
|
-
"callback",
|
|
19
|
-
"audit",
|
|
20
|
-
"pentest"
|
|
21
|
-
],
|
|
22
|
-
"author": "Jérémy",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"body-parser": "^1.20.2",
|
|
26
|
-
"cors": "^2.8.5",
|
|
27
|
-
"dotenv": "^16.4.5",
|
|
28
|
-
"express": "^4.19.2",
|
|
29
|
-
"ioredis": "^5.4.1",
|
|
30
|
-
"ws": "^8.21.0"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@types/body-parser": "^1.19.5",
|
|
34
|
-
"@types/cors": "^2.8.17",
|
|
35
|
-
"@types/express": "^4.17.21",
|
|
36
|
-
"@types/jest": "^29.5.14",
|
|
37
|
-
"@types/node": "^20.12.7",
|
|
38
|
-
"@types/ws": "^8.18.1",
|
|
39
|
-
"jest": "^29.7.0",
|
|
40
|
-
"rimraf": "^5.0.5",
|
|
41
|
-
"ts-jest": "^29.4.11",
|
|
42
|
-
"ts-node-dev": "^2.0.0",
|
|
43
|
-
"typescript": "^5.4.5"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@j3r3mcdev/oast-server",
|
|
3
|
+
"version": "1.1.8",
|
|
4
|
+
"description": "Modular OAST callback server for security auditing",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "ts-node-dev --respawn --transpile-only src/core/server.ts",
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"start": "node dist/core/server.js",
|
|
11
|
+
"clean": "rimraf dist",
|
|
12
|
+
"test": "jest --passWithNoTests"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"oast",
|
|
16
|
+
"security",
|
|
17
|
+
"ssrf",
|
|
18
|
+
"callback",
|
|
19
|
+
"audit",
|
|
20
|
+
"pentest"
|
|
21
|
+
],
|
|
22
|
+
"author": "Jérémy",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"body-parser": "^1.20.2",
|
|
26
|
+
"cors": "^2.8.5",
|
|
27
|
+
"dotenv": "^16.4.5",
|
|
28
|
+
"express": "^4.19.2",
|
|
29
|
+
"ioredis": "^5.4.1",
|
|
30
|
+
"ws": "^8.21.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/body-parser": "^1.19.5",
|
|
34
|
+
"@types/cors": "^2.8.17",
|
|
35
|
+
"@types/express": "^4.17.21",
|
|
36
|
+
"@types/jest": "^29.5.14",
|
|
37
|
+
"@types/node": "^20.12.7",
|
|
38
|
+
"@types/ws": "^8.18.1",
|
|
39
|
+
"jest": "^29.7.0",
|
|
40
|
+
"rimraf": "^5.0.5",
|
|
41
|
+
"ts-jest": "^29.4.11",
|
|
42
|
+
"ts-node-dev": "^2.0.0",
|
|
43
|
+
"typescript": "^5.4.5"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/sadmin list shadows
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
[33m8faa343[m[33m ([m[1;36mHEAD -> [m[1;32mfeat/core-event[m[33m, [m[1;31morigin/main[m[33m, [m[1;32mmain[m[33m)[m HEAD@{0}: checkout: moving from main to feat/core-event
|
|
2
|
-
[33m8faa343[m[33m ([m[1;36mHEAD -> [m[1;32mfeat/core-event[m[33m, [m[1;31morigin/main[m[33m, [m[1;32mmain[m[33m)[m HEAD@{1}: commit: ADD FULL architecture
|
|
3
|
-
[33m8f8f441[m HEAD@{2}: commit: ADD CI/CD
|
|
4
|
-
[33m0c987a0[m HEAD@{3}: commit: ADD CI/CD
|
|
5
|
-
[33m8aad148[m HEAD@{4}: commit: ADD CI/CD
|
|
6
|
-
[33me1f2959[m HEAD@{5}: commit: ADD CI/CD
|
|
7
|
-
[33m271e3b1[m HEAD@{6}: commit: ADD Architecture & package.json & tsconfig.json
|
|
8
|
-
[33mb840cd3[m HEAD@{7}: Branch: renamed refs/heads/master to refs/heads/main
|
|
9
|
-
[33mb840cd3[m HEAD@{9}: commit (initial): Initial project structure
|
|
1
|
+
[33m8faa343[m[33m ([m[1;36mHEAD -> [m[1;32mfeat/core-event[m[33m, [m[1;31morigin/main[m[33m, [m[1;32mmain[m[33m)[m HEAD@{0}: checkout: moving from main to feat/core-event
|
|
2
|
+
[33m8faa343[m[33m ([m[1;36mHEAD -> [m[1;32mfeat/core-event[m[33m, [m[1;31morigin/main[m[33m, [m[1;32mmain[m[33m)[m HEAD@{1}: commit: ADD FULL architecture
|
|
3
|
+
[33m8f8f441[m HEAD@{2}: commit: ADD CI/CD
|
|
4
|
+
[33m0c987a0[m HEAD@{3}: commit: ADD CI/CD
|
|
5
|
+
[33m8aad148[m HEAD@{4}: commit: ADD CI/CD
|
|
6
|
+
[33me1f2959[m HEAD@{5}: commit: ADD CI/CD
|
|
7
|
+
[33m271e3b1[m HEAD@{6}: commit: ADD Architecture & package.json & tsconfig.json
|
|
8
|
+
[33mb840cd3[m HEAD@{7}: Branch: renamed refs/heads/master to refs/heads/main
|
|
9
|
+
[33mb840cd3[m HEAD@{9}: commit (initial): Initial project structure
|
|
@@ -1,74 +1,74 @@
|
|
|
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
|
+
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 +1,10 @@
|
|
|
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
|
+
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 +1,7 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
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
|
+
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 +1,41 @@
|
|
|
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
|
+
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 +1,35 @@
|
|
|
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
|
+
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 +1,33 @@
|
|
|
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
|
+
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
|
+
}
|