From e4cb7960b1a198689ce9ad5e7ad82c4ba46c9bc7 Mon Sep 17 00:00:00 2001 From: gitaddremote Date: Tue, 2 Jun 2026 16:33:26 -0400 Subject: [PATCH] feat(catalog-etl): wrap shouldSkip() calls in outer try/catch to prevent unhandled cron errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enclose the full scheduledTerminalEtl() body in an outer try/catch so any DB error thrown by getLastSuccessfulStepRun() is caught, logged at ERROR, and returned rather than propagating as an unhandled scheduler exception - Inner try/catch blocks for runStep() calls remain unchanged — their error handling is unaffected - Add test: guard-throws path resolves without throwing and logs 'terminal ETL skip guard failed' Closes #227 --- .../schedulers/catalog-etl.scheduler.spec.ts | 12 +++++ .../schedulers/catalog-etl.scheduler.ts | 54 ++++++++++--------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.spec.ts b/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.spec.ts index 944d71f..67220dd 100644 --- a/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.spec.ts +++ b/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.spec.ts @@ -80,4 +80,16 @@ describe('CatalogEtlScheduler.scheduledTerminalEtl', () => { expect(mockRunStep).toHaveBeenCalledTimes(1); expect(mockRunStep).toHaveBeenCalledWith('terminals-sync'); }); + + it('logs error and does not throw when the skip guard throws', async () => { + mockGetLastSuccessfulStepRun.mockRejectedValueOnce( + new Error('db connection lost'), + ); + await expect(makeScheduler().scheduledTerminalEtl()).resolves.not.toThrow(); + expect(mockRunStep).not.toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalledWith( + expect.objectContaining({ err: expect.any(Error) }), + 'terminal ETL skip guard failed', + ); + }); }); diff --git a/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.ts b/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.ts index d873e7f..84a9765 100644 --- a/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.ts +++ b/backend/src/modules/catalog-etl/schedulers/catalog-etl.scheduler.ts @@ -15,39 +15,43 @@ export class CatalogEtlScheduler { @Cron('0 * * * *', { name: 'terminal-etl' }) async scheduledTerminalEtl(): Promise { - if (await this.shouldSkip('terminals-sync')) return; - this.logger.info('Starting scheduled terminal ETL'); - try { - await this.catalogEtlService.runStep('terminals-sync'); - } catch (err: unknown) { - if (err instanceof ConflictException) { - this.logger.debug( + if (await this.shouldSkip('terminals-sync')) return; + this.logger.info('Starting scheduled terminal ETL'); + + try { + await this.catalogEtlService.runStep('terminals-sync'); + } catch (err: unknown) { + if (err instanceof ConflictException) { + this.logger.debug( + { err }, + 'Scheduled terminal ETL skipped: ETL lock already held', + ); + return; + } + this.logger.error( { err }, - 'Scheduled terminal ETL skipped: ETL lock already held', + 'terminals-sync failed; skipping terminal-distances-sync', ); return; } - this.logger.error( - { err }, - 'terminals-sync failed; skipping terminal-distances-sync', - ); - return; - } - if (await this.shouldSkip('terminal-distances-sync')) return; + if (await this.shouldSkip('terminal-distances-sync')) return; - try { - await this.catalogEtlService.runStep('terminal-distances-sync'); - } catch (err: unknown) { - if (err instanceof ConflictException) { - this.logger.debug( - { err }, - 'terminal-distances-sync skipped: ETL lock already held', - ); - return; + try { + await this.catalogEtlService.runStep('terminal-distances-sync'); + } catch (err: unknown) { + if (err instanceof ConflictException) { + this.logger.debug( + { err }, + 'terminal-distances-sync skipped: ETL lock already held', + ); + return; + } + this.logger.error({ err }, 'terminal-distances-sync failed'); } - this.logger.error({ err }, 'terminal-distances-sync failed'); + } catch (err: unknown) { + this.logger.error({ err }, 'terminal ETL skip guard failed'); } }