generated from MapColonies/ts-server-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Ingestion swap update init(MAPCO-5073) (#25)
* feat: update job init task hanling * fix: remove liveServer form settings.json * fix: custom-environment-variables.json typo mistake task->tasks * fix: adjust to new partsData property from mc-model-types * fix: adjust to new partsData property from mc-model-types * fix: adjust to new partsData property from mc-model-types * fix: pr fixes * fix: pr fixes * fix: pr fixes * feat: handle ingestion_update finalize task * fix: pr fixes * fix: remove space * fix: markFinalizeStepAsCompletedchanged parameters order * Revert "fix: markFinalizeStepAsCompletedchanged parameters order" This reverts commit 85a70e3. * feat: handle ingestion_swap_update init task * fix: chnage additionalParmas jobTrackerServiceUrl to jobTrackerServiceURL
- Loading branch information
Showing
10 changed files
with
261 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,88 @@ | ||
import { randomUUID } from 'crypto'; | ||
import { inject, injectable } from 'tsyringe'; | ||
import { Logger } from '@map-colonies/js-logger'; | ||
import { IJobResponse, ITaskResponse } from '@map-colonies/mc-priority-queue'; | ||
import { UpdateRasterLayer } from '@map-colonies/mc-model-types'; | ||
import { IJobHandler } from '../../common/interfaces'; | ||
import { IJobResponse, ITaskResponse, OperationStatus, TaskHandler as QueueClient } from '@map-colonies/mc-priority-queue'; | ||
import { ZodError } from 'zod'; | ||
import { IngestionSwapUpdateFinalizeTaskParams, IngestionUpdateJobParams, UpdateRasterLayer } from '@map-colonies/mc-model-types'; | ||
import { Grid, IJobHandler, MergeTilesTaskParams } from '../../common/interfaces'; | ||
import { TileMergeTaskManager } from '../../task/models/tileMergeTaskManager'; | ||
import { swapUpdateAdditionalParamsSchema } from '../../utils/zod/schemas/jobParametersSchema'; | ||
import { SERVICES } from '../../common/constants'; | ||
import { JobHandler } from './jobHandler'; | ||
|
||
@injectable() | ||
export class SwapJobHandler implements IJobHandler { | ||
public constructor(@inject(SERVICES.LOGGER) private readonly logger: Logger) {} | ||
export class SwapJobHandler extends JobHandler implements IJobHandler { | ||
public constructor( | ||
@inject(SERVICES.LOGGER) logger: Logger, | ||
@inject(SERVICES.QUEUE_CLIENT) protected queueClient: QueueClient, | ||
@inject(TileMergeTaskManager) private readonly taskBuilder: TileMergeTaskManager | ||
) { | ||
super(logger, queueClient); | ||
} | ||
|
||
public async handleJobInit(job: IJobResponse<UpdateRasterLayer, unknown>, taskId: string): Promise<void> { | ||
const logger = this.logger.child({ jobId: job.id, taskId }); | ||
logger.info({ msg: `handling ${job.type} job with "init" task` }); | ||
await Promise.reject('not implemented'); | ||
public async handleJobInit(job: IJobResponse<IngestionUpdateJobParams, unknown>, taskId: string): Promise<void> { | ||
const logger = this.logger.child({ jobId: job.id, taskId, jobType: job.type }); | ||
|
||
try { | ||
logger.info({ msg: `handling ${job.type} job with "init" task` }); | ||
const { inputFiles, partsData, additionalParams } = job.parameters; | ||
|
||
const validAdditionalParams = this.validateAdditionalParams(additionalParams, swapUpdateAdditionalParamsSchema); | ||
const displayPath = randomUUID(); | ||
|
||
const taskBuildParams: MergeTilesTaskParams = { | ||
inputFiles, | ||
partsData, | ||
taskMetadata: { | ||
tileOutputFormat: validAdditionalParams.tileOutputFormat, | ||
isNewTarget: true, | ||
layerRelativePath: `${job.internalId}/${displayPath}`, | ||
grid: Grid.TWO_ON_ONE, | ||
}, | ||
}; | ||
|
||
logger.info({ msg: 'building tasks' }); | ||
const mergeTasks = this.taskBuilder.buildTasks(taskBuildParams); | ||
|
||
logger.info({ msg: 'pushing tasks' }); | ||
await this.taskBuilder.pushTasks(job.id, mergeTasks); | ||
|
||
logger.info({ msg: 'Acking task' }); | ||
await this.queueClient.ack(job.id, taskId); | ||
|
||
await this.updateJobAdditionalParams(job, validAdditionalParams, displayPath); | ||
} catch (err) { | ||
if (err instanceof ZodError) { | ||
const errorMsg = `Failed to validate additionalParams: ${err.message}`; | ||
logger.error({ msg: errorMsg, err }); | ||
await this.queueClient.reject(job.id, taskId, false, err.message); | ||
return await this.queueClient.jobManagerClient.updateJob(job.id, { status: OperationStatus.FAILED, reason: errorMsg }); | ||
} | ||
if (err instanceof Error) { | ||
logger.error({ msg: 'Failed to handle job init', error: err }); | ||
await this.queueClient.reject(job.id, taskId, true, err.message); | ||
} | ||
} | ||
} | ||
|
||
public async handleJobFinalize(job: IJobResponse<UpdateRasterLayer, unknown>, task: ITaskResponse<unknown>): Promise<void> { | ||
public async handleJobFinalize( | ||
job: IJobResponse<UpdateRasterLayer, unknown>, | ||
task: ITaskResponse<IngestionSwapUpdateFinalizeTaskParams> | ||
): Promise<void> { | ||
const logger = this.logger.child({ jobId: job.id, taskId: task.id }); | ||
logger.info({ msg: `handling ${job.type} job with "finalize" task` }); | ||
await Promise.reject('not implemented'); | ||
} | ||
|
||
private async updateJobAdditionalParams( | ||
job: IJobResponse<IngestionUpdateJobParams, unknown>, | ||
additionalParams: Record<string, unknown>, | ||
displayPath: string | ||
): Promise<void> { | ||
const newAdditionalParams = { ...additionalParams, displayPath }; | ||
this.logger.info({ msg: 'Updating job additional params with new displayPath', jobId: job.id, newAdditionalParams }); | ||
return this.queueClient.jobManagerClient.updateJob(job.id, { | ||
parameters: { ...job.parameters, additionalParams: newAdditionalParams }, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* eslint-disable @typescript-eslint/unbound-method */ | ||
import crypto from 'crypto'; | ||
import { ZodError } from 'zod'; | ||
import { OperationStatus } from '@map-colonies/mc-priority-queue'; | ||
import { swapUpdateAdditionalParamsSchema } from '../../../../src/utils/zod/schemas/jobParametersSchema'; | ||
import { registerDefaultConfig } from '../../mocks/configMock'; | ||
import { Grid, IMergeTaskParameters } from '../../../../src/common/interfaces'; | ||
import { ingestionSwapUpdateJob } from '../../mocks/jobsMockData'; | ||
import { setupSwapJobHandlerTest } from './swapJobHandlerSetup'; | ||
|
||
describe('swapJobHandler', () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
registerDefaultConfig(); | ||
}); | ||
|
||
describe('handleJobInit', () => { | ||
it('should handle job init successfully', async () => { | ||
const { swapJobHandler, queueClientMock, taskBuilderMock, jobManagerClientMock } = setupSwapJobHandlerTest(); | ||
const job = { ...ingestionSwapUpdateJob }; | ||
const taskId = '291bf779-efe0-42bd-8357-aaede47e4d37'; | ||
|
||
const additionalParams = swapUpdateAdditionalParamsSchema.parse(job.parameters.additionalParams); | ||
|
||
const newDisplayPath = crypto.randomUUID(); | ||
|
||
jest.spyOn(crypto, 'randomUUID').mockReturnValue(newDisplayPath); | ||
|
||
const taskBuildParams = { | ||
inputFiles: job.parameters.inputFiles, | ||
taskMetadata: { | ||
layerRelativePath: `${job.internalId}/${newDisplayPath}`, | ||
tileOutputFormat: additionalParams.tileOutputFormat, | ||
isNewTarget: true, | ||
grid: Grid.TWO_ON_ONE, | ||
}, | ||
partsData: job.parameters.partsData, | ||
}; | ||
|
||
const mergeTasks: AsyncGenerator<IMergeTaskParameters, void, void> = (async function* () {})(); | ||
|
||
taskBuilderMock.buildTasks.mockReturnValue(mergeTasks); | ||
taskBuilderMock.pushTasks.mockResolvedValue(undefined); | ||
queueClientMock.ack.mockResolvedValue(undefined); | ||
|
||
await swapJobHandler.handleJobInit(job, taskId); | ||
|
||
expect(taskBuilderMock.buildTasks).toHaveBeenCalledWith(taskBuildParams); | ||
expect(taskBuilderMock.pushTasks).toHaveBeenCalledWith(job.id, mergeTasks); | ||
expect(queueClientMock.ack).toHaveBeenCalledWith(job.id, taskId); | ||
expect(jobManagerClientMock.updateJob).toHaveBeenCalledWith(job.id, { | ||
parameters: { ...job.parameters, additionalParams: { ...additionalParams, displayPath: newDisplayPath } }, | ||
}); | ||
}); | ||
|
||
it('should handle job init failure and reject the task', async () => { | ||
const { swapJobHandler, taskBuilderMock, queueClientMock } = setupSwapJobHandlerTest(); | ||
|
||
const job = { ...ingestionSwapUpdateJob }; | ||
|
||
const taskId = '291bf779-efe0-42bd-8357-aaede47e4d37'; | ||
const tasks: AsyncGenerator<IMergeTaskParameters, void, void> = (async function* () {})(); | ||
|
||
const error = new Error('Test error'); | ||
|
||
taskBuilderMock.buildTasks.mockReturnValue(tasks); | ||
taskBuilderMock.pushTasks.mockRejectedValue(error); | ||
queueClientMock.reject.mockResolvedValue(undefined); | ||
|
||
await swapJobHandler.handleJobInit(job, taskId); | ||
|
||
expect(queueClientMock.reject).toHaveBeenCalledWith(job.id, taskId, true, error.message); | ||
}); | ||
|
||
it('should handle job init failure with ZodError and Failed the job', async () => { | ||
const { swapJobHandler, jobManagerClientMock, queueClientMock } = setupSwapJobHandlerTest(); | ||
const job = { ...ingestionSwapUpdateJob }; | ||
job.parameters.additionalParams = { wrongField: 'wrongValue' }; | ||
const taskId = '291bf779-efe0-42bd-8357-aaede47e4d37'; | ||
const validAdditionalParamsSpy = jest.spyOn(swapUpdateAdditionalParamsSchema, 'parse'); | ||
|
||
await swapJobHandler.handleJobInit(job, taskId); | ||
|
||
expect(validAdditionalParamsSpy).toThrow(ZodError); | ||
expect(queueClientMock.reject).toHaveBeenCalledWith(job.id, taskId, false, expect.any(String)); | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
expect(jobManagerClientMock.updateJob).toHaveBeenCalledWith(job.id, { status: OperationStatus.FAILED, reason: expect.any(String) }); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import jsLogger from '@map-colonies/js-logger'; | ||
import { JobManagerClient, TaskHandler as QueueClient } from '@map-colonies/mc-priority-queue'; | ||
import { TileMergeTaskManager } from '../../../../src/task/models/tileMergeTaskManager'; | ||
import { MapproxyApiClient } from '../../../../src/httpClients/mapproxyClient'; | ||
import { CatalogClient } from '../../../../src/httpClients/catalogClient'; | ||
import { SwapJobHandler } from '../../../../src/job/models/swapJobHandler'; | ||
|
||
export interface SwapJobHandlerTestContext { | ||
swapJobHandler: SwapJobHandler; | ||
taskBuilderMock: jest.Mocked<TileMergeTaskManager>; | ||
queueClientMock: jest.Mocked<QueueClient>; | ||
jobManagerClientMock: jest.Mocked<JobManagerClient>; | ||
mapproxyClientMock: jest.Mocked<MapproxyApiClient>; | ||
catalogClientMock: jest.Mocked<CatalogClient>; | ||
} | ||
|
||
export const setupSwapJobHandlerTest = (): SwapJobHandlerTestContext => { | ||
const taskBuilderMock = { | ||
buildTasks: jest.fn(), | ||
pushTasks: jest.fn(), | ||
} as unknown as jest.Mocked<TileMergeTaskManager>; | ||
|
||
const jobManagerClientMock = { | ||
updateJob: jest.fn(), | ||
updateTask: jest.fn(), | ||
} as unknown as jest.Mocked<JobManagerClient>; | ||
|
||
const queueClientMock = { | ||
jobManagerClient: jobManagerClientMock, | ||
ack: jest.fn(), | ||
reject: jest.fn(), | ||
} as unknown as jest.Mocked<QueueClient>; | ||
|
||
const mapproxyClientMock = { publish: jest.fn() } as unknown as jest.Mocked<MapproxyApiClient>; | ||
const catalogClientMock = { publish: jest.fn() } as unknown as jest.Mocked<CatalogClient>; | ||
|
||
const swapJobHandler = new SwapJobHandler(jsLogger({ enabled: false }), queueClientMock, taskBuilderMock); | ||
|
||
return { | ||
swapJobHandler, | ||
taskBuilderMock, | ||
queueClientMock, | ||
jobManagerClientMock, | ||
mapproxyClientMock, | ||
catalogClientMock, | ||
}; | ||
}; |
Oops, something went wrong.