From 990a1abad5c859a15f528065760efeb684079617 Mon Sep 17 00:00:00 2001 From: Tom Strassner Date: Tue, 1 Aug 2023 12:44:10 -0500 Subject: [PATCH] FI-1821: Add a handful of operations to the FHIR DSL (#376) * Add a handful of operations to the FHIR DSL Instance: vread, update, patch, history Type: history System: history, search * Incorporate PR feedback * Add fhir_history to documentation * Add more new methods to the docs --- docs/writing-tests/making-requests.md | 4 + lib/inferno/dsl/fhir_client.rb | 82 +++++- spec/inferno/dsl/fhir_client_spec.rb | 362 +++++++++++++++++++++++++- 3 files changed, 441 insertions(+), 7 deletions(-) diff --git a/docs/writing-tests/making-requests.md b/docs/writing-tests/making-requests.md index 21216a432..5c2384c64 100644 --- a/docs/writing-tests/making-requests.md +++ b/docs/writing-tests/making-requests.md @@ -151,7 +151,11 @@ The following methods are currently available for making FHIR requests: - `fhir_get_capability_statement` - `fhir_operation` - `fhir_read` +- `fhir_vread` +- `fhir_update` +- `fhir_patch` - `fhir_search` +- `fhir_history` - `fhir_transaction` For more details on these methods, see the [FHIR Client API documentation](/inferno-core/docs/Inferno/DSL/FHIRClient.html). If you need to diff --git a/lib/inferno/dsl/fhir_client.rb b/lib/inferno/dsl/fhir_client.rb index f534072dd..8eb728111 100644 --- a/lib/inferno/dsl/fhir_client.rb +++ b/lib/inferno/dsl/fhir_client.rb @@ -130,6 +130,78 @@ def fhir_read(resource_type, id, client: :default, name: nil) end end + # Perform a FHIR vread interaction. + # + # @param resource_type [String, Symbol, Class] + # @param id [String] + # @param version_id [String] + # @param client [Symbol] + # @param name [Symbol] Name for this request to allow it to be used by + # other tests + # @return [Inferno::Entities::Request] + def fhir_vread(resource_type, id, version_id, client: :default, name: nil) + store_request_and_refresh_token(fhir_client(client), name) do + tcp_exception_handler do + fhir_client(client).vread(fhir_class_from_resource_type(resource_type), id, version_id) + end + end + end + + # Perform a FHIR update interaction. + # + # @param resource [FHIR::Model] + # @param id [String] + # @param client [Symbol] + # @param name [Symbol] Name for this request to allow it to be used by + # other tests + # @return [Inferno::Entities::Request] + def fhir_update(resource, id, client: :default, name: nil) + store_request_and_refresh_token(fhir_client(client), name) do + tcp_exception_handler do + fhir_client(client).update(resource, id) + end + end + end + + # Perform a FHIR patch interaction. + # + # @param resource_type [String, Symbol, Class] + # @param id [String] + # @param patchset [Array] + # @param client [Symbol] + # @param name [Symbol] Name for this request to allow it to be used by + # other tests + # @return [Inferno::Entities::Request] + def fhir_patch(resource_type, id, patchset, client: :default, name: nil) + store_request_and_refresh_token(fhir_client(client), name) do + tcp_exception_handler do + fhir_client(client).partial_update(fhir_class_from_resource_type(resource_type), id, patchset) + end + end + end + + # Perform a FHIR history interaction. + # + # @param resource_type [String, Symbol, Class] + # @param id [String] + # @param client [Symbol] + # @param name [Symbol] Name for this request to allow it to be used by + # other tests + # @return [Inferno::Entities::Request] + def fhir_history(resource_type = nil, id = nil, client: :default, name: nil) + store_request_and_refresh_token(fhir_client(client), name) do + tcp_exception_handler do + if id + fhir_client(client).resource_instance_history(fhir_class_from_resource_type(resource_type), id) + elsif resource_type + fhir_client(client).resource_history(fhir_class_from_resource_type(resource_type)) + else + fhir_client(client).all_history + end + end + end + end + # Perform a FHIR search interaction. # # @param resource_type [String, Symbol, Class] @@ -139,7 +211,7 @@ def fhir_read(resource_type, id, client: :default, name: nil) # other tests # @param search_method [Symbol] Use `:post` to search via POST # @return [Inferno::Entities::Request] - def fhir_search(resource_type, client: :default, params: {}, name: nil, search_method: :get) + def fhir_search(resource_type = nil, client: :default, params: {}, name: nil, search_method: :get) search = if search_method == :post { body: params } @@ -149,8 +221,12 @@ def fhir_search(resource_type, client: :default, params: {}, name: nil, search_m store_request_and_refresh_token(fhir_client(client), name) do tcp_exception_handler do - fhir_client(client) - .search(fhir_class_from_resource_type(resource_type), { search: }) + if resource_type + fhir_client(client) + .search(fhir_class_from_resource_type(resource_type), { search: }) + else + fhir_client(client).search_all({ search: }) + end end end end diff --git a/spec/inferno/dsl/fhir_client_spec.rb b/spec/inferno/dsl/fhir_client_spec.rb index 02211865d..586a52d1f 100644 --- a/spec/inferno/dsl/fhir_client_spec.rb +++ b/spec/inferno/dsl/fhir_client_spec.rb @@ -34,9 +34,10 @@ def test_session_id let(:group) { FHIRClientDSLTestClass.new } let(:base_url) { 'http://www.example.com/fhir' } let(:resource_id) { '123' } - let(:resource) { FHIR::CarePlan.new(id: resource_id) } + let(:version_id) { '4' } + let(:resource) { FHIR::CarePlan.new(id: resource_id, meta: { versionId: version_id }) } let(:default_client) { group.fhir_clients[:default] } - let(:bundle) { FHIR::Bundle.new(entry: [{ resource: }]) } + let(:bundle) { FHIR::Bundle.new(type: 'history', entry: [{ resource: }]) } let(:session_data_repo) { Inferno::Repositories::SessionData.new } describe '#fhir_client' do @@ -391,6 +392,336 @@ def test_session_id end end + describe '#fhir_vread' do + let(:stub_vread_request) do + stub_request(:get, "#{base_url}/#{resource.resourceType}/#{resource_id}/_history/#{version_id}") + .to_return(status: 200, body: resource.to_json) + end + + before do + stub_vread_request + end + + it 'performs a FHIR vread' do + group.fhir_vread(resource.resourceType, resource_id, version_id) + + expect(stub_vread_request).to have_been_made.once + end + + it 'returns an Inferno::Entities::Request' do + result = group.fhir_vread(resource.resourceType, resource_id, version_id) + + expect(result).to be_a(Inferno::Entities::Request) + end + + it 'adds the request to the list of requests' do + result = group.fhir_vread(resource.resourceType, resource_id, version_id) + + expect(group.requests).to include(result) + expect(group.request).to eq(result) + end + + context 'with the client parameter' do + it 'uses that client' do + other_url = 'http://www.example.com/fhir/r4' + group.fhir_clients[:other_client] = FHIR::Client.new(other_url) + + other_request_stub = + stub_request(:get, "#{other_url}/#{resource.resourceType}/#{resource_id}/_history/#{version_id}") + .to_return(status: 200, body: resource.to_json) + + group.fhir_vread(resource.resourceType, resource_id, version_id, client: :other_client) + + expect(other_request_stub).to have_been_made + expect(stub_vread_request).to_not have_been_made + end + end + + context 'with a base url that causes a TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:vread) + .and_raise(SocketError, 'Failed to open TCP') + end + + it 'raises a test failure exception' do + expect do + group.fhir_vread :patient, '0', '1' + end.to raise_error(Inferno::Exceptions::AssertionException, 'Failed to open TCP') + end + end + + context 'with a base url that causes a non-TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:vread) + .and_raise(SocketError, 'not a TCP error') + end + + it 'raises the error' do + expect do + group.fhir_vread :patient, '0', '1' + end.to raise_error(SocketError, 'not a TCP error') + end + end + end + + describe '#fhir_update' do + let(:stub_update_request) do + stub_request(:put, "#{base_url}/#{resource.resourceType}/#{resource_id}") + .with(body: resource.to_json) + .to_return(status: 200, + headers: { 'Location' => "#{base_url}/#{resource.resourceType}/#{resource.id}/_history/555" }) + end + + before do + stub_update_request + end + + it 'performs a FHIR update' do + group.fhir_update(resource, resource_id) + + expect(stub_update_request).to have_been_made.once + end + + it 'returns an Inferno::Entities::Request' do + result = group.fhir_update(resource, resource_id) + + expect(result).to be_a(Inferno::Entities::Request) + end + + it 'adds the request to the list of requests' do + result = group.fhir_update(resource, resource_id) + + expect(group.requests).to include(result) + expect(group.request).to eq(result) + end + + context 'with the client parameter' do + it 'uses that client' do + other_url = 'http://www.example.com/fhir/r4' + group.fhir_clients[:other_client] = FHIR::Client.new(other_url) + + other_request_stub = + stub_request(:put, "#{other_url}/#{resource.resourceType}/#{resource_id}") + .with(body: resource.to_json) + .to_return(status: 200, + headers: { 'Location' => "#{other_url}/#{resource.resourceType}/#{resource.id}/_history/555" }) + + group.fhir_update(resource, resource_id, client: :other_client) + + expect(other_request_stub).to have_been_made + expect(stub_update_request).to_not have_been_made + end + end + + context 'with a base url that causes a TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:update) + .and_raise(SocketError, 'Failed to open TCP') + end + + it 'raises a test failure exception' do + expect do + group.fhir_update resource, resource_id + end.to raise_error(Inferno::Exceptions::AssertionException, 'Failed to open TCP') + end + end + + context 'with a base url that causes a non-TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:update) + .and_raise(SocketError, 'not a TCP error') + end + + it 'raises the error' do + expect do + group.fhir_update resource, resource_id + end.to raise_error(SocketError, 'not a TCP error') + end + end + end + + describe '#fhir_patch' do + let(:patch) { [{ op: 'replace', path: '/status/', value: 'active' }] } + + let(:stub_patch_request) do + stub_request(:patch, "#{base_url}/#{resource.resourceType}/#{resource_id}") + .with(body: patch.to_json) + .to_return(status: 200, + headers: { 'Location' => "#{base_url}/#{resource.resourceType}/#{resource.id}/_history/555" }) + end + + before do + stub_patch_request + end + + it 'performs a FHIR patch' do + group.fhir_patch(resource.resourceType, resource_id, patch) + + expect(stub_patch_request).to have_been_made.once + end + + it 'returns an Inferno::Entities::Request' do + result = group.fhir_patch(resource.resourceType, resource_id, patch) + + expect(result).to be_a(Inferno::Entities::Request) + end + + it 'adds the request to the list of requests' do + result = group.fhir_patch(resource.resourceType, resource_id, patch) + + expect(group.requests).to include(result) + expect(group.request).to eq(result) + end + + context 'with the client parameter' do + it 'uses that client' do + other_url = 'http://www.example.com/fhir/r4' + group.fhir_clients[:other_client] = FHIR::Client.new(other_url) + + other_request_stub = + stub_request(:patch, "#{other_url}/#{resource.resourceType}/#{resource_id}") + .with(body: patch.to_json) + .to_return(status: 200, + headers: { 'Location' => "#{other_url}/#{resource.resourceType}/#{resource.id}/_history/555" }) + + group.fhir_patch(resource.resourceType, resource_id, patch, client: :other_client) + + expect(other_request_stub).to have_been_made + expect(stub_patch_request).to_not have_been_made + end + end + + context 'with a base url that causes a TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:partial_update) + .and_raise(SocketError, 'Failed to open TCP') + end + + it 'raises a test failure exception' do + expect do + group.fhir_patch resource.resourceType, resource_id, patch + end.to raise_error(Inferno::Exceptions::AssertionException, 'Failed to open TCP') + end + end + + context 'with a base url that causes a non-TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:partial_update) + .and_raise(SocketError, 'not a TCP error') + end + + it 'raises the error' do + expect do + group.fhir_patch resource.resourceType, resource_id, patch + end.to raise_error(SocketError, 'not a TCP error') + end + end + end + + describe '#fhir_history' do + let(:stub_instance_history_request) do + stub_request(:get, "#{base_url}/#{resource.resourceType}/#{resource_id}/_history") + .to_return(status: 200, body: bundle.to_json) + end + + let(:stub_type_history_request) do + stub_request(:get, "#{base_url}/#{resource.resourceType}/_history") + .to_return(status: 200, body: bundle.to_json) + end + + let(:stub_all_history_request) do + stub_request(:get, "#{base_url}/_history") + .to_return(status: 200, body: bundle.to_json) + end + + before do + stub_instance_history_request + stub_type_history_request + stub_all_history_request + end + + it 'performs an instance level history interaction' do + group.fhir_history(resource.resourceType, resource_id) + + expect(stub_instance_history_request).to have_been_made.once + end + + it 'performs an type history interaction' do + group.fhir_history(resource.resourceType) + + expect(stub_type_history_request).to have_been_made.once + end + + it 'performs a whole system history interaction' do + group.fhir_history + + expect(stub_all_history_request).to have_been_made.once + end + + it 'returns an Inferno::Entities::Request' do + result = group.fhir_history + + expect(result).to be_a(Inferno::Entities::Request) + end + + it 'adds the request to the list of requests' do + result = group.fhir_history + + expect(group.requests).to include(result) + expect(group.request).to eq(result) + end + + context 'with the client parameter' do + it 'uses that client' do + other_url = 'http://www.example.com/fhir/r4' + group.fhir_clients[:other_client] = FHIR::Client.new(other_url) + + other_request_stub = + stub_request(:get, "#{other_url}/_history") + .to_return(status: 200, body: bundle.to_json) + + group.fhir_history(client: :other_client) + + expect(other_request_stub).to have_been_made + expect(stub_all_history_request).to_not have_been_made + end + end + + context 'with a base url that causes a TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:history) + .and_raise(SocketError, 'Failed to open TCP') + end + + it 'raises a test failure exception' do + expect do + group.fhir_history + end.to raise_error(Inferno::Exceptions::AssertionException, 'Failed to open TCP') + end + end + + context 'with a base url that causes a non-TCP error' do + before do + allow_any_instance_of(FHIR::Client) + .to receive(:history) + .and_raise(SocketError, 'not a TCP error') + end + + it 'raises the error' do + expect do + group.fhir_history + end.to raise_error(SocketError, 'not a TCP error') + end + end + end + describe '#fhir_create' do let(:stub_create_request) do stub_request(:post, "#{base_url}/#{resource.resourceType}") @@ -454,23 +785,39 @@ def test_session_id stub_request(:get, "#{base_url}/#{resource.resourceType}?patient=123") .to_return(status: 200, body: bundle.to_json) end + let(:stub_get_search_all_request) do + stub_request(:get, "#{base_url}/?patient=123") + .to_return(status: 200, body: bundle.to_json) + end let(:stub_post_search_request) do stub_request(:post, "#{base_url}/#{resource.resourceType}/_search") .with(body: search_params) .to_return(status: 200, body: bundle.to_json) end + let(:stub_post_search_all_request) do + stub_request(:post, "#{base_url}/_search") + .with(body: search_params) + .to_return(status: 200, body: bundle.to_json) + end context 'when performing a GET search' do before do stub_get_search_request + stub_get_search_all_request end - it 'performs a FHIR search' do + it 'performs a FHIR type level search' do group.fhir_search(resource.resourceType, params: { patient: 123 }) expect(stub_get_search_request).to have_been_made.once end + it 'performs a FHIR whole system search' do + group.fhir_search(params: { patient: 123 }) + + expect(stub_get_search_all_request).to have_been_made.once + end + it 'returns an Inferno::Entities::Request' do result = group.fhir_search(resource.resourceType, params: { patient: 123 }) @@ -506,13 +853,20 @@ def test_session_id before do stub_post_search_request + stub_post_search_all_request end - it 'performs a FHIR search' do + it 'performs a FHIR type level search' do group.fhir_search(resource.resourceType, params: search_params, search_method: :post) expect(stub_post_search_request).to have_been_made.once end + + it 'performs a FHIR whole system search' do + group.fhir_search(params: search_params, search_method: :post) + + expect(stub_post_search_all_request).to have_been_made.once + end end context 'with oauth_credentials' do