@@ -13,6 +13,13 @@ import {
1313 ExportImportTemplateSchema ,
1414 ScheduledExportSchema ,
1515 ExportApiContracts ,
16+ GetExportJobDownloadRequestSchema ,
17+ GetExportJobDownloadResponseSchema ,
18+ ListExportJobsRequestSchema ,
19+ ListExportJobsResponseSchema ,
20+ ExportJobSummarySchema ,
21+ ScheduleExportRequestSchema ,
22+ ScheduleExportResponseSchema ,
1623} from './export.zod' ;
1724
1825// ==========================================
@@ -474,22 +481,236 @@ describe('ScheduledExportSchema', () => {
474481 } ) ;
475482} ) ;
476483
484+ // ==========================================
485+ // Get Export Job Download
486+ // ==========================================
487+
488+ describe ( 'GetExportJobDownloadRequestSchema' , ( ) => {
489+ it ( 'should accept a valid request' , ( ) => {
490+ const req = GetExportJobDownloadRequestSchema . parse ( { jobId : 'job_001' } ) ;
491+ expect ( req . jobId ) . toBe ( 'job_001' ) ;
492+ } ) ;
493+
494+ it ( 'should reject missing jobId' , ( ) => {
495+ expect ( ( ) => GetExportJobDownloadRequestSchema . parse ( { } ) ) . toThrow ( ) ;
496+ } ) ;
497+ } ) ;
498+
499+ describe ( 'GetExportJobDownloadResponseSchema' , ( ) => {
500+ it ( 'should accept a valid response' , ( ) => {
501+ const resp = GetExportJobDownloadResponseSchema . parse ( {
502+ success : true ,
503+ data : {
504+ jobId : 'job_001' ,
505+ downloadUrl : 'https://storage.example.com/exports/job_001.csv?token=abc' ,
506+ fileName : 'account_export_2026-02-01.csv' ,
507+ fileSize : 5242880 ,
508+ format : 'csv' ,
509+ expiresAt : '2026-02-02T10:00:00Z' ,
510+ checksum : 'sha256:abc123' ,
511+ } ,
512+ } ) ;
513+ expect ( resp . data . downloadUrl ) . toContain ( 'storage.example.com' ) ;
514+ expect ( resp . data . fileName ) . toContain ( '.csv' ) ;
515+ expect ( resp . data . checksum ) . toBe ( 'sha256:abc123' ) ;
516+ } ) ;
517+
518+ it ( 'should accept response without optional checksum' , ( ) => {
519+ const resp = GetExportJobDownloadResponseSchema . parse ( {
520+ success : true ,
521+ data : {
522+ jobId : 'job_001' ,
523+ downloadUrl : 'https://storage.example.com/exports/job_001.json' ,
524+ fileName : 'export.json' ,
525+ fileSize : 1024 ,
526+ format : 'json' ,
527+ expiresAt : '2026-02-02T10:00:00Z' ,
528+ } ,
529+ } ) ;
530+ expect ( resp . data . checksum ) . toBeUndefined ( ) ;
531+ } ) ;
532+ } ) ;
533+
534+ // ==========================================
535+ // List Export Jobs
536+ // ==========================================
537+
538+ describe ( 'ListExportJobsRequestSchema' , ( ) => {
539+ it ( 'should apply defaults' , ( ) => {
540+ const req = ListExportJobsRequestSchema . parse ( { } ) ;
541+ expect ( req . limit ) . toBe ( 20 ) ;
542+ expect ( req . object ) . toBeUndefined ( ) ;
543+ expect ( req . status ) . toBeUndefined ( ) ;
544+ expect ( req . cursor ) . toBeUndefined ( ) ;
545+ } ) ;
546+
547+ it ( 'should accept a full request' , ( ) => {
548+ const req = ListExportJobsRequestSchema . parse ( {
549+ object : 'account' ,
550+ status : 'completed' ,
551+ limit : 50 ,
552+ cursor : 'cursor_abc' ,
553+ } ) ;
554+ expect ( req . object ) . toBe ( 'account' ) ;
555+ expect ( req . status ) . toBe ( 'completed' ) ;
556+ expect ( req . limit ) . toBe ( 50 ) ;
557+ expect ( req . cursor ) . toBe ( 'cursor_abc' ) ;
558+ } ) ;
559+
560+ it ( 'should reject limit exceeding max' , ( ) => {
561+ expect ( ( ) => ListExportJobsRequestSchema . parse ( { limit : 200 } ) ) . toThrow ( ) ;
562+ } ) ;
563+ } ) ;
564+
565+ describe ( 'ExportJobSummarySchema' , ( ) => {
566+ it ( 'should accept a valid summary' , ( ) => {
567+ const summary = ExportJobSummarySchema . parse ( {
568+ jobId : 'job_001' ,
569+ object : 'account' ,
570+ status : 'completed' ,
571+ format : 'csv' ,
572+ totalRecords : 5000 ,
573+ fileSize : 2048000 ,
574+ createdAt : '2026-02-01T10:00:00Z' ,
575+ completedAt : '2026-02-01T10:05:00Z' ,
576+ createdBy : 'user_admin' ,
577+ } ) ;
578+ expect ( summary . jobId ) . toBe ( 'job_001' ) ;
579+ expect ( summary . totalRecords ) . toBe ( 5000 ) ;
580+ } ) ;
581+ } ) ;
582+
583+ describe ( 'ListExportJobsResponseSchema' , ( ) => {
584+ it ( 'should accept a response with jobs' , ( ) => {
585+ const resp = ListExportJobsResponseSchema . parse ( {
586+ success : true ,
587+ data : {
588+ jobs : [
589+ {
590+ jobId : 'job_001' ,
591+ object : 'account' ,
592+ status : 'completed' ,
593+ format : 'csv' ,
594+ createdAt : '2026-02-01T10:00:00Z' ,
595+ } ,
596+ ] ,
597+ nextCursor : 'cursor_next' ,
598+ hasMore : true ,
599+ } ,
600+ } ) ;
601+ expect ( resp . data . jobs ) . toHaveLength ( 1 ) ;
602+ expect ( resp . data . nextCursor ) . toBe ( 'cursor_next' ) ;
603+ expect ( resp . data . hasMore ) . toBe ( true ) ;
604+ } ) ;
605+
606+ it ( 'should accept an empty list' , ( ) => {
607+ const resp = ListExportJobsResponseSchema . parse ( {
608+ success : true ,
609+ data : {
610+ jobs : [ ] ,
611+ hasMore : false ,
612+ } ,
613+ } ) ;
614+ expect ( resp . data . jobs ) . toHaveLength ( 0 ) ;
615+ expect ( resp . data . hasMore ) . toBe ( false ) ;
616+ } ) ;
617+ } ) ;
618+
619+ // ==========================================
620+ // Schedule Export Request/Response
621+ // ==========================================
622+
623+ describe ( 'ScheduleExportRequestSchema' , ( ) => {
624+ it ( 'should accept a valid request' , ( ) => {
625+ const req = ScheduleExportRequestSchema . parse ( {
626+ name : 'weekly_account_export' ,
627+ label : 'Weekly Account Export' ,
628+ object : 'account' ,
629+ format : 'csv' ,
630+ fields : [ 'name' , 'email' ] ,
631+ schedule : {
632+ cronExpression : '0 6 * * MON' ,
633+ timezone : 'America/New_York' ,
634+ } ,
635+ delivery : {
636+ method : 'email' ,
637+ recipients : [ 'admin@example.com' ] ,
638+ } ,
639+ } ) ;
640+ expect ( req . name ) . toBe ( 'weekly_account_export' ) ;
641+ expect ( req . schedule . cronExpression ) . toBe ( '0 6 * * MON' ) ;
642+ expect ( req . delivery . method ) . toBe ( 'email' ) ;
643+ } ) ;
644+
645+ it ( 'should apply defaults' , ( ) => {
646+ const req = ScheduleExportRequestSchema . parse ( {
647+ name : 'daily_export' ,
648+ object : 'order' ,
649+ schedule : { cronExpression : '0 0 * * *' } ,
650+ delivery : { method : 'storage' , storagePath : '/exports/daily/' } ,
651+ } ) ;
652+ expect ( req . format ) . toBe ( 'csv' ) ;
653+ expect ( req . schedule . timezone ) . toBe ( 'UTC' ) ;
654+ } ) ;
655+
656+ it ( 'should reject invalid name (not snake_case)' , ( ) => {
657+ expect ( ( ) => ScheduleExportRequestSchema . parse ( {
658+ name : 'WeeklyExport' ,
659+ object : 'account' ,
660+ schedule : { cronExpression : '0 6 * * MON' } ,
661+ delivery : { method : 'email' } ,
662+ } ) ) . toThrow ( ) ;
663+ } ) ;
664+ } ) ;
665+
666+ describe ( 'ScheduleExportResponseSchema' , ( ) => {
667+ it ( 'should accept a valid response' , ( ) => {
668+ const resp = ScheduleExportResponseSchema . parse ( {
669+ success : true ,
670+ data : {
671+ id : 'sched_001' ,
672+ name : 'weekly_account_export' ,
673+ enabled : true ,
674+ nextRunAt : '2026-02-24T06:00:00Z' ,
675+ createdAt : '2026-02-21T09:00:00Z' ,
676+ } ,
677+ } ) ;
678+ expect ( resp . data . id ) . toBe ( 'sched_001' ) ;
679+ expect ( resp . data . enabled ) . toBe ( true ) ;
680+ expect ( resp . data . nextRunAt ) . toBeDefined ( ) ;
681+ } ) ;
682+ } ) ;
683+
477684// ==========================================
478685// Export API Contracts
479686// ==========================================
480687
481688describe ( 'ExportApiContracts' , ( ) => {
482- it ( 'should define 2 contracts' , ( ) => {
483- expect ( Object . keys ( ExportApiContracts ) ) . toHaveLength ( 2 ) ;
689+ it ( 'should define 6 contracts' , ( ) => {
690+ expect ( Object . keys ( ExportApiContracts ) ) . toHaveLength ( 6 ) ;
484691 } ) ;
485692
486693 it ( 'should have correct HTTP methods' , ( ) => {
487694 expect ( ExportApiContracts . createExportJob . method ) . toBe ( 'POST' ) ;
488695 expect ( ExportApiContracts . getExportJobProgress . method ) . toBe ( 'GET' ) ;
696+ expect ( ExportApiContracts . getExportJobDownload . method ) . toBe ( 'GET' ) ;
697+ expect ( ExportApiContracts . listExportJobs . method ) . toBe ( 'GET' ) ;
698+ expect ( ExportApiContracts . scheduleExport . method ) . toBe ( 'POST' ) ;
699+ expect ( ExportApiContracts . cancelExportJob . method ) . toBe ( 'POST' ) ;
489700 } ) ;
490701
491702 it ( 'should have valid paths' , ( ) => {
492703 expect ( ExportApiContracts . createExportJob . path ) . toContain ( '/export' ) ;
493704 expect ( ExportApiContracts . getExportJobProgress . path ) . toContain ( '/export' ) ;
705+ expect ( ExportApiContracts . getExportJobDownload . path ) . toContain ( '/download' ) ;
706+ expect ( ExportApiContracts . listExportJobs . path ) . toBe ( '/api/v1/data/export' ) ;
707+ expect ( ExportApiContracts . scheduleExport . path ) . toContain ( '/schedules' ) ;
708+ expect ( ExportApiContracts . cancelExportJob . path ) . toContain ( '/cancel' ) ;
709+ } ) ;
710+
711+ it ( 'should have input/output schemas for all contracts' , ( ) => {
712+ Object . values ( ExportApiContracts ) . forEach ( ( contract ) => {
713+ expect ( contract . output ) . toBeDefined ( ) ;
714+ } ) ;
494715 } ) ;
495716} ) ;
0 commit comments