diff --git a/packages/longbridge-tools/src/errors.ts b/packages/longbridge-tools/src/errors.ts index 1e8d83b..d2f7d59 100644 --- a/packages/longbridge-tools/src/errors.ts +++ b/packages/longbridge-tools/src/errors.ts @@ -4,6 +4,7 @@ export type ErrorCode = | 'LONGBRIDGE_RATE_LIMITED' | 'LONGBRIDGE_TIMEOUT' | 'INVALID_SYMBOL' + | 'INVALID_ARGUMENT' | 'LONGBRIDGE_PARSE_FAILURE' | 'LONGBRIDGE_UNKNOWN'; diff --git a/packages/longbridge-tools/src/phase-two.test.ts b/packages/longbridge-tools/src/phase-two.test.ts index a9019d0..825683f 100644 --- a/packages/longbridge-tools/src/phase-two.test.ts +++ b/packages/longbridge-tools/src/phase-two.test.ts @@ -294,4 +294,31 @@ describe('phase-2 error handling', () => { /parse depth response/ ); }); + + it('rejects invalid calendar symbols before invoking the CLI', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', symbols: ['NVDA.US', 'not-a-symbol'] }) + ).rejects.toMatchObject({ code: 'INVALID_SYMBOL' }); + expect(execaMock).not.toHaveBeenCalled(); + }); + + it('rejects calendar dates that are not YYYY-MM-DD', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', start: '--symbol' }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + await expect( + getCalendarEvents({ eventType: 'dividend', end: '2026/01/01' }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + expect(execaMock).not.toHaveBeenCalled(); + }); + + it('rejects a non-positive or non-integer calendar count', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', count: -1 }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + await expect( + getCalendarEvents({ eventType: 'dividend', count: 1.5 }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + expect(execaMock).not.toHaveBeenCalled(); + }); }); diff --git a/packages/longbridge-tools/src/tools/calendar.ts b/packages/longbridge-tools/src/tools/calendar.ts index eaf6176..71f38d6 100644 --- a/packages/longbridge-tools/src/tools/calendar.ts +++ b/packages/longbridge-tools/src/tools/calendar.ts @@ -1,5 +1,7 @@ import { executeLongBridge } from '../executor.ts'; import { parseCalendarResponse } from '../parser.ts'; +import { LongBridgeError } from '../errors.ts'; +import { validateSymbolOrThrow } from '../validator.ts'; import type { CalendarEvent } from '../types.ts'; export type CalendarEventType = @@ -23,10 +25,30 @@ export interface GetCalendarEventsOptions { count?: number; } +const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/; + /** Upcoming finance-calendar events of a given type. */ export async function getCalendarEvents( options: GetCalendarEventsOptions ): Promise { + for (const symbol of options.symbols ?? []) { + validateSymbolOrThrow(symbol); + } + for (const [name, value] of [['start', options.start], ['end', options.end]] as const) { + if (value !== undefined && !DATE_REGEX.test(value)) { + throw new LongBridgeError( + `INVALID_ARGUMENT: ${name} must be YYYY-MM-DD, got "${value}"`, + 'INVALID_ARGUMENT' + ); + } + } + if (options.count !== undefined && (!Number.isInteger(options.count) || options.count < 1)) { + throw new LongBridgeError( + `INVALID_ARGUMENT: count must be a positive integer, got ${options.count}`, + 'INVALID_ARGUMENT' + ); + } + const args = ['finance-calendar', options.eventType]; for (const symbol of options.symbols ?? []) { args.push('--symbol', symbol);