public void GivenAValidBundle_WithNoNextLink_WhenParsingBatchData_CorrectResultShouldBeReturned()
        {
            var bundle            = TestDataProvider.GetBundleJsonFromFile(TestDataConstants.BundleFile2);
            var resources         = FhirBundleParser.ExtractResourcesFromBundle(bundle);
            var continuationToken = FhirBundleParser.ExtractContinuationToken(bundle);

            Assert.Single(resources);
            Assert.Null(continuationToken);
        }
        public void GivenAValidBundle_WhenParsingBundle_CorrectResultShouldBeReturned()
        {
            var bundle = TestDataProvider.GetBundleJsonFromFile(TestDataConstants.BundleFile1);

            var resources         = FhirBundleParser.ExtractResourcesFromBundle(bundle);
            var continuationToken = FhirBundleParser.ExtractContinuationToken(bundle);

            Assert.Equal(2, resources.Count());
            Assert.Equal("Y29udGludWF0aW9udG9rZW4=", continuationToken);
        }
        public void GivenANullBundle_WhenParsingBundle_EmptyResultShouldBeReturned()
        {
            JObject bundle = null;

            var resources         = FhirBundleParser.ExtractResourcesFromBundle(bundle);
            var continuationToken = FhirBundleParser.ExtractContinuationToken(bundle);

            Assert.Empty(resources);
            Assert.Null(continuationToken);
        }
        public void GivenAnEmptyBundle_WhenParsingBundle_EmptyResultShouldBeReturned()
        {
            var bundle = TestDataProvider.GetBundleJsonFromFile(TestDataConstants.EmptyBundleFile);

            var resources         = FhirBundleParser.ExtractResourcesFromBundle(bundle);
            var continuationToken = FhirBundleParser.ExtractContinuationToken(bundle);

            Assert.Empty(resources);
            Assert.Null(continuationToken);
        }
        public void GivenEmptyResources_WhenGetOperationOutcomes_EmptyResultShouldBeReturned()
        {
            var input   = new List <JObject>();
            var results = FhirBundleParser.GetOperationOutcomes(input);

            Assert.Empty(results);

            input.Add(null);
            results = FhirBundleParser.GetOperationOutcomes(input);
            Assert.Empty(results);
        }
        public void GivenInvalidOperationOutcomeResources_WhenGetOperationOutcomes_EmptyResultShouldBeReturned()
        {
            var jObj1 = JObject.Parse("{\"a\":1}");
            var jObj2 = JObject.Parse("{\"resourceType\":\"Patient\"}");
            var jObj3 = JObject.Parse("{\"resourceType\":\"operationOutcome\"}");

            var input = new List <JObject> {
                jObj1, jObj2, jObj3
            };
            var results = FhirBundleParser.GetOperationOutcomes(input);

            Assert.Empty(results);
        }
        public void GivenValidOperationOutcomeResources_WhenGetOperationOutcomes_EmptyResultShouldBeReturned()
        {
            var jObj1 = JObject.Parse("{\"resourceType\":\"OperationOutcome\"}");
            var jObj2 = JObject.Parse(TestDataProvider.GetBundleFromFile(TestDataConstants.InvalidResponseFile));
            var input = new List <JObject> {
                jObj1, jObj2
            };
            var results = FhirBundleParser.GetOperationOutcomes(input);

            Assert.Equal(2, results.Count());

            input.Add(JObject.Parse("{\"resourceType\":\"Patient\"}"));
            var updatedResults = FhirBundleParser.GetOperationOutcomes(input);

            Assert.Equal(2, updatedResults.Count());

            Assert.Equal(results.ToString(), updatedResults.ToString());
        }
 public void GivenNullResources_WhenGetOperationOutcomes_ExceptionShouldBeThrown()
 {
     Assert.Throws <ArgumentNullException>(() => FhirBundleParser.GetOperationOutcomes(null));
 }
        // the job/task main progress:
        // 1. The retrieved fhir resources and its search progress are in memory cache temporarily;
        // 2. the cached resources will be committed to blob through "TryCommitResultAsync()" function when the number of cached resources reaches the specified value or the task is finished,
        //    the fields of task context (statistical fields and searchProgress) are updated concurrently;
        // 3. when the task is completed, set the task status to completed
        // 4. once the task context is updated either from step 2 or step 3, calls "JobProgressUpdater.Produce()" to sync task context to job
        // 5. "JobProgressUpdater.Consume" will handle the updated task context,
        //    when the task is completed, add statistical fields and patient version id to job and remove it from runningTasks;
        // 6. call "_jobStore.UpdateJobAsync()" to save the job context to storage in "JobProgressUpdater.Consume()" at regular intervals or when completing producing task context
        public async Task <TaskResult> ExecuteAsync(
            TaskContext taskContext,
            JobProgressUpdater progressUpdater,
            CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                _logger.LogInformation($"Task is cancelled.");
                throw new OperationCanceledException();
            }

            _logger.LogInformation($"Start execute task {taskContext.TaskIndex}.");

            // Initialize cache result from the search progress of task context
            var cacheResult = new CacheResult(taskContext.SearchProgress.Copy());

            switch (taskContext.FilterScope)
            {
            case FilterScope.Group:
            {
                var isPatientResourcesRequired = IsPatientResourcesRequired(taskContext);

                for (var patientIndex = cacheResult.SearchProgress.CurrentIndex; patientIndex < taskContext.Patients.Count; patientIndex++)
                {
                    // start a new patient, reset all the search progress fields except currentIndex to initial value,
                    // otherwise, there are two possible cases:
                    // 1. this is a new task, the currentIndex is 0, in this case, all the search progress fields are initial values
                    // 2. this is a resumed task, continue with the recorded search progress
                    if (cacheResult.SearchProgress.CurrentIndex != patientIndex)
                    {
                        cacheResult.SearchProgress.UpdateCurrentIndex(patientIndex);
                    }

                    var patientInfo          = taskContext.Patients[patientIndex];
                    var lastPatientVersionId = patientInfo.VersionId;

                    // the patient resource isn't included in compartment search,
                    // so we need additional request to get the patient resource

                    // the patient resource is not retrieved yet,
                    // for resumed task, the current patient may be retrieved and its version id exists.
                    if (!cacheResult.SearchProgress.PatientVersionId.ContainsKey(patientInfo.PatientId))
                    {
                        var patientResource = await GetPatientResource(patientInfo, cancellationToken);

                        // the patient does not exist
                        if (patientResource == null)
                        {
                            continue;
                        }

                        var currentPatientVersionId = FhirBundleParser.ExtractVersionId(patientResource);

                        if (currentPatientVersionId == 0)
                        {
                            _logger.LogError($"Failed to extract version id for patient {patientInfo.PatientId}.");
                            throw new FhirSearchException($"Failed to extract version id for patient {patientInfo.PatientId}.");
                        }

                        // New patient or the patient is updated.
                        if (lastPatientVersionId != currentPatientVersionId)
                        {
                            // save the patient resource to cache if the patient resource type is required in the result
                            if (isPatientResourcesRequired)
                            {
                                AddFhirResourcesToCache(new List <JObject> {
                                        patientResource
                                    }, cacheResult);
                            }
                        }

                        // add this patient's version id in cacheResult,
                        // the version id will be synced to taskContext when the cache result is committed, and be recorded in job/schedule metadata further
                        cacheResult.SearchProgress.PatientVersionId[patientInfo.PatientId] = currentPatientVersionId;
                        _logger.LogInformation($"Get patient resource {patientInfo.PatientId} successfully.");
                    }

                    // the version id is 0 for newly patient
                    // for new patient, we will retrieve all its compartments resources from {since}
                    // for processed patient, we will only retrieve the updated compartment resources from last scheduled time
                    var startDateTime = lastPatientVersionId == 0
                            ? taskContext.Since
                            : taskContext.DataPeriod.Start;
                    var parameters = new List <KeyValuePair <string, string> >
                    {
                        new (FhirApiConstants.LastUpdatedKey, $"ge{startDateTime.ToInstantString()}"),
                        new (FhirApiConstants.LastUpdatedKey, $"lt{taskContext.DataPeriod.End.ToInstantString()}"),
                    };

                    // create initial compartment search option for this patient,
                    // the resource type and customized parameters of each filter will be set later.
                    var searchOption = new CompartmentSearchOptions(
                        FhirConstants.PatientResource,
                        patientInfo.PatientId,
                        null,
                        parameters);

                    // retrieve this patient's compartment resources for all the filters
                    await ProcessFiltersAsync(taskContext, searchOption, cacheResult, progressUpdater, cancellationToken);

                    _logger.LogInformation($"Process patient resource {patientInfo.PatientId} successfully.");
                }

                break;
            }