|
3 | 3 | import ca.uhn.fhir.context.FhirContext; |
4 | 4 | import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; |
5 | 5 | import ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity; |
| 6 | +import ca.uhn.fhir.rest.api.SummaryEnum; |
6 | 7 | import ca.uhn.fhir.rest.client.api.IGenericClient; |
| 8 | +import ca.uhn.fhir.rest.gclient.ICriterion; |
7 | 9 | import ca.uhn.fhir.rest.gclient.IOperation; |
8 | 10 | import ca.uhn.fhir.rest.gclient.IOperationUnnamed; |
9 | 11 | import ca.uhn.fhir.rest.gclient.IOperationUntyped; |
10 | 12 | import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; |
| 13 | +import ca.uhn.fhir.rest.gclient.IQuery; |
| 14 | +import ca.uhn.fhir.rest.gclient.IUntypedQuery; |
11 | 15 | import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; |
12 | 16 | import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; |
13 | 17 | import com.lantanagroup.link.shared.utils.LogUtils; |
| 18 | +import org.hl7.fhir.instance.model.api.IBaseBundle; |
| 19 | +import org.hl7.fhir.instance.model.api.IBaseResource; |
14 | 20 | import org.hl7.fhir.r4.model.*; |
15 | 21 | import org.junit.jupiter.api.Test; |
16 | 22 | import org.mockito.ArgumentCaptor; |
@@ -66,6 +72,34 @@ private IOperation stubClientChain(RemoteTermServiceValidation subject, Object e |
66 | 72 | return operation; |
67 | 73 | } |
68 | 74 |
|
| 75 | + /** |
| 76 | + * Stubs the fluent {@code client.search().forResource(..).where(..).summaryMode(..).returnBundle(..).execute()} |
| 77 | + * chain so that {@code execute()} returns the supplied bundle. Returns the query mock so callers can capture |
| 78 | + * the summary mode requested. |
| 79 | + */ |
| 80 | + @SuppressWarnings({"rawtypes", "unchecked"}) |
| 81 | + private IQuery stubSearchChain(RemoteTermServiceValidation subject, IBaseBundle executeResult) { |
| 82 | + IGenericClient client = mock(IGenericClient.class); |
| 83 | + IUntypedQuery untypedQuery = mock(IUntypedQuery.class); |
| 84 | + IQuery query = mock(IQuery.class); |
| 85 | + |
| 86 | + doReturn(client).when(subject).provideClient(); |
| 87 | + when(client.search()).thenReturn(untypedQuery); |
| 88 | + when(untypedQuery.forResource(anyString())).thenReturn(query); |
| 89 | + when(query.where(any(ICriterion.class))).thenReturn(query); |
| 90 | + when(query.summaryMode(any(SummaryEnum.class))).thenReturn(query); |
| 91 | + when(query.returnBundle(any(Class.class))).thenReturn(query); |
| 92 | + when(query.execute()).thenReturn(executeResult); |
| 93 | + return query; |
| 94 | + } |
| 95 | + |
| 96 | + private Bundle searchsetContaining(Resource resource) { |
| 97 | + Bundle bundle = new Bundle(); |
| 98 | + bundle.setType(Bundle.BundleType.SEARCHSET); |
| 99 | + bundle.addEntry().setResource(resource); |
| 100 | + return bundle; |
| 101 | + } |
| 102 | + |
69 | 103 | private Parameters validateCodeResponse(boolean result, String paramName, String paramValue) { |
70 | 104 | Parameters params = new Parameters(); |
71 | 105 | params.addParameter().setName("result").setValue(new BooleanType(result)); |
@@ -298,6 +332,56 @@ void validateCodeInValueSet_withoutCanonicalUrl_sendsValueSetInlineAndDetectsIna |
298 | 332 | verify(operation).onType("ValueSet"); |
299 | 333 | } |
300 | 334 |
|
| 335 | + // ---------- fetchValueSet summary mode ---------- |
| 336 | + // fetchValueSet(String) is the public IValidationSupport hook HAPI calls to resolve a bound ValueSet. |
| 337 | + // Requesting _summary=false makes the Link terminology service enumerate every code of the value set |
| 338 | + // into ValueSet.expansion.contains, which is the client-side trigger for terminology-service memory |
| 339 | + // exhaustion -- and the expansion is never read (validateCodeInValueSet uses only the canonical URL). |
| 340 | + // These tests pin the summary mode so it cannot be silently changed back. |
| 341 | + |
| 342 | + @Test |
| 343 | + @SuppressWarnings("rawtypes") |
| 344 | + void fetchValueSet_requestsSummaryModeTrue() { |
| 345 | + RemoteTermServiceValidation subject = newSpy(); |
| 346 | + ValueSet valueSet = new ValueSet(); |
| 347 | + valueSet.setUrl(VALUE_SET_URL); |
| 348 | + IQuery query = stubSearchChain(subject, searchsetContaining(valueSet)); |
| 349 | + |
| 350 | + IBaseResource result = subject.fetchValueSet(VALUE_SET_URL); |
| 351 | + |
| 352 | + ArgumentCaptor<SummaryEnum> summaryMode = ArgumentCaptor.forClass(SummaryEnum.class); |
| 353 | + verify(query).summaryMode(summaryMode.capture()); |
| 354 | + assertEquals(SummaryEnum.TRUE, summaryMode.getValue(), |
| 355 | + "fetchValueSet must not request _summary=false: that makes the terminology service " |
| 356 | + + "enumerate the full expansion for a resource whose expansion is never read."); |
| 357 | + assertSame(valueSet, result); |
| 358 | + } |
| 359 | + |
| 360 | + @Test |
| 361 | + @SuppressWarnings("rawtypes") |
| 362 | + void fetchValueSet_noMatch_returnsNull() { |
| 363 | + RemoteTermServiceValidation subject = newSpy(); |
| 364 | + Bundle empty = new Bundle(); |
| 365 | + empty.setType(Bundle.BundleType.SEARCHSET); |
| 366 | + IQuery query = stubSearchChain(subject, empty); |
| 367 | + |
| 368 | + assertNull(subject.fetchValueSet(VALUE_SET_URL)); |
| 369 | + verify(query).summaryMode(SummaryEnum.TRUE); |
| 370 | + } |
| 371 | + |
| 372 | + @Test |
| 373 | + @SuppressWarnings("rawtypes") |
| 374 | + void invokeIsValueSetSupported_requestsSummaryModeTrue() { |
| 375 | + // The existence check has always used _summary=true; fetchValueSet(String) now matches it. |
| 376 | + RemoteTermServiceValidation subject = newSpy(); |
| 377 | + ValueSet valueSet = new ValueSet(); |
| 378 | + valueSet.setUrl(VALUE_SET_URL); |
| 379 | + IQuery query = stubSearchChain(subject, searchsetContaining(valueSet)); |
| 380 | + |
| 381 | + assertTrue(subject.invokeIsValueSetSupported(VALUE_SET_URL)); |
| 382 | + verify(query).summaryMode(SummaryEnum.TRUE); |
| 383 | + } |
| 384 | + |
301 | 385 | // ---------- isCodeSystemSupported / isValueSetSupported delegation to cache ---------- |
302 | 386 | // These methods are hit per-system per-chain-traversal by HAPI's ValidationSupportChain, so the |
303 | 387 | // remote lookup has to go through ValidationCacheService. The tests below verify the delegation |
|
0 commit comments