diff --git a/listener/src/api/events-server.ts b/listener/src/api/events-server.ts index 5695de6..f779878 100644 --- a/listener/src/api/events-server.ts +++ b/listener/src/api/events-server.ts @@ -206,11 +206,18 @@ export function createEventsServer(options: EventsServerOptions): http.Server { return; } + const executeAt = new Date(data.executeAt); + if (isNaN(executeAt.getTime())) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'executeAt is not a valid date' })); + return; + } + const notificationId = await options.notificationAPI!.scheduleNotification({ payload: data.payload, notificationType: data.notificationType || NotificationType.DISCORD, targetRecipient: data.targetRecipient, - executeAt: new Date(data.executeAt), + executeAt, maxRetries: data.maxRetries, priority: data.priority, eventId: data.eventId, diff --git a/listener/src/services/notification-api.ts b/listener/src/services/notification-api.ts index 0671201..1894243 100644 --- a/listener/src/services/notification-api.ts +++ b/listener/src/services/notification-api.ts @@ -17,8 +17,12 @@ export class NotificationAPI { requestId?: string ): Promise { // Validate input - if (!input.executeAt || input.executeAt < new Date()) { - throw new Error('executeAt must be a future date'); + if (!input.executeAt || !(input.executeAt instanceof Date) || isNaN(input.executeAt.getTime())) { + throw new Error('executeAt must be a valid date'); + } + + if (input.executeAt <= new Date()) { + throw new Error('executeAt must be a future timestamp — the provided date has already expired'); } if (!input.payload || typeof input.payload !== 'object') { diff --git a/listener/src/tests/notification-scheduler-refactored.test.ts b/listener/src/tests/notification-scheduler-refactored.test.ts index 251f7f3..a06dd00 100644 --- a/listener/src/tests/notification-scheduler-refactored.test.ts +++ b/listener/src/tests/notification-scheduler-refactored.test.ts @@ -258,7 +258,7 @@ describe('NotificationScheduler (Refactored)', () => { await expect( api.scheduleNotification(pastInput) - ).rejects.toThrow('executeAt must be a future date'); + ).rejects.toThrow('executeAt must be a future timestamp'); }); test('should schedule Discord notification', async () => { diff --git a/listener/src/tests/notification-scheduler.test.ts b/listener/src/tests/notification-scheduler.test.ts index 1e51ab1..496cba0 100644 --- a/listener/src/tests/notification-scheduler.test.ts +++ b/listener/src/tests/notification-scheduler.test.ts @@ -255,7 +255,7 @@ describe('NotificationScheduler', () => { expect(notification!.status).toBe(NotificationStatus.PENDING); }); - test('should reject past execution time', async () => { + test('should reject expired execution time', async () => { const pastDate = new Date(Date.now() - 60000); await expect( @@ -265,7 +265,45 @@ describe('NotificationScheduler', () => { targetRecipient: 'test-webhook', executeAt: pastDate, }) - ).rejects.toThrow('executeAt must be a future date'); + ).rejects.toThrow('executeAt must be a future timestamp — the provided date has already expired'); + }); + + test('should reject execution time equal to now', async () => { + // A timestamp at (or just before) the current moment is already expired + const now = new Date(); + + await expect( + api.scheduleNotification({ + payload: { message: 'Test' }, + notificationType: NotificationType.DISCORD, + targetRecipient: 'test-webhook', + executeAt: now, + }) + ).rejects.toThrow('executeAt must be a future timestamp'); + }); + + test('should reject invalid date object', async () => { + await expect( + api.scheduleNotification({ + payload: { message: 'Test' }, + notificationType: NotificationType.DISCORD, + targetRecipient: 'test-webhook', + executeAt: new Date('not-a-date'), + }) + ).rejects.toThrow('executeAt must be a valid date'); + }); + + test('should accept execution time 1 second in the future', async () => { + const nearFuture = new Date(Date.now() + 1000); + + const id = await api.scheduleNotification({ + payload: { message: 'Test' }, + notificationType: NotificationType.DISCORD, + targetRecipient: 'test-webhook', + executeAt: nearFuture, + }); + + expect(id).toBeGreaterThan(0); }); test('should schedule Discord notification', async () => {