onc_certification_g10_test_kit 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/inferno/exceptions.rb +31 -0
  4. data/lib/inferno/ext/bloomer.rb +24 -0
  5. data/lib/inferno/repositiories/validators.rb +17 -0
  6. data/lib/inferno/repositiories/value_sets.rb +26 -0
  7. data/lib/inferno/terminology/bcp47.rb +95 -0
  8. data/lib/inferno/terminology/bcp_13.rb +26 -0
  9. data/lib/inferno/terminology/codesystem.rb +49 -0
  10. data/lib/inferno/terminology/expected_manifest.yml +1123 -0
  11. data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
  12. data/lib/inferno/terminology/loader.rb +298 -0
  13. data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
  14. data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
  15. data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
  16. data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
  17. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
  18. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
  19. data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
  20. data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
  21. data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
  22. data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
  23. data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
  24. data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
  25. data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
  26. data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
  27. data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
  28. data/lib/inferno/terminology/tasks.rb +11 -0
  29. data/lib/inferno/terminology/terminology_configuration.rb +52 -0
  30. data/lib/inferno/terminology/terminology_validation.rb +42 -0
  31. data/lib/inferno/terminology/validator.rb +64 -0
  32. data/lib/inferno/terminology/value_set.rb +462 -0
  33. data/lib/inferno/terminology.rb +16 -0
  34. data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
  35. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
  36. data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
  37. data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
  38. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
  39. data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
  40. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
  41. data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
  42. data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
  43. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
  44. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
  45. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
  46. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
  47. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
  48. data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
  49. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
  50. data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
  51. data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
  52. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
  53. data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
  54. data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
  55. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
  56. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
  57. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
  58. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
  59. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
  60. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
  61. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
  62. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
  63. data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
  64. data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
  65. data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
  66. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
  67. data/lib/onc_certification_g10_test_kit/version.rb +3 -0
  68. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
  69. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
  70. data/lib/onc_certification_g10_test_kit.rb +223 -0
  71. metadata +310 -0
@@ -0,0 +1,171 @@
1
+ require_relative 'profile_guesser'
2
+
3
+ module ONCCertificationG10TestKit
4
+ module BulkExportValidationTester
5
+ include USCoreTestKit::MustSupportTest
6
+ include ProfileGuesser
7
+
8
+ attr_reader :metadata
9
+
10
+ MAX_NUM_COLLECTED_LINES = 100
11
+ MIN_RESOURCE_COUNT = 2
12
+
13
+ def observation_metadata
14
+ [
15
+ USCoreTestKit::PediatricBmiForAgeGroup.metadata,
16
+ USCoreTestKit::PediatricWeightForHeightGroup.metadata,
17
+ USCoreTestKit::ObservationLabGroup.metadata,
18
+ USCoreTestKit::PulseOximetryGroup.metadata,
19
+ USCoreTestKit::SmokingstatusGroup.metadata,
20
+ USCoreTestKit::HeadCircumferenceGroup.metadata,
21
+ USCoreTestKit::BpGroup.metadata,
22
+ USCoreTestKit::BodyheightGroup.metadata,
23
+ USCoreTestKit::BodytempGroup.metadata,
24
+ USCoreTestKit::BodyweightGroup.metadata,
25
+ USCoreTestKit::HeartrateGroup.metadata,
26
+ USCoreTestKit::ResprateGroup.metadata
27
+ ]
28
+ end
29
+
30
+ def diagnostic_metadata
31
+ [USCoreTestKit::DiagnosticReportLabGroup.metadata, USCoreTestKit::DiagnosticReportNoteGroup.metadata]
32
+ end
33
+
34
+ def determine_metadata
35
+ return observation_metadata if resource_type == 'Observation'
36
+ return diagnostic_metadata if resource_type == 'DiagnosticReport'
37
+
38
+ if resource_type == 'Location' || resource_type == 'Medication'
39
+ return Array.wrap(USCoreTestKit::USCoreTestSuite.metadata.find do |meta|
40
+ meta.resource == resource_type
41
+ end)
42
+ end
43
+ ["USCoreTestKit::#{resource_type}Group".constantize.metadata]
44
+ end
45
+
46
+ def metadata_list
47
+ @metadata_list ||= determine_metadata
48
+ end
49
+
50
+ def patient_ids_seen
51
+ scratch[:patient_ids_seen] ||= []
52
+ end
53
+
54
+ def build_headers(use_token)
55
+ headers = { accept: 'application/fhir+ndjson' }
56
+ headers.merge!({ authorization: "Bearer #{bearer_token}" }) if use_token == 'true'
57
+ headers
58
+ end
59
+
60
+ def stream_ndjson(endpoint, headers, process_chunk_line, process_response)
61
+ hanging_chunk = String.new
62
+
63
+ process_body = proc { |chunk|
64
+ hanging_chunk << chunk
65
+ chunk_by_lines = hanging_chunk.lines
66
+
67
+ hanging_chunk = chunk_by_lines.pop || String.new
68
+
69
+ chunk_by_lines.each do |elem|
70
+ process_chunk_line.call(elem)
71
+ end
72
+ }
73
+
74
+ stream(process_body, endpoint, headers: headers)
75
+
76
+ process_chunk_line.call(hanging_chunk)
77
+ process_response.call(response)
78
+ end
79
+
80
+ def predefined_device_type?(resource) # rubocop:disable Metrics/CyclomaticComplexity
81
+ return true if bulk_device_types_in_group.blank?
82
+
83
+ expected = Set.new(bulk_device_types_in_group.split(',').map(&:strip))
84
+
85
+ actual = resource&.type&.coding&.filter_map do |coding|
86
+ coding.code if coding.system.nil? || coding.system == 'http://snomed.info/sct'
87
+ end
88
+
89
+ (expected & actual).any?
90
+ end
91
+
92
+ def determine_profile(resource)
93
+ return if resource.resourceType == 'Device' && !predefined_device_type?(resource)
94
+
95
+ guess_profile(resource)
96
+ end
97
+
98
+ def validate_conformance(resources)
99
+ metadata_list.each do |meta|
100
+ skip_if resources[meta.profile_url].blank?,
101
+ "No #{resource_type} resources found that conform to profile: #{meta.profile_url}."
102
+ @metadata = meta
103
+ @missing_elements = nil
104
+ @missing_slices = nil
105
+ begin
106
+ perform_must_support_test(resources[meta.profile_url])
107
+ rescue Inferno::Exceptions::PassException
108
+ next
109
+ end
110
+ end
111
+ end
112
+
113
+ def check_file_request(url) # rubocop:disable Metrics/CyclomaticComplexity
114
+ line_count = 0
115
+ resources = Hash.new { |h, k| h[k] = [] }
116
+
117
+ process_line = proc { |line|
118
+ next unless lines_to_validate.blank? ||
119
+ line_count < lines_to_validate.to_i ||
120
+ (resource_type == 'Patient' && patient_ids_seen.length < MIN_RESOURCE_COUNT)
121
+
122
+ line_count += 1
123
+
124
+ begin
125
+ resource = FHIR.from_contents(line)
126
+ rescue StandardError
127
+ skip "Server response at line \"#{line_count}\" is not a processable FHIR resource."
128
+ end
129
+
130
+ skip_if resource.resourceType != resource_type,
131
+ "Resource type \"#{resource.resourceType}\" at line \"#{line_count}\" does not match type " \
132
+ "defined in output \"#{resource_type}\""
133
+
134
+ profile_url = determine_profile(resource)
135
+ resources[profile_url] << resource
136
+ scratch[:patient_ids_seen] = patient_ids_seen | [resource.id] if resource_type == 'Patient'
137
+
138
+ skip_if !resource_is_valid?(resource: resource, profile_url: profile_url),
139
+ "Resource at line \"#{line_count}\" does not conform to profile \"#{profile_url}\"."
140
+ }
141
+
142
+ process_headers = proc { |response|
143
+ value = (response[:headers].find { |header| header.name.downcase == 'content-type' })&.value
144
+ unless value&.start_with?('application/fhir+ndjson')
145
+ skip "Content type must have 'application/fhir+ndjson' but found '#{value}'"
146
+ end
147
+ }
148
+
149
+ stream_ndjson(url, build_headers(requires_access_token), process_line, process_headers)
150
+ validate_conformance(resources)
151
+
152
+ line_count
153
+ end
154
+
155
+ def perform_bulk_export_validation
156
+ skip_if status_output.blank?, 'Could not verify this functionality when Bulk Status Output is not provided'
157
+ skip_if (requires_access_token == 'true' && bearer_token.blank?),
158
+ 'Could not verify this functionality when Bearer Token is required and not provided'
159
+
160
+ file_list = JSON.parse(status_output).select { |file| file['type'] == resource_type }
161
+ skip_if file_list.empty?, "No #{resource_type} resource file item returned by server."
162
+
163
+ success_count = 0
164
+ file_list.each do |file|
165
+ success_count += check_file_request(file['url'])
166
+ end
167
+
168
+ pass "Successfully validated #{success_count} #{resource_type} resource(s)."
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,104 @@
1
+ require_relative '../inferno/terminology/tasks/check_built_terminology'
2
+
3
+ module ONCCertificationG10TestKit
4
+ class ConfigurationChecker
5
+ EXPECTED_VALIDATOR_VERSION = '2.1.0'.freeze
6
+
7
+ def configuration_messages
8
+ validator_version_message + terminology_messages
9
+ end
10
+
11
+ def terminology_checker
12
+ @terminology_checker ||= Inferno::Terminology::Tasks::CheckBuiltTerminology.new
13
+ end
14
+
15
+ def mismatched_value_sets
16
+ terminology_checker.mismatched_value_sets
17
+ end
18
+
19
+ def validator_url
20
+ @validator_url ||= G10CertificationSuite.find_validator(:default).url
21
+ end
22
+
23
+ def validator_version_message
24
+ response = Faraday.get "#{validator_url}/version"
25
+ version = response.body
26
+
27
+ if version == EXPECTED_VALIDATOR_VERSION
28
+ [{
29
+ type: 'info',
30
+ message: "FHIR validator is the expected version `#{EXPECTED_VALIDATOR_VERSION}`"
31
+ }]
32
+ else
33
+ [{
34
+ type: 'error',
35
+ message: "Expected FHIR validator version `#{EXPECTED_VALIDATOR_VERSION}`, but found `#{version}`"
36
+ }]
37
+ end
38
+ rescue StandardError => e
39
+ [{
40
+ type: 'error',
41
+ message: "Unable to connect to Validator: `#{e.message}`"
42
+ }]
43
+ end
44
+
45
+ def terminology_messages # rubocop:disable Metrics/CyclomaticComplexity
46
+ success_messages = []
47
+ warning_messages = []
48
+ error_messages = []
49
+ messages = []
50
+ terminology_checker.expected_manifest.each do |expected_value_set|
51
+ url = expected_value_set[:url]
52
+ actual_value_set = terminology_checker.new_value_set(url)
53
+
54
+ if actual_value_set == expected_value_set
55
+ success_messages << "* `#{url}`: #{actual_value_set[:count]} codes"
56
+ elsif actual_value_set.nil?
57
+ error_messages << "* `#{url}`: Not loaded"
58
+ elsif terminology_checker.class::MIME_TYPE_SYSTEMS.include? url
59
+ warning_messages <<
60
+ "* `#{url}`: Expected codes: #{expected_value_set[:count]} Actual codes: #{actual_value_set[:count]}"
61
+ else
62
+ error_messages <<
63
+ "* `#{url}`: Expected codes: #{expected_value_set[:count]} Actual codes: #{actual_value_set[:count]}"
64
+ end
65
+ end
66
+
67
+ if success_messages.present?
68
+ messages << {
69
+ type: 'info',
70
+ message:
71
+ "The following value sets and code systems have been properly loaded:\n#{success_messages.join("\n")}"
72
+ }
73
+ end
74
+
75
+ if warning_messages.present?
76
+ warning_message = <<~WARNING
77
+ Mime-type based terminology did not exactly match. This can be the
78
+ result of using a slightly different version of the `mime-types-data`
79
+ gem and does not reflect a problem with the terminology build as long
80
+ as the expected and actual number of codes are close to each other.
81
+ WARNING
82
+ messages << {
83
+ type: 'warning',
84
+ message: warning_message + warning_messages.join("\n")
85
+ }
86
+ end
87
+
88
+ if error_messages.present?
89
+ error_message = <<~ERROR
90
+ There is a problem with the terminology resources. See the README for
91
+ the [G10 Certification Test Kit
92
+ README](https://github.com/inferno-framework/g10-certification-test-kit#terminology-support)
93
+ for instructions on building the required terminology resources:\n
94
+ ERROR
95
+ messages << {
96
+ type: 'error',
97
+ message: error_message + error_messages.join("\n")
98
+ }
99
+ end
100
+
101
+ messages
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,12 @@
1
+ module ONCCertificationG10TestKit
2
+ module ExportKickOffPerformer
3
+ def perform_export_kick_off_request(use_token: true)
4
+ skip_if use_token && bearer_token.blank?, 'Could not verify this functionality when bearer token is not set'
5
+
6
+ headers = { accept: 'application/fhir+json', prefer: 'respond-async' }
7
+ headers.merge!({ authorization: "Bearer #{bearer_token}" }) if use_token
8
+
9
+ get("Group/#{group_id}/$export", client: :bulk_server, name: :export, headers: headers)
10
+ end
11
+ end
12
+ end