From eed8266da24358aaafe6e081c55775015e54e3a7 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Tue, 4 Aug 2026 07:56:39 +0200 Subject: [PATCH] fix: add error handling for CashboxExportScheduler subscription processing - Wrap runOne(subscription) in try/catch to ensure one subscription failure doesn't block remaining subscriptions - Log failed subscriptions with new 'cashbox_export_subscription_run_fail' event - Add new LOGEVENT type for subscription run failures - Add test to verify second subscription processes even when first fails (continues processing independently) - All 7 tests passing: 6 original + 1 new failure handling test Co-Authored-By: Claude Sonnet 5 --- .../cashbox-export.scheduler.spec.ts | 27 ++++++++++++++++++- .../cashbox-export.scheduler.ts | 11 +++++++- .../logging/model/logging-event.type.ts | 3 ++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.spec.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.spec.ts index cf55f8a..6f2c3a5 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.spec.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.spec.ts @@ -5,7 +5,7 @@ describe('CashboxExportScheduler', () => { const subscriptionRepository = { find: jest.fn(), save: jest.fn((v) => v) }; const teamRepository = { findOne: jest.fn() }; const mailService = { cashboxExport: jest.fn() }; - const logger = { info: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; let scheduler: CashboxExportScheduler; beforeEach(() => { @@ -118,4 +118,29 @@ describe('CashboxExportScheduler', () => { expect(mailService.cashboxExport).toHaveBeenCalledTimes(2); }); + + it('continues processing when one subscription fails', async () => { + subscriptionRepository.find.mockResolvedValue([ + { id: 1, team: { id: 5 }, recipients: ['a@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true }, + { id: 2, team: { id: 6 }, recipients: ['b@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true }, + ]); + teamRepository.findOne.mockResolvedValue({ + id: 5, + name: 'Team A', + alias: 'team-a', + transactions: [], + players: [], + }); + mailService.cashboxExport.mockRejectedValueOnce(new Error('smtp down')); + + await scheduler.runDueSubscriptions(); + + expect(mailService.cashboxExport).toHaveBeenCalledTimes(2); + expect(subscriptionRepository.save).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith({ + event: 'cashbox_export_subscription_run_fail', + details: expect.stringContaining('subscriptionId=1 teamId=5'), + userId: -1, + }); + }); }); diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.ts index f4bbe6e..f458082 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.scheduler.ts @@ -35,7 +35,16 @@ export class CashboxExportScheduler { }); for (const subscription of due) { - await this.runOne(subscription); + try { + await this.runOne(subscription); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + await this.logger.error({ + event: 'cashbox_export_subscription_run_fail', + details: `recurring subscription failed: subscriptionId=${subscription.id} teamId=${subscription.team.id}: ${errorMessage}`, + userId: -1, + }); + } } } diff --git a/myteamwallet_backend/src/database/logging/model/logging-event.type.ts b/myteamwallet_backend/src/database/logging/model/logging-event.type.ts index 49a3503..2c9d6ec 100644 --- a/myteamwallet_backend/src/database/logging/model/logging-event.type.ts +++ b/myteamwallet_backend/src/database/logging/model/logging-event.type.ts @@ -31,6 +31,7 @@ export type LOGEVENT = | 'recurring_transaction_update' | 'recurring_transaction_delete' | 'recurring_transaction_run' - | 'cashbox_export_subscription_run'; + | 'cashbox_export_subscription_run' + | 'cashbox_export_subscription_run_fail'; export type LOGLEVEL = 'FATAL' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';