diff --git a/.gitignore b/.gitignore index 26674d9c3..680204130 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ coverage tmp/* .vscode/* resources/terminology/* -.ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..1b03fe63d --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.5.6 \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..789ef3502 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.5.6 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8b1e9ffb5..a3a89f791 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: ruby services: - docker -rvm: - - 2.5 - - 2.6 before_install: - gem update --system - gem install bundler diff --git a/Dockerfile b/Dockerfile index be8a0e645..fc3297976 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,28 @@ -FROM ruby:2.5 +FROM ruby:2.5.6 -# Install gems into a temporary directory -COPY Gemfile* ./ -RUN gem install bundler && bundle install +WORKDIR /var/www/inferno -# Expose the port +### Install dependencies + +COPY Gemfile* /var/www/inferno/ +RUN gem install bundler +# Throw an error if Gemfile & Gemfile.lock are out of sync +RUN bundle config --global frozen 1 +RUN bundle install + +### Install Inferno + +RUN mkdir data +COPY public /var/www/inferno/public +COPY resources /var/www/inferno/resources +COPY config* /var/www/inferno/ +COPY Rakefile /var/www/inferno/ +COPY test /var/www/inferno/test +COPY lib /var/www/inferno/lib + +### Set up environment + +ENV APP_ENV=production EXPOSE 4567 + +CMD ["bundle", "exec", "rackup", "-o", "0.0.0.0"] diff --git a/Gemfile b/Gemfile index 102c78d38..3962bf7bd 100644 --- a/Gemfile +++ b/Gemfile @@ -13,17 +13,19 @@ gem 'json-jwt' gem 'kramdown' gem 'pry' gem 'pry-byebug' -gem 'rack-test' gem 'rake' gem 'rb-readline' gem 'rest-client' -gem 'rubocop', require: false gem 'selenium-webdriver' gem 'sinatra' gem 'sinatra-contrib' gem 'sqlite3' gem 'thin' gem 'time_difference' -gem 'webmock' -gem 'simplecov', require: false, group: :test +group :test do + gem 'rack-test' + gem 'rubocop', require: false + gem 'simplecov', require: false + gem 'webmock' +end diff --git a/README.md b/README.md index 5b59878a9..c233fc8a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + [![Build Status](https://travis-ci.org/onc-healthit/inferno.svg?branch=master)](https://travis-ci.org/onc-healthit/inferno) @@ -82,7 +82,7 @@ bundle exec rake test ## Inspecting and Exporting Tests Tests are written to be easily understood, even by those who aren't familiar with Ruby. They can be -viewed directly [in this repository](https://github.com/onc-healthit/inferno/tree/master/lib/app/sequences). +viewed directly [in this repository](https://github.com/onc-healthit/inferno/tree/master/lib/app/modules). Tests contain metadata that provide additional details and traceability to standards. The active tests and related metadata can be exported into CSV format and saved to a file named `testlist.csv` with the following command: diff --git a/config.yml b/config.yml index df35e4f9d..12a2e9783 100644 --- a/config.yml +++ b/config.yml @@ -5,9 +5,6 @@ app_name: Inferno # There are a few exceptions, such as "/" and "/landing". base_path: "inferno" -# Show or hide tutorials -show_tutorial: true - # Useful during development to purge the database on each reload purge_database_on_reload: false diff --git a/docker-compose.yml b/docker-compose.yml index a67bf89a8..d4e4f0669 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,12 @@ version: '3' services: - # the main ruby server ruby_server: - # build the image from ./Dockerfile (this installs the dependencies into the image) build: context: ./ - # map the project directory into the running container volumes: - - .:/var/www/inferno - # expose the internal application to direct HTTP requests - #ports: - # - "4567:8080" - # run the application - working_dir: /var/www/inferno - command: rackup -o 0.0.0.0 + - ./config.yml:/var/www/inferno/config.yml + ports: + - "4567:8080" nginx_server: image: nginx volumes: @@ -24,4 +17,4 @@ services: links: - ruby_server:ruby_server depends_on: - - ruby_server \ No newline at end of file + - ruby_server diff --git a/generators/uscore-r4/generator.rb b/generators/uscore-r4/generator.rb index ab6d4ed03..58637deb1 100644 --- a/generators/uscore-r4/generator.rb +++ b/generators/uscore-r4/generator.rb @@ -6,9 +6,17 @@ require 'net/http' require 'fhir_models' require_relative './metadata_extractor' +require_relative '../../lib/app/utils/validation' -OUT_PATH = '../../lib/app/modules' -RESOURCE_PATH = '../../resources/us_core_r4/' +OUT_PATH = File.expand_path('../../lib/app/modules', __dir__) +RESOURCE_PATH = File.expand_path('../../resources/us_core_r4', __dir__) + +PROFILE_URIS = Inferno::ValidationUtil::US_CORE_R4_URIS + +def validation_profile_uri(sequence) + profile_uri = PROFILE_URIS.key(sequence[:profile]) + "Inferno::ValidationUtil::US_CORE_R4_URIS[:#{profile_uri}]" if profile_uri +end def run redownload_files = (ARGV&.first == '-d') @@ -31,8 +39,15 @@ def generate_search_validators(metadata) end def generate_tests(metadata) + # first isolate the profiles that don't have patient searches + mark_delayed_sequences(metadata) + metadata[:sequences].each do |sequence| puts "Generating test #{sequence[:name]}" + + # read reference if sequence contains no search sequences + create_read_test(sequence) if sequence[:delayed_sequence] + # authorization test create_authorization_test(sequence) @@ -52,6 +67,7 @@ def generate_tests(metadata) .each do |interaction| # specific edge cases interaction[:code] = 'history' if interaction[:code] == 'history-instance' + next if interaction[:code] == 'read' && sequence[:delayed_sequence] create_interaction_test(sequence, interaction) end @@ -62,16 +78,44 @@ def generate_tests(metadata) end end +def mark_delayed_sequences(metadata) + metadata[:sequences].each do |sequence| + sequence[:delayed_sequence] = sequence[:resource] != 'Patient' && sequence[:searches].none? { |search| search[:names].include? 'patient' } + end + metadata[:delayed_sequences] = metadata[:sequences].select { |seq| seq[:delayed_sequence] } + metadata[:non_delayed_sequences] = metadata[:sequences].reject { |seq| seq[:delayed_sequence] } +end + +def find_first_search(sequence) + sequence[:searches].find { |search_param| search_param[:expectation] == 'SHALL' } || + sequence[:searches].find { |search_param| search_param[:expectation] == 'SHOULD' } +end + def generate_sequence(sequence) puts "Generating #{sequence[:name]}\n" file_name = OUT_PATH + '/us_core_r4/' + sequence[:name].downcase + '_sequence.rb' - template = ERB.new(File.read('./templates/sequence.rb.erb')) + template = ERB.new(File.read(File.join(__dir__, 'templates/sequence.rb.erb'))) output = template.result_with_hash(sequence) FileUtils.mkdir_p(OUT_PATH + '/us_core_r4') unless File.directory?(OUT_PATH + '/us_core_r4') File.write(file_name, output) end +def create_read_test(sequence) + read_test = { + tests_that: "Can read #{sequence[:resource]} from the server", + index: sequence[:tests].length + 1, + link: 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + } + + read_test[:test_code] = %( + #{sequence[:resource].downcase}_id = @instance.resource_references.find { |reference| reference.resource_type == '#{sequence[:resource]}' }&.resource_id + skip 'No #{sequence[:resource]} references found from the prior searches' if #{sequence[:resource].downcase}_id.nil? + @#{sequence[:resource].downcase} = fetch_resource('#{sequence[:resource]}', #{sequence[:resource].downcase}_id) + @resources_found = !@#{sequence[:resource].downcase}.nil?) + sequence[:tests] << read_test +end + def create_authorization_test(sequence) authorization_test = { tests_that: "Server rejects #{sequence[:resource]} search without authorization", @@ -79,8 +123,7 @@ def create_authorization_test(sequence) link: 'http://www.fhir.org/guides/argonaut/r2/Conformance-server.html' } - first_search = sequence[:searches].find { |search_param| search_param[:expectation] == 'SHALL' } || - sequence[:searches].find { |search_param| search_param[:expectation] == 'SHOULD' } + first_search = find_first_search(sequence) return if first_search.nil? authorization_test[:test_code] = %( @@ -98,10 +141,17 @@ def create_search_test(sequence, search_param) search_test = { tests_that: "Server returns expected results from #{sequence[:resource]} search by #{search_param[:names].join('+')}", index: sequence[:tests].length + 1, - link: 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + link: 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html', + optional: search_param[:expectation] != 'SHALL' } - is_first_search = search_test[:index] == 2 # if first search - fix this check later + is_first_search = search_param == find_first_search(sequence) + save_resource_ids_in_bundle_arguments = [ + "versioned_resource_class('#{sequence[:resource]}')", + 'reply', + validation_profile_uri(sequence) + ].compact.join(', ') + search_test[:test_code] = if is_first_search %(#{get_search_params(search_param[:names], sequence)} @@ -116,7 +166,8 @@ def create_search_test(sequence, search_param) @#{sequence[:resource].downcase} = reply.try(:resource).try(:entry).try(:first).try(:resource) @#{sequence[:resource].downcase}_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('#{sequence[:resource]}'), reply) + save_resource_ids_in_bundle(#{save_resource_ids_in_bundle_arguments}) + save_delayed_sequence_references(@#{sequence[:resource].downcase}) validate_search_reply(versioned_resource_class('#{sequence[:resource]}'), reply, search_params)) else %( @@ -216,7 +267,7 @@ def create_resource_profile_test(sequence) } test[:test_code] = %( skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('#{sequence[:resource]}')) + test_resources_against_profile('#{sequence[:resource]}'#{', ' + validation_profile_uri(sequence) if validation_profile_uri(sequence)})) sequence[:tests] << test end @@ -259,6 +310,8 @@ def get_value_path_by_type(type) '.code' when 'HumanName' '.family' + when 'Address' + '.city' else '' end @@ -334,14 +387,13 @@ def get_comparator_searches(search_params, sequence) def search_param_constants(search_parameters, sequence) return "patient: @instance.patient_id, category: 'assess-plan'" if search_parameters == ['patient', 'category'] && sequence[:resource] == 'CarePlan' return "patient: @instance.patient_id, status: 'active'" if search_parameters == ['patient', 'status'] && sequence[:resource] == 'CareTeam' - return "patient: @instance.patient_id, name: 'Boston'" if search_parameters == ['name'] && (['Location', 'Organization'].include? sequence[:resource]) return "'_id': @instance.patient_id" if search_parameters == ['_id'] && sequence[:resource] == 'Patient' - return "patient: @instance.patient_id, code: '72166-2'" if search_parameters == ['patient', 'code'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-smokingstatus.json' - return "patient: @instance.patient_id, category: 'laboratory'" if search_parameters == ['patient', 'category'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-observation-lab.json' - return "patient: @instance.patient_id, code: '77606-2'" if search_parameters == ['patient', 'code'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-weight-for-height.json' - return "patient: @instance.patient_id, code: '59576-9'" if search_parameters == ['patient', 'code'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-bmi-for-age.json' - return "patient: @instance.patient_id, category: 'LAB'" if search_parameters == ['patient', 'category'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-lab.json' - return "patient: @instance.patient_id, code: 'LP29684-5'" if search_parameters == ['patient', 'category'] && sequence[:profile] == 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-note.json' + return "patient: @instance.patient_id, code: '72166-2'" if search_parameters == ['patient', 'code'] && sequence[:profile] == PROFILE_URIS[:smoking_status] + return "patient: @instance.patient_id, category: 'laboratory'" if search_parameters == ['patient', 'category'] && sequence[:profile] == PROFILE_URIS[:lab_results] + return "patient: @instance.patient_id, code: '77606-2'" if search_parameters == ['patient', 'code'] && sequence[:profile] == PROFILE_URIS[:pediatric_weight_height] + return "patient: @instance.patient_id, code: '59576-9'" if search_parameters == ['patient', 'code'] && sequence[:profile] == PROFILE_URIS[:pediatric_bmi_age] + return "patient: @instance.patient_id, category: 'LAB'" if search_parameters == ['patient', 'category'] && sequence[:profile] == PROFILE_URIS[:diagnostic_report_lab] + return "patient: @instance.patient_id, code: 'LP29684-5'" if search_parameters == ['patient', 'category'] && sequence[:profile] == PROFILE_URIS[:diagnostic_report_note] end def create_search_validation(sequence) @@ -418,7 +470,7 @@ def validate_resource_item(resource, property, value) def generate_module(module_info) file_name = OUT_PATH + '/us_core_module.yml' - template = ERB.new(File.read('./templates/module.yml.erb')) + template = ERB.new(File.read(File.join(__dir__, 'templates/module.yml.erb'))) output = template.result_with_hash(module_info) File.write(file_name, output) diff --git a/generators/uscore-r4/metadata_extractor.rb b/generators/uscore-r4/metadata_extractor.rb index 210e9c0d0..675cbd684 100644 --- a/generators/uscore-r4/metadata_extractor.rb +++ b/generators/uscore-r4/metadata_extractor.rb @@ -1,19 +1,23 @@ # frozen_string_literal: true class MetadataExtractor - CAPABILITY_STATEMENT_URI = 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.json' + CAPABILITY_STATEMENT_URI = 'https://www.hl7.org/fhir/us/core/CapabilityStatement-us-core-server.json' def profile_uri(profile) - "https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-#{profile}.json" + "http://hl7.org/fhir/us/core/StructureDefinition/#{profile}" + end + + def profile_json_uri(profile) + "https://www.hl7.org/fhir/us/core/StructureDefinition-#{profile}.json" end def search_param_uri(resource, param) param = 'id' if param == '_id' - "https://build.fhir.org/ig/HL7/US-Core-R4/SearchParameter-us-core-#{resource.downcase}-#{param}.json" + "https://www.hl7.org/fhir/us/core/SearchParameter-us-core-#{resource.downcase}-#{param}.json" end def get_json_from_uri(uri) - filename = RESOURCE_PATH + uri.split('/').last + filename = File.join(RESOURCE_PATH, uri.split('/').last) unless File.exist?(filename) puts "Downloading #{uri}\n" json_result = Net::HTTP.get(URI(uri)) @@ -33,7 +37,7 @@ def extract_metadata def build_new_sequence(resource, profile) base_name = profile.split('StructureDefinition/')[1] - profile_json = get_json_from_uri(profile_uri(base_name)) + profile_json = get_json_from_uri(profile_json_uri(base_name)) profile_title = profile_json['title'].gsub(/US\s*Core\s*/, '').gsub(/\s*Profile/, '').strip { name: base_name.tr('-', '_'), @@ -44,6 +48,7 @@ def build_new_sequence(resource, profile) .gsub('UsCore', 'USCoreR4') + 'Sequence', resource: resource['type'], profile: profile_uri(base_name), # link in capability statement is incorrect, + profile_json: profile_json_uri(base_name), title: profile_title, interactions: [], searches: [], @@ -67,7 +72,7 @@ def extract_metadata_from_resources(resources) add_combo_searches(resource, new_sequence) add_interactions(resource, new_sequence) - profile_definition = get_json_from_uri(new_sequence[:profile]) + profile_definition = get_json_from_uri(new_sequence[:profile_json]) add_must_support_elements(profile_definition, new_sequence) add_search_param_descriptions(profile_definition, new_sequence) add_element_definitions(profile_definition, new_sequence) @@ -203,9 +208,9 @@ def add_element_definitions(profile_definition, sequence) def add_special_cases category_first_profiles = [ - 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-lab.json', - 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-observation-lab.json', - 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-note.json' + PROFILE_URIS[:diagnostic_report_lab], + PROFILE_URIS[:lab_results], + PROFILE_URIS[:diagnostic_report_note] ] # search by patient first diff --git a/generators/uscore-r4/templates/module.yml.erb b/generators/uscore-r4/templates/module.yml.erb index cde8a380b..efe5656b7 100644 --- a/generators/uscore-r4/templates/module.yml.erb +++ b/generators/uscore-r4/templates/module.yml.erb @@ -18,9 +18,10 @@ test_sets: - ManualRegistrationSequence - StandaloneLaunchSequence - EHRLaunchSequence - - name: US Core R4 + - name: US Core R4 Patient Based Profiles run_all: true - sequences:<% sequences.each do |sequence| %> + sequences:<% non_delayed_sequences.each do |sequence| %> - <%=sequence[:classname]%><% end %> - R4ProvenanceSequence - - USCoreR4ClinicalNotesSequence + - USCoreR4ClinicalNotesSequence<% delayed_sequences.each do |sequence| %> + - <%=sequence[:classname]%><% end %> diff --git a/generators/uscore-r4/templates/sequence.rb.erb b/generators/uscore-r4/templates/sequence.rb.erb index cc15d93a0..175db2e24 100644 --- a/generators/uscore-r4/templates/sequence.rb.erb +++ b/generators/uscore-r4/templates/sequence.rb.erb @@ -11,8 +11,9 @@ module Inferno test_id_prefix '<%=resource%>' # change me - requires :token, :patient_id - conformance_supports :<%=resource%> + requires :token<%=", :patient_id" unless delayed_sequence%> + conformance_supports :<%=resource%><%=" + delayed_sequence" if delayed_sequence%> <%=search_validator%> details %( @@ -26,7 +27,8 @@ module Inferno test '<%=test[:tests_that]%>' do metadata do id '<%=format('%02d', test[:index])%>' - link '<%=test[:link]%>' + link '<%=test[:link]%>'<%if test[:optional]%> + optional<%end%> desc %( ) versions :r4 diff --git a/lib/app/endpoint.rb b/lib/app/endpoint.rb index 03e71dabc..9c446e0fa 100644 --- a/lib/app/endpoint.rb +++ b/lib/app/endpoint.rb @@ -20,6 +20,7 @@ class Endpoint < Sinatra::Base Inferno::EXTRAS = settings.include_extras if settings.logging_enabled + $stdout.sync = true # output in Docker is heavily delayed without this Inferno.logger = if ENV['RACK_ENV'] == 'test' FileUtils.mkdir_p 'tmp' diff --git a/lib/app/helpers/configuration.rb b/lib/app/helpers/configuration.rb index 47f4782c6..7f514c336 100644 --- a/lib/app/helpers/configuration.rb +++ b/lib/app/helpers/configuration.rb @@ -40,10 +40,6 @@ def valid_json?(json) def tls_testing_supported? TlsTester.testing_supported? end - - def show_tutorial - settings.show_tutorial - end end end end diff --git a/lib/app/models/server_capabilities.rb b/lib/app/models/server_capabilities.rb index cfa01e894..61d45ee7c 100644 --- a/lib/app/models/server_capabilities.rb +++ b/lib/app/models/server_capabilities.rb @@ -11,6 +11,8 @@ class ServerCapabilities belongs_to :testing_instance + SMART_EXTENSION_URL = 'http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities' + def supported_resources statement.rest.each_with_object(Set.new) do |rest, resources| rest.resource.each { |resource| resources << resource.type } @@ -28,12 +30,34 @@ def supported_interactions end end + def operation_supported?(operation_name) + statement.rest.any? { |rest| rest.operation.any? { |operation| operation.name == operation_name } } + end + + def smart_support? + smart_extensions.present? + end + + def smart_capabilities + smart_extensions.map(&:valueCode) + end + private def statement @statement ||= FHIR::CapabilityStatement.new(capabilities) end + def security_extensions + @security_extensions ||= + statement&.rest&.flat_map { |rest| rest&.security&.extension }&.compact || [] + end + + def smart_extensions + @smart_extensions ||= + security_extensions.select { |extension| extension.url == SMART_EXTENSION_URL } + end + def interaction_display(interaction) if interaction.code == 'search-type' 'search' diff --git a/lib/app/modules/argonaut/argonaut_conformance_sequence.rb b/lib/app/modules/argonaut/argonaut_conformance_sequence.rb index daed723a8..c27034b53 100644 --- a/lib/app/modules/argonaut/argonaut_conformance_sequence.rb +++ b/lib/app/modules/argonaut/argonaut_conformance_sequence.rb @@ -86,7 +86,8 @@ class ArgonautConformanceSequence < CapabilityStatementSequence ) end - assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource' + assert_valid_conformance + formats = ['json', 'applcation/json', 'application/json+fhir', 'application/fhir+json'] assert formats.any? { |format| @conformance.format.include? format }, 'Conformance does not state support for json.' end @@ -115,20 +116,17 @@ class ArgonautConformanceSequence < CapabilityStatementSequence 'permission-patient', 'permission-user'] - assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource' + assert_valid_conformance + + assert @server_capabilities.smart_support?, 'No SMART capabilities listed in conformance.' - extensions = @conformance.try(:rest).try(:first).try(:security).try(:extension) - assert !extensions.nil?, 'No SMART capabilities listed in conformance.' - capabilities = extensions.select { |x| x.url == 'http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities' } - assert !capabilities.nil?, 'No SMART capabilities listed in conformance.' - available_capabilities = capabilities.map(&:valueCode) - missing_capabilities = (required_capabilities - available_capabilities) + missing_capabilities = (required_capabilities - @server_capabilities.smart_capabilities) assert missing_capabilities.empty?, "Conformance statement does not list required SMART capabilties: #{missing_capabilities.join(', ')}" end test 'Conformance Statement lists supported Argonaut profiles, operations and search parameters' do metadata do - id '05' + id '06' link 'https://www.fhir.org/guides/argonaut/r2/Conformance-server.html' desc %( The Argonaut Data Query Implementation Guide states: @@ -140,16 +138,7 @@ class ArgonautConformanceSequence < CapabilityStatementSequence ) end - assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource' - - begin - Inferno::Models::ServerCapabilities.create( - testing_instance_id: @instance.id, - capabilities: @conformance.as_json - ) - rescue StandardError - assert false, 'Conformance Statement could not be parsed.' - end + assert_valid_conformance assert @instance.conformance_supported?(:Patient, [:read]), 'Patient resource with read interaction is not listed in conformance statement.' end diff --git a/lib/app/modules/bulk_data/bulk_data_capability_statement_sequence.rb b/lib/app/modules/bulk_data/bulk_data_capability_statement_sequence.rb new file mode 100644 index 000000000..8b4b36202 --- /dev/null +++ b/lib/app/modules/bulk_data/bulk_data_capability_statement_sequence.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require_relative '../core/capability_statement_sequence' + +module Inferno + module Sequence + class BulkDataCapabilityStatementSequence < CapabilityStatementSequence + extends_sequence CapabilityStatementSequence + + title 'Capability Statement' + + test_id_prefix 'C' + + requires :url + defines :oauth_authorize_endpoint, :oauth_token_endpoint, :oauth_register_endpoint + + description 'Retrieve information about supported server functionality in the Capability Statement.' + details %( + # Background + The #{title} Sequence tests a FHIR server's ability to formally describe features + supported by the API by using the [Capability Statement](https://www.hl7.org/fhir/capabilitystatement.html) resource. + The features described in the Capability Statement must be consistent with the required capabilities of an + Argonaut server. The Capability Statement must also advertise the location of the required SMART on FHIR endpoints + that enable authenticated access to the FHIR server resources. + + Not all servers are expected to implement all possible queries and data elements described in the Argonaut API. + For example, the Argonaut specification requires that the Patient resource and only one other Argonaut resource are required. + Implementing the Capability Statement resource allows clients to dynamically determine which of these resources + are supported at runtime, instead of having to specifically write the application to accomidate every known server implementation + at development time. Similarly, by providing information about the location of SMART on FHIR OAuth 2.0 endpoints, + the client does not have to be hard-coded with information about the authorization services associated with + every FHIR API. + + Note that the name of this resource changed to 'Capability Statement' in STU3 to better describe the intent of this resource. + This test refers to it as the Capability Statement as that is what it was called in DSTU2. + + # Test Methodology + + This test suite accesses the server endpoint at `/metadata` using a `GET` request. It parses the Capability Statement and + verifies that the server claims support of following features: + + * JSON encoding of resources + * Patient resource + * At least one of the other resources that form the basis of Argonaut profiles + * SMART on FHIR authorization + + It collects the following information that is saved in the testing session for use by later tests: + + * List of resources supported + * List of queries parameters supported + * SMART on FHIR endpoints + + For more information of the Capability Statement, visit these links: + + * [Capability](https://www.hl7.org/fhir/capabilitystatement.html) + * [Argonaut Capability Requirements](http://hl7.org/fhir/us/core/2019Jan/CapabilityStatement-us-core-server.html) + * [SMART on FHIR Conformance](http://hl7.org/fhir/smart-app-launch/conformance/index.html) + ) + + def assert_operation(op_name); end + + test 'FHIR server capability states JSON support' do + metadata do + id '04' + link 'http://hl7.org/fhir/us/core/2019Jan/CapabilityStatement-us-core-server.html' + desc %( + + FHIR provides multiple [representation formats](https://www.hl7.org/fhir/DSTU2/formats.html) for resources, including JSON and XML. + Argonaut profiles require servers to use the JSON representation: + + ``` + The Argonaut Data Query Server shall support JSON resource format for all Argonaut Data Query interactions. + ``` + [http://hl7.org/fhir/us/core/2019Jan/CapabilityStatement-us-core-server.html](http://hl7.org/fhir/us/core/2019Jan/CapabilityStatement-us-core-server.html) + + The FHIR capability interaction require servers to describe which formats are available for clients to use. The server must + explicitly state that JSON is supported. This is located in the [format element](https://www.hl7.org/fhir/capabilitystatement-definitions.html#CapabilityStatement.format) + of the Capability Resource. + + This test checks that one of the following values are located in the [format field](https://www.hl7.org/fhir/DSTU2/json.html). + + * json + * application/json + * application/json+fhir + + Note that FHIR changed the FHIR-specific JSON mime type to `application/fhir+json` in later versions of the specification. + + ) + end + + assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource' + formats = ['json', 'applcation/json', 'application/json+fhir', 'application/fhir+json'] + assert formats.any? { |format| @conformance.format.include? format }, 'Conformance does not state support for json.' + end + + test 'FHIR server capability SHOULD instantiate from CapabilityStatment-bulk-data' do + metadata do + id '05' + link 'https://build.fhir.org/ig/HL7/bulk-data/operations/index.html' + desc %( + + To declare conformance with this IG, a server should include the following URL in its own CapabilityStatement.instantiates: + http://www.hl7.org/fhir/bulk-data/CapabilityStatement-bulk-data.html + + ) + optional + end + + assert @conformance.instantiates&.include?('http://www.hl7.org/fhir/bulk-data/CapabilityStatement-bulk-data.html'), 'CapabilityStatement did not instantiate from "http://www.hl7.org/fhir/bulk-data/CapabilityStatement-bulk-data.html"' + end + + test 'FHIR server capability SHOULD have export operation' do + metadata do + id '06' + link 'https://build.fhir.org/ig/HL7/bulk-data/operations/index.html' + desc %( + + These OperationDefinitions have been defined for this implementation guide. + * Export: export any data from a FHIR server + * Patient Export: export patient data from a FHIR server + * Group Export: export data for groups of patients from a FHIR server + + ) + optional + end + + begin + Inferno::Models::ServerCapabilities.create( + testing_instance_id: @instance.id, + capabilities: @conformance.as_json + ) + rescue StandardError + assert false, 'Capability Statement could not be parsed.' + end + + assert_operation_supported(@instance.server_capabilities, 'export') + end + + test 'FHIR server capability SHOULD have export operation' do + metadata do + id '07' + link 'https://build.fhir.org/ig/HL7/bulk-data/operations/index.html' + desc %( + + These OperationDefinitions have been defined for this implementation guide. + * Export: export any data from a FHIR server + * Patient Export: export patient data from a FHIR server + * Group Export: export data for groups of patients from a FHIR server + + ) + optional + end + + assert_operation_supported(@instance.server_capabilities, 'patient-export') + end + + test 'FHIR server capability SHOULD have group-export operation' do + metadata do + id '07' + link 'https://build.fhir.org/ig/HL7/bulk-data/operations/index.html' + desc %( + + These OperationDefinitions have been defined for this implementation guide. + * Export: export any data from a FHIR server + * Patient Export: export patient data from a FHIR server + * Group Export: export data for groups of patients from a FHIR server + + ) + optional + end + + assert_operation_supported(@instance.server_capabilities, 'group-export') + end + end + end +end diff --git a/lib/app/modules/bulk_data/bulk_data_patient_export_sequence.rb b/lib/app/modules/bulk_data/bulk_data_patient_export_sequence.rb new file mode 100644 index 000000000..7f0ae69eb --- /dev/null +++ b/lib/app/modules/bulk_data/bulk_data_patient_export_sequence.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +module Inferno + module Sequence + class BulkDataPatientExportSequence < SequenceBase + group 'Bulk Data Patient Export' + + title 'Patient Tests' + + description 'Verify that Patient resources on the Bulk Data server follow the Bulk Data Access Implementation Guide' + + test_id_prefix 'Patient' + + requires :token + conformance_supports :Patient + + def assert_export_kick_off(klass) + reply = export_kick_off(klass) + + assert_response_accepted(reply) + @content_location = reply.response[:headers]['content-location'] + + assert @content_location.present?, 'Export response header did not include "Content-Location"' + end + + def assert_export_status(url, timeout: 180) + reply = export_status_check(url, timeout) + + # server response status code could be 202 (still processing), 200 (complete) or 4xx/5xx error code + # export_status_check processes reponses with status 202 code + # and returns server response when status code is not 202 or timed out + + skip "Server took more than #{timeout} seconds to process the request." if reply.code == 202 + + assert reply.code == 200, "Bad response code: expected 200, 202, but found #{reply.code}." + + assert_response_content_type(reply, 'application/json') + + response_body = JSON.parse(reply.body) + + assert_status_reponse_required_field(response_body) + + @output = response_body['output'] + end + + details %( + + The #{title} Sequence tests `#{title}` operations. The operation steps will be checked for consistency against the + [Bulk Data Access Implementation Guide](https://build.fhir.org/ig/HL7/bulk-data/) + + ) + + @resources_found = false + + test 'Server rejects $export request without authorization' do + metadata do + id '01' + link 'https://build.fhir.org/ig/HL7/bulk-data/export/index.html#bulk-data-kick-off-request' + desc %( + ) + end + + @client.set_no_auth + skip 'Could not verify this functionality when bearer token is not set' if @instance.token.blank? + + reply = export_kick_off('Patient') + @client.set_bearer_token(@instance.token) + assert_response_unauthorized reply + end + + test 'Server shall return "202 Accepted" and "Content-location" for $export operation' do + metadata do + id '02' + link 'https://build.fhir.org/ig/HL7/bulk-data/export/index.html#bulk-data-kick-off-request' + desc %( + ) + end + + assert_export_kick_off('Patient') + end + + test 'Server shall return "202 Accepted" or "200 OK"' do + metadata do + id '03' + link 'https://build.fhir.org/ig/HL7/bulk-data/export/index.html#bulk-data-status-request' + desc %( + ) + end + + assert_export_status(@content_location) + end + + private + + def export_kick_off(klass) + headers = { accept: 'application/fhir+json', prefer: 'respond-async' } + + url = '' + url += "/#{klass}" if klass.present? + url += '/$export' + + @client.get(url, @client.fhir_headers(headers)) + end + + def export_status_check(url, timeout) + wait_time = 1 + reply = nil + headers = { accept: 'application/json' } + start = Time.now + + loop do + reply = @client.get(url, @client.fhir_headers(headers)) + + wait_time = get_wait_time(wait_time, reply) + seconds_used = Time.now - start + wait_time + + break if reply.code != 202 || seconds_used > timeout + + sleep wait_time + end + + reply + end + + def get_wait_time(wait_time, reply) + retry_after = reply.response[:headers]['retry-after'] + retry_after_int = (retry_after.presence || 0).to_i + + if retry_after_int.positive? + retry_after_int + else + wait_time * 2 + end + end + + def assert_status_reponse_required_field(response_body) + ['transactionTime', 'request', 'requiresAccessToken', 'output', 'error'].each do |key| + assert response_body.key?(key), "Complete Status response did not contain \"#{key}\" as required" + end + end + end + end +end diff --git a/lib/app/modules/bulk_data_module.yml b/lib/app/modules/bulk_data_module.yml new file mode 100644 index 000000000..07803092a --- /dev/null +++ b/lib/app/modules/bulk_data_module.yml @@ -0,0 +1,16 @@ +name: bulk_data +title: FHIR Bulk Data Access Implementation Guide STU1 +description: Bulk Data Access Testing +fhir_version: stu3 +default_test_set: developer +test_sets: + developer: + view: default + tests: + - name: Discovery + sequences: + - BulkDataCapabilityStatementSequence + - name: Patient export + sequences: + - BulkDataPatientExportSequence + diff --git a/lib/app/modules/core/capability_statement_sequence.rb b/lib/app/modules/core/capability_statement_sequence.rb index ba8a27d91..8ead86db8 100644 --- a/lib/app/modules/core/capability_statement_sequence.rb +++ b/lib/app/modules/core/capability_statement_sequence.rb @@ -105,8 +105,22 @@ class CapabilityStatementSequence < SequenceBase [http://hl7.org/fhir/DSTU2/http.html#conformance](http://hl7.org/fhir/DSTU2/http.html#conformance) + for STU3 FHIR: + + > Applications SHALL return a Capability Statement that specifies which resource types and interactions are supported for the GET command. + + [http://hl7.org/fhir/STU3/http.html#capabilities](http://hl7.org/fhir/STU3/http.html#capabilities) + + or for R4 FHIR: + + > Applications SHALL return a resource that describes the functionality of the server end-point. + + [http://hl7.org/fhir/R4/http.html#capabilities](http://hl7.org/fhir/R4/http.html#capabilities) + It does this by checking that the server responds with an HTTP OK 200 status code and that the body of the - response contains a valid [DSTU2 Conformance resource](http://hl7.org/fhir/DSTU2/conformance.html). + response contains a valid [DSTU2 Conformance resource](http://hl7.org/fhir/DSTU2/conformance.html), + [STU3 CapabilityStatement resource](http://hl7.org/fhir/STU3/capabilitystatement.html), or + [R4 CapabilityStatement resource](http://hl7.org/fhir/R4/capabilitystatement.html). This test does not inspect the content of the Conformance resource to see if it contains the required information. It only checks to see if the RESTful interaction is supported and returns a valid Conformance resource. @@ -120,7 +134,16 @@ class CapabilityStatementSequence < SequenceBase @conformance = @client.conformance_statement assert_response_ok @client.reply - assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource.' + assert_valid_conformance + + begin + @server_capabilities = Inferno::Models::ServerCapabilities.create( + testing_instance_id: @instance.id, + capabilities: @conformance.as_json + ) + rescue StandardError + assert false, 'Capability Statement could not be parsed.' + end end end end diff --git a/lib/app/modules/onc_program/onc_standalone_launch_sequence.rb b/lib/app/modules/onc_program/onc_standalone_launch_sequence.rb index c3b1f1b49..5a88248b5 100644 --- a/lib/app/modules/onc_program/onc_standalone_launch_sequence.rb +++ b/lib/app/modules/onc_program/onc_standalone_launch_sequence.rb @@ -11,7 +11,7 @@ class OncStandaloneLaunchSequence < StandaloneLaunchSequence description 'Demonstrate the ONC SMART Standalone Launch Sequence.' - test_id_prefix 'OELS' + test_id_prefix 'OSLS' requires :client_id, :confidential_client, :client_secret, :oauth_authorize_endpoint, :oauth_token_endpoint, :scopes, :initiate_login_uri, :redirect_uris diff --git a/lib/app/modules/us_core_module.yml b/lib/app/modules/us_core_module.yml index e700775e1..cfb5e4225 100644 --- a/lib/app/modules/us_core_module.yml +++ b/lib/app/modules/us_core_module.yml @@ -18,7 +18,7 @@ test_sets: - ManualRegistrationSequence - StandaloneLaunchSequence - EHRLaunchSequence - - name: US Core R4 + - name: US Core R4 Patient Based Profiles run_all: true sequences: - USCoreR4AllergyintoleranceSequence @@ -32,18 +32,18 @@ test_sets: - USCoreR4EncounterSequence - USCoreR4GoalSequence - USCoreR4ImmunizationSequence - - USCoreR4LocationSequence - - USCoreR4MedicationSequence - USCoreR4MedicationrequestSequence - USCoreR4MedicationstatementSequence - USCoreR4SmokingstatusSequence - PediatricWeightForHeightSequence - USCoreR4ObservationLabSequence - PediatricBmiForAgeSequence - - USCoreR4OrganizationSequence - USCoreR4PatientSequence - - USCoreR4PractitionerSequence - - USCoreR4PractitionerroleSequence - USCoreR4ProcedureSequence - R4ProvenanceSequence - USCoreR4ClinicalNotesSequence + - USCoreR4LocationSequence + - USCoreR4MedicationSequence + - USCoreR4OrganizationSequence + - USCoreR4PractitionerSequence + - USCoreR4PractitionerroleSequence diff --git a/lib/app/modules/us_core_r4/pediatric_bmi_for_age_sequence.rb b/lib/app/modules/us_core_r4/pediatric_bmi_for_age_sequence.rb index 072736d10..46815dbdf 100644 --- a/lib/app/modules/us_core_r4/pediatric_bmi_for_age_sequence.rb +++ b/lib/app/modules/us_core_r4/pediatric_bmi_for_age_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [PediatricBmiForAge Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-bmi-for-age) + returned will be checked for consistency against the [PediatricBmiForAge Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @observation = reply.try(:resource).try(:entry).try(:first).try(:resource) @observation_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply) + save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_bmi_age]) + save_delayed_sequence_references(@observation) validate_search_reply(versioned_resource_class('Observation'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -251,14 +254,14 @@ def validate_resource_item(resource, property, value) test 'Observation resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-bmi-for-age.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('Observation') + test_resources_against_profile('Observation', Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_bmi_age]) end test 'At least one of every must support element is provided in any Observation for this patient.' do diff --git a/lib/app/modules/us_core_r4/pediatric_weight_for_height_sequence.rb b/lib/app/modules/us_core_r4/pediatric_weight_for_height_sequence.rb index 472da6498..2224b7511 100644 --- a/lib/app/modules/us_core_r4/pediatric_weight_for_height_sequence.rb +++ b/lib/app/modules/us_core_r4/pediatric_weight_for_height_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [PediatricWeightForHeight Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-weight-for-height) + returned will be checked for consistency against the [PediatricWeightForHeight Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/pediatric-weight-for-height) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @observation = reply.try(:resource).try(:entry).try(:first).try(:resource) @observation_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply) + save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_weight_height]) + save_delayed_sequence_references(@observation) validate_search_reply(versioned_resource_class('Observation'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -251,14 +254,14 @@ def validate_resource_item(resource, property, value) test 'Observation resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-pediatric-weight-for-height.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/pediatric-weight-for-height' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('Observation') + test_resources_against_profile('Observation', Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_weight_height]) end test 'At least one of every must support element is provided in any Observation for this patient.' do diff --git a/lib/app/modules/us_core_r4/us_core_allergyintolerance_sequence.rb b/lib/app/modules/us_core_r4/us_core_allergyintolerance_sequence.rb index 1ce0d3e01..31af9bc12 100644 --- a/lib/app/modules/us_core_r4/us_core_allergyintolerance_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_allergyintolerance_sequence.rb @@ -31,7 +31,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Allergyintolerance Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-allergyintolerance) + returned will be checked for consistency against the [Allergyintolerance Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-allergyintolerance) ) @@ -51,6 +51,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('AllergyIntolerance'), search_params) @client.set_bearer_token(@instance.token) @@ -82,6 +83,7 @@ def validate_resource_item(resource, property, value) @allergyintolerance = reply.try(:resource).try(:entry).try(:first).try(:resource) @allergyintolerance_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('AllergyIntolerance'), reply) + save_delayed_sequence_references(@allergyintolerance) validate_search_reply(versioned_resource_class('AllergyIntolerance'), reply, search_params) end @@ -89,6 +91,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -155,7 +158,7 @@ def validate_resource_item(resource, property, value) test 'AllergyIntolerance resources associated with Patient conform to US Core R4 profiles' do metadata do id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-allergyintolerance.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-allergyintolerance' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_careplan_sequence.rb b/lib/app/modules/us_core_r4/us_core_careplan_sequence.rb index 14ac40341..e91f0f73f 100644 --- a/lib/app/modules/us_core_r4/us_core_careplan_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_careplan_sequence.rb @@ -41,7 +41,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Careplan Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-careplan) + returned will be checked for consistency against the [Careplan Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-careplan) ) @@ -89,6 +89,7 @@ def validate_resource_item(resource, property, value) @careplan = reply.try(:resource).try(:entry).try(:first).try(:resource) @careplan_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('CarePlan'), reply) + save_delayed_sequence_references(@careplan) validate_search_reply(versioned_resource_class('CarePlan'), reply, search_params) end @@ -96,6 +97,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -119,6 +121,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -151,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -226,7 +230,7 @@ def validate_resource_item(resource, property, value) test 'CarePlan resources associated with Patient conform to US Core R4 profiles' do metadata do id '09' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-careplan.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-careplan' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_careteam_sequence.rb b/lib/app/modules/us_core_r4/us_core_careteam_sequence.rb index b88f600cc..507bb20fb 100644 --- a/lib/app/modules/us_core_r4/us_core_careteam_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_careteam_sequence.rb @@ -31,7 +31,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Careteam Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-careteam) + returned will be checked for consistency against the [Careteam Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-careteam) ) @@ -79,6 +79,7 @@ def validate_resource_item(resource, property, value) @careteam = reply.try(:resource).try(:entry).try(:first).try(:resource) @careteam_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('CareTeam'), reply) + save_delayed_sequence_references(@careteam) validate_search_reply(versioned_resource_class('CareTeam'), reply, search_params) end @@ -130,7 +131,7 @@ def validate_resource_item(resource, property, value) test 'CareTeam resources associated with Patient conform to US Core R4 profiles' do metadata do id '06' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-careteam.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-careteam' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_condition_sequence.rb b/lib/app/modules/us_core_r4/us_core_condition_sequence.rb index 27615ebfe..68c76b689 100644 --- a/lib/app/modules/us_core_r4/us_core_condition_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_condition_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Condition Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-condition) + returned will be checked for consistency against the [Condition Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition) ) @@ -65,6 +65,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Condition'), search_params) @client.set_bearer_token(@instance.token) @@ -96,6 +97,7 @@ def validate_resource_item(resource, property, value) @condition = reply.try(:resource).try(:entry).try(:first).try(:resource) @condition_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Condition'), reply) + save_delayed_sequence_references(@condition) validate_search_reply(versioned_resource_class('Condition'), reply, search_params) end @@ -103,6 +105,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -133,6 +136,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -155,6 +159,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -177,6 +182,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -243,7 +249,7 @@ def validate_resource_item(resource, property, value) test 'Condition resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-condition.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_device_sequence.rb b/lib/app/modules/us_core_r4/us_core_device_sequence.rb index 18ac82ea1..fc099f4dd 100644 --- a/lib/app/modules/us_core_r4/us_core_device_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_device_sequence.rb @@ -31,7 +31,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Device Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-device) + returned will be checked for consistency against the [Device Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-device) ) @@ -51,6 +51,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Device'), search_params) @client.set_bearer_token(@instance.token) @@ -82,6 +83,7 @@ def validate_resource_item(resource, property, value) @device = reply.try(:resource).try(:entry).try(:first).try(:resource) @device_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Device'), reply) + save_delayed_sequence_references(@device) validate_search_reply(versioned_resource_class('Device'), reply, search_params) end @@ -89,6 +91,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -155,7 +158,7 @@ def validate_resource_item(resource, property, value) test 'Device resources associated with Patient conform to US Core R4 profiles' do metadata do id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-device.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-device' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_diagnosticreport_lab_sequence.rb b/lib/app/modules/us_core_r4/us_core_diagnosticreport_lab_sequence.rb index abcbfd3e9..6760be109 100644 --- a/lib/app/modules/us_core_r4/us_core_diagnosticreport_lab_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_diagnosticreport_lab_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [DiagnosticreportLab Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-lab) + returned will be checked for consistency against the [DiagnosticreportLab Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-lab) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @diagnosticreport = reply.try(:resource).try(:entry).try(:first).try(:resource) @diagnosticreport_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('DiagnosticReport'), reply) + save_resource_ids_in_bundle(versioned_resource_class('DiagnosticReport'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_lab]) + save_delayed_sequence_references(@diagnosticreport) validate_search_reply(versioned_resource_class('DiagnosticReport'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -206,6 +209,7 @@ def validate_resource_item(resource, property, value) metadata do id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -225,6 +229,7 @@ def validate_resource_item(resource, property, value) metadata do id '08' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -315,14 +320,14 @@ def validate_resource_item(resource, property, value) test 'DiagnosticReport resources associated with Patient conform to US Core R4 profiles' do metadata do id '13' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-lab.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-lab' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('DiagnosticReport') + test_resources_against_profile('DiagnosticReport', Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_lab]) end test 'At least one of every must support element is provided in any DiagnosticReport for this patient.' do diff --git a/lib/app/modules/us_core_r4/us_core_diagnosticreport_note_sequence.rb b/lib/app/modules/us_core_r4/us_core_diagnosticreport_note_sequence.rb index f5004464a..f423aadd5 100644 --- a/lib/app/modules/us_core_r4/us_core_diagnosticreport_note_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_diagnosticreport_note_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [DiagnosticreportNote Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-note) + returned will be checked for consistency against the [DiagnosticreportNote Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-note) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @diagnosticreport = reply.try(:resource).try(:entry).try(:first).try(:resource) @diagnosticreport_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('DiagnosticReport'), reply) + save_resource_ids_in_bundle(versioned_resource_class('DiagnosticReport'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_note]) + save_delayed_sequence_references(@diagnosticreport) validate_search_reply(versioned_resource_class('DiagnosticReport'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -206,6 +209,7 @@ def validate_resource_item(resource, property, value) metadata do id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -225,6 +229,7 @@ def validate_resource_item(resource, property, value) metadata do id '08' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -315,14 +320,14 @@ def validate_resource_item(resource, property, value) test 'DiagnosticReport resources associated with Patient conform to US Core R4 profiles' do metadata do id '13' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-diagnosticreport-note.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-note' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('DiagnosticReport') + test_resources_against_profile('DiagnosticReport', Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_note]) end test 'At least one of every must support element is provided in any DiagnosticReport for this patient.' do diff --git a/lib/app/modules/us_core_r4/us_core_documentreference_sequence.rb b/lib/app/modules/us_core_r4/us_core_documentreference_sequence.rb index a24f08210..a901a219b 100644 --- a/lib/app/modules/us_core_r4/us_core_documentreference_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_documentreference_sequence.rb @@ -53,7 +53,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Documentreference Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-documentreference) + returned will be checked for consistency against the [Documentreference Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-documentreference) ) @@ -73,6 +73,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('DocumentReference'), search_params) @client.set_bearer_token(@instance.token) @@ -104,6 +105,7 @@ def validate_resource_item(resource, property, value) @documentreference = reply.try(:resource).try(:entry).try(:first).try(:resource) @documentreference_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('DocumentReference'), reply) + save_delayed_sequence_references(@documentreference) validate_search_reply(versioned_resource_class('DocumentReference'), reply, search_params) end @@ -199,6 +201,7 @@ def validate_resource_item(resource, property, value) metadata do id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -221,6 +224,7 @@ def validate_resource_item(resource, property, value) metadata do id '08' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -311,7 +315,7 @@ def validate_resource_item(resource, property, value) test 'DocumentReference resources associated with Patient conform to US Core R4 profiles' do metadata do id '13' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-documentreference.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-documentreference' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_encounter_sequence.rb b/lib/app/modules/us_core_r4/us_core_encounter_sequence.rb index ec408e5a9..d196b2c4e 100644 --- a/lib/app/modules/us_core_r4/us_core_encounter_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_encounter_sequence.rb @@ -53,7 +53,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Encounter Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-encounter) + returned will be checked for consistency against the [Encounter Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter) ) @@ -73,6 +73,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Encounter'), search_params) @client.set_bearer_token(@instance.token) @@ -104,6 +105,7 @@ def validate_resource_item(resource, property, value) @encounter = reply.try(:resource).try(:entry).try(:first).try(:resource) @encounter_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Encounter'), reply) + save_delayed_sequence_references(@encounter) validate_search_reply(versioned_resource_class('Encounter'), reply, search_params) end @@ -162,6 +164,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -183,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -205,6 +209,7 @@ def validate_resource_item(resource, property, value) metadata do id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -227,6 +232,7 @@ def validate_resource_item(resource, property, value) metadata do id '08' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -293,7 +299,7 @@ def validate_resource_item(resource, property, value) test 'Encounter resources associated with Patient conform to US Core R4 profiles' do metadata do id '12' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-encounter.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_goal_sequence.rb b/lib/app/modules/us_core_r4/us_core_goal_sequence.rb index 033e8f5f4..9c155df24 100644 --- a/lib/app/modules/us_core_r4/us_core_goal_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_goal_sequence.rb @@ -37,7 +37,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Goal Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-goal) + returned will be checked for consistency against the [Goal Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-goal) ) @@ -57,6 +57,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Goal'), search_params) @client.set_bearer_token(@instance.token) @@ -88,6 +89,7 @@ def validate_resource_item(resource, property, value) @goal = reply.try(:resource).try(:entry).try(:first).try(:resource) @goal_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Goal'), reply) + save_delayed_sequence_references(@goal) validate_search_reply(versioned_resource_class('Goal'), reply, search_params) end @@ -95,6 +97,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -125,6 +128,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -191,7 +195,7 @@ def validate_resource_item(resource, property, value) test 'Goal resources associated with Patient conform to US Core R4 profiles' do metadata do id '08' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-goal.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-goal' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_immunization_sequence.rb b/lib/app/modules/us_core_r4/us_core_immunization_sequence.rb index f20b53d88..fae1ad326 100644 --- a/lib/app/modules/us_core_r4/us_core_immunization_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_immunization_sequence.rb @@ -37,7 +37,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Immunization Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-immunization) + returned will be checked for consistency against the [Immunization Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-immunization) ) @@ -57,6 +57,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Immunization'), search_params) @client.set_bearer_token(@instance.token) @@ -88,6 +89,7 @@ def validate_resource_item(resource, property, value) @immunization = reply.try(:resource).try(:entry).try(:first).try(:resource) @immunization_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Immunization'), reply) + save_delayed_sequence_references(@immunization) validate_search_reply(versioned_resource_class('Immunization'), reply, search_params) end @@ -95,6 +97,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -125,6 +128,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -191,7 +195,7 @@ def validate_resource_item(resource, property, value) test 'Immunization resources associated with Patient conform to US Core R4 profiles' do metadata do id '08' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-immunization.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-immunization' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_location_sequence.rb b/lib/app/modules/us_core_r4/us_core_location_sequence.rb index 00881a850..30e308a3d 100644 --- a/lib/app/modules/us_core_r4/us_core_location_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_location_sequence.rb @@ -11,8 +11,9 @@ class USCoreR4LocationSequence < SequenceBase test_id_prefix 'Location' # change me - requires :token, :patient_id + requires :token conformance_supports :Location + delayed_sequence def validate_resource_item(resource, property, value) case property @@ -22,7 +23,7 @@ def validate_resource_item(resource, property, value) assert value_found, 'name on resource does not match name requested' when 'address' - value_found = can_resolve_path(resource, 'address') { |value_in_resource| value_in_resource == value } + value_found = can_resolve_path(resource, 'address.city') { |value_in_resource| value_in_resource == value } assert value_found, 'address on resource does not match address requested' when 'address-city' @@ -43,15 +44,30 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Location Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-location) + returned will be checked for consistency against the [Location Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-location) ) @resources_found = false - test 'Server rejects Location search without authorization' do + test 'Can read Location from the server' do metadata do id '01' + link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + desc %( + ) + versions :r4 + end + + location_id = @instance.resource_references.find { |reference| reference.resource_type == 'Location' }&.resource_id + skip 'No Location references found from the prior searches' if location_id.nil? + @location = fetch_resource('Location', location_id) + @resources_found = !@location.nil? + end + + test 'Server rejects Location search without authorization' do + metadata do + id '02' link 'http://www.fhir.org/guides/argonaut/r2/Conformance-server.html' desc %( ) @@ -61,7 +77,9 @@ def validate_resource_item(resource, property, value) @client.set_no_auth skip 'Could not verify this functionality when bearer token is not set' if @instance.token.blank? - search_params = { patient: @instance.patient_id, name: 'Boston' } + name_val = resolve_element_from_path(@location, 'name') + search_params = { 'name': name_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Location'), search_params) @client.set_bearer_token(@instance.token) @@ -70,14 +88,16 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Location search by name' do metadata do - id '02' + id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) versions :r4 end - search_params = { patient: @instance.patient_id, name: 'Boston' } + name_val = resolve_element_from_path(@location, 'name') + search_params = { 'name': name_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Location'), search_params) assert_response_ok(reply) @@ -91,12 +111,13 @@ def validate_resource_item(resource, property, value) @location = reply.try(:resource).try(:entry).try(:first).try(:resource) @location_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Location'), reply) + save_delayed_sequence_references(@location) validate_search_reply(versioned_resource_class('Location'), reply, search_params) end test 'Server returns expected results from Location search by address' do metadata do - id '03' + id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -106,7 +127,7 @@ def validate_resource_item(resource, property, value) skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found assert !@location.nil?, 'Expected valid Location resource to be present' - address_val = resolve_element_from_path(@location, 'address') + address_val = resolve_element_from_path(@location, 'address.city') search_params = { 'address': address_val } search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } @@ -117,8 +138,9 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Location search by address-city' do metadata do - id '04' + id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -138,8 +160,9 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Location search by address-state' do metadata do - id '05' + id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -159,8 +182,9 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Location search by address-postalcode' do metadata do - id '06' + id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -178,21 +202,6 @@ def validate_resource_item(resource, property, value) assert_response_ok(reply) end - test 'Location read resource supported' do - metadata do - id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' - desc %( - ) - versions :r4 - end - - skip_if_not_supported(:Location, [:read]) - skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - - validate_read_reply(@location, versioned_resource_class('Location')) - end - test 'Location vread resource supported' do metadata do id '08' @@ -226,7 +235,7 @@ def validate_resource_item(resource, property, value) test 'Location resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-location.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-location' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_medication_sequence.rb b/lib/app/modules/us_core_r4/us_core_medication_sequence.rb index 6543d5242..42ed88a8c 100644 --- a/lib/app/modules/us_core_r4/us_core_medication_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_medication_sequence.rb @@ -11,19 +11,20 @@ class USCoreR4MedicationSequence < SequenceBase test_id_prefix 'Medication' # change me - requires :token, :patient_id + requires :token conformance_supports :Medication + delayed_sequence details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Medication Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medication) + returned will be checked for consistency against the [Medication Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication) ) @resources_found = false - test 'Medication read resource supported' do + test 'Can read Medication from the server' do metadata do id '01' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' @@ -32,10 +33,10 @@ class USCoreR4MedicationSequence < SequenceBase versions :r4 end - skip_if_not_supported(:Medication, [:read]) - skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - - validate_read_reply(@medication, versioned_resource_class('Medication')) + medication_id = @instance.resource_references.find { |reference| reference.resource_type == 'Medication' }&.resource_id + skip 'No Medication references found from the prior searches' if medication_id.nil? + @medication = fetch_resource('Medication', medication_id) + @resources_found = !@medication.nil? end test 'Medication vread resource supported' do @@ -71,7 +72,7 @@ class USCoreR4MedicationSequence < SequenceBase test 'Medication resources associated with Patient conform to US Core R4 profiles' do metadata do id '04' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medication.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_medicationrequest_sequence.rb b/lib/app/modules/us_core_r4/us_core_medicationrequest_sequence.rb index 7c5cf4b96..289efd0c9 100644 --- a/lib/app/modules/us_core_r4/us_core_medicationrequest_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_medicationrequest_sequence.rb @@ -35,7 +35,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Medicationrequest Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medicationrequest) + returned will be checked for consistency against the [Medicationrequest Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest) ) @@ -55,6 +55,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('MedicationRequest'), search_params) @client.set_bearer_token(@instance.token) @@ -86,6 +87,7 @@ def validate_resource_item(resource, property, value) @medicationrequest = reply.try(:resource).try(:entry).try(:first).try(:resource) @medicationrequest_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('MedicationRequest'), reply) + save_delayed_sequence_references(@medicationrequest) validate_search_reply(versioned_resource_class('MedicationRequest'), reply, search_params) end @@ -115,6 +117,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -181,7 +184,7 @@ def validate_resource_item(resource, property, value) test 'MedicationRequest resources associated with Patient conform to US Core R4 profiles' do metadata do id '08' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medicationrequest.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_medicationstatement_sequence.rb b/lib/app/modules/us_core_r4/us_core_medicationstatement_sequence.rb index 5081eb44d..b18841b65 100644 --- a/lib/app/modules/us_core_r4/us_core_medicationstatement_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_medicationstatement_sequence.rb @@ -37,7 +37,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Medicationstatement Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medicationstatement) + returned will be checked for consistency against the [Medicationstatement Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationstatement) ) @@ -57,6 +57,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('MedicationStatement'), search_params) @client.set_bearer_token(@instance.token) @@ -88,6 +89,7 @@ def validate_resource_item(resource, property, value) @medicationstatement = reply.try(:resource).try(:entry).try(:first).try(:resource) @medicationstatement_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('MedicationStatement'), reply) + save_delayed_sequence_references(@medicationstatement) validate_search_reply(versioned_resource_class('MedicationStatement'), reply, search_params) end @@ -95,6 +97,7 @@ def validate_resource_item(resource, property, value) metadata do id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -125,6 +128,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -191,7 +195,7 @@ def validate_resource_item(resource, property, value) test 'MedicationStatement resources associated with Patient conform to US Core R4 profiles' do metadata do id '08' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-medicationstatement.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationstatement' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_observation_lab_sequence.rb b/lib/app/modules/us_core_r4/us_core_observation_lab_sequence.rb index a2b658ad0..83288e8c8 100644 --- a/lib/app/modules/us_core_r4/us_core_observation_lab_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_observation_lab_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [ObservationLab Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-observation-lab) + returned will be checked for consistency against the [ObservationLab Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @observation = reply.try(:resource).try(:entry).try(:first).try(:resource) @observation_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply) + save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:lab_results]) + save_delayed_sequence_references(@observation) validate_search_reply(versioned_resource_class('Observation'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -251,14 +254,14 @@ def validate_resource_item(resource, property, value) test 'Observation resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-observation-lab.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('Observation') + test_resources_against_profile('Observation', Inferno::ValidationUtil::US_CORE_R4_URIS[:lab_results]) end test 'At least one of every must support element is provided in any Observation for this patient.' do diff --git a/lib/app/modules/us_core_r4/us_core_organization_sequence.rb b/lib/app/modules/us_core_r4/us_core_organization_sequence.rb index 474d93d13..2084fef6b 100644 --- a/lib/app/modules/us_core_r4/us_core_organization_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_organization_sequence.rb @@ -11,8 +11,9 @@ class USCoreR4OrganizationSequence < SequenceBase test_id_prefix 'Organization' # change me - requires :token, :patient_id + requires :token conformance_supports :Organization + delayed_sequence def validate_resource_item(resource, property, value) case property @@ -22,7 +23,7 @@ def validate_resource_item(resource, property, value) assert value_found, 'name on resource does not match name requested' when 'address' - value_found = can_resolve_path(resource, 'address') { |value_in_resource| value_in_resource == value } + value_found = can_resolve_path(resource, 'address.city') { |value_in_resource| value_in_resource == value } assert value_found, 'address on resource does not match address requested' end @@ -31,15 +32,30 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Organization Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-organization) + returned will be checked for consistency against the [Organization Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization) ) @resources_found = false - test 'Server rejects Organization search without authorization' do + test 'Can read Organization from the server' do metadata do id '01' + link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + desc %( + ) + versions :r4 + end + + organization_id = @instance.resource_references.find { |reference| reference.resource_type == 'Organization' }&.resource_id + skip 'No Organization references found from the prior searches' if organization_id.nil? + @organization = fetch_resource('Organization', organization_id) + @resources_found = !@organization.nil? + end + + test 'Server rejects Organization search without authorization' do + metadata do + id '02' link 'http://www.fhir.org/guides/argonaut/r2/Conformance-server.html' desc %( ) @@ -49,7 +65,9 @@ def validate_resource_item(resource, property, value) @client.set_no_auth skip 'Could not verify this functionality when bearer token is not set' if @instance.token.blank? - search_params = { patient: @instance.patient_id, name: 'Boston' } + name_val = resolve_element_from_path(@organization, 'name') + search_params = { 'name': name_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Organization'), search_params) @client.set_bearer_token(@instance.token) @@ -58,14 +76,16 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Organization search by name' do metadata do - id '02' + id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) versions :r4 end - search_params = { patient: @instance.patient_id, name: 'Boston' } + name_val = resolve_element_from_path(@organization, 'name') + search_params = { 'name': name_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Organization'), search_params) assert_response_ok(reply) @@ -79,12 +99,13 @@ def validate_resource_item(resource, property, value) @organization = reply.try(:resource).try(:entry).try(:first).try(:resource) @organization_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Organization'), reply) + save_delayed_sequence_references(@organization) validate_search_reply(versioned_resource_class('Organization'), reply, search_params) end test 'Server returns expected results from Organization search by address' do metadata do - id '03' + id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -94,7 +115,7 @@ def validate_resource_item(resource, property, value) skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found assert !@organization.nil?, 'Expected valid Organization resource to be present' - address_val = resolve_element_from_path(@organization, 'address') + address_val = resolve_element_from_path(@organization, 'address.city') search_params = { 'address': address_val } search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } @@ -103,21 +124,6 @@ def validate_resource_item(resource, property, value) assert_response_ok(reply) end - test 'Organization read resource supported' do - metadata do - id '04' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' - desc %( - ) - versions :r4 - end - - skip_if_not_supported(:Organization, [:read]) - skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - - validate_read_reply(@organization, versioned_resource_class('Organization')) - end - test 'Organization vread resource supported' do metadata do id '05' @@ -151,7 +157,7 @@ def validate_resource_item(resource, property, value) test 'Organization resources associated with Patient conform to US Core R4 profiles' do metadata do id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-organization.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_patient_sequence.rb b/lib/app/modules/us_core_r4/us_core_patient_sequence.rb index 05a662335..71fcf848e 100644 --- a/lib/app/modules/us_core_r4/us_core_patient_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_patient_sequence.rb @@ -60,7 +60,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Patient Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-patient) + returned will be checked for consistency against the [Patient Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient) ) @@ -108,6 +108,7 @@ def validate_resource_item(resource, property, value) @patient = reply.try(:resource).try(:entry).try(:first).try(:resource) @patient_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Patient'), reply) + save_delayed_sequence_references(@patient) validate_search_reply(versioned_resource_class('Patient'), reply, search_params) end @@ -201,6 +202,7 @@ def validate_resource_item(resource, property, value) metadata do id '07' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -223,6 +225,7 @@ def validate_resource_item(resource, property, value) metadata do id '08' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -289,7 +292,7 @@ def validate_resource_item(resource, property, value) test 'Patient resources associated with Patient conform to US Core R4 profiles' do metadata do id '12' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-patient.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_practitioner_sequence.rb b/lib/app/modules/us_core_r4/us_core_practitioner_sequence.rb index cba4f623a..ed8fc3ec5 100644 --- a/lib/app/modules/us_core_r4/us_core_practitioner_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_practitioner_sequence.rb @@ -11,8 +11,9 @@ class USCoreR4PractitionerSequence < SequenceBase test_id_prefix 'Practitioner' # change me - requires :token, :patient_id + requires :token conformance_supports :Practitioner + delayed_sequence def validate_resource_item(resource, property, value) case property @@ -38,15 +39,30 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Practitioner Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-practitioner) + returned will be checked for consistency against the [Practitioner Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner) ) @resources_found = false - test 'Server rejects Practitioner search without authorization' do + test 'Can read Practitioner from the server' do metadata do id '01' + link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + desc %( + ) + versions :r4 + end + + practitioner_id = @instance.resource_references.find { |reference| reference.resource_type == 'Practitioner' }&.resource_id + skip 'No Practitioner references found from the prior searches' if practitioner_id.nil? + @practitioner = fetch_resource('Practitioner', practitioner_id) + @resources_found = !@practitioner.nil? + end + + test 'Server rejects Practitioner search without authorization' do + metadata do + id '02' link 'http://www.fhir.org/guides/argonaut/r2/Conformance-server.html' desc %( ) @@ -56,8 +72,9 @@ def validate_resource_item(resource, property, value) @client.set_no_auth skip 'Could not verify this functionality when bearer token is not set' if @instance.token.blank? - name_val = @practitioner&.name&.first&.family + name_val = resolve_element_from_path(@practitioner, 'name.family') search_params = { 'name': name_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Practitioner'), search_params) @client.set_bearer_token(@instance.token) @@ -66,7 +83,7 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from Practitioner search by name' do metadata do - id '02' + id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -89,12 +106,13 @@ def validate_resource_item(resource, property, value) @practitioner = reply.try(:resource).try(:entry).try(:first).try(:resource) @practitioner_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Practitioner'), reply) + save_delayed_sequence_references(@practitioner) validate_search_reply(versioned_resource_class('Practitioner'), reply, search_params) end test 'Server returns expected results from Practitioner search by identifier' do metadata do - id '03' + id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -113,21 +131,6 @@ def validate_resource_item(resource, property, value) assert_response_ok(reply) end - test 'Practitioner read resource supported' do - metadata do - id '04' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' - desc %( - ) - versions :r4 - end - - skip_if_not_supported(:Practitioner, [:read]) - skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - - validate_read_reply(@practitioner, versioned_resource_class('Practitioner')) - end - test 'Practitioner vread resource supported' do metadata do id '05' @@ -161,7 +164,7 @@ def validate_resource_item(resource, property, value) test 'Practitioner resources associated with Patient conform to US Core R4 profiles' do metadata do id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-practitioner.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_practitionerrole_sequence.rb b/lib/app/modules/us_core_r4/us_core_practitionerrole_sequence.rb index 0d38b973f..b22d017a3 100644 --- a/lib/app/modules/us_core_r4/us_core_practitionerrole_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_practitionerrole_sequence.rb @@ -11,8 +11,9 @@ class USCoreR4PractitionerroleSequence < SequenceBase test_id_prefix 'PractitionerRole' # change me - requires :token, :patient_id + requires :token conformance_supports :PractitionerRole + delayed_sequence def validate_resource_item(resource, property, value) case property @@ -31,15 +32,30 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Practitionerrole Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-practitionerrole) + returned will be checked for consistency against the [Practitionerrole Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole) ) @resources_found = false - test 'Server rejects PractitionerRole search without authorization' do + test 'Can read PractitionerRole from the server' do metadata do id '01' + link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + desc %( + ) + versions :r4 + end + + practitionerrole_id = @instance.resource_references.find { |reference| reference.resource_type == 'PractitionerRole' }&.resource_id + skip 'No PractitionerRole references found from the prior searches' if practitionerrole_id.nil? + @practitionerrole = fetch_resource('PractitionerRole', practitionerrole_id) + @resources_found = !@practitionerrole.nil? + end + + test 'Server rejects PractitionerRole search without authorization' do + metadata do + id '02' link 'http://www.fhir.org/guides/argonaut/r2/Conformance-server.html' desc %( ) @@ -49,8 +65,9 @@ def validate_resource_item(resource, property, value) @client.set_no_auth skip 'Could not verify this functionality when bearer token is not set' if @instance.token.blank? - specialty_val = @practitionerrole&.specialty&.coding&.first&.code + specialty_val = resolve_element_from_path(@practitionerrole, 'specialty.coding.code') search_params = { 'specialty': specialty_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('PractitionerRole'), search_params) @client.set_bearer_token(@instance.token) @@ -59,7 +76,7 @@ def validate_resource_item(resource, property, value) test 'Server returns expected results from PractitionerRole search by specialty' do metadata do - id '02' + id '03' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -82,12 +99,13 @@ def validate_resource_item(resource, property, value) @practitionerrole = reply.try(:resource).try(:entry).try(:first).try(:resource) @practitionerrole_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('PractitionerRole'), reply) + save_delayed_sequence_references(@practitionerrole) validate_search_reply(versioned_resource_class('PractitionerRole'), reply, search_params) end test 'Server returns expected results from PractitionerRole search by practitioner' do metadata do - id '03' + id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' desc %( ) @@ -106,21 +124,6 @@ def validate_resource_item(resource, property, value) assert_response_ok(reply) end - test 'PractitionerRole read resource supported' do - metadata do - id '04' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' - desc %( - ) - versions :r4 - end - - skip_if_not_supported(:PractitionerRole, [:read]) - skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - - validate_read_reply(@practitionerrole, versioned_resource_class('PractitionerRole')) - end - test 'PractitionerRole vread resource supported' do metadata do id '05' @@ -154,7 +157,7 @@ def validate_resource_item(resource, property, value) test 'PractitionerRole resources associated with Patient conform to US Core R4 profiles' do metadata do id '07' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-practitionerrole.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_procedure_sequence.rb b/lib/app/modules/us_core_r4/us_core_procedure_sequence.rb index 732d469d7..408c510cd 100644 --- a/lib/app/modules/us_core_r4/us_core_procedure_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_procedure_sequence.rb @@ -41,7 +41,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Procedure Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-procedure) + returned will be checked for consistency against the [Procedure Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-procedure) ) @@ -61,6 +61,7 @@ def validate_resource_item(resource, property, value) patient_val = @instance.patient_id search_params = { 'patient': patient_val } + search_params.each { |param, value| skip "Could not resolve #{param} in given resource" if value.nil? } reply = get_resource_by_params(versioned_resource_class('Procedure'), search_params) @client.set_bearer_token(@instance.token) @@ -92,6 +93,7 @@ def validate_resource_item(resource, property, value) @procedure = reply.try(:resource).try(:entry).try(:first).try(:resource) @procedure_ary = reply&.resource&.entry&.map { |entry| entry&.resource } save_resource_ids_in_bundle(versioned_resource_class('Procedure'), reply) + save_delayed_sequence_references(@procedure) validate_search_reply(versioned_resource_class('Procedure'), reply, search_params) end @@ -129,6 +131,7 @@ def validate_resource_item(resource, property, value) metadata do id '04' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -160,6 +163,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -226,7 +230,7 @@ def validate_resource_item(resource, property, value) test 'Procedure resources associated with Patient conform to US Core R4 profiles' do metadata do id '09' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-procedure.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-procedure' desc %( ) versions :r4 diff --git a/lib/app/modules/us_core_r4/us_core_r4_capability_statement_sequence.rb b/lib/app/modules/us_core_r4/us_core_r4_capability_statement_sequence.rb index 537aa16ec..7905c0a73 100644 --- a/lib/app/modules/us_core_r4/us_core_r4_capability_statement_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_r4_capability_statement_sequence.rb @@ -86,7 +86,8 @@ class UsCoreR4CapabilityStatementSequence < CapabilityStatementSequence ) end - assert @conformance.class == versioned_conformance_class, 'Expected valid Conformance resource' + assert_valid_conformance + formats = ['json', 'applcation/json', 'application/json+fhir', 'application/fhir+json'] assert formats.any? { |format| @conformance.format.include? format }, 'Conformance does not state support for json.' end @@ -115,14 +116,11 @@ class UsCoreR4CapabilityStatementSequence < CapabilityStatementSequence 'permission-patient', 'permission-user'] - assert @conformance.class == versioned_conformance_class, 'Expected valid Capability resource' + assert_valid_conformance + + assert @server_capabilities.smart_support?, 'No SMART capabilities listed in conformance.' - extensions = @conformance.try(:rest).try(:first).try(:security).try(:extension) - assert !extensions.nil?, 'No SMART capabilities listed in conformance.' - capabilities = extensions.select { |x| x.url == 'http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities' } - assert !capabilities.nil?, 'No SMART capabilities listed in capability.' - available_capabilities = capabilities.map(&:valueCode) - missing_capabilities = (required_capabilities - available_capabilities) + missing_capabilities = (required_capabilities - @server_capabilities.smart_capabilities) assert missing_capabilities.empty?, "Conformance statement does not list required SMART capabilties: #{missing_capabilities.join(', ')}" end @@ -140,16 +138,7 @@ class UsCoreR4CapabilityStatementSequence < CapabilityStatementSequence ) end - assert @conformance.class == versioned_conformance_class, 'Expected valid Capability resource' - - begin - Inferno::Models::ServerCapabilities.create( - testing_instance_id: @instance.id, - capabilities: @conformance.as_json - ) - rescue StandardError - assert false, 'Capability Statement could not be parsed.' - end + assert_valid_conformance assert @instance.conformance_supported?(:Patient, [:read]), 'Patient resource with read interaction is not listed in capability statement.' end diff --git a/lib/app/modules/us_core_r4/us_core_smokingstatus_sequence.rb b/lib/app/modules/us_core_r4/us_core_smokingstatus_sequence.rb index f629acdee..7b112f01a 100644 --- a/lib/app/modules/us_core_r4/us_core_smokingstatus_sequence.rb +++ b/lib/app/modules/us_core_r4/us_core_smokingstatus_sequence.rb @@ -45,7 +45,7 @@ def validate_resource_item(resource, property, value) details %( The #{title} Sequence tests `#{title.gsub(/\s+/, '')}` resources associated with the provided patient. The resources - returned will be checked for consistency against the [Smokingstatus Argonaut Profile](https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-smokingstatus) + returned will be checked for consistency against the [Smokingstatus Argonaut Profile](http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus) ) @@ -92,7 +92,8 @@ def validate_resource_item(resource, property, value) @observation = reply.try(:resource).try(:entry).try(:first).try(:resource) @observation_ary = reply&.resource&.entry&.map { |entry| entry&.resource } - save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply) + save_resource_ids_in_bundle(versioned_resource_class('Observation'), reply, Inferno::ValidationUtil::US_CORE_R4_URIS[:smoking_status]) + save_delayed_sequence_references(@observation) validate_search_reply(versioned_resource_class('Observation'), reply, search_params) end @@ -153,6 +154,7 @@ def validate_resource_item(resource, property, value) metadata do id '05' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -184,6 +186,7 @@ def validate_resource_item(resource, property, value) metadata do id '06' link 'https://build.fhir.org/ig/HL7/US-Core-R4/CapabilityStatement-us-core-server.html' + optional desc %( ) versions :r4 @@ -251,14 +254,14 @@ def validate_resource_item(resource, property, value) test 'Observation resources associated with Patient conform to US Core R4 profiles' do metadata do id '10' - link 'https://build.fhir.org/ig/HL7/US-Core-R4/StructureDefinition-us-core-smokingstatus.json' + link 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus' desc %( ) versions :r4 end skip 'No resources appear to be available for this patient. Please use patients with more information.' unless @resources_found - test_resources_against_profile('Observation') + test_resources_against_profile('Observation', Inferno::ValidationUtil::US_CORE_R4_URIS[:smoking_status]) end test 'At least one of every must support element is provided in any Observation for this patient.' do diff --git a/lib/app/sequence_base.rb b/lib/app/sequence_base.rb index e972b12d6..e997773b2 100644 --- a/lib/app/sequence_base.rb +++ b/lib/app/sequence_base.rb @@ -11,6 +11,7 @@ require_relative 'utils/terminology' require_relative 'utils/result_statuses' require_relative 'utils/search_validation' +require_relative 'models/testing_instance' require 'bloomer' require 'bloomer/msgpackable' @@ -41,6 +42,7 @@ class SequenceBase @@optional = [] @@show_uris = [] + @@delayed_sequences = [] @@test_id_prefixes = {} @@ -264,6 +266,10 @@ def self.versions(*versions) @@versions[sequence_name] || FHIR::VERSIONS end + def self.delayed_sequence + @@delayed_sequences << sequence_name + end + def self.missing_requirements(instance, recurse = false) return [] unless @@requires.key?(sequence_name) @@ -350,7 +356,6 @@ def self.preconditions_met_for?(instance) def self.extends_sequence(klass) @@test_metadata[klass.sequence_name].each do |metadata| @@test_metadata[sequence_name] << metadata - @@test_metadata[sequence_name].last[:test_index] = @@test_metadata[sequence_name].length - 1 define_method metadata[:method_name], metadata[:method] end end @@ -723,6 +728,22 @@ def validate_reference_resolutions(resource) assert(problems.empty?, problems.join("
\n")) end + def save_delayed_sequence_references(resource) + delayed_resource_types = @@conformance_supports.select { |sequence, _resources| @@delayed_sequences.include? sequence }.values.flatten + walk_resource(resource) do |value, meta, _path| + next if meta['type'] != 'Reference' + + if value.relative? + begin + resource_class = value.resource_class.name.demodulize + @instance.save_resource_reference(resource_class, value.reference.split('/').last) if delayed_resource_types.include? resource_class.to_sym + rescue NameError + next + end + end + end + end + def check_resource_against_profile(resource, resource_type, specified_profile = nil) assert resource.is_a?("FHIR::DSTU2::#{resource_type}".constantize), "Expected resource to be of type #{resource_type}" diff --git a/lib/app/utils/assertions.rb b/lib/app/utils/assertions.rb index dcd67cea7..7e601a5b2 100644 --- a/lib/app/utils/assertions.rb +++ b/lib/app/utils/assertions.rb @@ -25,37 +25,43 @@ def assert_equal(expected, actual, message = '', data = '') def assert_response_ok(response, error_message = '') return if assertion_negated([200, 201].include?(response.code)) - raise AssertionException, "Bad response code: expected 200, 201, but found #{response.code}.#{' ' + error_message}" # ,response.body + raise AssertionException, "Bad response code: expected 200, 201, but found #{response.code}. #{error_message}" + end + + def assert_response_accepted(response) + return if assertion_negated([202].include?(response.code)) + + raise AssertionException, "Bad response code: expected 202, but found #{response.code}" end def assert_response_not_found(response) return if assertion_negated([404].include?(response.code)) - raise AssertionException, "Bad response code: expected 404, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected 404, but found #{response.code}" end def assert_response_unauthorized(response) return if assertion_negated([401, 406].include?(response.code)) - raise AssertionException, "Bad response code: expected 401 or 406, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected 401 or 406, but found #{response.code}" end def assert_response_bad_or_unauthorized(response) return if assertion_negated([400, 401].include?(response.code)) - raise AssertionException, "Bad response code: expected 400 or 401, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected 400 or 401, but found #{response.code}" end def assert_response_bad(response) return if assertion_negated([400].include?(response.code)) - raise AssertionException, "Bad response code: expected 400, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected 400, but found #{response.code}" end def assert_response_conflict(response) return if assertion_negated([409, 412].include?(response.code)) - raise AssertionException, "Bad response code: expected 409 or 412, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected 409 or 412, but found #{response.code}" end def assert_navigation_links(bundle) @@ -74,7 +80,7 @@ def assert_bundle_response(response) rescue StandardError found = nil end - raise AssertionException, "Expected FHIR Bundle but found: #{found.class.name.demodulize}" # ,response.body + raise AssertionException, "Expected FHIR Bundle but found: #{found.class.name.demodulize}" end def assert_bundle_transactions_okay(response) @@ -90,14 +96,14 @@ def assert_bundle_transactions_okay(response) end end - def assert_resource_content_type(client_reply, content_type) + def assert_response_content_type(client_reply, content_type) header = client_reply.response[:headers]['content-type'] response_content_type = header response_content_type = header[0, header.index(';')] unless header.index(';').nil? - return if assertion_negated(response_content_type == "application/fhir+#{content_type}") + return if assertion_negated(response_content_type == content_type) - raise AssertionException.new "Expected content-type application/fhir+#{content_type} but found #{response_content_type}", response_content_type + raise AssertionException.new "Expected content-type #{content_type} but found #{response_content_type}", response_content_type end # Based on MIME Types defined in @@ -138,13 +144,13 @@ def assert_valid_content_location_present(client_reply) def assert_response_code(response, code) return if assertion_negated(code.to_s == response.code.to_s) - raise AssertionException, "Bad response code: expected #{code}, but found #{response.code}" # ,response.body + raise AssertionException, "Bad response code: expected #{code}, but found #{response.code}" end def assert_resource_type(response, resource_type) return if assertion_negated(!response.resource.nil? && response.resource.class == resource_type) - raise AssertionException, "Bad response type: expected #{resource_type}, but found #{response.resource.class}." # ,response.body + raise AssertionException, "Bad response type: expected #{resource_type}, but found #{response.resource.class}." end def assertion_negated(expression) @@ -267,5 +273,17 @@ def assert_valid_http_uri(uri, message = nil) error_message = message || "\"#{uri}\" is not a valid URI" assert (uri =~ /\A#{URI.regexp(['http', 'https'])}\z/), error_message end + + def assert_operation_supported(server_capabilities, op_name) + assert server_capabilities.operation_supported?(op_name), "FHIR server capability statement did not support #{op_name} operation" + end + + def assert_valid_conformance(conformance = @conformance) + conformance_resource_name = versioned_conformance_class.name.demodulize + assert( + conformance.class == versioned_conformance_class, + "Expected valid #{conformance_resource_name} resource." + ) + end end end diff --git a/lib/app/utils/validation.rb b/lib/app/utils/validation.rb index ee58a3fae..723a9f678 100644 --- a/lib/app/utils/validation.rb +++ b/lib/app/utils/validation.rb @@ -19,7 +19,7 @@ def self.get_resource(json, version) RESOURCES = { dstu2: {}, stu3: {}, r4: {} } VALUESETS = {} - VERSION_MAP = { '1.0.2' => :dstu2, '3.0.1' => :stu3, '4.0.0' => :r4 } + VERSION_MAP = { '1.0.2' => :dstu2, '3.0.1' => :stu3, '4.0.0' => :r4 }.freeze Dir.glob(validation_packs).each do |definition| json = File.read(definition) @@ -54,6 +54,15 @@ def self.get_resource(json, version) snf: 'https://bluebutton.cms.gov/assets/ig/StructureDefinition-bluebutton-snf-claim' }.freeze + US_CORE_R4_URIS = { + smoking_status: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus', + diagnostic_report_lab: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-lab', + diagnostic_report_note: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-note', + lab_results: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab', + pediatric_bmi_age: 'http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age', + pediatric_weight_height: 'http://hl7.org/fhir/us/core/StructureDefinition/pediatric-weight-for-height' + }.freeze + def self.guess_profile(resource, version) # if the profile is given, we don't need to guess if resource&.meta&.profile&.present? @@ -133,7 +142,23 @@ def self.guess_r4_profile(resource) return if resource.blank? candidates = RESOURCES[:r4][resource.resourceType] - return candidates.first if candidates.present? + return if candidates.blank? + + if resource.resourceType == 'Observation' + return DEFINITIONS[US_CORE_R4_URIS[:smoking_status]] if resource&.code&.coding&.any? { |coding| coding&.code == '72166-2' } + + return DEFINITIONS[US_CORE_R4_URIS[:lab_results]] if resource&.category&.first&.coding&.any? { |coding| coding&.code == 'laboratory' } + + return DEFINITIONS[US_CORE_R4_URIS[:pediatric_bmi_age]] if resource&.code&.coding&.any? { |coding| coding&.code == '59576-9' } + + return DEFINITIONS[US_CORE_R4_URIS[:pediatric_weight_height]] if resource&.code&.coding&.any? { |coding| coding&.code == '77606-2' } + elsif resource.resourceType == 'DiagnosticReport' + return DEFINITIONS[US_CORE_R4_URIS[:diagnostic_report_lab]] if resource&.category&.first&.coding&.any? { |coding| coding&.code == 'LAB' } + + return DEFINITIONS[US_CORE_R4_URIS[:diagnostic_report_note]] + end + + candidates.first end end end diff --git a/lib/app/views/default.erb b/lib/app/views/default.erb index 8c460aa37..d4f530de4 100644 --- a/lib/app/views/default.erb +++ b/lib/app/views/default.erb @@ -222,7 +222,6 @@ instance: instance, value: instance.id_token, })%> - <%= erb(:prerequisite_field,{},{prerequisite: :refresh_token, label: 'Refresh Token', instance: instance, diff --git a/lib/app/views/report.erb b/lib/app/views/report.erb index 57a82014a..e057f49f9 100644 --- a/lib/app/views/report.erb +++ b/lib/app/views/report.erb @@ -145,14 +145,14 @@ -
<% if sequence_results[test_case.id].nil? %> - <%= test_case.sequence.test_count %> tests + <%= test_case.sequence.test_count %> <% 'test'.pluralize(test_case.sequence.test_count) %> <% else %> <%= sequence_results[test_case.id].required_passed %>/<%= sequence_results[test_case.id].total_required_tests_except_omitted%> Required Tests Passed <% if sequence_results[test_case.id].optional_total > 0%> - <%= sequence_results[test_case.id].optional_passed%>/<%= sequence_results[test_case.id].optional_total%> Optional Tests Passed <% end%> <% if sequence_results[test_case.id].total_omitted.positive?%> - - <%= sequence_results[test_case.id].total_omitted %> Test <% if sequence_results[test_case.id].total_omitted != 1%>s<% end%> Omitted + <%= sequence_results[test_case.id].total_omitted %> <%= 'Test'.pluralize(sequence_results[test_case.id].total_omitted) %> Omitted <% end%> <% end %>
diff --git a/lib/app/views/test_case.erb b/lib/app/views/test_case.erb index 32d9ba9f1..7cfc0c244 100644 --- a/lib/app/views/test_case.erb +++ b/lib/app/views/test_case.erb @@ -68,7 +68,7 @@ -
<% if sequence_results[test_case.id].nil? %> - <%= test_case.sequence.test_count %> tests + <%= test_case.sequence.test_count %> <%= 'test'.pluralize(test_case.sequence.test_count) %> <% else %> <%= sequence_results[test_case.id].required_passed %>/<%= sequence_results[test_case.id].total_required_tests_except_omitted %> Required Tests Passed @@ -77,7 +77,7 @@ <% end%> <% if sequence_results[test_case.id].total_omitted.positive?%> - - <%= sequence_results[test_case.id].total_omitted %> Test <% if sequence_results[test_case.id].total_omitted != 1%>s<% end%> Omitted + <%= sequence_results[test_case.id].total_omitted %> <%= 'Test'.pluralize(sequence_results[test_case.id].total_omitted) %> Omitted <% end%> <% end %> - diff --git a/lib/app/views/test_list.erb b/lib/app/views/test_list.erb index 6572743dd..dc8b6d45d 100644 --- a/lib/app/views/test_list.erb +++ b/lib/app/views/test_list.erb @@ -42,8 +42,8 @@
<% end %> <% if result.test_warnings.length > 0 %> -
- +
+
<% end %> diff --git a/lib/tasks/tasks.rake b/lib/tasks/tasks.rake index 610e892a8..5299eba62 100644 --- a/lib/tasks/tasks.rake +++ b/lib/tasks/tasks.rake @@ -788,3 +788,11 @@ namespace :terminology do |_argv| Inferno::Terminology.create_validators(validator_type) end end + +namespace :generator do |_argv| + desc 'Generate US Core R4 Tests' + task :us_core_r4 do + path = File.expand_path('../../generators/uscore-r4/generator.rb', __dir__) + system('ruby', path) + end +end diff --git a/lib/version.rb b/lib/version.rb index 800dcb9e9..382dba3de 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Inferno - VERSION = '2.5.0' + VERSION = '2.6.0' end diff --git a/test/fixtures/bulk_data_conformance.json b/test/fixtures/bulk_data_conformance.json new file mode 100644 index 000000000..e754424e5 --- /dev/null +++ b/test/fixtures/bulk_data_conformance.json @@ -0,0 +1,102 @@ +{ + "resourceType": "CapabilityStatement", + "status": "active", + "date": "2019-08-29 01:06:12", + "publisher": "Not provided", + "kind": "instance", + "software": { + "name": "SMART Sample Bulk FHIR Server", + "version": "1.0" + }, + "implementation": { + "description": "SMART Sample Bulk FHIR Server" + }, + "instantiates": [ + "http://www.hl7.org/fhir/bulk-data/CapabilityStatement-bulk-data.html" + ], + "fhirVersion": "3.0.1", + "acceptUnknown": "extensions", + "format": [ + "application/fhir+json" + ], + "rest": [ + { + "mode": "server", + "security": { + "extension": [ + { + "url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris", + "extension": [ + { + "url": "token", + "valueUri": "https://bulk-data.smarthealthit.org/auth/token" + }, + { + "url": "register", + "valueUri": "https://bulk-data.smarthealthit.org/auth/register" + } + ] + } + ], + "service": [ + { + "coding": [ + { + "system": "http://hl7.org/fhir/restful-security-service", + "code": "SMART-on-FHIR", + "display": "SMART-on-FHIR" + } + ], + "text": "OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org)" + } + ] + }, + "resource": [ + { + "type": "Group", + "profile": { + "reference": "http://hl7.org/fhir/Profile/Group" + }, + "interaction": [ + { + "code": "read" + } + ], + "searchParam": [] + }, + { + "type": "OperationDefinition", + "profile": { + "reference": "http://hl7.org/fhir/Profile/OperationDefinition" + }, + "interaction": [ + { + "code": "read" + } + ], + "searchParam": [] + } + ], + "operation": [ + { + "name": "export", + "definition": { + "reference": "OperationDefinition/BulkDataExport" + } + }, + { + "name": "patient-export", + "definition": { + "reference": "OperationDefinition/PatientLevelExport" + } + }, + { + "name": "group-export", + "definition": { + "reference": "OperationDefinition/GroupLevelExport" + } + } + ] + } + ] +} diff --git a/test/fixtures/us_core_r4_allergy_intolerance.json b/test/fixtures/us_core_r4_allergy_intolerance.json new file mode 100644 index 000000000..3c2b2e66a --- /dev/null +++ b/test/fixtures/us_core_r4_allergy_intolerance.json @@ -0,0 +1,86 @@ +{ + "resourceType": "Bundle", + "id": "44019429-c060-456b-b4ca-bea91e654bd3", + "meta": { + "lastUpdated": "2019-08-21T19:43:53.216+00:00" + }, + "type": "searchset", + "total": 1, + "entry": [ + { + "fullUrl": "https://api-v8-r4.hspconsortium.org/czhour4/data/AllergyIntolerance/SMART-AllergyIntolerance-28", + "resource": { + "resourceType": "AllergyIntolerance", + "id": "SMART-AllergyIntolerance-28", + "meta": { + "versionId": "1", + "lastUpdated": "2019-03-07T08:49:12.000+00:00" + }, + "text": { + "status": "generated", + "div": "
Sensitivity to sulfonamide antibacterial
" + }, + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", + "code": "active" + } + ] + }, + "criticality": "low", + "code": { + "coding": [ + { + "system": "http://rxnav.nlm.nih.gov/REST/Ndfrt", + "code": "N0000175503", + "display": "sulfonamide antibacterial", + "userSelected": false + } + ], + "text": "sulfonamide antibacterial" + }, + "patient": { + "reference": "Patient/1234" + }, + "recordedDate": "2000-01-01T00:00:00-07:00", + "recorder": { + "reference": "Practitioner/SMART-1234" + }, + "reaction": [ + { + "manifestation": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "271807003", + "display": "skin rash", + "userSelected": false + } + ], + "text": "skin rash" + } + ], + "severity": "mild" + } + ], + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification", + "code": "confirmed" + } + ] + } + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} \ No newline at end of file diff --git a/test/fixtures/us_core_r4_diagnostic_report_lab.json b/test/fixtures/us_core_r4_diagnostic_report_lab.json new file mode 100644 index 000000000..2691d54e7 --- /dev/null +++ b/test/fixtures/us_core_r4_diagnostic_report_lab.json @@ -0,0 +1,119 @@ +{ + "resourceType" : "DiagnosticReport", + "id" : "urinalysis", + "meta" : { + "versionId" : "209563", + "lastUpdated" : "2016-04-19T17:28:11.308+00:00", + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-lab" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: urinalysis

meta:

status: final

category: Laboratory (Details : {http://terminology.hl7.org/CodeSystem/v2-0074 code 'LAB' = 'Laboratory', given as 'Laboratory'})

code: Complete Urinalysis (Details : {LOINC code '24356-8' = 'Urinalysis complete panel - Urine', given as 'Urinalysis complete panel - Urine'})

subject: Amy Shaw. Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

effective: Jul 5, 2005 12:00:00 AM

issued: Jul 5, 2005 8:45:33 PM

performer: Acme Laboratory, Inc. Generated Summary: id: acme-lab; 1144221847, 2523423456; active; Healthcare Provider; name: Acme Labs; ph: (+1) 734-677-7777, hq@acme.org

result:

" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://terminology.hl7.org/CodeSystem/v2-0074", + "code" : "LAB", + "display" : "Laboratory" + } + ] + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "24356-8", + "display" : "Urinalysis complete panel - Urine" + } + ], + "text" : "Complete Urinalysis" + }, + "subject" : { + "reference" : "Patient/example", + "display" : "Amy Shaw" + }, + "effectiveDateTime" : "2005-07-05", + "issued" : "2005-07-06T11:45:33+11:00", + "performer" : [ + { + "reference" : "Organization/acme-lab", + "display" : "Acme Laboratory, Inc" + } + ], + "result" : [ + { + "reference" : "Observation/urine-color", + "display" : "COLOR=Yellow" + }, + { + "reference" : "Observation/urine-clarity", + "display" : "APPEARANCE=Hazy" + }, + { + "reference" : "Observation/urine-glucose", + "display" : "GLUCOSE=Negative" + }, + { + "reference" : "Observation/urine-bilirubin", + "display" : "BILIRUBIN=Negative" + }, + { + "reference" : "Observation/urine-ketone", + "display" : "KETONE=Negative" + }, + { + "reference" : "Observation/usg", + "display" : "SPEC GRAV=1.017" + }, + { + "reference" : "Observation/urine-ph", + "display" : "PH=6 (pH)" + }, + { + "reference" : "Observation/urine-protein", + "display" : "PROTEIN=Negative" + }, + { + "reference" : "Observation/urobilinogen", + "display" : "UROBILINOGEN=0.3 mg/dL" + }, + { + "reference" : "Observation/urine-nitrite", + "display" : "NITRITE=Positive" + }, + { + "reference" : "Observation/urine-hemoglobin", + "display" : "BLOOD=Trace" + }, + { + "reference" : "Observation/urine-leukocyte-esterase", + "display" : "LEUK ESTERASE=2+e" + }, + { + "reference" : "Observation/urine-sediment", + "display" : "COMMENT=Recommend Urine Culture" + }, + { + "reference" : "Observation/urine-bacteria", + "display" : "BACTERIA=4+" + }, + { + "reference" : "Observation/urine-epi-cells", + "display" : "EPITHELIAL CELLS=1-5 cells/HPF" + }, + { + "reference" : "Observation/urine-wbcs", + "display" : "WBC=20-30 cells/HPF" + }, + { + "reference" : "Observation/urine-rbcs", + "display" : "RBC=Occasional cells/HPF" + } + ] + } \ No newline at end of file diff --git a/test/fixtures/us_core_r4_diagnostic_report_note.json b/test/fixtures/us_core_r4_diagnostic_report_note.json new file mode 100644 index 000000000..e48b21a6e --- /dev/null +++ b/test/fixtures/us_core_r4_diagnostic_report_note.json @@ -0,0 +1,40 @@ +{ + "resourceType" : "DiagnosticReport", + "id" : "cardiology-report", + "meta" : { + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-note" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: cardiology-report

meta:

status: final

category: Radiology (Details : {LOINC code 'LP29684-5' = 'LP29684-5', given as 'Radiology'})

code: MR Abdomen W contrast IV (Details : {LOINC code '36134-5' = 'MR Abdomen W contrast IV', given as 'MR Abdomen W contrast IV'})

subject: Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

effective: Jan 1, 2011 4:39:30 PM

" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "LP29684-5", + "display" : "Radiology" + } + ], + "text" : "Radiology" + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "36134-5", + "display" : "MR Abdomen W contrast IV" + } + ], + "text" : "MR Abdomen W contrast IV" + }, + "subject" : { + "reference" : "Patient/example" + }, + "effectiveDateTime" : "2011-01-01T21:39:30.000Z" +} \ No newline at end of file diff --git a/test/fixtures/us_core_r4_observation_lab.json b/test/fixtures/us_core_r4_observation_lab.json new file mode 100644 index 000000000..5057ab7e4 --- /dev/null +++ b/test/fixtures/us_core_r4_observation_lab.json @@ -0,0 +1,62 @@ +{ + "resourceType" : "Observation", + "id" : "usg", + "meta" : { + "versionId" : "206588", + "lastUpdated" : "2016-04-18T04:10:12.426+00:00", + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: usg

meta:

status: final

category: Laboratory (Details : {http://terminology.hl7.org/CodeSystem/observation-category code 'laboratory' = 'Laboratory', given as 'Laboratory'})

code: SPEC GRAV (Details : {LOINC code '5811-5' = 'Specific gravity of Urine by Test strip', given as 'Specific gravity of Urine by Test strip'})

subject: Amy Shaw. Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

effective: Jul 5, 2005 12:00:00 AM

value: 1.017 {urine specific gravity} (Details: UCUM code {urine specific gravity} = '{urine specific gravity}')

ReferenceRanges

-LowHigh
*1.003 {urine specific gravity} (Details: UCUM code {urine specific gravity} = '{urine specific gravity}')1.035 {urine specific gravity} (Details: UCUM code {urine specific gravity} = '{urine specific gravity}')
" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://terminology.hl7.org/CodeSystem/observation-category", + "code" : "laboratory", + "display" : "Laboratory" + } + ], + "text" : "Laboratory" + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "5811-5", + "display" : "Specific gravity of Urine by Test strip" + } + ], + "text" : "SPEC GRAV" + }, + "subject" : { + "reference" : "Patient/example", + "display" : "Amy Shaw" + }, + "effectiveDateTime" : "2005-07-05", + "valueQuantity" : { + "value" : 1.017, + "system" : "http://unitsofmeasure.org", + "code" : "{urine specific gravity}" + }, + "referenceRange" : [ + { + "low" : { + "value" : 1.003, + "system" : "http://unitsofmeasure.org", + "code" : "{urine specific gravity}" + }, + "high" : { + "value" : 1.035, + "system" : "http://unitsofmeasure.org", + "code" : "{urine specific gravity}" + } + } + ] +} \ No newline at end of file diff --git a/test/fixtures/us_core_r4_pediatric_bmi_for_age.json b/test/fixtures/us_core_r4_pediatric_bmi_for_age.json new file mode 100644 index 000000000..9faf8b84e --- /dev/null +++ b/test/fixtures/us_core_r4_pediatric_bmi_for_age.json @@ -0,0 +1,50 @@ +{ + "resourceType" : "Observation", + "id" : "pediatric-bmi-example", + "meta" : { + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/pediatric-bmi-for-age" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: pediatric-bmi-example

meta:

status: final

category: Vital Signs (Details : {http://terminology.hl7.org/CodeSystem/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})

code: BMI (Details : {LOINC code '59576-9' = 'Body mass index (BMI) [Percentile] Per age and gender', given as 'Body mass index (BMI) [Percentile] Per age and gender'})

subject: Infant Amy Shaw. Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

encounter: GP Visit

effective: May 4, 2019 3:12:29 PM

value: 65 % (Details: UCUM code % = '%')

" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://terminology.hl7.org/CodeSystem/observation-category", + "code" : "vital-signs", + "display" : "Vital Signs" + } + ], + "text" : "Vital Signs" + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "59576-9", + "display" : "Body mass index (BMI) [Percentile] Per age and gender" + } + ], + "text" : "BMI" + }, + "subject" : { + "reference" : "Patient/example", + "display" : "Infant Amy Shaw" + }, + "encounter" : { + "display" : "GP Visit" + }, + "effectiveDateTime" : "2019-05-04T12:12:29-07:00", + "valueQuantity" : { + "value" : 65, + "unit" : "%", + "system" : "http://unitsofmeasure.org", + "code" : "%" + } +} \ No newline at end of file diff --git a/test/fixtures/us_core_r4_pediatric_weight_for_height.json b/test/fixtures/us_core_r4_pediatric_weight_for_height.json new file mode 100644 index 000000000..2192b24fe --- /dev/null +++ b/test/fixtures/us_core_r4_pediatric_weight_for_height.json @@ -0,0 +1,50 @@ +{ + "resourceType" : "Observation", + "id" : "pediatric-wt-example", + "meta" : { + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/pediatric-weight-for-height" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: pediatric-wt-example

meta:

status: final

category: Vital Signs (Details : {http://terminology.hl7.org/CodeSystem/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})

code: BMI (Details : {LOINC code '77606-2' = 'Weight-for-length Per age and gender', given as 'Weight-for-length Per age and gender'})

subject: Infant Amy Shaw. Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

encounter: GP Visit

effective: May 4, 2019 3:12:29 PM

value: 65 % (Details: UCUM code % = '%')

" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://terminology.hl7.org/CodeSystem/observation-category", + "code" : "vital-signs", + "display" : "Vital Signs" + } + ], + "text" : "Vital Signs" + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "77606-2", + "display" : "Weight-for-length Per age and gender" + } + ], + "text" : "BMI" + }, + "subject" : { + "reference" : "Patient/example", + "display" : "Infant Amy Shaw" + }, + "encounter" : { + "display" : "GP Visit" + }, + "effectiveDateTime" : "2019-05-04T12:12:29-07:00", + "valueQuantity" : { + "value" : 65, + "unit" : "%", + "system" : "http://unitsofmeasure.org", + "code" : "%" + } +} \ No newline at end of file diff --git a/test/fixtures/us_core_r4_smoking_status.json b/test/fixtures/us_core_r4_smoking_status.json new file mode 100644 index 000000000..a678087aa --- /dev/null +++ b/test/fixtures/us_core_r4_smoking_status.json @@ -0,0 +1,50 @@ +{ + "resourceType" : "Observation", + "id" : "some-day-smoker", + "meta" : { + "profile" : [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus" + ] + }, + "text" : { + "status" : "generated", + "div" : "

Generated Narrative with Details

id: some-day-smoker

meta:

status: final

category: Social History (Details : {http://terminology.hl7.org/CodeSystem/observation-category code 'social-history' = 'Social History', given as 'Social History'})

code: Tobacco smoking status NHIS (Details : {LOINC code '72166-2' = 'Tobacco smoking status NHIS', given as 'Tobacco smoking status NHIS'})

subject: Amy Shaw. Generated Summary: id: example; Medical Record Number = 1032702 (USUAL); active; Amy V. Shaw ; ph: 555-555-5555(HOME), amy.shaw@example.com; gender: female; birthDate: Feb 20, 2007

issued: Mar 18, 2016 1:27:04 AM

value: Current some day smoker (Details : {SNOMED CT code '428041000124106' = 'Occasional tobacco smoker)

" + }, + "status" : "final", + "category" : [ + { + "coding" : [ + { + "system" : "http://terminology.hl7.org/CodeSystem/observation-category", + "code" : "social-history", + "display" : "Social History" + } + ], + "text" : "Social History" + } + ], + "code" : { + "coding" : [ + { + "system" : "http://loinc.org", + "code" : "72166-2", + "display" : "Tobacco smoking status NHIS" + } + ], + "text" : "Tobacco smoking status NHIS" + }, + "subject" : { + "reference" : "Patient/example", + "display" : "Amy Shaw" + }, + "issued" : "2016-03-18T05:27:04Z", + "valueCodeableConcept" : { + "coding" : [ + { + "system" : "http://snomed.info/sct", + "code" : "428041000124106" + } + ], + "text" : "Current some day smoker" + } +} \ No newline at end of file diff --git a/test/model/server_capabilities_test.rb b/test/model/server_capabilities_test.rb index 1e4e44a7f..340d64d14 100644 --- a/test/model/server_capabilities_test.rb +++ b/test/model/server_capabilities_test.rb @@ -38,6 +38,30 @@ def setup testing_instance_id: Inferno::Models::TestingInstance.create.id, capabilities: @capability_statement ) + + @smart_capability_statement = { + rest: [ + { + security: { + extension: [ + { + url: 'http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities', + valueCode: 'launch-ehr' + }, + { + url: 'http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities', + valueCode: 'launch-standalone' + } + ] + } + } + ] + } + + @smart_capabilities = Inferno::Models::ServerCapabilities.new( + testing_instance_id: Inferno::Models::TestingInstance.create.id, + capabilities: @smart_capability_statement + ) end def test_supported_resources @@ -64,4 +88,36 @@ def test_supported_interactions assert @capabilities.supported_interactions == expected_interactions end + + def test_operation_supported_pass + conformance = load_json_fixture(:bulk_data_conformance) + + server_capabilities = Inferno::Models::ServerCapabilities.new( + testing_instance_id: Inferno::Models::TestingInstance.create.id, + capabilities: conformance.as_json + ) + + assert server_capabilities.operation_supported?('patient-export') + end + + def test_operation_supported_fail_invalid_name + conformance = load_json_fixture(:bulk_data_conformance) + + server_capabilities = Inferno::Models::ServerCapabilities.new( + testing_instance_id: Inferno::Models::TestingInstance.create.id, + capabilities: conformance.as_json + ) + + assert !server_capabilities.operation_supported?('this_is_a_test') + end + + def test_smart_support + assert !@capabilities.smart_support? + assert @smart_capabilities.smart_support? + end + + def test_smart_capabilities + assert @capabilities.smart_capabilities == [] + assert @smart_capabilities.smart_capabilities == ['launch-ehr', 'launch-standalone'] + end end diff --git a/test/sequence/bulk_data/conformance_sequence_test.rb b/test/sequence/bulk_data/conformance_sequence_test.rb new file mode 100644 index 000000000..751df6a39 --- /dev/null +++ b/test/sequence/bulk_data/conformance_sequence_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative '../../test_helper' + +class BulkDataCapabilityStatementSequenceTest < MiniTest::Test + def setup + instance = Inferno::Models::TestingInstance.new( + url: 'http://www.example.com', + selected_module: 'bulk_data' + ) + + @request_headers = { accept: 'application/fhir+json' } + @response_headers = { content_type: 'application/json+fhir' } + + instance.save! + + client = FHIR::Client.new(instance.url) + client.use_stu3 + client.default_json + + @sequence = Inferno::Sequence::BulkDataCapabilityStatementSequence.new(instance, client, true) + @conformance = load_json_fixture(:bulk_data_conformance) + end + + def test_all_pass + WebMock.reset! + stub_request(:get, 'http://www.example.com/metadata') + .with(headers: @request_headers) + .to_return(status: 200, body: @conformance.to_json, headers: @response_headers) + + sequence_result = @sequence.start + assert sequence_result.pass?, 'The sequence should be marked as pass.' + assert sequence_result.test_results.all? { |r| r.pass? || r.skip? || r.omit? }, 'All tests should pass' + end +end diff --git a/test/sequence/bulk_data/patient_export_sequence_test.rb b/test/sequence/bulk_data/patient_export_sequence_test.rb new file mode 100644 index 000000000..524c9a295 --- /dev/null +++ b/test/sequence/bulk_data/patient_export_sequence_test.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require_relative '../../test_helper' + +class BulkDataPatientExportSequenceTest < MiniTest::Test + def setup + @complete_status = { + 'transactionTime' => '2019-08-01', + 'request' => '[base]/Patient/$export?_type=Patient,Observation', + 'requiresAccessToken' => 'true', + 'output' => 'output', + 'error' => 'error' + } + + @instance = Inferno::Models::TestingInstance.new( + url: 'http://www.example.com', + client_name: 'Inferno', + base_url: 'http://localhost:4567', + client_endpoint_key: Inferno::SecureRandomBase62.generate(32), + client_id: SecureRandom.uuid, + selected_module: 'bulk_data', + oauth_authorize_endpoint: 'http://oauth_reg.example.com/authorize', + oauth_token_endpoint: 'http://oauth_reg.example.com/token', + scopes: 'launch openid patient/*.* profile', + token: 99_897_979 + ) + + @instance.save! + + @export_request_headers = { accept: 'application/fhir+json', + prefer: 'respond-async', + authorization: "Bearer #{@instance.token}" } + + @export_request_headers_no_token = { accept: 'application/fhir+json', prefer: 'respond-async' } + + @status_request_headers = { accept: 'application/json', + authorization: "Bearer #{@instance.token}" } + + @content_location = 'http://www.example.com/status' + + client = FHIR::Client.new(@instance.url) + client.use_stu3 + client.default_json + @sequence = Inferno::Sequence::BulkDataPatientExportSequence.new(@instance, client, true) + end + + def include_export_stub(status_code: 202, + response_headers: { content_location: @content_location }) + stub_request(:get, 'http://www.example.com/Patient/$export') + .with(headers: @export_request_headers) + .to_return( + status: status_code, + headers: response_headers + ) + end + + def include_status_check_stub(status_code: 200, + response_headers: { content_type: 'application/json' }, + response_body: @complete_status) + stub_request(:get, @content_location) + .with(headers: @status_request_headers) + .to_return( + status: status_code, + headers: response_headers, + body: response_body.to_json + ) + end + + def test_all_pass + WebMock.reset! + + stub_request(:get, 'http://www.example.com/Patient/$export') + .with(headers: @export_request_headers_no_token) + .to_return( + status: 401 + ) + + include_status_check_stub + include_export_stub + + sequence_result = @sequence.start + failures = sequence_result.failures + assert failures.empty?, "All tests should pass. First error: #{failures&.first&.message}" + assert !sequence_result.skip?, 'No tests should be skipped.' + assert sequence_result.pass?, 'The sequence should be marked as pass.' + end + + def test_export_fail_wrong_status + WebMock.reset! + + include_export_stub(status_code: 200) + + assert_raises Inferno::AssertionException do + @sequence.assert_export_kick_off('Patient') + end + end + + def test_export_fail_no_content_location + WebMock.reset! + + include_export_stub(response_headers: {}) + + assert_raises Inferno::AssertionException do + @sequence.assert_export_kick_off('Patient') + end + end + + def test_status_check_skip_timeout + WebMock.reset! + stub_request(:get, @content_location) + .with(headers: @status_request_headers) + .to_return( + status: 202, + headers: { content_type: 'application/json', 'retry-after': '1' } + ) + + assert_raises Inferno::SkipException do + @sequence.assert_export_status(@content_location, timeout: 1) + end + end + + def test_status_check_fail_wrong_status_code + WebMock.reset! + + include_status_check_stub(status_code: 201) + + assert_raises Inferno::AssertionException do + @sequence.assert_export_status(@content_location) + end + end + + def test_status_check_fail_no_output + WebMock.reset! + + response_body = @complete_status.clone + response_body.delete('output') + + include_status_check_stub(response_body: response_body) + + assert_raises Inferno::AssertionException do + @sequence.assert_export_status(@content_location) + end + end + + def test_status_check_fail_invalid_response_header + WebMock.reset! + + include_status_check_stub(response_headers: { content_type: 'application/xml' }) + + assert_raises Inferno::AssertionException do + @sequence.assert_export_status(@content_location) + end + end +end diff --git a/test/unit/sequence_base_test.rb b/test/unit/sequence_base_test.rb new file mode 100644 index 000000000..faddf6478 --- /dev/null +++ b/test/unit/sequence_base_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative '../test_helper' + +class SequenceBaseTest < MiniTest::Test + def setup + allergy_intolerance_bundle = FHIR.from_contents(load_fixture(:us_core_r4_allergy_intolerance)) + @allergy_intolerance_resource = allergy_intolerance_bundle.entry.first.resource + @instance = Inferno::Models::TestingInstance.new( + url: 'http://www.example.com', + client_name: 'Inferno', + base_url: 'http://localhost:4567', + client_endpoint_key: Inferno::SecureRandomBase62.generate(32), + client_id: SecureRandom.uuid, + selected_module: 'us_core_r4', + oauth_authorize_endpoint: 'http://oauth_reg.example.com/authorize', + oauth_token_endpoint: 'http://oauth_reg.example.com/token', + scopes: 'launch openid patient/*.* profile', + token: 99_897_979 + ) + + @instance.save! + + client = FHIR::Client.new(@instance.url) + client.use_r4 + client.default_json + @sequence = Inferno::Sequence::SequenceBase.new(@instance, client, true) + end + + def test_save_delayed_resource_references + delayed_resources = ['Location', 'Medication', 'Organization', 'Practitioner', 'PractitionerRole'] + some_non_delayed_resources = ['AllergyIntolerance', 'CarePlan', 'Careteam', 'Condition', 'Device', 'Observation', 'Encounter', 'Goal'] + + delayed_resources.each do |res| + set_resource_reference(@allergy_intolerance_resource, res) + @sequence.save_delayed_sequence_references(@allergy_intolerance_resource) + assert @instance.resource_references.any? { |ref| ref.resource_type == res }, "#{res} reference should be saved" + end + some_non_delayed_resources.each do |res| + set_resource_reference(@allergy_intolerance_resource, res) + @sequence.save_delayed_sequence_references(@allergy_intolerance_resource) + assert @instance.resource_references.none? { |ref| ref.resource_type == res }, "#{res} reference should not be saved" + end + end + + def set_resource_reference(resource, type) + new_reference = FHIR::Reference.new + new_reference.reference = "#{type}/1234" + resource.recorder = new_reference + end +end diff --git a/test/unit/validation_test.rb b/test/unit/validation_test.rb new file mode 100644 index 000000000..ef62e21c6 --- /dev/null +++ b/test/unit/validation_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative '../test_helper' + +class ValidationTest < Minitest::Test + def setup + @diagnostic_report_lab = FHIR.from_contents(load_fixture(:us_core_r4_diagnostic_report_lab)) + @diagnostic_report_note = FHIR.from_contents(load_fixture(:us_core_r4_diagnostic_report_note)) + @lab_results = FHIR.from_contents(load_fixture(:us_core_r4_observation_lab)) + @smoking_status = FHIR.from_contents(load_fixture(:us_core_r4_smoking_status)) + @pediatric_weight_for_height = FHIR.from_contents(load_fixture(:us_core_r4_pediatric_weight_for_height)) + @pediatric_bmi_for_age = FHIR.from_contents(load_fixture(:us_core_r4_pediatric_bmi_for_age)) + end + + def test_guess_r4_profiles + assert Inferno::ValidationUtil.guess_r4_profile(@diagnostic_report_lab).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_lab] + assert Inferno::ValidationUtil.guess_r4_profile(@diagnostic_report_note).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:diagnostic_report_note] + assert Inferno::ValidationUtil.guess_r4_profile(@lab_results).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:lab_results] + assert Inferno::ValidationUtil.guess_r4_profile(@smoking_status).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:smoking_status] + assert Inferno::ValidationUtil.guess_r4_profile(@pediatric_weight_for_height).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_weight_height] + assert Inferno::ValidationUtil.guess_r4_profile(@pediatric_bmi_for_age).url == Inferno::ValidationUtil::US_CORE_R4_URIS[:pediatric_bmi_age] + end +end