diff --git a/src/modules/platform-api/platform-api.controller.spec.ts b/src/modules/platform-api/platform-api.controller.spec.ts new file mode 100644 index 0000000..a654b72 --- /dev/null +++ b/src/modules/platform-api/platform-api.controller.spec.ts @@ -0,0 +1,64 @@ +import { PlatformApiController } from './platform-api.controller'; +import { RequestUser } from '../../decorators/user.decorator'; + +jest.mock('../customers/customers.service', () => ({ + CustomersService: class CustomersService {}, +})); +jest.mock('../catalog/catalog.service', () => ({ + CatalogService: class CatalogService {}, +})); +jest.mock('../inputs/inputs.service', () => ({ + InputsService: class InputsService {}, +})); + +describe('PlatformApiController pipeline run jobs', () => { + const proxy = jest.fn(); + const user = {} as RequestUser; + let controller: PlatformApiController; + + beforeEach(() => { + proxy.mockReset(); + controller = new PlatformApiController( + { proxy } as any, + null as any, + null as any, + null as any, + null as any, + null as any, + { logger: {} } as any, + ); + }); + + it('normalizes only the pipeline id and forwards the run id unchanged', async () => { + proxy.mockResolvedValue({ jobs: [] }); + + const result = await controller.getPipelineRunJobs( + 'b6b2aae4-e52c-4291-8f8b-5fe612ce0a7d', + 'manual__job-run-tracking', + user, + ); + + expect(proxy).toHaveBeenCalledWith( + 'GET', + '/pipeline/b6b2aae4_e52c_4291_8f8b_5fe612ce0a7d/pipeline_run/manual__job-run-tracking/jobs', + user, + ); + expect(result).toEqual({ jobs: [] }); + }); + + it('decodes a scheduled run id before forwarding it', async () => { + proxy.mockResolvedValue({ jobs: [] }); + + await controller.getPipelineRunJobs( + 'pipeline-1', + 'scheduled__2026-06-19T00%3A00%3A00%2B00%3A00', + user, + ); + + expect(proxy).toHaveBeenCalledWith( + 'GET', + '/pipeline/pipeline_1/pipeline_run/scheduled__2026-06-19T00:00:00+00:00/jobs', + user, + ); + }); +}); diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 16d27b3..a12ec27 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -14,7 +14,7 @@ import { NotFoundException, UseGuards, } from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiOkResponse } from '@nestjs/swagger'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { @@ -72,6 +72,10 @@ export class PlatformApiController { return id?.replace(/-/g, '_') || ''; } + private decodePathParam(value: string): string { + return value ? decodeURIComponent(value) : ''; + } + /** * Denormalize ID back to UUID format (replace _ with -). * Used when we receive a normalized ID but need the original UUID. @@ -832,6 +836,41 @@ export class PlatformApiController { ); } + @Get('pipelines/:pipelineId/pipeline_run/:runId/jobs') + @ApiOperation({ + summary: 'Get pipeline run jobs', + description: + 'Proxies platform-api DB-backed job runs and returns `{ jobs: [...] }`.', + }) + @ApiOkResponse({ + description: 'DB-backed job runs for the selected pipeline run.', + schema: { + type: 'object', + properties: { + jobs: { + type: 'array', + items: { type: 'object' }, + }, + }, + required: ['jobs'], + }, + }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelineRunJobs( + @Param('pipelineId') pipelineId: string, + @Param('runId') runId: string, + @User() user: RequestUser, + ) { + const normalizedPipelineId = this.normalizePipelineId(pipelineId); + const decodedRunId = this.decodePathParam(runId); + + return this.platformApiService.proxy( + 'GET', + `/pipeline/${normalizedPipelineId}/pipeline_run/${decodedRunId}/jobs`, + user, + ); + } + // ==================== JOBS - COLUMN EDITING ROUTES ==================== @Put('jobs/:jobId/input')