diff --git a/src/ingestion/models/ingestionManager.ts b/src/ingestion/models/ingestionManager.ts index bf502dd..95fe2de 100644 --- a/src/ingestion/models/ingestionManager.ts +++ b/src/ingestion/models/ingestionManager.ts @@ -1,6 +1,6 @@ import { relative } from 'node:path'; import { randomUUID } from 'node:crypto'; -import { BadRequestError, ConflictError, NotFoundError } from '@map-colonies/error-types'; +import { ConflictError, NotFoundError } from '@map-colonies/error-types'; import { Logger } from '@map-colonies/js-logger'; import { IFindJobsByCriteriaBody, @@ -234,37 +234,7 @@ export class IngestionManager { const job: IJobResponse = await this.jobManagerWrapper.getJob(jobId); const validationTask = await this.getValidationTask(jobId, { ...logCtx }); - if (validationTask.parameters.isValid === true) { - return; - } - if (job.status !== OperationStatus.SUSPENDED) { - throw new BadRequestError('cannot bypass validation errors when the job is not suspended'); - } - if (validationTask.parameters.errorsSummary === undefined) { - throw new UnsupportedEntityError('cannot bypass validation errors when there are no validation errors in task params'); - } - - const errorsSummary = validationTask.parameters.errorsSummary; - const exceededResolutionThreshold = errorsSummary.thresholds.resolution.exceeded; - - for (const [errorType, errorCount] of Object.entries(errorsSummary.errorsCount)) { - if (errorCount > 0 && !allowedValidationErrors.includes(errorType)) { - throw new UnsupportedEntityError('validation task has additional errors that are not in the allowed list'); - } - } - if (exceededResolutionThreshold) { - throw new UnsupportedEntityError('cannot bypass validation error of type: resolution, because the resolution exceeded threshold'); - } - - const existingChecksums = validationTask.parameters.checksums; - const metadataShapefilePath = await this.validateAndGetAbsoluteInputFiles(job.parameters.inputFiles); - const newChecksums = await this.getChecksum(metadataShapefilePath.metadataShapefilePath); - if (this.isChecksumChanged(existingChecksums, newChecksums)) { - throw new ConflictError( - 'cannot bypass validation errors because the metadata shapefile has been changed since the validation was performed,re-run the process' - ); - } - + await this.canBypassValidationTask({ ...body, ingestionValidationTaskParams: validationTask.parameters, job }); await this.makeValidationTaskCompleted(validationTask); await this.jobManagerWrapper.updateJob(jobId, { status: OperationStatus.IN_PROGRESS, @@ -783,6 +753,47 @@ export class IngestionManager { })); } + private async canBypassValidationTask( + options: { + ingestionValidationTaskParams: IngestionValidationTaskParams; + job: IJobResponse; + } & IBypassValidationErrorsRequestBody + ): Promise { + const { allowedValidationErrors, ingestionValidationTaskParams, job } = options; + + if (ingestionValidationTaskParams.isValid === true) { + throw new UnsupportedEntityError('cannot bypass validation errors since the validation was already successfully completed'); + } + if (job.status !== OperationStatus.SUSPENDED) { + throw new UnsupportedEntityError('cannot bypass validation errors when the job is not suspended'); + } + if (ingestionValidationTaskParams.errorsSummary === undefined) { + throw new UnsupportedEntityError('cannot bypass validation errors when there are no validation errors in task params'); + } + + const errorsSummary = ingestionValidationTaskParams.errorsSummary; + const exceededResolutionThreshold = errorsSummary.thresholds.resolution.exceeded; + + if (exceededResolutionThreshold) { + throw new UnsupportedEntityError('cannot bypass validation error of type: resolution, because the resolution exceeded threshold'); + } + + for (const [errorType, errorCount] of Object.entries(errorsSummary.errorsCount)) { + if (errorCount > 0 && !allowedValidationErrors.includes(errorType)) { + throw new UnsupportedEntityError('validation task has additional errors that are not in the allowed list'); + } + } + + const existingChecksums = ingestionValidationTaskParams.checksums; + const metadataShapefilePath = await this.validateAndGetAbsoluteInputFiles(job.parameters.inputFiles); + const newChecksums = await this.getChecksum(metadataShapefilePath.metadataShapefilePath); + if (this.isChecksumChanged(existingChecksums, newChecksums)) { + throw new ConflictError( + 'cannot bypass validation errors because the metadata shapefile has been changed since the validation was performed, re-run the process' + ); + } + } + private async makeValidationTaskCompleted(task: ITaskResponse): Promise { try { await this.jobManagerWrapper.updateTask(task.jobId, task.id, { diff --git a/tests/integration/ingestion/ingestion.spec.ts b/tests/integration/ingestion/ingestion.spec.ts index ac391ef..d6357f5 100644 --- a/tests/integration/ingestion/ingestion.spec.ts +++ b/tests/integration/ingestion/ingestion.spec.ts @@ -2260,25 +2260,26 @@ describe('Ingestion', () => { nock(configMock.get('services.jobTrackerServiceURL')).post(`/tasks/${taskId}/notify`).reply(httpStatusCodes.OK); const response = await requestSender.bypassValidationErrors(jobId, requestBody); - console.log('BYPASS RES:', response.body); expect(response).toSatisfyApiSpec(); expect(response.status).toBe(httpStatusCodes.OK); }); + }); - it('should return 200 when task is valid', async () => { + describe('Bad Path', () => { + it('should return 422 UNPROCESSABLE_ENTITY when job is not suspended', async () => { const jobId = faker.string.uuid(); const taskId = faker.string.uuid(); - const bypassJob = createBypassJob({ jobId }); + const bypassJob = createBypassJob({ jobId, status: OperationStatus.FAILED }); const requestBody = { allowedValidationErrors: ['resolution'], approver: 'approverName' }; const validationTask = { id: taskId, jobId, type: configMock.get('jobManager.validationTaskType'), - status: OperationStatus.SUSPENDED, + status: OperationStatus.FAILED, parameters: { - isValid: true, + isValid: false, checksums: validInputFiles.checksums, errorsSummary: { errorsCount: { resolution: 1 }, @@ -2293,28 +2294,25 @@ describe('Ingestion', () => { const response = await requestSender.bypassValidationErrors(jobId, requestBody); expect(response).toSatisfyApiSpec(); - expect(response.status).toBe(httpStatusCodes.OK); + expect(response.status).toBe(httpStatusCodes.UNPROCESSABLE_ENTITY); }); - }); - describe('Bad Path', () => { - it('should return 400 BAD_REQUEST when job is not suspended', async () => { + it('should return 422 UNPROCESSABLE_ENTITY when validation task was already successfully accomplished', async () => { const jobId = faker.string.uuid(); const taskId = faker.string.uuid(); - const bypassJob = createBypassJob({ jobId, status: OperationStatus.PENDING }); + const bypassJob = createBypassJob({ jobId, status: OperationStatus.COMPLETED }); const requestBody = { allowedValidationErrors: ['resolution'], approver: 'approverName' }; - const validationTask = { id: taskId, jobId, type: configMock.get('jobManager.validationTaskType'), - status: OperationStatus.FAILED, + status: OperationStatus.COMPLETED, parameters: { - isValid: false, + isValid: true, checksums: validInputFiles.checksums, errorsSummary: { - errorsCount: { resolution: 1 }, - thresholds: { resolution: { exceeded: false } }, + errorsCount: { resolution: 0, geometryValidity: 0, metadata: 0, smallGeometries: 0, smallHoles: 0, unknown: 0, vertices: 0 }, + thresholds: { resolution: { exceeded: false }, smallGeometries: { exceeded: false }, smallHoles: { count: 0, exceeded: false } }, }, }, }; @@ -2325,7 +2323,7 @@ describe('Ingestion', () => { const response = await requestSender.bypassValidationErrors(jobId, requestBody); expect(response).toSatisfyApiSpec(); - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.status).toBe(httpStatusCodes.UNPROCESSABLE_ENTITY); }); it('should return 422 UNPROCESSABLE_ENTITY when there are no validation errors in task params', async () => { diff --git a/tests/unit/ingestion/models/ingestionManager.spec.ts b/tests/unit/ingestion/models/ingestionManager.spec.ts index 3addc86..561277c 100644 --- a/tests/unit/ingestion/models/ingestionManager.spec.ts +++ b/tests/unit/ingestion/models/ingestionManager.spec.ts @@ -1209,7 +1209,7 @@ describe('IngestionManager', () => { expect(mockJobTrackerClient.notify).toHaveBeenCalledWith(mockTask); }); - it('should return and do nothing when task is valid', async () => { + it('should throw UnsupportedEntityError when task is valid', async () => { const mockJobId = faker.string.uuid(); const mockJob = generateMockJob({ status: OperationStatus.SUSPENDED }); const mockTask = { @@ -1235,11 +1235,12 @@ describe('IngestionManager', () => { getJobSpy.mockResolvedValue(mockJob); getTasksForJobSpy.mockResolvedValue([mockTask]); - await ingestionManager.bypassValidationErrors(body, mockJobId); + const response = ingestionManager.bypassValidationErrors(body, mockJobId); expect(mockJobTrackerClient.notify).not.toHaveBeenCalled(); + await expect(response).rejects.toThrow(UnsupportedEntityError); }); - it('should throw BadRequestError if job is not suspended', async () => { + it('should throw UnsupportedEntityError if job is not suspended', async () => { const mockJobId = faker.string.uuid(); const mockJob = generateMockJob({ status: OperationStatus.PENDING }); const mockTask = { @@ -1265,7 +1266,8 @@ describe('IngestionManager', () => { getJobSpy.mockResolvedValue(mockJob); getTasksForJobSpy.mockResolvedValue([mockTask]); - await expect(ingestionManager.bypassValidationErrors(body, mockJobId)).rejects.toThrow(BadRequestError); + const response = ingestionManager.bypassValidationErrors(body, mockJobId); + await expect(response).rejects.toThrow(UnsupportedEntityError); }); it('should throw UnsupportedEntityError if task has unallowed errors', async () => {