From 1090901f430e3cb4cedf08a9e2cd3d5a9c6072de Mon Sep 17 00:00:00 2001 From: Tom Strassner Date: Wed, 20 Nov 2024 18:16:49 -0600 Subject: [PATCH 1/3] Allow for possibility of Bundle response in payer suite The payer suite assumed the response to the questionnaire-package operation was a Parameters instance, but it can instead be a Bundle. This change accounts for both possibilities. There are a lot of changes, because there are many places in the code that relied on the assumption that the request was a Parameters. --- lib/davinci_dtr_test_kit/cql_test.rb | 177 ++++-------------- .../adaptive_form_libraries_test.rb | 4 +- ...ive_form_questionnaire_expressions_test.rb | 7 +- ...tive_form_questionnaire_extensions_test.rb | 5 +- ...ive_next_questionnaire_expressions_test.rb | 7 +- ...tive_next_questionnaire_extensions_test.rb | 5 +- ...aptive_response_bundles_validation_test.rb | 15 +- ...daptive_response_search_validation_test.rb | 27 +-- ...erver_adaptive_response_validation_test.rb | 56 +++--- ...ayer_server_next_response_complete_test.rb | 7 +- ...er_server_next_response_validation_test.rb | 28 +-- .../static_form_libraries_test.rb | 4 +- ...tic_form_questionnaire_expressions_test.rb | 7 +- ...atic_form_questionnaire_extensions_test.rb | 5 +- .../static_form_response_validation_test.rb | 56 +++--- lib/davinci_dtr_test_kit/validation_test.rb | 5 +- spec/davinci_dtr_test_kit/cql_test_spec.rb | 12 +- ...e_group_static_response_validation_spec.rb | 2 +- 18 files changed, 174 insertions(+), 255 deletions(-) diff --git a/lib/davinci_dtr_test_kit/cql_test.rb b/lib/davinci_dtr_test_kit/cql_test.rb index 94a2e55..cadc48a 100644 --- a/lib/davinci_dtr_test_kit/cql_test.rb +++ b/lib/davinci_dtr_test_kit/cql_test.rb @@ -48,53 +48,10 @@ def reset_cql_tests extension_presence.each_key { |k| extension_presence[k] = false } end - def evaluate_responses_extensions(resource) - found_questionnaire = false - resource.each do |individual_resource| - next unless individual_resource.resourceType == 'QuestionnaireResponse' - - individual_resource.contained.each_with_index do |questionnaire, q_index| - # Do out put parameters have a bundle? - next unless questionnaire.resourceType == 'Questionnaire' - - # check the libraries first so references in questionnaires can be checked after - found_questionnaire = true - check_questionnaire_extensions(questionnaire, q_index) - end - end - found_questionnaire - end - - def evaluate_parameters_extensions(resource) - found_questionnaire = false - resource.parameter.each do |param| - # Do out put parameters have a bundle? - next unless param.resource.resourceType == 'Bundle' - - param.resource.entry.each_with_index do |entry, q_index| - # check questionnaire extensions - next unless entry.resource.resourceType == 'Questionnaire' - - found_questionnaire = true - check_questionnaire_extensions(entry.resource, q_index) - ## NEED TO FIGURE OUT HOW TO FAIL TEST WHEN POORLY FORMATTED EXPRESSIONS FOUND - end - end - found_questionnaire - end - - # extensions - def questionnaire_extensions_test(response) - resource = process_response(response) - assert !resource.nil?, 'Response is null or not a valid type.' - found_questionnaire = false - if resource.instance_of? Array - found_questionnaire = evaluate_responses_extensions(resource) - elsif resource.resourceType == 'Parameters' - found_questionnaire = evaluate_parameters_extensions(resource) - end + def verify_questionnaire_extensions(questionnaires) + assert questionnaires&.any? && questionnaires.all? { |q| q.is_a? FHIR::Questionnaire }, 'No questionnaires found.' + questionnaires.each_with_index { |q, q_index| check_questionnaire_extensions(q, q_index) } check_library_references - assert found_questionnaire, 'No questionnaires found.' assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.' assert cql_presence['variable'], 'Variable expression logic not written in CQL.' assert cql_presence['launch_context'], 'Launch context expression logic not written in CQL.' @@ -140,53 +97,11 @@ def check_questionnaire_extensions(questionnaire, q_index) assert misformatted_expressions.compact.empty?, 'Expression in questionnaire misformatted.' end - # items - def evaluate_responses_items(resource) - found_questionnaire = false - resource.each_with_index do |individual_resource, q_index| - next unless individual_resource.resourceType == 'QuestionnaireResponse' - - individual_resource.contained.each do |questionnaire| - next unless questionnaire.resourceType == 'Questionnaire' + def verify_questionnaire_items(questionnaires, final_cql_test: false) + assert questionnaires&.any? && questionnaires.all? { |q| q.is_a? FHIR::Questionnaire }, 'No questionnaires found.' + questionnaires.each_with_index { |q, q_index| check_questionnaire_items(q, q_index) } - # check the libraries first so references in questionnaires can be checked after - found_questionnaire = true - check_questionnaire_items(questionnaire, q_index) - end - end - found_questionnaire - end - - def evaluate_parameters_items(resource) - found_bundle = found_questionnaire = false - resource.parameter.each do |param| - next unless param.resource.resourceType == 'Bundle' - - found_bundle = true - # check the libraries first so references in questionnaires can be checked after - param.resource.entry.each_with_index do |entry, q_index| - if entry.resource.resourceType == 'Questionnaire' - found_questionnaire = true - check_questionnaire_items(entry.resource, q_index) - end - end - end - [found_bundle, found_questionnaire] - end - - def questionnaire_items_test(response, final_cql_test) - resource = process_response(response) - assert !resource.nil?, 'Response is null or not a valid type.' - found_questionnaire = false - # are extensions present in any questionnaire? - if resource.instance_of? Array - found_questionnaire = evaluate_responses_items(resource) - elsif resource.resourceType == 'Parameters' - found_bundle, found_questionnaire = evaluate_parameters_items(resource) - assert found_bundle, 'No questionnaire bundles found.' - end begin - assert found_questionnaire, 'No questionnaires found.' assert !found_non_cql_expression, 'Found non-cql expression.' assert extension_presence.value?(true), 'No extensions found. Questionnaire must demonstrate prepopulation.' assert cql_presence['init_expression'], 'Initial expression logic not written in CQL.' @@ -281,12 +196,12 @@ def check_questionnaire_items(questionnaire, q_index) assert misformatted_expressions.compact.to_set.empty?, 'Expression in questionnaire misformatted.' end - def evaluate_library(entry, lib_index) + def evaluate_library(library) found_cql = found_elm = false - entry.resource.content.each do |content| + library.content.each do |content| if content.data.nil? messages << { type: 'info', - message: format_markdown("[library #{lib_index + 1}] content element included no data.") } + message: format_markdown("[library #{library.url}] content element included no data.") } end if content.contentType == 'text/cql' found_cql = true @@ -294,42 +209,30 @@ def evaluate_library(entry, lib_index) found_elm = true else messages << { type: 'info', - message: format_markdown("[library #{lib_index + 1}] has non-cql/elm content.") } + message: format_markdown("[library #{library.url}] has non-cql/elm content.") } true end - next unless library_names.include? entry.resource.name + next unless library_names.include? library.name found_duplicate_library_name = true - messages << { type: 'info', message: format_markdown("[library #{lib_index + 1}] has a name, - #{entry.resource.name}, that is already included in the bundle.") } + messages << { type: 'info', message: format_markdown("[library #{library.url}] has a name, + #{library.name}, that is already included in the bundle.") } assert !found_duplicate_library_name, 'Found duplicate library names - all names must be unique.' end - assert found_cql, "[library #{lib_index + 1}] does not include CQL." - assert found_elm, "[library #{lib_index + 1}] does not include ELM." + assert found_cql, "[library #{library.url}] does not include CQL." + assert found_elm, "[library #{library.url}] does not include ELM." end - def check_libraries(payer_response) - resource = process_response(payer_response) - assert !resource.nil?, 'Response is null or not a valid type.' - found_bundle = found_libraries = false - # are extensions present in any questionnaire? - resource.parameter.each do |param| - # Do out put parameters have a bundle? - next unless param.resource.resourceType == 'Bundle' - - found_bundle = true - # check the libraries first so references in questionnaires can be checked after - param.resource.entry.each_with_index do |entry, index| - next unless entry.resource.resourceType == 'Library' - - found_libraries = true - library_urls.add(entry.resource.url) unless entry.resource.url.nil? - evaluate_library(entry, index) - library_names.add(entry.resource.name) - end - assert found_libraries, 'No Libraries found.' + def check_libraries(questionnaire_bundles) + libraries = extract_libraries_from_bundles(questionnaire_bundles) + + assert libraries.any?, 'No Libraries found.' + + libraries.each do |lib| + library_urls.add(lib.url) unless lib.url.nil? + evaluate_library(lib) + library_names.add(lib.name) end - assert found_bundle, 'No questionnaire bundles found.' end def check_library_references @@ -405,28 +308,22 @@ def check_for_cql(extension, extension_name, index, q_index, url, link_id = '') end end - def q_responses(response) - questionnaire_responses = [] - response.each do |resource| - fhir_resource = FHIR.from_contents(resource.response_body) - questionnaire_responses << fhir_resource if fhir_resource.resourceType == 'QuestionnaireResponse' - next unless resource.instance_of? Inferno::Entities::Request - - if fhir_resource.resourceType == 'Questionnaire' || fhir_resource.resourceType == 'Parameters' - return fhir_resource - end - end - questionnaire_responses + def extract_contained_questionnaires(questionnaire_responses) + questionnaire_responses&.filter_map do |qr| + qr.contained&.filter { |resource| resource.is_a?(FHIR::Questionnaire) } + end&.flatten&.compact end - def process_response(response) - if response.instance_of?(FHIR::Parameters) || response.instance_of?(FHIR::QuestionnaireResponse) - return response - elsif response.instance_of? Array - return q_responses(response) - end + def extract_questionnaires_from_bundles(questionnaire_bundles) + questionnaire_bundles.filter_map do |qb| + qb.entry.filter_map { |entry| entry.resource if entry.resource.is_a?(FHIR::Questionnaire) } + end&.flatten&.compact + end - nil + def extract_libraries_from_bundles(questionnaire_bundles) + questionnaire_bundles.filter_map do |qb| + qb.entry.filter_map { |entry| entry.resource if entry&.resource.is_a?(FHIR::Library) } + end&.flatten&.compact end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb index 22db9ee..cf604c5 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_libraries_test.rb @@ -12,8 +12,8 @@ class PayerAdaptiveFormLibrariesTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.' - check_libraries(scratch[:adaptive_responses]) + skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + check_libraries(scratch[:adaptive_questionnaire_bundles]) end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb index 4ca0463..f96a4e9 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_expressions_test.rb @@ -12,9 +12,10 @@ class PayerAdaptiveFormExpressionsTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.' - questionnaire_items_test(scratch[:adaptive_responses], final_cql_test: false) - scratch[:adaptive_responses] = nil + skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles]) + verify_questionnaire_items(questionnaires) + scratch[:adaptive_questionnaire_bundles] = nil end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb index 8c9d7e2..682b595 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_form_questionnaire_extensions_test.rb @@ -12,8 +12,9 @@ class PayerAdaptiveFormExtensionsTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - skip_if scratch[:adaptive_responses].nil?, 'No questionnaire bundle returned.' - questionnaire_extensions_test(scratch[:adaptive_responses]) + skip_if scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + questionnaires = extract_questionnaires_from_bundles(scratch[:adaptive_questionnaire_bundles]) + verify_questionnaire_extensions(questionnaires) end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb index d39781d..610d9e3 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb @@ -12,9 +12,10 @@ class PayerStaticFormExpressionsTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - skip_if scratch[:next_responses].nil?, 'No questionnaires returned.' - questionnaire_items_test(scratch[:next_responses], final_cql_test: true) - scratch[:next_responses] = nil + skip_if scratch[:next_question_questionnaire_responses].nil?, 'No questionnaires returned.' + questionnaires = extract_contained_questionnaires(scratch[:next_question_questionnaire_responses]) + verify_questionnaire_items(questionnaires, final_cql_test: true) + scratch[:next_question_questionnaire_responses] = nil end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb index 1d52f09..f5b88ed 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb @@ -12,8 +12,9 @@ class PayerStaticFormExtensionsTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - skip_if scratch[:next_responses].nil?, 'No questionnaires returned.' - questionnaire_extensions_test(scratch[:next_responses]) + skip_if scratch[:next_question_questionnaire_responses].nil?, 'No questionnaires returned.' + questionnaires = extract_contained_questionnaires(scratch[:next_question_questionnaire_responses]) + verify_questionnaire_extensions(questionnaires) end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb index 6167ca2..d54eee0 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_bundles_validation_test.rb @@ -22,15 +22,12 @@ class PayerAdaptiveFormResponseBundlesTest < Inferno::Test skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' test_passed = true profile_url = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1' - assert !scratch[:adaptive_responses].nil?, 'No resources to validate.' - scratch[:adaptive_responses].each_with_index do |resource, index| - fhir_resource = FHIR.from_contents(resource.response[:body]) - fhir_resource.parameter.each do |param| - resource_is_valid = validate_resource(param.resource, :bundle, profile_url, index) - test_passed = false unless resource_is_valid - rescue StandardError - next - end + assert !scratch[:adaptive_questionnaire_bundles].nil?, 'No resources to validate.' + scratch[:adaptive_questionnaire_bundles].each_with_index do |bundle, index| + resource_is_valid = validate_resource(bundle, :bundle, profile_url, index) + test_passed = false unless resource_is_valid + rescue StandardError + next end if !test_passed && !tests_failed[profile_url].blank? assert test_passed, "Not all returned resources conform to the profile: #{profile_url}" diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb index 1d3056f..d4a2451 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_search_validation_test.rb @@ -3,7 +3,7 @@ module DaVinciDTRTestKit class PayerAdaptiveFormResponseSearchTest < Inferno::Test include DaVinciDTRTestKit::ValidationTest id :payer_server_adaptive_response_search_validation_test - title 'Validate that the adaptive response contains valid a Adaptive Form Search resource' + title 'Validate that the adaptive response contains a valid Adaptive Form Search resource' description %( This test validates the conformance of the payer's response to the [DTR Questionnaire for Adaptive Form Search](http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search) @@ -22,18 +22,21 @@ class PayerAdaptiveFormResponseSearchTest < Inferno::Test skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' test_passed = true profile_url = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-questionnaire-adapt-search|2.0.1' - assert !scratch[:adaptive_responses].nil?, 'No resources to validate.' - scratch[:adaptive_responses].each_with_index do |resource, index| - fhir_resource = FHIR.from_contents(resource.response[:body]) - fhir_resource.parameter.each do |param| - param.resource.entry.each do |entry| - resource_is_valid = validate_resource(entry.resource, :questionnaire, profile_url, index) - test_passed = false unless resource_is_valid - end - rescue StandardError - next - end + assert !scratch[:adaptive_questionnaire_bundles].nil?, 'No questionnaire bundles to validate.' + + questionnaires = scratch[:adaptive_questionnaire_bundles].filter_map do |bundle| + bundle.entry&.filter_map { |entry| entry.resource if entry.resource&.resourceType == 'Questionnaire' } + end&.flatten&.compact + + assert questionnaires&.any?, 'No adaptive questionnaires to validate.' + + questionnaires.each_with_index do |questionnaire, index| + resource_is_valid = validate_resource(questionnaire, :questionnaire, profile_url, index) + test_passed = false unless resource_is_valid + rescue StandardError + next end + if !test_passed && !tests_failed[profile_url].blank? assert test_passed, "Not all returned resources conform to the profile: #{profile_url}" end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb index c6f19c5..f884e70 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_response_validation_test.rb @@ -3,12 +3,16 @@ module DaVinciDTRTestKit class PayerAdaptiveFormQuestionnaireResponseTest < Inferno::Test include DaVinciDTRTestKit::ValidationTest id :payer_server_adaptive_response_validation_test - title 'Validate that the adaptive response conforms to the Output Parameters profile' + title 'Validate that the adaptive response conforms to the DTR Questionnaire Package operation definition' # output :questionnaire_response description %( - This test validates the conformance of the payer's response to the - [DTR Output Parameters](http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters) - structure. + Inferno will validate that the payer server's response to the questionnaire-package operation is conformant + to the + [Questionnaire Package operation definition](https://hl7.org/fhir/us/davinci-dtr/STU2/OperationDefinition-questionnaire-package.html). + This includes verifying that the response conforms to the + [DTR Questionnaire Package Bundle profile](https://hl7.org/fhir/us/davinci-dtr/STU2/StructureDefinition-DTR-QPackageBundle.html) + and, in the event that the server includes that Bundle in a Parameters object, the + [DTR Questionnaire Package Output Parameters profile](https://hl7.org/fhir/us/davinci-dtr/STU2/StructureDefinition-dtr-qpackage-output-parameters.html). It verifies the presence of mandatory elements and that elements with required bindings contain appropriate values. CodeableConcept element bindings will fail if none of their codings have a code/system belonging @@ -21,28 +25,34 @@ class PayerAdaptiveFormQuestionnaireResponseTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - profile_with_version = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters|2.0.1' endpoint = custom_endpoint.blank? ? '/Questionnaire/$questionnaire-package' : custom_endpoint - if initial_adaptive_questionnaire_request.nil? - # making the assumption that only one response was received - if there were multiple, we are only validating the - # first - response = load_tagged_requests(QUESTIONNAIRE_TAG)[0] - scratch[:adaptive_responses] = [response] - resource = FHIR.from_contents(response.response[:body]) + req = if initial_adaptive_questionnaire_request.nil? + # making the assumption that only one response was received - if there were multiple, we are only + # validating the first + load_tagged_requests(QUESTIONNAIRE_TAG)[0] + else + fhir_operation("#{url}#{endpoint}", body: JSON.parse(initial_adaptive_questionnaire_request), + headers: { 'Content-Type': 'application/json' }) + end + + assert_response_status([200, 201], response: req.response) + + resource = FHIR.from_contents(req.response_body) + if resource&.resourceType == 'Parameters' + scratch[:adaptive_questionnaire_bundles] = resource.parameter.filter_map do |param| + param.resource if param.resource&.resourceType == 'Bundle' + end + assert_valid_resource(resource:, + profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters|2.0.1') + questionnaire_bundle = resource.parameter.find { |param| param.resource.resourceType == 'Bundle' }&.resource + assert questionnaire_bundle, 'No questionnaire bundle found in the response' + elsif resource&.resourceType == 'Bundle' + scratch[:adaptive_questionnaire_bundles] = [resource] + assert_valid_resource(resource:, + profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1') else - response = fhir_operation("#{url}#{endpoint}", body: JSON.parse(initial_adaptive_questionnaire_request), - headers: { 'Content-Type': 'application/json' }) - resource = FHIR.from_contents(response.response[:body]) - scratch[:adaptive_responses] = [response] + assert(false, "Unexpected resourceType: #{resource&.resourceType}. Expected Parameters or Bundle") end - - assert !scratch[:adaptive_responses].nil?, 'No resources to validate.' - assert_response_status([200, 201], response: response.response) - assert_resource_type(:parameters, resource:) - assert_valid_resource(resource:, profile_url: profile_with_version) - questionnaire_bundle = resource.parameter.find { |param| param.resource.resourceType == 'Bundle' }&.resource - assert questionnaire_bundle, 'No questionnaire bundle found in the response' - assert_valid_resource(resource: questionnaire_bundle, profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1') end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb index 78b35ab..86f15aa 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_complete_test.rb @@ -8,10 +8,9 @@ class PayerAdaptiveFormCompleteTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - assert !scratch[:next_responses].nil?, 'No resources to validate.' - assert scratch[:next_responses].any? { |r| - JSON.parse(r.response_body)['status'] == 'completed' - }, 'Next request sequence did not result in a completed questionnaire.' + assert !scratch[:next_question_questionnaire_responses].nil?, 'No resources to validate.' + assert scratch[:next_question_questionnaire_responses].any? { |qr| qr.status == 'completed' }, + 'Next request sequence did not result in a completed questionnaire.' end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb index 42ab52c..588113a 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_next_response_validation_test.rb @@ -20,20 +20,24 @@ class PayerAdaptiveFormNextResponseTest < Inferno::Test run do skip_if retrieval_method == 'Static', 'Performing only static flow tests - only one flow is required.' - if next_question_requests.nil? - resources = load_tagged_requests(NEXT_TAG) - else - json_requests = JSON.parse(next_question_requests) - resources = json_requests.map do |resource| - fhir_operation("#{url}/Questionnaire/$next-question", - body: resource, - headers: { 'Content-Type': 'application/json' }) - end + reqs = if next_question_requests.nil? + load_tagged_requests(NEXT_TAG) + else + json_requests = JSON.parse(next_question_requests) + json_requests.map do |resource| + fhir_operation("#{url}/Questionnaire/$next-question", + body: resource, + headers: { 'Content-Type': 'application/json' }) + end + end + assert !reqs.nil?, 'No requests to validate.' + scratch[:next_question_questionnaire_responses] = reqs.map do |req| + assert_response_status([200, 202], request: req) + FHIR.from_contents(req.response_body) end - assert !resources.nil?, 'No resources to validate.' - scratch[:next_responses] = resources + perform_response_validation_test( - resources, + scratch[:next_question_questionnaire_responses], :questionnaireResponse, 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse' ) diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb index 1054a61..904e00e 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_libraries_test.rb @@ -12,8 +12,8 @@ class PayerStaticFormLibrariesTest < Inferno::Test run do skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.' - skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.' - check_libraries(scratch[:output_parameters]) + skip_if scratch[:static_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + check_libraries(scratch[:static_questionnaire_bundles]) end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb index bed1002..4d89ec3 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb @@ -12,9 +12,10 @@ class PayerStaticFormExpressionsTest < Inferno::Test run do skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.' - skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.' - questionnaire_items_test(scratch[:output_parameters], final_cql_test: true) - scratch[:output_parameters] = nil + skip_if scratch[:static_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + questionnaires = extract_questionnaires_from_bundles(scratch[:static_questionnaire_bundles]) + verify_questionnaire_items(questionnaires, final_cql_test: true) + scratch[:static_questionnaire_bundles] = nil end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb index 627aba6..c270e31 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb @@ -12,8 +12,9 @@ class PayerStaticFormExtensionsTest < Inferno::Test run do skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.' - skip_if scratch[:output_parameters].nil?, 'No questionnaire bundle returned.' - questionnaire_extensions_test(scratch[:output_parameters]) + skip_if scratch[:static_questionnaire_bundles].nil?, 'No questionnaire bundle returned.' + questionnaires = extract_questionnaires_from_bundles(scratch[:static_questionnaire_bundles]) + verify_questionnaire_extensions(questionnaires) end end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb index 849b590..cfe5cd1 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_response_validation_test.rb @@ -5,44 +5,50 @@ class PayerStaticFormResponseTest < Inferno::Test include URLs include DaVinciDTRTestKit::ValidationTest id :dtr_v201_payer_static_form_response_test - title 'Validate that the static response conforms to the Output Parameters profile' + title 'Validate that the static response conforms to the DTR Questionnaire Package operation definition.' description %( - Inferno will validate that the payer server response conforms to the - [Output Parameters profile](http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters). + Inferno will validate that the payer server's response to the questionnaire-package operation is conformant + to the + [Questionnaire Package operation definition](https://hl7.org/fhir/us/davinci-dtr/STU2/OperationDefinition-questionnaire-package.html). + This includes verifying that the response conforms to the + [DTR Questionnaire Package Bundle profile](https://hl7.org/fhir/us/davinci-dtr/STU2/StructureDefinition-DTR-QPackageBundle.html) + and, in the event that the server includes that Bundle in a Parameters object, the + [DTR Questionnaire Package Output Parameters profile](https://hl7.org/fhir/us/davinci-dtr/STU2/StructureDefinition-dtr-qpackage-output-parameters.html). It verifies the presence of mandatory elements and that elements with required bindings contain appropriate values. CodeableConcept element bindings will fail if none of their codings have a code/system belonging to the bound ValueSet. Quantity, Coding, and code element bindings will fail if their code/system are not found in the valueset. - ) input :url run do skip_if retrieval_method == 'Adaptive', 'Performing only adaptive flow tests - only one flow is required.' - profile_with_version = 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters|2.0.1' - if initial_static_questionnaire_request.nil? - skip_if access_token.nil?, 'No access token provided - required for client flow.' - resources = load_tagged_requests(QUESTIONNAIRE_TAG) - skip_if resources.nil?, 'No request resource received from the client.' - scratch[:output_parameters] = resources - # making the assumption that only one response was received- if there were multiple, we are only validating the - # first - assert_valid_resource(resource: FHIR.from_contents(resources[0].request[:body]), - profile_url: profile_with_version) - else - request = fhir_operation("#{url}/Questionnaire/$questionnaire-package", - body: JSON.parse(initial_static_questionnaire_request), - headers: { 'Content-Type': 'application/json' }) - assert_valid_json(request.response[:body]) - resource = FHIR.from_contents(request.response[:body]) - scratch[:output_parameters] = resource - assert_response_status([200, 201], response: request.response) - assert_resource_type(:parameters, resource:) - assert_valid_resource(resource:, profile_url: profile_with_version) + req = if initial_static_questionnaire_request.nil? + load_tagged_requests(QUESTIONNAIRE_TAG) + else + fhir_operation("#{url}/Questionnaire/$questionnaire-package", + body: JSON.parse(initial_static_questionnaire_request), + headers: { 'Content-Type': 'application/json' }) + end + + assert_response_status([200, 201], response: request.response) + + resource = FHIR.from_contents(req.response_body) + if resource&.resourceType == 'Parameters' + scratch[:static_questionnaire_bundles] = resource.parameter&.filter_map do |param| + param.resource if param.resource&.resourceType == 'Bundle' + end + assert_valid_resource(resource:, + profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters|2.0.1') questionnaire_bundle = resource.parameter.find { |param| param.resource.resourceType == 'Bundle' }&.resource assert questionnaire_bundle, 'No questionnaire bundle found in the response' - assert_valid_resource(resource: questionnaire_bundle, profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1') + elsif resource&.resourceType == 'Bundle' + scratch[:static_questionnaire_bundles] = [resource] + assert_valid_resource(resource:, + profile_url: 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/DTR-QPackageBundle|2.0.1') + else + assert(false, "Unexpected resourceType: #{resource&.resourceType}. Expected Parameters or Bundle") end end end diff --git a/lib/davinci_dtr_test_kit/validation_test.rb b/lib/davinci_dtr_test_kit/validation_test.rb index 0a0dee6..70b9b01 100644 --- a/lib/davinci_dtr_test_kit/validation_test.rb +++ b/lib/davinci_dtr_test_kit/validation_test.rb @@ -59,10 +59,7 @@ def perform_response_validation_test( omit_if resources.blank?, "No #{resource_type} resources provided so the #{profile_url} profile does not apply" resources.each_with_index do |resource, index| - assert_valid_json(resource.response[:body]) - fhir_resource = FHIR.from_contents(resource.response[:body]) - assert_response_status([200, 202], request: resource, response: resource.response) - validate_resource(fhir_resource, resource_type, profile_url, index) + validate_resource(resource, resource_type, profile_url, index) end return if tests_failed[profile_url].blank? diff --git a/spec/davinci_dtr_test_kit/cql_test_spec.rb b/spec/davinci_dtr_test_kit/cql_test_spec.rb index e6cbbef..981450f 100644 --- a/spec/davinci_dtr_test_kit/cql_test_spec.rb +++ b/spec/davinci_dtr_test_kit/cql_test_spec.rb @@ -10,10 +10,9 @@ context 'when output is valid' do let(:scratch) do - { output_parameters: FHIR.from_contents( - File.read(File.join(__dir__, '..', 'fixtures', - 'questionnaire_package_output_params_conformant.json')) - ) } + output_params = FHIR.from_contents(File.read(File.join(__dir__, '..', 'fixtures', + 'questionnaire_package_output_params_conformant.json'))) + { static_questionnaire_bundles: [output_params.parameter.first.resource] } end describe 'static questionnaire package libraries test' do @@ -37,10 +36,11 @@ context 'when output is invalid' do let(:scratch) do - { output_parameters: FHIR.from_contents( + output_params = FHIR.from_contents( File.read(File.join(__dir__, '..', 'fixtures', 'questionnaire_package_output_params_non_conformant.json')) - ) } + ) + { static_questionnaire_bundles: [output_params.parameter.first.resource] } end describe 'static questionnaire package has no libraries test' do diff --git a/spec/davinci_dtr_test_kit/dtr_payer_server_questionnaire_package_group_static_response_validation_spec.rb b/spec/davinci_dtr_test_kit/dtr_payer_server_questionnaire_package_group_static_response_validation_spec.rb index 26cbe35..400255c 100644 --- a/spec/davinci_dtr_test_kit/dtr_payer_server_questionnaire_package_group_static_response_validation_spec.rb +++ b/spec/davinci_dtr_test_kit/dtr_payer_server_questionnaire_package_group_static_response_validation_spec.rb @@ -43,7 +43,7 @@ def resource_type assert_response_status([200, 201], response: request.response) assert_resource_type(:parameters, resource:) perform_response_validation_test( - [request], + [resource], :parameters, 'http://hl7.org/fhir/us/davinci-dtr/StructureDefinition/dtr-qpackage-output-parameters|2.0.1' ) From ebd3288b597a1ae9f374cf7a5b60114dd1d94a5b Mon Sep 17 00:00:00 2001 From: Tom Strassner Date: Thu, 21 Nov 2024 10:46:18 -0600 Subject: [PATCH 2/3] Resolve naming conflicts between static and adaptive tests --- .../adaptive_next_questionnaire_expressions_test.rb | 6 +++--- .../adaptive_next_questionnaire_extensions_test.rb | 6 +++--- .../payer_server_groups/payer_server_adaptive_group.rb | 4 ++-- .../static_form_questionnaire_expressions_test.rb | 2 +- .../static_form_questionnaire_extensions_test.rb | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb index 610d9e3..2551893 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_expressions_test.rb @@ -1,10 +1,10 @@ require_relative '../cql_test' module DaVinciDTRTestKit - class PayerStaticFormExpressionsTest < Inferno::Test + class PayerAdaptiveNextQuestionExpressionsTest < Inferno::Test include DaVinciDTRTestKit::CQLTest - id :dtr_v201_payer_adaptive_next_form_expressions_test - title 'Questionnaire(s) contains items with expressions necessary for pre-population' + id :dtr_v201_payer_adaptive_next_question_expressions_test + title 'Adaptive Next Question questionnaire(s) contain items with expressions necessary for pre-population' description %( Inferno checks that the payer server response to $next-question operation has appropriate expressions and that expressions are written in cql. diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb index f5b88ed..aa9cc28 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/adaptive_next_questionnaire_extensions_test.rb @@ -1,10 +1,10 @@ require_relative '../cql_test' module DaVinciDTRTestKit - class PayerStaticFormExtensionsTest < Inferno::Test + class PayerAdaptiveNexQuestionExtensionsTest < Inferno::Test include DaVinciDTRTestKit::CQLTest - id :dtr_v201_payer_adaptive_next_form_extensions_test - title 'Questionnaire(s) contains extensions necessary for pre-population' + id :dtr_v201_payer_adaptive_next_question_extensions_test + title 'Adaptive Next Question questionnaire(s) contain extensions necessary for pre-population' description %( Inferno checks that the payer server response has appropriate extensions and references to libraries within those extensions. diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb index 37e306c..b03fb86 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/payer_server_adaptive_group.rb @@ -81,8 +81,8 @@ class DTRPayerServerAdaptiveQuestionnairePackageGroup < Inferno::TestGroup # pass request to payer server, validate adaptive questionnaire response test from: :payer_server_next_response_validation_test - test from: :dtr_v201_payer_adaptive_next_form_extensions_test - test from: :dtr_v201_payer_adaptive_next_form_expressions_test + test from: :dtr_v201_payer_adaptive_next_question_extensions_test + test from: :dtr_v201_payer_adaptive_next_question_expressions_test test from: :payer_server_adaptive_completion_test end end diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb index 4d89ec3..42ae329 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_expressions_test.rb @@ -4,7 +4,7 @@ class PayerStaticFormExpressionsTest < Inferno::Test include DaVinciDTRTestKit::CQLTest id :dtr_v201_payer_static_form_expressions_test - title 'Questionnaire(s) contains items with expressions necessary for pre-population' + title 'Static questionnaire(s) contain items with expressions necessary for pre-population' description %( Inferno checks that the payer server response has appropriate expressions and that expressions are written in cql. diff --git a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb index c270e31..d4d6945 100644 --- a/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb +++ b/lib/davinci_dtr_test_kit/payer_server_groups/static_form_questionnaire_extensions_test.rb @@ -4,7 +4,7 @@ class PayerStaticFormExtensionsTest < Inferno::Test include DaVinciDTRTestKit::CQLTest id :dtr_v201_payer_static_form_extensions_test - title 'Questionnaire(s) contains extensions necessary for pre-population' + title 'Static questionnaire(s) contain extensions necessary for pre-population' description %( Inferno checks that the payer server response has appropriate extensions and references to libraries within those extensions. From 2f1be3c9b5cc80d740e0aa1e0c5caa620939cfd0 Mon Sep 17 00:00:00 2001 From: Tom Strassner Date: Thu, 21 Nov 2024 10:53:58 -0600 Subject: [PATCH 3/3] Remove unneeded preset --- config/presets/input_params_ri.json | 37 ----------------------------- 1 file changed, 37 deletions(-) delete mode 100644 config/presets/input_params_ri.json diff --git a/config/presets/input_params_ri.json b/config/presets/input_params_ri.json deleted file mode 100644 index 8473cea..0000000 --- a/config/presets/input_params_ri.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "title": "DTR Reference Implementation", - "id": "dtr-ri", - "test_suite_id": "dtr_payer_server", - "inputs": [ - { - "name": "initial_static_questionnaire_request", - "title": "Questionnaire Static Input Parameters", - "description": "Questionnaire Static Input Parameters", - "type": "textarea", - "value": "{\n \"resourceType\": \"Parameters\",\n \"id\": \"param1\",\n \"parameter\": [\n {\n \"name\": \"coverage\",\n \"resource\": {\n \"resourceType\": \"Coverage\",\n \"id\": \"cov1\"\n }\n },\n {\n \"name\": \"order\",\n \"resource\": {\n \"resourceType\": \"DeviceRequest\",\n \"id\": \"dr1\"\n }\n }\n ]\n }" - }, - { - "name": "initial_adaptive_questionnaire_request", - "title": "Questionnaire Adaptive Input Parameters", - "description": "Questionnaire Adaptive Input Parameters", - "type": "textarea", - "value": "{\n \"resourceType\": \"Parameters\",\n \"id\": \"param1\",\n \"parameter\": [\n {\n \"name\": \"coverage\",\n \"resource\": {\n \"resourceType\": \"Coverage\",\n \"id\": \"cov1\"\n }\n },\n {\n \"name\": \"order\",\n \"resource\": {\n \"resourceType\": \"DeviceRequest\",\n \"id\": \"dr1\"\n }\n }\n ]\n }" - }, - { - "name": "smart_credentials", - "value": null, - "title": "OAuth Credentials", - "type": "oauth_credentials", - "optional": true - }, - { - "name": "url", - "value": "http://localhost:8090/fhir/R4", - "title": "FHIR Server Base URL", - "description": "FHIR Server Base URL", - "type": "text" - } - ] -} - -