Skip to content

Commit

Permalink
Merge pull request #24 from FHIR/dev
Browse files Browse the repository at this point in the history
POST Search fixes and unit tests.
  • Loading branch information
GinoCanessa authored Jan 29, 2025
2 parents b0790ae + bdd950d commit 7df4659
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 34 deletions.
22 changes: 13 additions & 9 deletions src/FhirStore.CommonVersioned/Storage/ResourceStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ public class ResourceStore<T> : IVersionedResourceStore
private Dictionary<string, ExecutableSubscriptionInfo> _executableSubscriptions = new();

/// <summary>The supported includes.</summary>
private string[] _supportedIncludes = Array.Empty<string>();
private string[] _supportedIncludes = [];

/// <summary>The supported reverse includes.</summary>
private string[] _supportedRevIncludes = Array.Empty<string>();
private string[] _supportedRevIncludes = [];

/// <summary>Gets the keys.</summary>
/// <typeparam name="string"> Type of the string.</typeparam>
Expand Down Expand Up @@ -525,7 +525,7 @@ public bool TryResolveIdentifier(string system, string value, out Hl7.Fhir.Model

ParsedSubscriptionTopic? parsedSubscriptionTopic = null;
ParsedSubscription? parsedSubscription = null;

// perform any mandatory validation
switch (source.TypeName)
{
Expand Down Expand Up @@ -585,7 +585,7 @@ public bool TryResolveIdentifier(string system, string value, out Hl7.Fhir.Model
source.Meta.Tag = new List<Coding>();
}

if (!source.Meta.Tag.Any(c =>
if (!source.Meta.Tag.Any(c =>
c.System.Equals("http://hl7.org/fhir/us/core/CodeSystem/us-core-tags", StringComparison.Ordinal) &&
c.Code.Equals("patient-supplied")))
{
Expand Down Expand Up @@ -701,8 +701,8 @@ public bool TryResolveIdentifier(string system, string value, out Hl7.Fhir.Model
/// <param name="outcome"> [out] The outcome.</param>
/// <returns>The updated resource, or null if it could not be performed.</returns>
public Resource? InstanceUpdate(
Resource source,
bool allowCreate,
Resource source,
bool allowCreate,
string ifMatch,
string ifNoneMatch,
HashSet<string> protectedResources,
Expand Down Expand Up @@ -1409,7 +1409,7 @@ private void PerformSubscriptionTest(
{
additionalContext.AddRange(reverses);
}
}
}

SubscriptionEvent subEvent = new()
{
Expand Down Expand Up @@ -1648,7 +1648,7 @@ public void RemoveExecutableSearchParameter(string name)

if (_searchParameters.TryGetValue(name, out ModelInfo.SearchParamDefinition? spd))
{
if ((!string.IsNullOrEmpty(spd.Url)) &&
if ((!string.IsNullOrEmpty(spd.Url)) &&
_searchParamUrlsToNames.ContainsKey(spd.Url))
{
_ = _searchParamUrlsToNames.Remove(spd.Url);
Expand Down Expand Up @@ -1757,7 +1757,11 @@ public bool TryGetSearchParamDefinition(string name, [NotNullWhen(true)] out Mod
/// An enumerator that allows foreach to be used to process the search includes in this
/// collection.
/// </returns>
public IEnumerable<string> GetSearchIncludes() => _supportedIncludes;
public IEnumerable<string> GetSearchIncludes() => _searchParameters.Values
.Where(sp => sp.Type == SearchParamType.Reference)
.Where(sp => sp.Name != null)
.Select(sp => this._resourceName + ":" + sp.Name!)
.Order();

/// <summary>Gets the search reverse includes supported by this store.</summary>
/// <returns>
Expand Down
29 changes: 14 additions & 15 deletions src/FhirStore.CommonVersioned/Storage/VersionedFhirStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ public void Init(TenantConfiguration config)
DiscoverInteractionHooks();

// generate our initial capability statement
_ = GenerateCapabilities(new FhirRequestContext(this, "GET", _config.BaseUrl + "/metadata"));
_ = generateCapabilities(new FhirRequestContext(this, "GET", _config.BaseUrl + "/metadata"));

// create a timer to check max resource count if we are monitoring that
_maxResourceCount = config.MaxResourceCount;
Expand Down Expand Up @@ -4360,7 +4360,7 @@ internal bool DoTypeSearch(
FhirRequestContext ctx,
out FhirResponseContext response)
{
string searchQueryParams = string.IsNullOrEmpty(ctx.SourceContent) || ctx.SourceContent != "application/x-www-form-urlencoded"
string searchQueryParams = string.IsNullOrEmpty(ctx.SourceContent) || (ctx.SourceFormat != "application/x-www-form-urlencoded")
? ctx.UrlQuery
: ctx.SourceContent;

Expand Down Expand Up @@ -4674,7 +4674,7 @@ internal bool DoSystemSearch(
FhirRequestContext ctx,
out FhirResponseContext response)
{
string searchQueryParams = string.IsNullOrEmpty(ctx.SourceContent) || ctx.SourceContent != "application/x-www-form-urlencoded"
string searchQueryParams = string.IsNullOrEmpty(ctx.SourceContent) || (ctx.SourceFormat != "application/x-www-form-urlencoded")
? ctx.UrlQuery
: ctx.SourceContent;

Expand Down Expand Up @@ -5572,7 +5572,7 @@ private CapabilityStatement GetCapabilities(FhirRequestContext? ctx)
{
if (_capabilitiesAreStale || (ctx?.Forwarded != null))
{
return GenerateCapabilities(ctx);
return generateCapabilities(ctx);
}

// bypass read to avoid instance read hooks (firing meta hooks)
Expand All @@ -5581,9 +5581,8 @@ private CapabilityStatement GetCapabilities(FhirRequestContext? ctx)

/// <summary>Updates the current capabilities of this store.</summary>
/// <param name="ctx"> The request context.</param>
/// <param name="store">(Optional) The store.</param>
/// <returns>The capability statement.</returns>
private CapabilityStatement GenerateCapabilities(FhirRequestContext? ctx)
private CapabilityStatement generateCapabilities(FhirRequestContext? ctx)
{
string root = getBaseUrl(ctx);
string smartRoot = fhirUrlToSmart(root);
Expand Down Expand Up @@ -5624,7 +5623,7 @@ private CapabilityStatement GenerateCapabilities(FhirRequestContext? ctx)
new() { Code = Hl7.Fhir.Model.CapabilityStatement.SystemRestfulInteraction.SearchSystem },
new() { Code = Hl7.Fhir.Model.CapabilityStatement.SystemRestfulInteraction.Transaction },
},
//SearchParam = new(), // currently, search parameters are expanded out to all-resource
//SearchParam = new(), // search parameters are expanded out to each resource
Operation = _operations.Values
.Where(o => o.AllowSystemLevel)
.Select(o => new CapabilityStatement.OperationComponent()
Expand Down Expand Up @@ -5688,19 +5687,19 @@ private CapabilityStatement GenerateCapabilities(FhirRequestContext? ctx)
Versioning = CapabilityStatement.ResourceVersionPolicy.NoVersion,
//ReadHistory = true,
UpdateCreate = true,
//ConditionalCreate = true,
ConditionalRead = CapabilityStatement.ConditionalReadStatus.NotSupported,
//ConditionalUpdate = true,
ConditionalCreate = true,
ConditionalRead = CapabilityStatement.ConditionalReadStatus.FullSupport,
ConditionalUpdate = true,
//ConditionalPatch = true,
ConditionalDelete = CapabilityStatement.ConditionalDeleteStatus.NotSupported,
ReferencePolicy = new CapabilityStatement.ReferenceHandlingPolicy?[]
{
ReferencePolicy =
[
CapabilityStatement.ReferenceHandlingPolicy.Literal,
//CapabilityStatement.ReferenceHandlingPolicy.Logical,
CapabilityStatement.ReferenceHandlingPolicy.Logical,
//CapabilityStatement.ReferenceHandlingPolicy.Resolves,
//CapabilityStatement.ReferenceHandlingPolicy.Enforced,
CapabilityStatement.ReferenceHandlingPolicy.Local,
},
],
SearchInclude = resourceStore.GetSearchIncludes(),
SearchRevInclude = resourceStore.GetSearchRevIncludes(),
SearchParam = resourceStore.GetSearchParamDefinitions().Select(sp => new CapabilityStatement.SearchParamComponent()
Expand Down Expand Up @@ -5927,7 +5926,7 @@ private void fixTransactionEntryReferencesRecurse(
foreach (Base child in resource.Children)
{
fixTransactionEntryReferencesRecurse(
entryFullUrl,
entryFullUrl,
child,
fullUrlLookup,
originalIdLookup,
Expand Down
79 changes: 79 additions & 0 deletions src/fhir-candle.Tests/R4BTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,45 @@ public void ObservationSearch(string search, int matchCount, int? entryCount = n
}

//_testOutputHelper.WriteLine(bundle);

ctx = new()
{
TenantName = _fixture._store.Config.ControllerName,
Store = _fixture._store,
HttpMethod = "POST",
Url = _fixture._store.Config.BaseUrl + "/Observation/_search",
Forwarded = null,
Authorization = null,
SourceContent = search,
SourceFormat = "application/x-www-form-urlencoded",
DestinationFormat = "application/fhir+json",
};

success = _fixture._store.TypeSearch(
ctx,
out response);

success.ShouldBeTrue();
response.StatusCode.ShouldBe(HttpStatusCode.OK);
response.SerializedResource.ShouldNotBeNullOrEmpty();

results = JsonSerializer.Deserialize<MinimalBundle>(response.SerializedResource);

results.ShouldNotBeNull();
results!.Total.ShouldBe(matchCount);
if (entryCount != null)
{
results!.Entries.ShouldHaveCount((int)entryCount);
}

results!.Links.ShouldNotBeNullOrEmpty();
selfLink = results!.Links!.Where(l => l.Relation.Equals("self"))?.Select(l => l.Url).First() ?? string.Empty;
selfLink.ShouldNotBeNullOrEmpty();
selfLink.ShouldStartWith(_fixture._config.BaseUrl + "/Observation?");
foreach (string searchPart in search.Split('&'))
{
selfLink.ShouldContain(searchPart);
}
}
}

Expand Down Expand Up @@ -324,6 +363,46 @@ public void PatientSearch(string search, int matchCount, int? entryCount = null)
}

//_testOutputHelper.WriteLine(bundle);


ctx = new()
{
TenantName = _fixture._store.Config.ControllerName,
Store = _fixture._store,
HttpMethod = "POST",
Url = _fixture._store.Config.BaseUrl + "/Patient/_search",
Forwarded = null,
Authorization = null,
SourceContent = search,
SourceFormat = "application/x-www-form-urlencoded",
DestinationFormat = "application/fhir+json",
};

success = _fixture._store.TypeSearch(
ctx,
out response);

success.ShouldBeTrue();
response.StatusCode.ShouldBe(HttpStatusCode.OK);
response.SerializedResource.ShouldNotBeNullOrEmpty();

results = JsonSerializer.Deserialize<MinimalBundle>(response.SerializedResource);

results.ShouldNotBeNull();
results!.Total.ShouldBe(matchCount);
if (entryCount != null)
{
results!.Entries.ShouldHaveCount((int)entryCount);
}

results!.Links.ShouldNotBeNullOrEmpty();
selfLink = results!.Links!.Where(l => l.Relation.Equals("self"))?.Select(l => l.Url).First() ?? string.Empty;
selfLink.ShouldNotBeNullOrEmpty();
selfLink.ShouldStartWith(_fixture._config.BaseUrl + "/Patient?");
foreach (string searchPart in search.Split('&'))
{
selfLink.ShouldContain(searchPart);
}
}
}

Expand Down
99 changes: 90 additions & 9 deletions src/fhir-candle.Tests/R4Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,47 @@ public void ObservationSearch(string search, int matchCount, int? entryCount = n
selfLink.ShouldContain(searchPart);
}


ctx = new()
{
TenantName = _fixture._store.Config.ControllerName,
Store = _fixture._store,
HttpMethod = "POST",
Url = _fixture._store.Config.BaseUrl + "/Observation/_search",
Forwarded = null,
Authorization = null,
SourceContent = search,
SourceFormat = "application/x-www-form-urlencoded",
DestinationFormat = "application/fhir+json",
};

success = _fixture._store.TypeSearch(
ctx,
out response);

success.ShouldBeTrue();
response.StatusCode.ShouldBe(HttpStatusCode.OK);
response.SerializedResource.ShouldNotBeNullOrEmpty();

results = JsonSerializer.Deserialize<MinimalBundle>(response.SerializedResource);

results.ShouldNotBeNull();
results!.Total.ShouldBe(matchCount);
if (entryCount != null)
{
results!.Entries.ShouldHaveCount((int)entryCount);
}

results!.Links.ShouldNotBeNullOrEmpty();
selfLink = results!.Links!.Where(l => l.Relation.Equals("self"))?.Select(l => l.Url).First() ?? string.Empty;
selfLink.ShouldNotBeNullOrEmpty();
selfLink.ShouldStartWith(_fixture._config.BaseUrl + "/Observation?");
foreach (string searchPart in search.Split('&'))
{
selfLink.ShouldContain(searchPart);
}


//_testOutputHelper.WriteLine(bundle);
}
}
Expand Down Expand Up @@ -336,6 +377,46 @@ public void PatientSearch(string search, int matchCount, int? entryCount = null)
}

//_testOutputHelper.WriteLine(bundle);

ctx = new()
{
TenantName = _fixture._store.Config.ControllerName,
Store = _fixture._store,
HttpMethod = "POST",
Url = _fixture._store.Config.BaseUrl + "/Patient/_search",
Forwarded = null,
Authorization = null,
SourceContent = search,
SourceFormat = "application/x-www-form-urlencoded",
DestinationFormat = "application/fhir+json",
};

success = _fixture._store.TypeSearch(
ctx,
out response);

success.ShouldBeTrue();
response.StatusCode.ShouldBe(HttpStatusCode.OK);
response.SerializedResource.ShouldNotBeNullOrEmpty();

results = JsonSerializer.Deserialize<MinimalBundle>(response.SerializedResource);

results.ShouldNotBeNull();
results!.Total.ShouldBe(matchCount);
if (entryCount != null)
{
results!.Entries.ShouldHaveCount((int)entryCount);
}

results!.Links.ShouldNotBeNullOrEmpty();
selfLink = results!.Links!.Where(l => l.Relation.Equals("self"))?.Select(l => l.Url).First() ?? string.Empty;
selfLink.ShouldNotBeNullOrEmpty();
selfLink.ShouldStartWith(_fixture._config.BaseUrl + "/Patient?");
foreach (string searchPart in search.Split('&'))
{
selfLink.ShouldContain(searchPart);
}

}
}

Expand Down Expand Up @@ -669,9 +750,9 @@ public R4TestSubscriptions(R4Tests fixture, ITestOutputHelper testOutputHelper)
public void ParseTopic(string json)
{
HttpStatusCode sc = candleR4.FhirCandle.Serialization.SerializationUtils.TryDeserializeFhir(
json,
"application/fhir+json",
out Hl7.Fhir.Model.Resource? r,
json,
"application/fhir+json",
out Hl7.Fhir.Model.Resource? r,
out _);

sc.ShouldBe(HttpStatusCode.OK);
Expand Down Expand Up @@ -767,11 +848,11 @@ public void ParseHandshake(string json)
[InlineData("(%previous.id.empty() or (%previous.status != 'finished')) and (%current.status = 'finished')", true, true, true, true, false, false)]
[InlineData("(%previous.id.empty() | (%previous.status != 'finished')) and (%current.status = 'finished')", true, true, true, true, false, false)]
public void TestSubEncounterNoFilters(
string fpCriteria,
string fpCriteria,
bool onCreate,
bool createResult,
bool createResult,
bool onUpdate,
bool updateResult,
bool updateResult,
bool onDelete,
bool deleteResult)
{
Expand All @@ -789,10 +870,10 @@ public void TestSubEncounterNoFilters(
Url = topicUrl,
ResourceTriggers = new()
{
{
resourceType,
{
resourceType,
new List<ParsedSubscriptionTopic.ResourceTrigger>()
{
{
new ParsedSubscriptionTopic.ResourceTrigger()
{
ResourceType = resourceType,
Expand Down
Loading

0 comments on commit 7df4659

Please sign in to comment.