onc_certification_g10_test_kit 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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