@@ -15,6 +15,7 @@ import {
1515 scopeFhirRequest ,
1616 isCompleteShare ,
1717 isSelectiveScopeActive ,
18+ shareQueryHints ,
1819 preScreenSelectiveRequest ,
1920 applySelectiveFilter ,
2021 isResourceExcluded ,
@@ -291,14 +292,52 @@ describe('emptySearchBundle', () => {
291292 } )
292293} )
293294
294- describe ( 'isCompleteShare — what the recipient is told the link carries' , ( ) => {
295+ const scopeOf = ( partial : Partial < SelectiveScope > ) : SelectiveScope => ( {
296+ excludedTypes : [ ] ,
297+ excludedIds : [ ] ,
298+ excludedObservationCategories : [ ] ,
299+ ...partial ,
300+ } )
301+
302+ /**
303+ * The spec's answer to what `complete` was invented for: a scoped link saying what
304+ * it IS rather than what it is not. The hint has to match the identifier filter
305+ * isFhirPathAllowed forces, or the recipient is told to run a query the proxy denies.
306+ */
307+ describe ( 'shareQueryHints — telling the recipient what the share covers' , ( ) => {
308+ it ( 'points a study-scoped share at exactly that study' , ( ) => {
309+ expect ( shareQueryHints ( { studyInstanceUID : STUDY } ) ) . toEqual ( [
310+ `ImagingStudy?identifier=urn:oid:${ STUDY } ` ,
311+ ] )
312+ } )
313+
314+ it ( 'agrees with the identifier the FHIR proxy forces' , ( ) => {
315+ const [ hint ] = shareQueryHints ( { studyInstanceUID : STUDY } ) ?? [ ]
316+ const [ path , search ] = hint . split ( '?' )
317+ const decision = scopeFhirRequest ( path , `?${ search } ` , {
318+ patientId : PATIENT ,
319+ studyInstanceUID : STUDY ,
320+ } )
321+ expect ( decision . allowed ) . toBe ( true )
322+ // Already filtered, so the proxy has nothing to rewrite.
323+ expect ( decision . rewrittenSearch ) . toBeUndefined ( )
324+ } )
325+
326+ /** Naming the reachable types would name the withheld ones by omission. */
327+ it ( 'offers no hints for a whole-patient share' , ( ) => {
328+ expect ( shareQueryHints ( { } ) ) . toBeUndefined ( )
329+ expect ( shareQueryHints ( { studyInstanceUID : undefined } ) ) . toBeUndefined ( )
330+ } )
331+ } )
332+
333+ describe ( 'isCompleteShare — deprecated, kept until both viewers move off it' , ( ) => {
295334 it ( 'is complete when nothing narrows the share' , ( ) => {
296335 expect ( isCompleteShare ( { } ) ) . toBe ( true )
297336 expect ( isCompleteShare ( { selectiveScope : undefined , studyInstanceUID : undefined } ) ) . toBe ( true )
298337 } )
299338
300339 it ( 'is NOT complete when the patient de-selected records' , ( ) => {
301- expect ( isCompleteShare ( { selectiveScope : { excludedTypes : [ 'Condition' ] } } ) ) . toBe ( false )
340+ expect ( isCompleteShare ( { selectiveScope : scopeOf ( { excludedTypes : [ 'Condition' ] } ) } ) ) . toBe ( false )
302341 } )
303342
304343 /**
@@ -311,6 +350,17 @@ describe('isCompleteShare — what the recipient is told the link carries', () =
311350 } )
312351
313352 it ( 'is NOT complete when both narrowings apply' , ( ) => {
314- expect ( isCompleteShare ( { selectiveScope : { excludedTypes : [ 'Condition' ] } , studyInstanceUID : STUDY } ) ) . toBe ( false )
353+ expect (
354+ isCompleteShare ( { selectiveScope : scopeOf ( { excludedTypes : [ 'Condition' ] } ) , studyInstanceUID : STUDY } ) ,
355+ ) . toBe ( false )
356+ } )
357+
358+ /**
359+ * Truthiness on the object read a present-but-empty scope as narrowing. The mint
360+ * site normalises that to undefined, so it never fired — but the invariant lived
361+ * at the call site rather than here, where the question is asked.
362+ */
363+ it ( 'is complete when a scope is present but excludes nothing' , ( ) => {
364+ expect ( isCompleteShare ( { selectiveScope : scopeOf ( { } ) } ) ) . toBe ( true )
315365 } )
316366} )
0 commit comments