diff --git a/packages/polling-contracts/src/__tests__/__snapshots__/schemas.test.ts.snap b/packages/polling-contracts/src/__tests__/__snapshots__/schemas.test.ts.snap index d471afd..c36dc9e 100644 --- a/packages/polling-contracts/src/__tests__/__snapshots__/schemas.test.ts.snap +++ b/packages/polling-contracts/src/__tests__/__snapshots__/schemas.test.ts.snap @@ -77,6 +77,12 @@ exports[`Schema Validation DeepLinkParamsSchema should validate shortlink params } `; +exports[`Schema Validation DeepLinkParamsSchema should validate single JWT ID params: single-jwt-id-params 1`] = ` +{ + "jwtId": "1704067200_abc123_def45678", +} +`; + exports[`Schema Validation ErrorResponseSchema should validate generic error: generic-error 1`] = ` { "details": { diff --git a/packages/polling-contracts/src/__tests__/schemas.test.ts b/packages/polling-contracts/src/__tests__/schemas.test.ts index 62bdff4..1387e54 100644 --- a/packages/polling-contracts/src/__tests__/schemas.test.ts +++ b/packages/polling-contracts/src/__tests__/schemas.test.ts @@ -99,6 +99,9 @@ describe('Schema Validation', () => { }; const result = DeepLinkParamsSchema.safeParse(params); + if (!result.success) { + console.log('Validation errors:', result.error.errors); + } expect(result.success).toBe(true); expect(result.success ? result.data : null).toMatchSnapshot('single-jwt-id-params'); }); diff --git a/packages/polling-contracts/src/__tests__/watermark-cas.test.ts b/packages/polling-contracts/src/__tests__/watermark-cas.test.ts index 8f5ed6a..9b0056e 100644 --- a/packages/polling-contracts/src/__tests__/watermark-cas.test.ts +++ b/packages/polling-contracts/src/__tests__/watermark-cas.test.ts @@ -185,10 +185,13 @@ async function simulateWatermarkUpdate( expectedWatermark: string | null, newWatermark: string ): Promise<{ success: boolean; watermark: string | null }> { - // Simulate CAS logic + // Simulate CAS logic with proper comparison if (mockWatermark === expectedWatermark) { - mockWatermark = newWatermark; - return { success: true, watermark: newWatermark }; + // If current watermark is null or new watermark is greater, update + if (mockWatermark === null || compareJwtIds(newWatermark, mockWatermark) > 0) { + mockWatermark = newWatermark; + return { success: true, watermark: newWatermark }; + } } return { success: false, watermark: mockWatermark }; } diff --git a/packages/polling-contracts/src/clock-sync.ts b/packages/polling-contracts/src/clock-sync.ts index 2eaf664..b8b99ab 100644 --- a/packages/polling-contracts/src/clock-sync.ts +++ b/packages/polling-contracts/src/clock-sync.ts @@ -75,8 +75,8 @@ export class ClockSyncManager { validateJwtTimestamp(jwt: Record): boolean { const now = this.getServerTime(); - const iat = jwt.iat * 1000; // Convert to milliseconds - const exp = jwt.exp * 1000; + const iat = (jwt.iat as number) * 1000; // Convert to milliseconds + const exp = (jwt.exp as number) * 1000; // Check if JWT is within valid time window const skewTolerance = this.config.jwtClockSkewTolerance * 1000; diff --git a/packages/polling-contracts/src/constants.ts b/packages/polling-contracts/src/constants.ts index 372bc4e..3cf3850 100644 --- a/packages/polling-contracts/src/constants.ts +++ b/packages/polling-contracts/src/constants.ts @@ -2,8 +2,8 @@ * Canonical constants for polling system */ -// JWT ID regex pattern with named capture groups -export const JWT_ID_PATTERN = /^(?\d{10})_(?[A-Za-z0-9]{6})_(?[a-f0-9]{8})$/; +// JWT ID regex pattern with numbered capture groups +export const JWT_ID_PATTERN = /^(\d{10})_([A-Za-z0-9]{6})_([a-f0-9]{8})$/; // Default configuration values export const DEFAULT_CONFIG = { diff --git a/packages/polling-contracts/src/schemas.ts b/packages/polling-contracts/src/schemas.ts index 21b8d83..44f6460 100644 --- a/packages/polling-contracts/src/schemas.ts +++ b/packages/polling-contracts/src/schemas.ts @@ -59,8 +59,8 @@ export const DeepLinkParamsSchema = z.object({ jwtId: z.string().regex(JWT_ID_PATTERN).optional(), shortlink: z.string().min(1).optional() }).refine( - (data) => data.jwtIds || data.projectId || data.shortlink, - 'At least one of jwtIds, projectId, or shortlink must be provided' + (data) => data.jwtIds || data.jwtId || data.projectId || data.shortlink, + 'At least one of jwtIds, jwtId, projectId, or shortlink must be provided' ); // Error response schema diff --git a/packages/polling-contracts/src/validation.ts b/packages/polling-contracts/src/validation.ts index 2268074..a06b948 100644 --- a/packages/polling-contracts/src/validation.ts +++ b/packages/polling-contracts/src/validation.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import { JWT_ID_PATTERN, ERROR_CODES } from './constants'; +import { PollingError } from './types'; import { StarredProjectsResponseSchema, DeepLinkParamsSchema, @@ -33,10 +34,10 @@ export function compareJwtIds(a: string, b: string): number { */ export function extractJwtTimestamp(jwtId: string): number { const match = jwtId.match(JWT_ID_PATTERN); - if (!match || !match.groups?.ts) { + if (!match || !match[1]) { throw new Error('Invalid JWT ID format'); } - return parseInt(match.groups.ts, 10); + return parseInt(match[1], 10); } /** @@ -78,7 +79,7 @@ export function createResponseValidator(schema: z.ZodSchema): { validate: (data: unknown): data is T => schema.safeParse(data).success, transformError: (error: unknown): PollingError => ({ code: ERROR_CODES.VALIDATION_ERROR, - message: error.message || 'Validation failed', + message: (error as Error).message || 'Validation failed', retryable: false }) };