diff --git a/service/.yarn/cache/@types-validator-npm-13.11.9-ac3f0e748e-2d397c6929.zip b/service/.yarn/cache/@types-validator-npm-13.11.9-ac3f0e748e-2d397c6929.zip new file mode 100644 index 0000000..10a4a8b Binary files /dev/null and b/service/.yarn/cache/@types-validator-npm-13.11.9-ac3f0e748e-2d397c6929.zip differ diff --git a/service/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip b/service/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip new file mode 100644 index 0000000..7101eac Binary files /dev/null and b/service/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip differ diff --git a/service/.yarn/cache/class-validator-npm-0.14.1-f0ce3b1130-0c34592a1c.zip b/service/.yarn/cache/class-validator-npm-0.14.1-f0ce3b1130-0c34592a1c.zip new file mode 100644 index 0000000..acfa223 Binary files /dev/null and b/service/.yarn/cache/class-validator-npm-0.14.1-f0ce3b1130-0c34592a1c.zip differ diff --git a/service/.yarn/cache/libphonenumber-js-npm-1.10.57-b94f521f3d-7344dd7e8f.zip b/service/.yarn/cache/libphonenumber-js-npm-1.10.57-b94f521f3d-7344dd7e8f.zip new file mode 100644 index 0000000..78baa62 Binary files /dev/null and b/service/.yarn/cache/libphonenumber-js-npm-1.10.57-b94f521f3d-7344dd7e8f.zip differ diff --git a/service/.yarn/cache/validator-npm-13.11.0-f0143e2784-4bf094641e.zip b/service/.yarn/cache/validator-npm-13.11.0-f0143e2784-4bf094641e.zip new file mode 100644 index 0000000..7acd970 Binary files /dev/null and b/service/.yarn/cache/validator-npm-13.11.0-f0143e2784-4bf094641e.zip differ diff --git a/service/main/src/main.ts b/service/main/src/main.ts index 32f16d7..9c1dce0 100644 --- a/service/main/src/main.ts +++ b/service/main/src/main.ts @@ -1,8 +1,11 @@ import {NestFactory} from "@nestjs/core" import {AppModule} from "./app.module" +import {globalValidationPipe} from "./validation-pipe" async function bootstrap() { const app = await NestFactory.create(AppModule) + app.useGlobalPipes(globalValidationPipe) await app.listen(3000) } + bootstrap() diff --git a/service/main/src/validation-pipe.ts b/service/main/src/validation-pipe.ts new file mode 100644 index 0000000..7eeceb2 --- /dev/null +++ b/service/main/src/validation-pipe.ts @@ -0,0 +1,21 @@ +import {BadRequestException, ValidationPipe} from "@nestjs/common" + +export const globalValidationPipe = new ValidationPipe({ + stopAtFirstError: false, + exceptionFactory: errors => { + const mappedErrors = [] + + for (const error of errors) { + for (const key in error.constraints) { + mappedErrors.push({ + code: "VALIDATION_ERROR", + message: error.constraints[key] + }) + } + } + + return new BadRequestException({ + errors: mappedErrors + }) + } +}) diff --git a/service/main/test/controller/run.controller.integration.test.ts b/service/main/test/controller/run.controller.integration.test.ts index a73f08c..a4ed540 100644 --- a/service/main/test/controller/run.controller.integration.test.ts +++ b/service/main/test/controller/run.controller.integration.test.ts @@ -8,9 +8,11 @@ import {persistSourceCodeMock} from "@libs/testing" import {NestApplication} from "@nestjs/core" import {TestingModule, Test} from "@nestjs/testing" import {PrismaClient} from "@prisma/client" - // eslint-disable-next-line node/no-unpublished-import import * as request from "supertest" +import "expect-more-jest" +import {randomUUID} from "crypto" +import {globalValidationPipe} from "@app/validation-pipe" describe("POST /runs", () => { let app: NestApplication @@ -29,8 +31,8 @@ describe("POST /runs", () => { getDbConnectionUrl: () => isolatedDb }) .compile() - app = module.createNestApplication() + app.useGlobalPipes(globalValidationPipe) await app.init() prisma = module.get(DatabaseClient) @@ -76,6 +78,56 @@ describe("POST /runs", () => { expect(run?.sourceCodeId).toBe(requestBody.source_code_id) }) + it("should return 400 if the plan_id is not a valid uuid", async () => { + // Given + const requestBody: CreateRunRequestBody = { + plan_id: "not-a-uuid", + source_code_id: randomUUID() + } + + // When + const response = await request(app.getHttpServer()) + .post(endpoint) + .send(requestBody) + + // Expect + expect(response.body.errors).toBeArrayOf({ + code: expect.toBeString(), + message: expect.toBeString() + }) + expect( + response.body.errors.map( + (it: {code: string; message: string}) => it.message + ) + ).toBeArrayIncludingAllOf(["plan_id must be a UUID"]) + expect(response.status).toBe(400) + }) + + it("should return 400 if the source_code_id is not a valid uuid", async () => { + // Given + const requestBody: CreateRunRequestBody = { + plan_id: "not-a-uuid", + source_code_id: "not-a-uuid" + } + + // When + const response = await request(app.getHttpServer()) + .post(endpoint) + .send(requestBody) + + // Expect + expect(response.body.errors).toBeArrayOf({ + code: expect.toBeString(), + message: expect.toBeString() + }) + expect( + response.body.errors.map( + (it: {code: string; message: string}) => it.message + ) + ).toBeArrayIncludingAllOf(["source_code_id must be a UUID"]) + expect(response.status).toBe(400) + }) + afterAll(async () => { await cleanDatabase(prisma) await prisma.$disconnect() diff --git a/service/package.json b/service/package.json index b1487f4..7687ffe 100644 --- a/service/package.json +++ b/service/package.json @@ -33,6 +33,8 @@ "@nestjs/core": "10.2.8", "@nestjs/platform-express": "10.3.1", "@prisma/client": "5.9.0", + "class-transformer": "0.5.1", + "class-validator": "0.14.1", "express": "4.18.2", "fp-ts": "2.16.2", "kafkajs": "2.2.4", diff --git a/service/yarn.lock b/service/yarn.lock index 373a305..07579b5 100644 --- a/service/yarn.lock +++ b/service/yarn.lock @@ -1515,6 +1515,13 @@ __metadata: languageName: node linkType: hard +"@types/validator@npm:^13.11.8": + version: 13.11.9 + resolution: "@types/validator@npm:13.11.9" + checksum: 2d397c69293cc726e0cf1b4c74c563ca4e459b00f216f3ff0ac184c9648103be27169e8c67f85be9c6e7a3fcbb149c6add66a2547b185a1b25aa79e4b61261bd + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -2538,6 +2545,24 @@ __metadata: languageName: node linkType: hard +"class-transformer@npm:^0.5.1": + version: 0.5.1 + resolution: "class-transformer@npm:0.5.1" + checksum: 750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7 + languageName: node + linkType: hard + +"class-validator@npm:^0.14.1": + version: 0.14.1 + resolution: "class-validator@npm:0.14.1" + dependencies: + "@types/validator": "npm:^13.11.8" + libphonenumber-js: "npm:^1.10.53" + validator: "npm:^13.9.0" + checksum: 0c34592a1cbdd5e9c35cd02f4babd94120339e875fc7627aa2bf5dffb45ecc373275e854389c6ff3d39781cddb85a18193b4e9e8f4d77d6d90e445fd0b8b8e11 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -5030,6 +5055,13 @@ __metadata: languageName: node linkType: hard +"libphonenumber-js@npm:^1.10.53": + version: 1.10.57 + resolution: "libphonenumber-js@npm:1.10.57" + checksum: 7344dd7e8f35b0bd19c5a4fecfd5f252ba3e1f2fa938e552662a34fbc0bc3bf02d3c0a7ce5adf369280a741c7c7d54940232092e997890b71cb3cb1114e9184b + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -6736,6 +6768,8 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:6.10.0" "@typescript-eslint/parser": "npm:6.10.0" chance: "npm:1.1.11" + class-transformer: "npm:^0.5.1" + class-validator: "npm:^0.14.1" dotenv-cli: "npm:7.3.0" eslint: "npm:8.53.0" eslint-config-prettier: "npm:8.10.0" @@ -7201,6 +7235,13 @@ __metadata: languageName: node linkType: hard +"validator@npm:^13.9.0": + version: 13.11.0 + resolution: "validator@npm:13.11.0" + checksum: 4bf094641eb71729c06a42d669840e7189597ba655a8264adabac9bf03f95cd6fde5fbc894b0a13ee861bd4a852f56d2afdc9391aeaeb3fc0f9633a974140e12 + languageName: node + linkType: hard + "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2"