@@ -469,4 +469,124 @@ describe('HttpDispatcher', () => {
469469 ) . rejects . toThrow ( 'Disk full' ) ;
470470 } ) ;
471471 } ) ;
472+
473+ // ═══════════════════════════════════════════════════════════════
474+ // Package Publish / Revert Endpoints
475+ // ═══════════════════════════════════════════════════════════════
476+
477+ describe ( 'Package publish/revert endpoints' , ( ) => {
478+ it ( 'should handle POST /packages/:id/publish via metadata service' , async ( ) => {
479+ const mockMetadata = {
480+ publishPackage : vi . fn ( ) . mockResolvedValue ( {
481+ success : true ,
482+ packageId : 'com.acme.crm' ,
483+ version : 2 ,
484+ publishedAt : '2025-06-01T00:00:00Z' ,
485+ itemsPublished : 3 ,
486+ } ) ,
487+ } ;
488+ const mockRegistry = {
489+ getAllPackages : vi . fn ( ) . mockReturnValue ( [ ] ) ,
490+ enablePackage : vi . fn ( ) ,
491+ disablePackage : vi . fn ( ) ,
492+ } ;
493+ ( kernel as any ) . getService = vi . fn ( ) . mockImplementation ( ( name : string ) => {
494+ if ( name === 'metadata' ) return Promise . resolve ( mockMetadata ) ;
495+ if ( name === 'objectql' ) return Promise . resolve ( { registry : mockRegistry } ) ;
496+ return null ;
497+ } ) ;
498+
499+ const result = await dispatcher . handlePackages ( '/com.acme.crm/publish' , 'POST' , { publishedBy : 'admin' } , { } , { request : { } } ) ;
500+ expect ( result . handled ) . toBe ( true ) ;
501+ expect ( result . response ?. status ) . toBe ( 200 ) ;
502+ expect ( mockMetadata . publishPackage ) . toHaveBeenCalledWith ( 'com.acme.crm' , { publishedBy : 'admin' } ) ;
503+ } ) ;
504+
505+ it ( 'should handle POST /packages/:id/revert via metadata service' , async ( ) => {
506+ const mockMetadata = {
507+ revertPackage : vi . fn ( ) . mockResolvedValue ( undefined ) ,
508+ } ;
509+ const mockRegistry = {
510+ getAllPackages : vi . fn ( ) . mockReturnValue ( [ ] ) ,
511+ enablePackage : vi . fn ( ) ,
512+ disablePackage : vi . fn ( ) ,
513+ } ;
514+ ( kernel as any ) . getService = vi . fn ( ) . mockImplementation ( ( name : string ) => {
515+ if ( name === 'metadata' ) return Promise . resolve ( mockMetadata ) ;
516+ if ( name === 'objectql' ) return Promise . resolve ( { registry : mockRegistry } ) ;
517+ return null ;
518+ } ) ;
519+
520+ const result = await dispatcher . handlePackages ( '/com.acme.crm/revert' , 'POST' , { } , { } , { request : { } } ) ;
521+ expect ( result . handled ) . toBe ( true ) ;
522+ expect ( result . response ?. status ) . toBe ( 200 ) ;
523+ expect ( mockMetadata . revertPackage ) . toHaveBeenCalledWith ( 'com.acme.crm' ) ;
524+ } ) ;
525+
526+ it ( 'should fallback to broker for publish when metadata service unavailable' , async ( ) => {
527+ const mockRegistry = {
528+ getAllPackages : vi . fn ( ) . mockReturnValue ( [ ] ) ,
529+ } ;
530+ ( kernel as any ) . getService = vi . fn ( ) . mockImplementation ( ( name : string ) => {
531+ if ( name === 'metadata' ) return Promise . resolve ( null ) ;
532+ if ( name === 'objectql' ) return Promise . resolve ( { registry : mockRegistry } ) ;
533+ return null ;
534+ } ) ;
535+ mockBroker . call . mockResolvedValue ( { success : true , packageId : 'crm' , version : 1 , publishedAt : '2025-01-01T00:00:00Z' , itemsPublished : 2 } ) ;
536+
537+ const result = await dispatcher . handlePackages ( '/crm/publish' , 'POST' , { } , { } , { request : { } } ) ;
538+ expect ( result . handled ) . toBe ( true ) ;
539+ expect ( mockBroker . call ) . toHaveBeenCalledWith ( 'metadata.publishPackage' , { packageId : 'crm' } , { request : { } } ) ;
540+ } ) ;
541+ } ) ;
542+
543+ // ═══════════════════════════════════════════════════════════════
544+ // Metadata getPublished Endpoint
545+ // ═══════════════════════════════════════════════════════════════
546+
547+ describe ( 'Metadata getPublished endpoint' , ( ) => {
548+ it ( 'should handle GET /metadata/:type/:name/published via metadata service' , async ( ) => {
549+ const mockMetadata = {
550+ getPublished : vi . fn ( ) . mockResolvedValue ( { name : 'account' , label : 'Account' } ) ,
551+ } ;
552+ ( kernel as any ) . getService = vi . fn ( ) . mockImplementation ( ( name : string ) => {
553+ if ( name === 'metadata' ) return Promise . resolve ( mockMetadata ) ;
554+ return null ;
555+ } ) ;
556+
557+ const result = await dispatcher . handleMetadata ( '/object/account/published' , { request : { } } , 'GET' ) ;
558+ expect ( result . handled ) . toBe ( true ) ;
559+ expect ( result . response ?. status ) . toBe ( 200 ) ;
560+ expect ( result . response ?. body ?. data ) . toEqual ( { name : 'account' , label : 'Account' } ) ;
561+ expect ( mockMetadata . getPublished ) . toHaveBeenCalledWith ( 'object' , 'account' ) ;
562+ } ) ;
563+
564+ it ( 'should return 404 when published item not found' , async ( ) => {
565+ const mockMetadata = {
566+ getPublished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
567+ } ;
568+ ( kernel as any ) . getService = vi . fn ( ) . mockImplementation ( ( name : string ) => {
569+ if ( name === 'metadata' ) return Promise . resolve ( mockMetadata ) ;
570+ return null ;
571+ } ) ;
572+
573+ const result = await dispatcher . handleMetadata ( '/object/nonexistent/published' , { request : { } } , 'GET' ) ;
574+ expect ( result . handled ) . toBe ( true ) ;
575+ expect ( result . response ?. status ) . toBe ( 404 ) ;
576+ } ) ;
577+
578+ it ( 'should fallback to broker for getPublished when metadata service unavailable' , async ( ) => {
579+ ( kernel as any ) . getService = vi . fn ( ) . mockResolvedValue ( null ) ;
580+ mockBroker . call . mockResolvedValue ( { name : 'account' , fields : [ 'name' ] } ) ;
581+
582+ const result = await dispatcher . handleMetadata ( '/object/account/published' , { request : { } } , 'GET' ) ;
583+ expect ( result . handled ) . toBe ( true ) ;
584+ expect ( result . response ?. status ) . toBe ( 200 ) ;
585+ expect ( mockBroker . call ) . toHaveBeenCalledWith (
586+ 'metadata.getPublished' ,
587+ { type : 'object' , name : 'account' } ,
588+ { request : { } }
589+ ) ;
590+ } ) ;
591+ } ) ;
472592} ) ;
0 commit comments