diff --git a/src/export/controllers/exportController.ts b/src/export/controllers/exportController.ts index aab1d3c..2b0b19e 100644 --- a/src/export/controllers/exportController.ts +++ b/src/export/controllers/exportController.ts @@ -1,9 +1,10 @@ import { Logger } from '@map-colonies/js-logger'; import { RequestHandler } from 'express'; import httpStatus from 'http-status-codes'; +import { HttpError } from '@map-colonies/error-types'; import { injectable, inject } from 'tsyringe'; import { CallbackExportResponse } from '@map-colonies/raster-shared'; -import { CreateExportRequest, createExportRequestSchema } from '@src/utils/zod/schemas'; +import { createExportRequestSchema } from '@src/utils/zod/schemas'; import { SERVICES } from '../../common/constants'; import { ExportManager } from '../models/exportManager'; import { ICreateExportJobResponse, IJobStatusResponse } from '../../common/interfaces'; @@ -19,10 +20,13 @@ export class ExportController { ) {} public createExport: CreateExportHandler = async (req, res, next) => { - const exportRequest: CreateExportRequest = createExportRequestSchema.parse(req.body); try { + const exportRequest = createExportRequestSchema.safeParse(req.body); + if (!exportRequest.success) { + throw new HttpError(exportRequest.error.message, httpStatus.BAD_REQUEST); + } this.logger.debug({ msg: `Creating export request:`, exportRequest }); - const jobCreated = await this.manager.createExport(exportRequest); + const jobCreated = await this.manager.createExport(exportRequest.data); return res.status(httpStatus.OK).json(jobCreated); } catch (err) { next(err); diff --git a/tests/integration/export/export.spec.ts b/tests/integration/export/export.spec.ts index 56d1cc8..c80c4e6 100644 --- a/tests/integration/export/export.spec.ts +++ b/tests/integration/export/export.spec.ts @@ -44,6 +44,7 @@ import { CallbackUrlsTargetArray, ExportJobParameters } from '@map-colonies/rast import { JobExportResponse } from '@src/common/interfaces'; import { JobManagerWrapper } from '@src/clients/jobManagerWrapper'; import { layerWithMultiPolygonFootprint } from '@tests/mocks/geometryMocks'; +import { CreateExportRequest } from '@src/utils/zod/schemas'; import { getTestContainerConfig, resetContainer } from '../testContainerConfig'; import { getApp } from '../../../src/app'; import { ExportSender } from './helpers/exportSender'; @@ -478,6 +479,37 @@ describe('export', function () { }); describe('Bad Path', function () { + it('should return 400 bad request when zod validation fails with invalid request body', async function () { + // Send completely invalid request body that will fail Zod parsing + const invalidRequest = { + dbId: 123, // Should be string + crs: ['invalid'], // Should be string + priority: 'high', // Should be number + roi: 'not-a-valid-geojson', // Should be valid FeatureCollection + callbackURLs: 'not-an-array', // Should be array + }; + + const response = await requestSender.export(invalidRequest as unknown as CreateExportRequest); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toHaveProperty('message'); + expect(response).toSatisfyApiSpec(); + }); + + it('should return 400 bad request when zod validation fails with missing required fields', async function () { + // Send request without required dbId field + const invalidRequest = { + crs: 'EPSG:4326', + description: 'test', + }; + + const response = await requestSender.export(invalidRequest as unknown as CreateExportRequest); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toHaveProperty('message'); + expect(response).toSatisfyApiSpec(); + }); + it('should return not found status code when layer not found', async function () { const layerId = createExportRequestWithoutCallback.dbId;