diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..bc26b20a --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v1.1.0/c4bb_smart_launch_group.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV110 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v110_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + + group from: :smart_discovery, + run_as_group: true + + group from: :smart_standalone_launch, + run_as_group: true, + config: { + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + }, + description: %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..7d25a750 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class SmartScopesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + PATIENT_COMPARTMENT_RESOURCE_TYPES = %w[ + Patient + ExplanationOfBenefit + Coverage + Organization + Practitioner + ].freeze + + def patient_compartment_resource_types + PATIENT_COMPARTMENT_RESOURCE_TYPES + end + + def required_scopes + config.options[:required_scopes] + end + + def access_level_regex + /\A(\*|\b(read|c?ru?d?s?)\b)/ + end + + def received_scope_test(scopes) + # check if openid, fhirUser, & launch/patient was granted + scope_subset = scopes - ['openid', 'fhirUser', 'launch/patient'] + assert scope_subset.length == scopes.length - 3, + 'openid, fhirUser, & launch/patient scopes must be supported. Received scopes: ' \ + "#{scope_subset.join(', ')}." + + granted_patient_level_resource_types = [] + granted_user_level_resource_types = [] + + scope_subset.each do |scope| + scope_pieces = scope.split('/') + next unless scope_pieces.length == 2 + + scope_type, resource_scope = scope_pieces + next unless %w[patient user].include?(scope_type) + + resource_scope_parts = resource_scope.split('.') + next unless resource_scope_parts.length == 2 + + resource_type, access_level = resource_scope_parts + next unless access_level =~ access_level_regex + + if scope_type == 'patient' + granted_patient_level_resource_types << resource_type + else + granted_user_level_resource_types << resource_type + end + end + + # Check if the required patient and user level scopes are granted + missing_patient_level_resource_types = patient_compartment_resource_types - granted_patient_level_resource_types + missing_patient_level_resource_types = [] if granted_patient_level_resource_types.include?('*') + + assert missing_patient_level_resource_types.empty?, + "Requested patient-level scopes #{missing_patient_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + + missing_user_level_resource_types = patient_compartment_resource_types - granted_user_level_resource_types + missing_user_level_resource_types = [] if granted_user_level_resource_types.include?('*') + assert missing_user_level_resource_types.empty?, + "Requested user-level scopes #{missing_user_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + + if received_or_requested == 'requested' + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + else + received_scope_test(scopes) + end + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..2350b5d8 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200devnonfinancial_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + " + Server did not advertise support for the following required capabilities: + #{missing_capabilities_string} + " + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..fdecebad --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200DEVNONFINANCIAL + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200devnonfinancial_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + input_order :url, + :standalone_client_id, + :standalone_client_secret, + :standalone_requested_scopes, + :use_pkce, + :pkce_code_challenge_method, + :standalone_authorization_method, + :client_auth_type, + :client_auth_encryption_method + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200devnonfinancial_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200devnonfinancial_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb new file mode 100644 index 00000000..ad6417a0 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/smart_scopes_test.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class SmartScopesTest < Inferno::Test + id :c4bb_v200_smart_scopes + title 'Server supports the required authorization scopes.' + description %( + All required scopes requested are expected to be granted. + Server SHALL support, at a minimum, the following requested authorization scopes: + * `openid` + * `fhirUser` + * `launch/patient` + * `patient/ExplanationOfBenefit.read` + * `patient/Coverage.read` + * `patient/Patient.read` + * `patient/Organization.read` + * `patient/Practitioner.read` + * `user/ExplanationOfBenefit.read` + * `user/Coverage.read` + * `user/Patient.read` + * `user/Organization.read` + * `user/Practitioner.read` + ) + input :requested_scopes, :received_scopes + uses_request :token + + PATIENT_COMPARTMENT_RESOURCE_TYPES = %w[ + Patient + ExplanationOfBenefit + Coverage + Organization + Practitioner + ].freeze + + def patient_compartment_resource_types + PATIENT_COMPARTMENT_RESOURCE_TYPES + end + + def required_scopes + config.options[:required_scopes] + end + + def access_level_regex + /\A(\*|\b(read|c?ru?d?s?)\b)/ + end + + def received_scope_test(scopes) + # check if openid, fhirUser, & launch/patient was granted + scope_subset = scopes - ['openid', 'fhirUser', 'launch/patient'] + assert scope_subset.length == scopes.length - 3, + 'openid, fhirUser, & launch/patient scopes must be supported. Received scopes: ' \ + "#{scope_subset.join(', ')}." + + granted_patient_level_resource_types = [] + granted_user_level_resource_types = [] + + scope_subset.each do |scope| + scope_pieces = scope.split('/') + next unless scope_pieces.length == 2 + + scope_type, resource_scope = scope_pieces + next unless %w[patient user].include?(scope_type) + + resource_scope_parts = resource_scope.split('.') + next unless resource_scope_parts.length == 2 + + resource_type, access_level = resource_scope_parts + next unless access_level =~ access_level_regex + + if scope_type == 'patient' + granted_patient_level_resource_types << resource_type + else + granted_user_level_resource_types << resource_type + end + end + + # Check if the required patient and user level scopes are granted + missing_patient_level_resource_types = patient_compartment_resource_types - granted_patient_level_resource_types + missing_patient_level_resource_types = [] if granted_patient_level_resource_types.include?('*') + + assert missing_patient_level_resource_types.empty?, + "Requested patient-level scopes #{missing_patient_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + + missing_user_level_resource_types = patient_compartment_resource_types - granted_user_level_resource_types + missing_user_level_resource_types = [] if granted_user_level_resource_types.include?('*') + assert missing_user_level_resource_types.empty?, + "Requested user-level scopes #{missing_user_level_resource_types.join(', ')} " \ + 'were not granted by authorization server.' + end + + run do + skip_if request.status != 200, 'Token exchange was unsuccessful' + [ + { + scopes: requested_scopes, + received_or_requested: 'requested' + }, + { + scopes: received_scopes, + received_or_requested: 'received' + } + ].each do |metadata| + scopes = metadata[:scopes].split + received_or_requested = metadata[:received_or_requested] + missing_scopes = required_scopes - scopes + + if received_or_requested == 'requested' + assert missing_scopes.empty?, + "Required scopes were not #{received_or_requested}: #{missing_scopes.join(', ')}" + else + received_scope_test(scopes) + end + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb new file mode 100644 index 00000000..f548d0be --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch/well_known_capabilities_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class WellKnownCapabilitiesTest < Inferno::Test + id :c4bb_v200_smart_capabilities + title 'Server Well-known configuration declares support for the required SMART capabilities' + description %( + Servers SHALL support the following [SMART on FHIR + capabilities](https://hl7.org/fhir/us/carin-bb/Security_And_Privacy_Considerations.html#authentication-and-authorization-requirements): + + * `launch-standalone` + * `client-public` + * `client-confidential-symmetric` + * `sso-openid-connect` + * `context-standalone-patient` + * `permission-offline` + * `permission-patient` + * `permission-user` + ) + + input :well_known_configuration + + run do + skip_if well_known_configuration.blank?, 'No SMART well-known configuration received' + + assert_valid_json(well_known_configuration) + + advertised_capabilities = JSON.parse(well_known_configuration)['capabilities'] + + assert advertised_capabilities.is_a?(Array), + "Expected `capabilities` field to be an Array, but found #{advertised_capabilities.class.name}" + + required_capabilities = config.options[:required_capabilities] || [] + + missing_capabilities = required_capabilities - advertised_capabilities + + missing_capabilities_string = + missing_capabilities + .map { |capability| "\n* `#{capability}`" } + .join + + assert missing_capabilities.empty?, + "Server did not advertise support for the following required capabilities: #{missing_capabilities_string}" + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb new file mode 100644 index 00000000..1e62b8e3 --- /dev/null +++ b/lib/carin_for_blue_button_test_kit/custom_groups/v2.0.0/c4bb_smart_launch_group.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require_relative 'c4bb_smart_launch/well_known_capabilities_test' +require_relative 'c4bb_smart_launch/smart_scopes_test' + +module CarinForBlueButtonTestKit + module CARIN4BBV200 + class C4BBSMARTLaunchGroup < Inferno::TestGroup + id :c4bb_v200_smart_launch + title 'SMART App Launch' + description %( + # Background + Applications authorize to gain access to a patient record using the + [SMART App Launch + Protocol](http://hl7.org/fhir/smart-app-launch/1.0.0/)'s standalone + launch sequence. + + # Testing Methodology + These tests first access the server's SMART discovery endpoints and + verify that they are available and that the server advertises support + for the required SMART capabilities and required authorization scopes. + They then perform a standalone launch to obtain an access token which + can be used by the remaining tests to access patient data. + ) + input_order :url, + :standalone_client_id, + :standalone_client_secret, + :standalone_requested_scopes, + :use_pkce, + :pkce_code_challenge_method, + :standalone_authorization_method, + :client_auth_type, + :client_auth_encryption_method + + group from: :smart_discovery do + run_as_group + + test from: :c4bb_v200_smart_capabilities do + config( + options: { + required_capabilities: %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + } + ) + end + end + + group from: :smart_standalone_launch do + run_as_group + description %( + # Background + + The [Standalone + Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + allows an app, like Inferno, to be launched independent of an + existing EHR session. It is one of the two launch methods described in + the SMART App Launch Framework alongside EHR Launch. The app will + request authorization for the provided scope from the authorization + endpoint, ultimately receiving an authorization token which can be used + to gain access to resources on the FHIR server. + + # Test Methodology + + Inferno will redirect the user to the authorization endpoint so that + they can provide any required credentials and authorize the application. + Upon successful authorization, Inferno will exchange the authorization + code provided for an access token. + + For more information on the #{title}: + + * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) + ) + + config( + inputs: { + requested_scopes: { + default: %( + launch/patient openid fhirUser + patient/ExplanationOfBenefit.read patient/Coverage.read + patient/Patient.read patient/Organization.read + patient/Practitioner.read user/ExplanationOfBenefit.read + user/Coverage.read user/Patient.read + user/Organization.read user/Practitioner.read + ).gsub(/\s{2,}/, ' ').strip + } + }, + outputs: { + patient_id: { name: :patient_ids }, + smart_credentials: { name: :smart_credentials } + } + ) + + test from: :c4bb_v200_smart_scopes do + config( + inputs: { + requested_scopes: { name: :standalone_requested_scopes }, + received_scopes: { name: :standalone_received_scopes } + }, + options: { + required_scopes: %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + } + ) + end + end + end + end +end diff --git a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb index ffd7cfe2..3f1c8987 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v1.1.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v1.1.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -55,46 +56,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v110_smart_launch + group from: :capability_statement_group group from: :c4bb_v110_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb index ca948f01..ab4ddb08 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0-dev-nonfinancial/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0-dev-nonfinancial/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -29,7 +30,7 @@ class C4BBTestKit < Inferno::TestSuite The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](http://hl7.org/fhir/us/carin-bb/history.html). - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). ) version VERSION @@ -64,46 +65,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200devnonfinancial_smart_launch + group from: :capability_statement_group group from: :c4bb_v200devnonfinancial_patient diff --git a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb index d953abeb..4cdc6f5d 100644 --- a/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb +++ b/lib/carin_for_blue_button_test_kit/generated/v2.0.0/c4bb_test_suite.rb @@ -3,6 +3,7 @@ require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '../../custom_groups/v2.0.0/c4bb_smart_launch_group' require_relative 'patient_group' require_relative 'coverage_group' @@ -57,46 +58,13 @@ def self.metadata type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :c4bb_v200_smart_launch + group from: :capability_statement_group group from: :c4bb_v200_patient diff --git a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb index c277db1e..54502cd8 100644 --- a/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb +++ b/lib/carin_for_blue_button_test_kit/generator/suite_generator.rb @@ -89,6 +89,13 @@ def capability_statement_group_id "c4bb_#{ig_metadata.reformatted_version}_capability_statement" end + def smart_launch_file_name + "../../custom_groups/#{ig_metadata.ig_version}/c4bb_smart_launch_group" + end + + def smart_launch_group_id + "c4bb_#{ig_metadata.reformatted_version}_smart_launch" + end end end end diff --git a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb index e14696bc..e0a512ed 100644 --- a/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb +++ b/lib/carin_for_blue_button_test_kit/generator/templates/suite.rb.erb @@ -3,6 +3,7 @@ require 'smart_app_launch_test_kit' require_relative '../../version' require_relative '../../capability_statement/capability_statement_group' +require_relative '<%= smart_launch_file_name %>' <% group_file_list.each do |file_name| %>require_relative '<%= file_name %>' <% end %> @@ -11,11 +12,11 @@ module CarinForBlueButtonTestKit class <%= class_name %> < Inferno::TestSuite title '<%= title %>' description %( - The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%=ig_link %>). + The CARIN for Blue Button test suite validates system conformance to the HL7® FHIR® [CARIN for Blue Button® Implementation Guide](<%= ig_link %>). - <% if module_name.include? ('FINANCIAL')%> - Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). - <% end%> + <% if module_name.include? ("FINANCIAL") %> + Development build for the [non-financial branch](https://build.fhir.org/ig/HL7/carin-bb/branches/non-financial/). + <% end %> ) version VERSION links [ @@ -49,46 +50,13 @@ module CarinForBlueButtonTestKit type: :oauth_credentials, optional: true - # SMART Test Suite requirement - group from: :smart_discovery, - run_as_group: true - group from: :smart_standalone_launch, - run_as_group: true, - config: { - outputs: { - patient_id: { name: :patient_ids }, - smart_credentials: { name: :smart_credentials } - } - }, - description: %( - # Background - - The [Standalone - Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - allows an app, like Inferno, to be launched independent of an - existing EHR session. It is one of the two launch methods described in - the SMART App Launch Framework alongside EHR Launch. The app will - request authorization for the provided scope from the authorization - endpoint, ultimately receiving an authorization token which can be used - to gain access to resources on the FHIR server. - - # Test Methodology - - Inferno will redirect the user to the authorization endpoint so that - they can provide any required credentials and authorize the application. - Upon successful authorization, Inferno will exchange the authorization - code provided for an access token. - - For more information on the #{title}: - - * [Standalone Launch Sequence](https://www.hl7.org/fhir/smart-app-launch/1.0.0/index.html#standalone-launch-sequence) - ) - fhir_client do url :url oauth_credentials :smart_credentials end + group from: :<%= smart_launch_group_id %> + group from: :capability_statement_group <% group_id_list.each do |id| %> group from: :<%= id %><% end %> diff --git a/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb new file mode 100644 index 00000000..b2895929 --- /dev/null +++ b/spec/carin_for_blue_button/v200_smart_scopes_test_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200::SmartScopesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:required_scopes) do + %w[ + openid + fhirUser + launch/patient + patient/ExplanationOfBenefit.read + patient/Coverage.read + patient/Patient.read + patient/Organization.read + patient/Practitioner.read + user/ExplanationOfBenefit.read + user/Coverage.read + user/Patient.read + user/Organization.read + user/Practitioner.read + ] + end + + before do + repo_create(:request, test_session_id: test_session.id, name: :token) + allow_any_instance_of(test).to receive(:required_scopes).and_return(required_scopes) + end + + context 'All required scopes are not requested' do + it 'fails if a required scope was not requested' do + result = run(test, requested_scopes: 'online_access launch', + received_scopes: 'launch') + + expect(result.result).to eq('fail') + expect(result.result_message).to include('Required scopes were not requested: ') + end + end + + context 'All required scopes requested' do + it 'fails if all the required scopes were not granted' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: 'patient/*.*') + + expect(result.result).to eq('fail') + end + + it 'passes if the granted scopes are the super set of the required requested scopes' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: 'patient/*.* user/*.* openid fhirUser launch/patient') + + expect(result.result).to eq('pass') + end + + it 'passes if the granted scopes are the exact required requested scopes' do + result = run(test, requested_scopes: required_scopes.join(' '), + received_scopes: required_scopes.join(' ')) + + expect(result.result).to eq('pass') + end + end +end diff --git a/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb new file mode 100644 index 00000000..27d0b166 --- /dev/null +++ b/spec/carin_for_blue_button/v200_well_known_capabilities_test_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe CarinForBlueButtonTestKit::CARIN4BBV200DEVNONFINANCIAL::WellKnownCapabilitiesTest do + def run(runnable, inputs = {}) + test_run_params = { test_session_id: test_session.id }.merge(runnable.reference_hash) + test_run = Inferno::Repositories::TestRuns.new.create(test_run_params) + inputs.each do |name, value| + session_data_repo.save( + test_session_id: test_session.id, + name:, + value:, + type: runnable.config.input_type(name) + ) + end + Inferno::TestRunner.new(test_session:, test_run:).run(runnable) + end + + let(:test) { described_class } + let(:test_session) { repo_create(:test_session, test_suite_id: 'c4bb_v200') } + let(:session_data_repo) { Inferno::Repositories::SessionData.new } + let(:well_known_config) do + { + capabilities: required_capabilities + } + end + let(:well_known_config_bad) do + { + capabilities: [] + } + end + let(:required_capabilities) do + %w[ + launch-standalone + client-public + client-confidential-symmetric + sso-openid-connect + context-standalone-patient + permission-offline + permission-patient + permission-user + ] + end + let(:test_config) do + Inferno::DSL::Configurable::Configuration.new(options: { required_capabilities: }) + end + before do + allow_any_instance_of(test).to(receive(:config).and_return(test_config)) + end + + it 'passes if all required capabilities are advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config) } + + result = run(test, inputs) + + expect(result.result).to eq('pass') + end + + it 'fails if all required capabilities are not advertised in the Well-known configuration`' do + inputs = { well_known_configuration: JSON.generate(well_known_config_bad) } + + result = run(test, inputs) + + expect(result.result).to eq('fail') + end +end