@@ -422,4 +422,174 @@ describe('$cache-control routing (scaffolding)', () => {
422422 expect ( codes ) . toContain ( 'b1' ) ;
423423 } ) ;
424424 } ) ;
425+
426+ // ---- batch front-loading (two-pass) ----
427+ //
428+ // A batch against an unsealed cache front-loads every resource it supplies
429+ // (tx-resource + primary valueSet/codeSystem) into the cache before any entry is
430+ // evaluated. So the batch is order-independent (an entry may reference by url a
431+ // resource a later entry supplies) and a failing entry does not withhold what it
432+ // carried. A sealed cache does not grow, so there is no cross-entry sharing.
433+ describe ( 'batch front-loading (two-pass)' , ( ) => {
434+ const BATCH = '/tx/r5/ValueSet/$batch-validate-code' ;
435+
436+ const entry = ( parameter ) => ( {
437+ name : 'validation' ,
438+ resource : { resourceType : 'Parameters' , parameter }
439+ } ) ;
440+ const results = ( body ) => ( body . parameter || [ ] ) . filter ( x => x . name === 'validation' ) ;
441+
442+ async function startCache ( sealed ) {
443+ const parameter = [ ] ;
444+ if ( sealed !== undefined ) parameter . push ( { name : 'sealed' , valueBoolean : sealed } ) ;
445+ const started = await request ( app ) . post ( BASE ) . query ( { mode : 'start' } )
446+ . set ( 'Content-Type' , 'application/json' )
447+ . send ( { resourceType : 'Parameters' , parameter } ) ;
448+ return cacheIdFrom ( started . body ) ;
449+ }
450+
451+ const mkCS = ( tag ) => ( {
452+ resourceType : 'CodeSystem' , url : `http://example.org/batch/${ tag } -cs` ,
453+ version : '1.0.0' , status : 'active' , content : 'complete' ,
454+ concept : [ { code : `${ tag } 1` , display : `${ tag } one` } ]
455+ } ) ;
456+ const mkVS = ( tag , cs ) => ( {
457+ resourceType : 'ValueSet' , url : `http://example.org/batch/${ tag } -vs` ,
458+ version : '1.0.0' , status : 'active' ,
459+ compose : { include : [ { system : cs . url } ] }
460+ } ) ;
461+
462+ test ( 'an entry resolves a url supplied only by a LATER entry (unsealed)' , async ( ) => {
463+ const cacheId = await startCache ( false ) ;
464+ const cs = mkCS ( 'fwd' ) ; const vs = mkVS ( 'fwd' , cs ) ;
465+ const res = await request ( app ) . post ( BATCH )
466+ . set ( 'Content-Type' , 'application/json' )
467+ . set ( 'x-cache-id' , cacheId )
468+ . send ( { resourceType : 'Parameters' , parameter : [
469+ // entry 0: references vs by url only (forward reference)
470+ entry ( [
471+ { name : 'url' , valueString : vs . url } ,
472+ { name : 'coding' , valueCoding : { system : cs . url , code : 'fwd1' } }
473+ ] ) ,
474+ // entry 1: supplies vs + cs inline, AFTER the entry that references them
475+ entry ( [
476+ { name : 'tx-resource' , resource : cs } ,
477+ { name : 'valueSet' , resource : vs } ,
478+ { name : 'coding' , valueCoding : { system : cs . url , code : 'fwd1' } }
479+ ] )
480+ ] } ) ;
481+ expect ( res . status ) . toBe ( 200 ) ;
482+ const rs = results ( res . body ) ;
483+ expect ( rs . length ) . toBe ( 2 ) ;
484+ // the forward-referencing entry validated true because pass 1 pooled the
485+ // resources from the later entry before any entry ran.
486+ const r0 = ( rs [ 0 ] . resource . parameter || [ ] ) . find ( x => x . name === 'result' ) ;
487+ expect ( r0 && r0 . valueBoolean ) . toBe ( true ) ;
488+ } ) ;
489+
490+ test ( 'resources are front-loaded even when the carrying entry fails, and persist (unsealed)' , async ( ) => {
491+ const cacheId = await startCache ( false ) ;
492+ const cs = mkCS ( 'fail' ) ; const vs = mkVS ( 'fail' , cs ) ;
493+ const batch = await request ( app ) . post ( BATCH )
494+ . set ( 'Content-Type' , 'application/json' )
495+ . set ( 'x-cache-id' , cacheId )
496+ . send ( { resourceType : 'Parameters' , parameter : [
497+ // this entry supplies vs+cs but validates a code that isn't in the system
498+ entry ( [
499+ { name : 'tx-resource' , resource : cs } ,
500+ { name : 'valueSet' , resource : vs } ,
501+ { name : 'coding' , valueCoding : { system : cs . url , code : 'NOPE' } }
502+ ] )
503+ ] } ) ;
504+ expect ( batch . status ) . toBe ( 200 ) ;
505+
506+ // Despite that entry not validating cleanly, vs was populated: a separate
507+ // by-reference $expand on the same cache now resolves it.
508+ const exp = await request ( app ) . post ( '/tx/r5/ValueSet/$expand' )
509+ . set ( 'Content-Type' , 'application/json' )
510+ . set ( 'x-cache-id' , cacheId )
511+ . send ( { resourceType : 'Parameters' , parameter : [ { name : 'url' , valueUri : vs . url } ] } ) ;
512+ expect ( exp . status ) . toBe ( 200 ) ;
513+ const codes = ( ( exp . body . expansion || { } ) . contains || [ ] ) . map ( c => c . code ) ;
514+ expect ( codes ) . toContain ( 'fail1' ) ;
515+ } ) ;
516+
517+ test ( 'a sealed batch does NOT share resources across entries' , async ( ) => {
518+ const cacheId = await startCache ( true ) ;
519+ const cs = mkCS ( 'seal' ) ; const vs = mkVS ( 'seal' , cs ) ;
520+ const res = await request ( app ) . post ( BATCH )
521+ . set ( 'Content-Type' , 'application/json' )
522+ . set ( 'x-cache-id' , cacheId )
523+ . send ( { resourceType : 'Parameters' , parameter : [
524+ // entry 0 references vs by url only
525+ entry ( [
526+ { name : 'url' , valueString : vs . url } ,
527+ { name : 'coding' , valueCoding : { system : cs . url , code : 'seal1' } }
528+ ] ) ,
529+ // entry 1 supplies vs - but a sealed cache does not share it to entry 0
530+ entry ( [
531+ { name : 'tx-resource' , resource : cs } ,
532+ { name : 'valueSet' , resource : vs } ,
533+ { name : 'coding' , valueCoding : { system : cs . url , code : 'seal1' } }
534+ ] )
535+ ] } ) ;
536+ expect ( res . status ) . toBe ( 200 ) ;
537+ const rs = results ( res . body ) ;
538+ // entry 0 could not resolve vs (no cross-entry sharing when sealed):
539+ // either an OperationOutcome or result=false, but not a clean true.
540+ const r0res = rs [ 0 ] . resource ;
541+ const r0 = ( r0res . parameter || [ ] ) . find ( x => x . name === 'result' ) ;
542+ const unresolved = r0res . resourceType === 'OperationOutcome' || ( r0 && r0 . valueBoolean === false ) ;
543+ expect ( unresolved ) . toBe ( true ) ;
544+ // entry 1, which carried the resource itself, still validates true.
545+ const r1 = ( rs [ 1 ] . resource . parameter || [ ] ) . find ( x => x . name === 'result' ) ;
546+ expect ( r1 && r1 . valueBoolean ) . toBe ( true ) ;
547+ } ) ;
548+
549+ test ( 'an unknown cache-id fails the whole batch with a coded 404' , async ( ) => {
550+ const cs = mkCS ( 'unk' ) ; const vs = mkVS ( 'unk' , cs ) ;
551+ const res = await request ( app ) . post ( BATCH )
552+ . set ( 'Content-Type' , 'application/json' )
553+ . set ( 'x-cache-id' , 'never-issued-this-id' )
554+ . send ( { resourceType : 'Parameters' , parameter : [
555+ entry ( [
556+ { name : 'valueSet' , resource : vs } ,
557+ { name : 'coding' , valueCoding : { system : cs . url , code : 'unk1' } }
558+ ] )
559+ ] } ) ;
560+ expect ( res . status ) . toBe ( 404 ) ;
561+ expect ( res . body . resourceType ) . toBe ( 'OperationOutcome' ) ;
562+ const coding = ( ( ( res . body . issue || [ ] ) [ 0 ] || { } ) . details || { } ) . coding || [ ] ;
563+ expect ( coding . some ( c => c . code === 'cache-id-unknown' ) ) . toBe ( true ) ;
564+ } ) ;
565+
566+ // CodeSystem batch: same front-loading, but the primary being validated is a
567+ // code system (system+code), not a value set.
568+ test ( 'a CodeSystem batch front-loads and resolves a system supplied by a later entry (unsealed)' , async ( ) => {
569+ const CS_BATCH = '/tx/r5/CodeSystem/$batch-validate-code' ;
570+ const cacheId = await startCache ( false ) ;
571+ const cs = mkCS ( 'csbatch' ) ;
572+ const res = await request ( app ) . post ( CS_BATCH )
573+ . set ( 'Content-Type' , 'application/json' )
574+ . set ( 'x-cache-id' , cacheId )
575+ . send ( { resourceType : 'Parameters' , parameter : [
576+ // entry 0: validates a code against cs by system url only (forward ref)
577+ entry ( [
578+ { name : 'system' , valueUri : cs . url } ,
579+ { name : 'code' , valueCode : 'csbatch1' }
580+ ] ) ,
581+ // entry 1: supplies cs inline, AFTER the entry that references it
582+ entry ( [
583+ { name : 'tx-resource' , resource : cs } ,
584+ { name : 'system' , valueUri : cs . url } ,
585+ { name : 'code' , valueCode : 'csbatch1' }
586+ ] )
587+ ] } ) ;
588+ expect ( res . status ) . toBe ( 200 ) ;
589+ const rs = results ( res . body ) ;
590+ expect ( rs . length ) . toBe ( 2 ) ;
591+ const r0 = ( rs [ 0 ] . resource . parameter || [ ] ) . find ( x => x . name === 'result' ) ;
592+ expect ( r0 && r0 . valueBoolean ) . toBe ( true ) ;
593+ } ) ;
594+ } ) ;
425595} ) ;
0 commit comments