Module: BulkDataTestKit::BulkExportValidationTester

Constant Summary collapse

MAX_NUM_COLLECTED_LINES =
100
MIN_RESOURCE_COUNT =
2

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



5
6
7
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 5

def 
  @metadata
end

Instance Method Details

#build_headers(use_token) ⇒ Object



30
31
32
33
34
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 30

def build_headers(use_token)
  headers = { accept: 'application/fhir+ndjson' }
  headers.merge!({ authorization: "Bearer #{bearer_token}" }) if use_token == 'true'
  headers
end

#check_file_request(url, bulk_requires_access_token) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 74

def check_file_request(url, bulk_requires_access_token) # rubocop:disable Metrics/CyclomaticComplexity
  line_count = 0
  resources = Hash.new { |h, k| h[k] = [] }

  process_line = proc do |line|
    next unless lines_to_validate.blank? ||
                line_count < lines_to_validate.to_i ||
                (@resource_type == 'Patient' && patient_ids_seen.length < MIN_RESOURCE_COUNT)

    line_count += 1

    begin
      resource = FHIR.from_contents(line)
    rescue StandardError
      skip "Server response at line \"#{line_count}\" is not a processable FHIR resource."
    end

    assert !resource.nil?,
           "Resource at line \"#{line_count}\" could not be converted to a #{@resource_type} FHIR resource"

    if resource.resourceType != @resource_type
      assert false, "Resource type \"#{resource.resourceType}\" at line \"#{line_count}\" does not match type " \
                    "defined in output \"#{@resource_type}\""
    end

    scratch[:patient_ids_seen] = patient_ids_seen | [resource.id] if @resource_type == 'Patient'

    unless resource_is_valid?(resource:)
      if first_error.key?(:line_number)
        @invalid_resource_count_all += 1
        @invalid_resource_count += 1
      else
        @invalid_resource_count_all.zero? ? @invalid_resource_count_all = 1 : @invalid_resource_count_all += 1

        @invalid_resource_count = 1
        first_error[:line_number] = line_count
        first_error[:messages] = messages.dup
      end
    end
  end

  process_headers = proc { |response|
    value = (response[:headers].find { |header| header.name.downcase == 'content-type' })&.value
    unless value&.start_with?('application/fhir+ndjson')
      skip "Content type must have 'application/fhir+ndjson' but found '#{value}'"
    end
  }

  stream_ndjson(url, build_headers(bulk_requires_access_token), process_line, process_headers)
  resources_from_all_files.merge!(resources) do |_key, all_resources, file_resources|
    all_resources | file_resources
  end
  line_count
end

#export_multiple_patients_checkObject



203
204
205
206
207
208
209
210
211
212
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 203

def export_multiple_patients_check
  skip 'No Patient resources processed from bulk data export.' unless patient_ids_seen.present?

  begin
    assert patient_ids_seen.length >= BulkExportValidationTester::MIN_RESOURCE_COUNT,
           'Bulk data export did not have multiple Patient resources.'
  ensure
    scratch[:patient_ids_seen] = []
  end
end

#first_errorObject



14
15
16
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 14

def first_error
  @first_error ||= {}
end

#ndjson_download_requiresAccessToken_check(bulk_data_download_url: '', bulk_requires_access_token: '') ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 191

def ndjson_download_requiresAccessToken_check(bulk_data_download_url: '', bulk_requires_access_token: '')
  skip_if bulk_data_download_url.blank?, 'Could not verify this functionality when no download link was provided'
  skip_if bulk_requires_access_token.blank?,
          'Could not verify this functionality when requiresAccessToken is not provided'
  omit_if bulk_requires_access_token == 'false',
          'Could not verify this functionality when requiresAccessToken is false'
  skip_if bearer_token.blank?, 'Could not verify this functionality when Bearer Token is not provided'

  get(bulk_data_download_url, headers: { accept: 'application/fhir+ndjson' })
  assert_response_status([400, 401])
end

#patient_ids_seenObject



18
19
20
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 18

def patient_ids_seen
  scratch[:patient_ids_seen] ||= []
end

#perform_bulk_export_validation(bulk_status_output: '', bulk_requires_access_token: '') ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 143

def perform_bulk_export_validation(bulk_status_output: '', bulk_requires_access_token: '')
  skip_if bulk_status_output.blank?, 'Could not verify this functionality when Bulk Status Output is not provided'
  skip_if (bulk_requires_access_token == 'true' && bearer_token.blank?),
          'Could not verify this functionality when Bearer Token is required and not provided'

  $num_messages = 0
  $capped_message = false
  $num_errors = 0
  $capped_errors = false

  assert_valid_json(bulk_status_output)

  full_file_list = JSON.parse(bulk_status_output)
  if full_file_list.empty?
    message = 'No resource file items returned by server.'
    skip message
  end

  @resources_from_all_files = {}

  resource_types = full_file_list.map { |file| file['type'] }.uniq
  all_resource_count = 0
  @invalid_resource_count_all = 0
  @validation_errors = []

  resource_types.each do |type|
    @resource_type = type
    @first_error = {}
    @invalid_resource_count = 0
    resource_count = 0

    file_list = full_file_list.select { |file| file['type'] == @resource_type }

    file_list.each do |file|
      count = check_file_request(file['url'], bulk_requires_access_token)
      all_resource_count += count
      resource_count += count
    end

    process_validation_errors(resource_count)
  end

  assert @invalid_resource_count_all.zero?,
         "#{@invalid_resource_count_all} / #{all_resource_count} Resources failed validation. \n" + @validation_errors.join("\n")

  pass "Successfully validated #{all_resource_count} Resources"
end

#process_validation_errors(resource_count) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 129

def process_validation_errors(resource_count)
  return if @invalid_resource_count.nil? || @invalid_resource_count.zero?

  first_error_message = "The line number for the first failed #{@resource_type} resource is #{first_error[:line_number]}."

  messages.clear
  messages.concat(first_error[:messages])

  error_message = "#{@invalid_resource_count} / #{resource_count} #{@resource_type} resources failed profile validation. " \
          "#{first_error_message}"

  validation_errors.append(error_message)
end

#resource_typeObject



22
23
24
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 22

def resource_type
  @resource_type ||= ''
end

#resources_from_all_filesObject



10
11
12
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 10

def resources_from_all_files
  @resources_from_all_files ||= {}
end

#stream_ndjson(endpoint, headers, process_chunk_line, process_response) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 36

def stream_ndjson(endpoint, headers, process_chunk_line, process_response) # rubocop:disable Metrics/CyclomaticComplexity
  hanging_chunk = String.new

  process_body = proc { |chunk|
    hanging_chunk << chunk
     = hanging_chunk.lines

    hanging_chunk = .pop || String.new

    .each do |elem|
      process_chunk_line.call(elem)
    end
  }

  stream(process_body, endpoint, headers:)

  max_redirect = 5

  while [301, 302, 303, 307].include?(response[:status]) &&
        request.response_header('location')&.value.present? &&
        max_redirect.positive?

    max_redirect -= 1

    redirect_url = request.response_header('location')&.value

    # handle relative redirects
    redirect_url = URI.parse(endpoint).merge(redirect_url).to_s unless redirect_url.start_with?('http')

    redirect_headers = headers.except(:authorization)

    stream(process_body, redirect_url, headers: redirect_headers)
  end

  process_chunk_line.call(hanging_chunk)
  process_response.call(response)
end

#validation_errorsObject



26
27
28
# File 'lib/bulk_data_test_kit/bulk_export_validation_tester.rb', line 26

def validation_errors
  @validation_errors ||= []
end