Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FI-2015 GET requests added to fhir_operation #380

Merged
merged 22 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3acc04b
GET requests added to fhir_operation
360dgries Aug 4, 2023
43eac91
Updated function contract
360dgries Aug 4, 2023
82aed41
linting
360dgries Aug 4, 2023
a07f634
added tests for helper function
360dgries Aug 4, 2023
83d2ada
deferred affectsState detection to test author
360dgries Aug 29, 2023
354674c
linting
360dgries Aug 29, 2023
53276a3
package-lock from main
360dgries Aug 29, 2023
fc94781
adjusted body_to_path to take one argument
360dgries Aug 29, 2023
e7dfb4d
Merge branch 'main' into FI-2015-enable-get-request-fhir_operation
360dgries Aug 30, 2023
aa9d5e8
Repeated parameter support, contract updating, let changes in spec te…
360dgries Aug 31, 2023
c999f43
Linting and Cyclomatic complexity handling
360dgries Aug 31, 2023
5044914
Merge branch 'FI-2015-enable-get-request-fhir_operation' of https://g…
360dgries Aug 31, 2023
d4dc3c8
Reverted Package-lock.json to original state
360dgries Sep 11, 2023
ac8a754
bundle update
360dgries Oct 2, 2023
d77c0bf
reorganization of spec tests and changes to checking parameter for pr…
360dgries Oct 6, 2023
268a585
Merge branch 'main' into FI-2015-enable-get-request-fhir_operation
360dgries Oct 6, 2023
94600a2
Merge branch 'main' into FI-2015-enable-get-request-fhir_operation
360dgries Oct 6, 2023
4e7d126
conflict resolution
360dgries Oct 6, 2023
8ac86f9
Taking package and gemfile from Main Branch
360dgries Oct 9, 2023
ac83e98
Linting spec tests
360dgries Oct 9, 2023
25da3d6
added rspec test for unhandled REST methods
360dgries Oct 10, 2023
cd511f2
linting of rspec file
360dgries Oct 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ group :test do
gem 'simplecov-cobertura'
gem 'webmock'
gem 'factory_bot'
end
end
48 changes: 43 additions & 5 deletions lib/inferno/dsl/fhir_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,64 @@ def fhir_clients
@fhir_clients ||= {}
end

# Wrapper for checking if parameter contents are primitive
#
# @param param [FHIR::Parameters::Parameter] Parameter to be checked
# @private
def primitive_parameter?(param)
param_val = param.to_hash.except('name')
param_val.any? { |datatype, param_value| FHIR.primitive?(datatype: datatype[5..], value: param_value) }
end

# Converts a list of FHIR Parameters into a query string for GET requests
#
# @param body [FHIR::Parameters] Must all be primitive if making GET request
# @private
def body_to_path(body)
query_hashes = body.parameter.map do |param|
if primitive_parameter?(param)
{ param.name => param.to_hash.except('name').values[0] }
else
Inferno::Application[:logger].error "Cannot use GET request with non-primitive datatype #{param.name}"
raise ArgumentError, "Cannot use GET request with non-primitive datatype #{param.name}"
end
end
query_hashes.map(&:to_query).join('&')
end

# Perform a FHIR operation
#
# @note This is a placeholder method until the FHIR::Client supports
# general operations
# general operations. Note that while both POST and GET methods are allowed,
# GET is only allowed when the operation does not affect the server's state.
# See https://build.fhir.org/operationdefinition-definitions.html#OperationDefinition.affectsState
#
# @note Currently does not allow for repeated parameters if using GET
#
# @param path [String]
# @param body [FHIR::Parameters]
# @param body [FHIR::Parameters] Must all be primitive if making GET request
# @param client [Symbol]
# @param name [Symbol] Name for this request to allow it to be used by
# other tests
# @param headers [Hash] custom headers for this operation
# @param operation_method [Symbol] indicates which request type to use for the operation
# @return [Inferno::Entities::Request]
def fhir_operation(path, body: nil, client: :default, name: nil, headers: {})
def fhir_operation(path, body: nil, client: :default, name: nil, headers: {}, operation_method: :post)
store_request_and_refresh_token(fhir_client(client), name) do
tcp_exception_handler do
operation_headers = fhir_client(client).fhir_headers
operation_headers.merge!('Content-Type' => 'application/fhir+json') if body.present?
operation_headers.merge!(headers) if headers.present?

fhir_client(client).send(:post, path, body, operation_headers)
case operation_method
when :post
fhir_client(client).send(:post, path, body, operation_headers)
when :get
path = "#{path}?#{body_to_path(body)}" if body.present?
fhir_client(client).send(:get, path, operation_headers)
else
Inferno::Application[:logger].error "Cannot perform #{operation_method} requests, use GET or POST"
raise ArgumentError, "Cannot perform #{operation_method} requests, use GET or POST"
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@
"keywords": [],
"author": "",
"license": "ISC"
}
}
136 changes: 132 additions & 4 deletions spec/inferno/dsl/fhir_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,65 @@ def test_session_id
let(:default_client) { group.fhir_clients[:default] }
let(:bundle) { FHIR::Bundle.new(type: 'history', entry: [{ resource: }]) }
let(:session_data_repo) { Inferno::Repositories::SessionData.new }
let(:boolean_parameter) do
FHIR::Parameters::Parameter.new.tap do |param|
param.name = 'PARAM_BOOL'
param.valueBoolean = true
end
end
let(:ratio_parameter) do
FHIR::Parameters::Parameter.new.tap do |param|
param.name = 'PARAM_RATIO'
param.valueRatio = FHIR::Ratio.new.tap do |ratio|
ratio.numerator = FHIR::Quantity.new
ratio.denominator = FHIR::Quantity.new
end
end
end
let(:resource_parameter) do
FHIR::Parameters::Parameter.new.tap do |param|
param.name = 'PARAM_RESOURCE'
param.resource = FHIR::Patient.new
end
end
let(:body_with_two_primitives) do
FHIR::Parameters.new.tap do |body|
body.parameter = [
boolean_parameter,
FHIR::Parameters::Parameter.new.tap do |param|
param.name = 'PARAM_STRING'
param.valueString = 'STRING'
end
]
end
end
let(:body_with_repeated_parameters) do
FHIR::Parameters.new.tap do |body|
body.parameter = [
boolean_parameter,
FHIR::Parameters::Parameter.new.tap do |param|
param.name = 'PARAM_BOOL'
param.valueBoolean = false
end
]
end
end
let(:body_with_nonprimitive) do
FHIR::Parameters.new.tap do |body|
body.parameter = [
boolean_parameter,
ratio_parameter
]
end
end
let(:body_with_resource) do
FHIR::Parameters.new.tap do |body|
body.parameter = [
boolean_parameter,
resource_parameter
]
end
end

describe '#fhir_client' do
context 'without an argument' do
Expand Down Expand Up @@ -76,23 +135,41 @@ def test_session_id
end
end

describe '#body_to_path' do
it 'handles repeated parameters' do
expected_body = [{ PARAM_BOOL: true }, { PARAM_BOOL: false }].map(&:to_query).join('&')
expect(group.body_to_path(body_with_repeated_parameters)).to eq(expected_body)
end
end

describe '#fhir_operation' do
let(:path) { 'abc' }
let(:stub_operation_request) do
stub_request(:post, "#{base_url}/#{path}")
.to_return(status: 200, body: resource.to_json)
end
let(:stub_operation_get_request) do
stub_request(:get, "#{base_url}/#{path}")
.to_return(status: 200, body: resource.to_json)
end

before do
stub_operation_request
stub_operation_get_request
end

it 'performs a get' do
it 'performs a post' do
group.fhir_operation(path)

expect(stub_operation_request).to have_been_made.once
end

it 'performs a get' do
group.fhir_operation(path, operation_method: :get)

expect(stub_operation_get_request).to have_been_made.once
end

it 'returns an Inferno::Entities::Request' do
result = group.fhir_operation(path)

Expand All @@ -106,6 +183,57 @@ def test_session_id
expect(group.request).to eq(result)
end

context 'with a body of parameters' do
it 'uses get when all parameters are primitive and GET specified' do
body = body_with_two_primitives
get_with_body_request_stub =
stub_request(:get, "#{base_url}/#{path}")
.with(query: { PARAM_BOOL: true, PARAM_STRING: 'STRING' })
.to_return(status: 200, body: resource.to_json)

group.fhir_operation(path, body:, operation_method: :get)

expect(get_with_body_request_stub).to have_been_made.once
end

# This test left for testing DoD of FI-2223
# https://oncprojectracking.healthit.gov/support/browse/FI-2223
#
# it 'correctly handles repeated parameters' do
# body = body_with_repeated_parameters
# get_with_body_request_stub =
# stub_request(:get, "#{base_url}/#{path}")
# .with(body: URI.encode_www_form({PARAM_BOOL: [true, false]}))
# .to_return(status: 200, body: resource.to_json)
# puts get_with_body_request_stub.to_s

# group.fhir_operation(path, body:, operation_method: :get)

# expect(get_with_body_request_stub).to have_been_made.once
# end

it 'prevents get when parameters are non-primitive' do
body = body_with_nonprimitive
expect do
group.fhir_operation(path, body:, operation_method: :get)
end.to raise_error(ArgumentError, 'Cannot use GET request with non-primitive datatype PARAM_RATIO')
end

it 'prevents get when parameters contain resources' do
body = body_with_resource
expect do
group.fhir_operation(path, body:, operation_method: :get)
end.to raise_error(ArgumentError, 'Cannot use GET request with non-primitive datatype PARAM_RESOURCE')
end

it 'prevents REST methods other than GET and POST' do
body = body_with_two_primitives
expect do
group.fhir_operation(path, body:, operation_method: :put)
end.to raise_error(ArgumentError, 'Cannot perform put requests, use GET or POST')
end
end

context 'with the client parameter' do
it 'uses that client' do
other_url = 'http://www.example.com/fhir/r4'
Expand Down Expand Up @@ -150,7 +278,7 @@ def test_session_id
.to_return(status: 200, body: resource.to_json)
end

it 'as custom only, performs a get' do
it 'as custom only, performs a post' do
operation_request =
stub_request(:post, "#{base_url}/#{path}")
.with(headers: { 'CustomHeader' => 'CustomTest' })
Expand All @@ -161,7 +289,7 @@ def test_session_id
expect(operation_request).to have_been_made.once
end

it 'as default only, performs a get' do
it 'as default only, performs a post' do
operation_request =
stub_request(:post, "#{base_url}/#{path}")
.with(headers: { 'DefaultHeader' => 'ClientHeader' })
Expand All @@ -173,7 +301,7 @@ def test_session_id
expect(operation_request).to have_been_made.once
end

it 'as both default and custom, performs a get' do
it 'as both default and custom, performs a post' do
operation_request =
stub_request(:post, "#{base_url}/#{path}")
.with(headers: { 'DefaultHeader' => 'ClientHeader', 'CustomHeader' => 'CustomTest' })
Expand Down