From 4f27693f5364ffff9d79de3c7d75779fcde32c07 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Mon, 29 Dec 2025 14:54:24 +0100 Subject: [PATCH 01/10] Update retrieve to accept query params --- lib/chartmogul/api/actions/retrieve.rb | 9 +++- lib/chartmogul/data_source.rb | 5 -- spec/chartmogul/customer_invoices_spec.rb | 14 ++++++ spec/chartmogul/data_source_spec.rb | 61 +++++++++++++++++++++++ spec/chartmogul/invoice_spec.rb | 24 +++++++++ 5 files changed, 106 insertions(+), 7 deletions(-) diff --git a/lib/chartmogul/api/actions/retrieve.rb b/lib/chartmogul/api/actions/retrieve.rb index 7cfe7bb..8f0d82f 100644 --- a/lib/chartmogul/api/actions/retrieve.rb +++ b/lib/chartmogul/api/actions/retrieve.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'uri' + module ChartMogul module API module Actions @@ -10,12 +12,15 @@ def self.included(base) module ClassMethods def retrieve(uuid, options = {}) + path = "#{resource_path.apply(options)}/#{uuid}" + path_param_keys = resource_path.named_params.values + query_params = options.reject { |key| path_param_keys.include?(key) } + path = "#{path}?#{URI.encode_www_form(query_params)}" if query_params.any? resp = handling_errors do - connection.get("#{resource_path.apply(options)}/#{uuid}") do |req| + connection.get(path) do |req| req.headers['Content-Type'] = 'application/json' end end - json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: immutable_keys) new_from_json(json) end diff --git a/lib/chartmogul/data_source.rb b/lib/chartmogul/data_source.rb index 6ae71e3..0f47a2c 100644 --- a/lib/chartmogul/data_source.rb +++ b/lib/chartmogul/data_source.rb @@ -24,11 +24,6 @@ class DataSource < APIResource def self.all(options = {}) DataSources.all(options) end - - def self.retrieve(uuid, options = {}) - path = ChartMogul::ResourcePath.new('/v1/data_sources/:uuid') - custom!(:get, path.apply_with_get_params(options.merge(uuid: uuid))) - end end class DataSources < APIResource diff --git a/spec/chartmogul/customer_invoices_spec.rb b/spec/chartmogul/customer_invoices_spec.rb index e19dd3f..feac6e8 100644 --- a/spec/chartmogul/customer_invoices_spec.rb +++ b/spec/chartmogul/customer_invoices_spec.rb @@ -407,5 +407,19 @@ ) end end + + context 'with validation_type query parameter' do + let(:customer_uuid) { 'cus_23551596-2c7e-11ee-9ea1-2bfe193640c0' } + + it 'accepts validation_type=all query parameter', uses_api: false do + allow(ChartMogul::CustomerInvoices).to receive(:connection).and_return(double('connection')) + expect(ChartMogul::CustomerInvoices.connection).to receive(:get) do |path| + expect(path).to eq("/v1/import/customers/#{customer_uuid}/invoices?validation_type=all") + double('response', body: '{"invoices": [], "cursor": null, "has_more": false}') + end + + ChartMogul::CustomerInvoices.all(customer_uuid, validation_type: 'all') + end + end end end diff --git a/spec/chartmogul/data_source_spec.rb b/spec/chartmogul/data_source_spec.rb index 3704942..a667b7e 100644 --- a/spec/chartmogul/data_source_spec.rb +++ b/spec/chartmogul/data_source_spec.rb @@ -130,5 +130,66 @@ data_source = described_class.retrieve('ds_5ee8bf93-b0b4-4722-8a17-6b624a3af072') expect(data_source).to be end + + context 'with query parameters' do + it 'accepts with_processing_status query parameter', uses_api: false do + # Mock the connection to verify the URL includes the query parameter + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=true') + double('response', body: '{"uuid": "ds_123", "name": "Test"}') + end + + described_class.retrieve('ds_123', with_processing_status: true) + end + + it 'accepts with_auto_churn_subscription_setting query parameter', uses_api: false do + # Mock the connection to verify the URL includes the query parameter + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq('/v1/data_sources/ds_123?with_auto_churn_subscription_setting=true') + double('response', body: '{"uuid": "ds_123", "name": "Test"}') + end + + described_class.retrieve('ds_123', with_auto_churn_subscription_setting: true) + end + + it 'accepts with_invoice_handling_setting query parameter', uses_api: false do + # Mock the connection to verify the URL includes the query parameter + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq('/v1/data_sources/ds_123?with_invoice_handling_setting=true') + double('response', body: '{"uuid": "ds_123", "name": "Test"}') + end + + described_class.retrieve('ds_123', with_invoice_handling_setting: true) + end + + it 'accepts all three query parameters together', uses_api: false do + # Mock the connection to verify the URL includes all query parameters + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=true&with_auto_churn_subscription_setting=true&with_invoice_handling_setting=true') + double('response', body: '{"uuid": "ds_123", "name": "Test"}') + end + + described_class.retrieve('ds_123', + with_processing_status: true, + with_auto_churn_subscription_setting: true, + with_invoice_handling_setting: true + ) + end + + it 'accepts with_processing_status=false query parameter', uses_api: false do + # Mock the connection to verify the URL includes the query parameter + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=false') + double('response', body: '{"uuid": "ds_123", "name": "Test"}') + end + + described_class.retrieve('ds_123', with_processing_status: false) + end + end end end diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index 9e6af8f..69d6b5e 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -273,5 +273,29 @@ ) end end + + context 'with validation_type query parameter' do + let(:invoice_uuid) { 'inv_94d194de-04fa-4c81-8871-cc78af388eb3' } + + it 'accepts validation_type=all in list', uses_api: false do + allow(ChartMogul::Invoices).to receive(:connection).and_return(double('connection')) + expect(ChartMogul::Invoices.connection).to receive(:get) do |path| + expect(path).to eq('/v1/invoices?validation_type=all') + double('response', body: '{"invoices": [], "cursor": null, "has_more": false}') + end + + described_class.all(validation_type: 'all') + end + + it 'accepts validation_type=all in retrieve', uses_api: false do + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to eq("/v1/invoices/#{invoice_uuid}?validation_type=all") + double('response', body: "{\"uuid\": \"#{invoice_uuid}\", \"external_id\": \"test\", \"currency\": \"USD\"}") + end + + described_class.retrieve(invoice_uuid, validation_type: 'all') + end + end end end From 4db5f3a868a40a6ed29eaa8da4377026274024be Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Wed, 31 Dec 2025 10:51:15 +0100 Subject: [PATCH 02/10] Add Invoice#edit_history_summary --- lib/chartmogul/invoice.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/chartmogul/invoice.rb b/lib/chartmogul/invoice.rb index 3b488a8..62ba377 100644 --- a/lib/chartmogul/invoice.rb +++ b/lib/chartmogul/invoice.rb @@ -7,6 +7,7 @@ class Invoice < APIResource readonly_attr :uuid readonly_attr :customer_uuid + readonly_attr :edit_history_summary writeable_attr :date, type: :time writeable_attr :currency From de57552a5442626e94816ac6591c824e978ed118 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Wed, 31 Dec 2025 10:52:35 +0100 Subject: [PATCH 03/10] Add #disabled, #disabled_at and #disabled_by to Invoice --- lib/chartmogul/invoice.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/chartmogul/invoice.rb b/lib/chartmogul/invoice.rb index 62ba377..c0790f2 100644 --- a/lib/chartmogul/invoice.rb +++ b/lib/chartmogul/invoice.rb @@ -7,6 +7,9 @@ class Invoice < APIResource readonly_attr :uuid readonly_attr :customer_uuid + readonly_attr :disabled + readonly_attr :disabled_at + readonly_attr :disabled_by readonly_attr :edit_history_summary writeable_attr :date, type: :time From 5d570d2464a29a78518e18f8c0be78fd7c28ae40 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Wed, 31 Dec 2025 11:13:52 +0100 Subject: [PATCH 04/10] Add Invoice#errors --- lib/chartmogul/invoice.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/chartmogul/invoice.rb b/lib/chartmogul/invoice.rb index c0790f2..64db788 100644 --- a/lib/chartmogul/invoice.rb +++ b/lib/chartmogul/invoice.rb @@ -11,6 +11,7 @@ class Invoice < APIResource readonly_attr :disabled_at readonly_attr :disabled_by readonly_attr :edit_history_summary + readonly_attr :errors writeable_attr :date, type: :time writeable_attr :currency From 1ec7d96950a7a8a050471845290efb0795602556 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Wed, 31 Dec 2025 12:33:59 +0100 Subject: [PATCH 05/10] Update tests --- spec/chartmogul/invoice_spec.rb | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index 69d6b5e..e207bb3 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -7,6 +7,20 @@ { date: '2016-01-01 12:00:00', currency: 'USD', + disabled: false, + disabled_at: nil, + disabled_by: nil, + edit_history_summary: { + values_changed: { + amount_in_cents: { + original_value: 900, + edited_value: 1000 + } + }, + latest_edit_author: 'admin@example.com', + latest_edit_performed_at: '2024-01-10T12:00:00.000Z' + }, + errors: nil, line_items: [ { type: 'subscription', @@ -296,6 +310,70 @@ described_class.retrieve(invoice_uuid, validation_type: 'all') end + + it 'accepts all params in retrieve', uses_api: false do + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get) do |path| + expect(path).to include("/v1/invoices/#{invoice_uuid}") + expect(path).to include('validation_type=invalid') + expect(path).to include('include_edit_histories=true') + expect(path).to include('with_disabled=false') + double('response', body: <<-JSON + { + "uuid": "#{invoice_uuid}", + "external_id": "test", + "currency": "USD", + "disabled": true, + "disabled_at": "2024-01-15T10:30:00.000Z", + "disabled_by": "user@example.com", + "edit_history_summary": { + "values_changed": { + "currency": { + "original_value": "EUR", + "edited_value": "USD" + }, + "date": { + "original_value": "2024-01-01T00:00:00.000Z", + "edited_value": "2024-01-02T00:00:00.000Z" + } + }, + "latest_edit_author": "editor@example.com", + "latest_edit_performed_at": "2024-01-20T15:45:00.000Z" + }, + "errors": { + "currency": ["Currency is invalid", "Currency must be supported"], + "date": ["Date is in the future"] + } + } + JSON + ) + end + + invoice = described_class.retrieve( + invoice_uuid, + validation_type: 'invalid', + include_edit_histories: true, + with_disabled: false + ) + + expect(invoice.disabled).to eq(true) + expect(invoice.disabled_at).to eq('2024-01-15T10:30:00.000Z') + expect(invoice.disabled_by).to eq('user@example.com') + expect(invoice.edit_history_summary).to be_a(Hash) + expect(invoice.edit_history_summary[:values_changed]).to be_a(Hash) + expect(invoice.edit_history_summary[:values_changed][:currency][:original_value]).to eq('EUR') + expect(invoice.edit_history_summary[:values_changed][:currency][:edited_value]).to eq('USD') + expect(invoice.edit_history_summary[:latest_edit_author]).to eq('editor@example.com') + expect(invoice.edit_history_summary[:latest_edit_performed_at]).to eq('2024-01-20T15:45:00.000Z') + expect(invoice.errors).to be_a(Hash) + expect(invoice.errors[:currency]).to be_an(Array) + expect(invoice.errors[:currency].length).to eq(2) + expect(invoice.errors[:currency][0]).to eq('Currency is invalid') + expect(invoice.errors[:currency][1]).to eq('Currency must be supported') + expect(invoice.errors[:date]).to be_an(Array) + expect(invoice.errors[:date].length).to eq(1) + expect(invoice.errors[:date][0]).to eq('Date is in the future') + end end end end From 7359d0d5d7aff78d5b87aefa435bdeeaaf684338 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Tue, 6 Jan 2026 12:42:09 +0100 Subject: [PATCH 06/10] Refactor to use Faraday query params --- lib/chartmogul/api/actions/retrieve.rb | 4 +- spec/chartmogul/data_source_spec.rb | 70 +++++++++++++++++--------- spec/chartmogul/invoice_spec.rb | 25 ++++++--- 3 files changed, 65 insertions(+), 34 deletions(-) diff --git a/lib/chartmogul/api/actions/retrieve.rb b/lib/chartmogul/api/actions/retrieve.rb index 8f0d82f..31653f3 100644 --- a/lib/chartmogul/api/actions/retrieve.rb +++ b/lib/chartmogul/api/actions/retrieve.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'uri' - module ChartMogul module API module Actions @@ -15,10 +13,10 @@ def retrieve(uuid, options = {}) path = "#{resource_path.apply(options)}/#{uuid}" path_param_keys = resource_path.named_params.values query_params = options.reject { |key| path_param_keys.include?(key) } - path = "#{path}?#{URI.encode_www_form(query_params)}" if query_params.any? resp = handling_errors do connection.get(path) do |req| req.headers['Content-Type'] = 'application/json' + req.params = query_params end end json = ChartMogul::Utils::JSONParser.parse(resp.body, immutable_keys: immutable_keys) diff --git a/spec/chartmogul/data_source_spec.rb b/spec/chartmogul/data_source_spec.rb index a667b7e..fb88e0f 100644 --- a/spec/chartmogul/data_source_spec.rb +++ b/spec/chartmogul/data_source_spec.rb @@ -47,7 +47,7 @@ data_sources = ChartMogul::DataSource.all( with_processing_status: true, with_auto_churn_subscription_setting: true, - with_invoice_handling_setting: true, + with_invoice_handling_setting: true ) expect(data_sources.size).to eq(1) @@ -78,10 +78,9 @@ expect(data_source.invoice_handling_setting[:manual][:prevent_subscription_for_invoice_written_off]).to eq(true) data_source = ChartMogul::DataSource.retrieve(ds.uuid, - with_processing_status: true, - with_auto_churn_subscription_setting: true, - with_invoice_handling_setting: true, - ) + with_processing_status: true, + with_auto_churn_subscription_setting: true, + with_invoice_handling_setting: true) expect(data_source.uuid).to eq(ds.uuid) expect(data_source.name).to eq(ds.name) expect(data_source.created_at).to eq(ds.created_at) @@ -133,10 +132,14 @@ context 'with query parameters' do it 'accepts with_processing_status query parameter', uses_api: false do - # Mock the connection to verify the URL includes the query parameter allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=true') + expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(with_processing_status: true)) + block.call(req) double('response', body: '{"uuid": "ds_123", "name": "Test"}') end @@ -144,10 +147,14 @@ end it 'accepts with_auto_churn_subscription_setting query parameter', uses_api: false do - # Mock the connection to verify the URL includes the query parameter allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq('/v1/data_sources/ds_123?with_auto_churn_subscription_setting=true') + expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(with_auto_churn_subscription_setting: true)) + block.call(req) double('response', body: '{"uuid": "ds_123", "name": "Test"}') end @@ -155,10 +162,14 @@ end it 'accepts with_invoice_handling_setting query parameter', uses_api: false do - # Mock the connection to verify the URL includes the query parameter allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq('/v1/data_sources/ds_123?with_invoice_handling_setting=true') + expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(with_invoice_handling_setting: true)) + block.call(req) double('response', body: '{"uuid": "ds_123", "name": "Test"}') end @@ -166,25 +177,36 @@ end it 'accepts all three query parameters together', uses_api: false do - # Mock the connection to verify the URL includes all query parameters allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=true&with_auto_churn_subscription_setting=true&with_invoice_handling_setting=true') + expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including( + with_processing_status: true, + with_auto_churn_subscription_setting: true, + with_invoice_handling_setting: true + )) + block.call(req) double('response', body: '{"uuid": "ds_123", "name": "Test"}') end described_class.retrieve('ds_123', - with_processing_status: true, - with_auto_churn_subscription_setting: true, - with_invoice_handling_setting: true - ) + with_processing_status: true, + with_auto_churn_subscription_setting: true, + with_invoice_handling_setting: true) end it 'accepts with_processing_status=false query parameter', uses_api: false do - # Mock the connection to verify the URL includes the query parameter allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq('/v1/data_sources/ds_123?with_processing_status=false') + expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(with_processing_status: false)) + block.call(req) double('response', body: '{"uuid": "ds_123", "name": "Test"}') end diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index e207bb3..51525d4 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -303,8 +303,13 @@ it 'accepts validation_type=all in retrieve', uses_api: false do allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to eq("/v1/invoices/#{invoice_uuid}?validation_type=all") + expect(described_class.connection).to receive(:get).with("/v1/invoices/#{invoice_uuid}") do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(validation_type: 'all')) + block.call(req) double('response', body: "{\"uuid\": \"#{invoice_uuid}\", \"external_id\": \"test\", \"currency\": \"USD\"}") end @@ -313,11 +318,17 @@ it 'accepts all params in retrieve', uses_api: false do allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get) do |path| - expect(path).to include("/v1/invoices/#{invoice_uuid}") - expect(path).to include('validation_type=invalid') - expect(path).to include('include_edit_histories=true') - expect(path).to include('with_disabled=false') + expect(described_class.connection).to receive(:get).with("/v1/invoices/#{invoice_uuid}") do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including( + validation_type: 'invalid', + include_edit_histories: true, + with_disabled: false + )) + block.call(req) double('response', body: <<-JSON { "uuid": "#{invoice_uuid}", From 7e8c54c52be01e80987eadb01d003dbe2498aeed Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Tue, 6 Jan 2026 12:54:37 +0100 Subject: [PATCH 07/10] Refactor to shared example --- spec/chartmogul/data_source_spec.rb | 102 ++++-------------- spec/chartmogul/invoice_spec.rb | 17 +-- spec/spec_helper.rb | 1 + ...red_examples_retrieve_with_query_params.rb | 40 +++++++ 4 files changed, 65 insertions(+), 95 deletions(-) create mode 100644 spec/support/shared_examples_retrieve_with_query_params.rb diff --git a/spec/chartmogul/data_source_spec.rb b/spec/chartmogul/data_source_spec.rb index fb88e0f..674efcc 100644 --- a/spec/chartmogul/data_source_spec.rb +++ b/spec/chartmogul/data_source_spec.rb @@ -131,87 +131,27 @@ end context 'with query parameters' do - it 'accepts with_processing_status query parameter', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including(with_processing_status: true)) - block.call(req) - double('response', body: '{"uuid": "ds_123", "name": "Test"}') - end - - described_class.retrieve('ds_123', with_processing_status: true) - end - - it 'accepts with_auto_churn_subscription_setting query parameter', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including(with_auto_churn_subscription_setting: true)) - block.call(req) - double('response', body: '{"uuid": "ds_123", "name": "Test"}') - end - - described_class.retrieve('ds_123', with_auto_churn_subscription_setting: true) - end - - it 'accepts with_invoice_handling_setting query parameter', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including(with_invoice_handling_setting: true)) - block.call(req) - double('response', body: '{"uuid": "ds_123", "name": "Test"}') - end - - described_class.retrieve('ds_123', with_invoice_handling_setting: true) - end - - it 'accepts all three query parameters together', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including( - with_processing_status: true, - with_auto_churn_subscription_setting: true, - with_invoice_handling_setting: true - )) - block.call(req) - double('response', body: '{"uuid": "ds_123", "name": "Test"}') - end - - described_class.retrieve('ds_123', - with_processing_status: true, - with_auto_churn_subscription_setting: true, - with_invoice_handling_setting: true) - end - - it 'accepts with_processing_status=false query parameter', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with('/v1/data_sources/ds_123') do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including(with_processing_status: false)) - block.call(req) - double('response', body: '{"uuid": "ds_123", "name": "Test"}') - end - - described_class.retrieve('ds_123', with_processing_status: false) - end + it_behaves_like 'retrieve with query params', 'ds_123', + { with_processing_status: true }, + '{"uuid": "ds_123", "name": "Test"}' + + it_behaves_like 'retrieve with query params', 'ds_123', + { with_auto_churn_subscription_setting: true }, + '{"uuid": "ds_123", "name": "Test"}' + + it_behaves_like 'retrieve with query params', 'ds_123', + { with_invoice_handling_setting: true }, + '{"uuid": "ds_123", "name": "Test"}' + + it_behaves_like 'retrieve with query params', 'ds_123', + { with_processing_status: true, + with_auto_churn_subscription_setting: true, + with_invoice_handling_setting: true }, + '{"uuid": "ds_123", "name": "Test"}' + + it_behaves_like 'retrieve with query params', 'ds_123', + { with_processing_status: false }, + '{"uuid": "ds_123", "name": "Test"}' end end end diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index 51525d4..52a520a 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -301,20 +301,9 @@ described_class.all(validation_type: 'all') end - it 'accepts validation_type=all in retrieve', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with("/v1/invoices/#{invoice_uuid}") do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including(validation_type: 'all')) - block.call(req) - double('response', body: "{\"uuid\": \"#{invoice_uuid}\", \"external_id\": \"test\", \"currency\": \"USD\"}") - end - - described_class.retrieve(invoice_uuid, validation_type: 'all') - end + it_behaves_like 'retrieve with query params', 'inv_94d194de-04fa-4c81-8871-cc78af388eb3', + { validation_type: 'all' }, + "{\"uuid\": \"inv_94d194de-04fa-4c81-8871-cc78af388eb3\", \"external_id\": \"test\", \"currency\": \"USD\"}" it 'accepts all params in retrieve', uses_api: false do allow(described_class).to receive(:connection).and_return(double('connection')) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a05b504..b3f42c8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,6 +12,7 @@ require 'vcr' require 'webmock/rspec' require_relative 'support/shared_example_raises_deprecated_param_error' +require_relative 'support/shared_examples_retrieve_with_query_params' VCR.configure do |config| config.cassette_library_dir = 'spec/fixtures/vcr_cassettes' diff --git a/spec/support/shared_examples_retrieve_with_query_params.rb b/spec/support/shared_examples_retrieve_with_query_params.rb new file mode 100644 index 0000000..1275a00 --- /dev/null +++ b/spec/support/shared_examples_retrieve_with_query_params.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# RSpec.shared_examples 'accepts query param' do |method_name, path, query_params, response_body, *args| +# it "accepts #{query_params.keys.join(', ')} query parameter(s)", uses_api: false do +# allow(described_class).to receive(:connection).and_return(double('connection')) +# expect(described_class.connection).to receive(method_name).with(path) do |&block| +# req = double('request') +# headers = {} +# allow(req).to receive(:headers).and_return(headers) +# allow(req).to receive(:[]=) { |k, v| headers[k] = v } +# expect(req).to receive(:params=).with(hash_including(query_params)) +# block.call(req) +# double('response', body: response_body) +# end + +# described_class.public_send(*args) +# end +# end + +RSpec.shared_examples 'retrieve with query params' do |resource_id, query_params, response_body_mock| + it "accepts #{query_params.keys.join(', ')} query parameter(s)", uses_api: false do + # Evaluate lambdas/procs for dynamic values - this happens inside the it block where let bindings are available + # id = resource_id.respond_to?(:call) ? instance_exec(&resource_id) : resource_id + # body = response_body_mock.respond_to?(:call) ? instance_exec(&response_body_mock) : response_body_mock + + path = "#{described_class.resource_path.path}/#{resource_id}" + allow(described_class).to receive(:connection).and_return(double('connection')) + expect(described_class.connection).to receive(:get).with(path) do |&block| + req = double('request') + headers = {} + allow(req).to receive(:headers).and_return(headers) + allow(req).to receive(:[]=) { |k, v| headers[k] = v } + expect(req).to receive(:params=).with(hash_including(query_params)) + block.call(req) + double('response', body: response_body_mock) + end + + described_class.retrieve(resource_id, query_params) + end +end From b8dfe0f1d21d9ad74b9e52bdee2bc082894a8174 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Tue, 6 Jan 2026 12:55:42 +0100 Subject: [PATCH 08/10] Fix rubocop violations --- spec/chartmogul/invoice_spec.rb | 2 +- spec/spec_helper.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index 52a520a..cb7e3b9 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -303,7 +303,7 @@ it_behaves_like 'retrieve with query params', 'inv_94d194de-04fa-4c81-8871-cc78af388eb3', { validation_type: 'all' }, - "{\"uuid\": \"inv_94d194de-04fa-4c81-8871-cc78af388eb3\", \"external_id\": \"test\", \"currency\": \"USD\"}" + '{"uuid": "inv_94d194de-04fa-4c81-8871-cc78af388eb3", "external_id": "test", "currency": "USD"}' it 'accepts all params in retrieve', uses_api: false do allow(described_class).to receive(:connection).and_return(double('connection')) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b3f42c8..7fc0079 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,8 +29,6 @@ config.before(:each) do |example| Thread.current[ChartMogul::CONFIG_THREAD_KEY] = nil - if example.metadata[:uses_api] - ChartMogul.api_key = ENV['TEST_API_KEY'] || 'dummy-token' - end + ChartMogul.api_key = ENV['TEST_API_KEY'] || 'dummy-token' if example.metadata[:uses_api] end end From 35a1e9de1051fc355928f7e56b53a3fb19f479eb Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Tue, 6 Jan 2026 13:09:38 +0100 Subject: [PATCH 09/10] Refactor new shared example to accept extra assertions --- spec/chartmogul/invoice_spec.rb | 120 +++++++----------- ...red_examples_retrieve_with_query_params.rb | 31 +---- 2 files changed, 53 insertions(+), 98 deletions(-) diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index cb7e3b9..3c88595 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -289,8 +289,6 @@ end context 'with validation_type query parameter' do - let(:invoice_uuid) { 'inv_94d194de-04fa-4c81-8871-cc78af388eb3' } - it 'accepts validation_type=all in list', uses_api: false do allow(ChartMogul::Invoices).to receive(:connection).and_return(double('connection')) expect(ChartMogul::Invoices.connection).to receive(:get) do |path| @@ -303,77 +301,53 @@ it_behaves_like 'retrieve with query params', 'inv_94d194de-04fa-4c81-8871-cc78af388eb3', { validation_type: 'all' }, - '{"uuid": "inv_94d194de-04fa-4c81-8871-cc78af388eb3", "external_id": "test", "currency": "USD"}' - - it 'accepts all params in retrieve', uses_api: false do - allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with("/v1/invoices/#{invoice_uuid}") do |&block| - req = double('request') - headers = {} - allow(req).to receive(:headers).and_return(headers) - allow(req).to receive(:[]=) { |k, v| headers[k] = v } - expect(req).to receive(:params=).with(hash_including( - validation_type: 'invalid', - include_edit_histories: true, - with_disabled: false - )) - block.call(req) - double('response', body: <<-JSON - { - "uuid": "#{invoice_uuid}", - "external_id": "test", - "currency": "USD", - "disabled": true, - "disabled_at": "2024-01-15T10:30:00.000Z", - "disabled_by": "user@example.com", - "edit_history_summary": { - "values_changed": { - "currency": { - "original_value": "EUR", - "edited_value": "USD" - }, - "date": { - "original_value": "2024-01-01T00:00:00.000Z", - "edited_value": "2024-01-02T00:00:00.000Z" - } - }, - "latest_edit_author": "editor@example.com", - "latest_edit_performed_at": "2024-01-20T15:45:00.000Z" - }, - "errors": { - "currency": ["Currency is invalid", "Currency must be supported"], - "date": ["Date is in the future"] - } - } - JSON - ) - end - - invoice = described_class.retrieve( - invoice_uuid, - validation_type: 'invalid', - include_edit_histories: true, - with_disabled: false - ) - - expect(invoice.disabled).to eq(true) - expect(invoice.disabled_at).to eq('2024-01-15T10:30:00.000Z') - expect(invoice.disabled_by).to eq('user@example.com') - expect(invoice.edit_history_summary).to be_a(Hash) - expect(invoice.edit_history_summary[:values_changed]).to be_a(Hash) - expect(invoice.edit_history_summary[:values_changed][:currency][:original_value]).to eq('EUR') - expect(invoice.edit_history_summary[:values_changed][:currency][:edited_value]).to eq('USD') - expect(invoice.edit_history_summary[:latest_edit_author]).to eq('editor@example.com') - expect(invoice.edit_history_summary[:latest_edit_performed_at]).to eq('2024-01-20T15:45:00.000Z') - expect(invoice.errors).to be_a(Hash) - expect(invoice.errors[:currency]).to be_an(Array) - expect(invoice.errors[:currency].length).to eq(2) - expect(invoice.errors[:currency][0]).to eq('Currency is invalid') - expect(invoice.errors[:currency][1]).to eq('Currency must be supported') - expect(invoice.errors[:date]).to be_an(Array) - expect(invoice.errors[:date].length).to eq(1) - expect(invoice.errors[:date][0]).to eq('Date is in the future') - end + <<-JSON, + { + "uuid": "inv_94d194de-04fa-4c81-8871-cc78af388eb3", + "external_id": "test", + "currency": "USD", + "disabled": true, + "disabled_at": "2024-01-15T10:30:00.000Z", + "disabled_by": "user@example.com", + "edit_history_summary": { + "values_changed": { + "currency": { + "original_value": "EUR", + "edited_value": "USD" + }, + "date": { + "original_value": "2024-01-01T00:00:00.000Z", + "edited_value": "2024-01-02T00:00:00.000Z" + } + }, + "latest_edit_author": "editor@example.com", + "latest_edit_performed_at": "2024-01-20T15:45:00.000Z" + }, + "errors": { + "currency": ["Currency is invalid", "Currency must be supported"], + "date": ["Date is in the future"] + } + } + JSON + lambda { |invoice| + expect(invoice.disabled).to eq(true) + expect(invoice.disabled_at).to eq('2024-01-15T10:30:00.000Z') + expect(invoice.disabled_by).to eq('user@example.com') + expect(invoice.edit_history_summary).to be_a(Hash) + expect(invoice.edit_history_summary[:values_changed]).to be_a(Hash) + expect(invoice.edit_history_summary[:values_changed][:currency][:original_value]).to eq('EUR') + expect(invoice.edit_history_summary[:values_changed][:currency][:edited_value]).to eq('USD') + expect(invoice.edit_history_summary[:latest_edit_author]).to eq('editor@example.com') + expect(invoice.edit_history_summary[:latest_edit_performed_at]).to eq('2024-01-20T15:45:00.000Z') + expect(invoice.errors).to be_a(Hash) + expect(invoice.errors[:currency]).to be_an(Array) + expect(invoice.errors[:currency].length).to eq(2) + expect(invoice.errors[:currency][0]).to eq('Currency is invalid') + expect(invoice.errors[:currency][1]).to eq('Currency must be supported') + expect(invoice.errors[:date]).to be_an(Array) + expect(invoice.errors[:date].length).to eq(1) + expect(invoice.errors[:date][0]).to eq('Date is in the future') + } end end end diff --git a/spec/support/shared_examples_retrieve_with_query_params.rb b/spec/support/shared_examples_retrieve_with_query_params.rb index 1275a00..5c69f3e 100644 --- a/spec/support/shared_examples_retrieve_with_query_params.rb +++ b/spec/support/shared_examples_retrieve_with_query_params.rb @@ -1,40 +1,21 @@ # frozen_string_literal: true -# RSpec.shared_examples 'accepts query param' do |method_name, path, query_params, response_body, *args| -# it "accepts #{query_params.keys.join(', ')} query parameter(s)", uses_api: false do -# allow(described_class).to receive(:connection).and_return(double('connection')) -# expect(described_class.connection).to receive(method_name).with(path) do |&block| -# req = double('request') -# headers = {} -# allow(req).to receive(:headers).and_return(headers) -# allow(req).to receive(:[]=) { |k, v| headers[k] = v } -# expect(req).to receive(:params=).with(hash_including(query_params)) -# block.call(req) -# double('response', body: response_body) -# end - -# described_class.public_send(*args) -# end -# end - -RSpec.shared_examples 'retrieve with query params' do |resource_id, query_params, response_body_mock| +RSpec.shared_examples 'retrieve with query params' do |resource_id, query_params, response_body_mock, assertions = nil| it "accepts #{query_params.keys.join(', ')} query parameter(s)", uses_api: false do - # Evaluate lambdas/procs for dynamic values - this happens inside the it block where let bindings are available - # id = resource_id.respond_to?(:call) ? instance_exec(&resource_id) : resource_id - # body = response_body_mock.respond_to?(:call) ? instance_exec(&response_body_mock) : response_body_mock - path = "#{described_class.resource_path.path}/#{resource_id}" allow(described_class).to receive(:connection).and_return(double('connection')) - expect(described_class.connection).to receive(:get).with(path) do |&block| + expect(described_class.connection).to receive(:get).with(path) do |&req_block| req = double('request') headers = {} allow(req).to receive(:headers).and_return(headers) allow(req).to receive(:[]=) { |k, v| headers[k] = v } expect(req).to receive(:params=).with(hash_including(query_params)) - block.call(req) + + req_block.call(req) double('response', body: response_body_mock) end - described_class.retrieve(resource_id, query_params) + retrieved_resource = described_class.retrieve(resource_id, query_params) + instance_exec(retrieved_resource, &assertions) if assertions end end From 39bc53f52b7831c1077c2b344883b5f686f24695 Mon Sep 17 00:00:00 2001 From: Wiktor Plaga Date: Tue, 6 Jan 2026 13:12:36 +0100 Subject: [PATCH 10/10] Update the test description and add all accepted query params --- spec/chartmogul/invoice_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/chartmogul/invoice_spec.rb b/spec/chartmogul/invoice_spec.rb index 3c88595..d925afb 100644 --- a/spec/chartmogul/invoice_spec.rb +++ b/spec/chartmogul/invoice_spec.rb @@ -288,7 +288,7 @@ end end - context 'with validation_type query parameter' do + context 'with query params' do it 'accepts validation_type=all in list', uses_api: false do allow(ChartMogul::Invoices).to receive(:connection).and_return(double('connection')) expect(ChartMogul::Invoices.connection).to receive(:get) do |path| @@ -300,7 +300,7 @@ end it_behaves_like 'retrieve with query params', 'inv_94d194de-04fa-4c81-8871-cc78af388eb3', - { validation_type: 'all' }, + { validation_type: 'all', include_edit_history: true, with_disabled: true }, <<-JSON, { "uuid": "inv_94d194de-04fa-4c81-8871-cc78af388eb3",