From 68e311c76f103628179f62277a0458b12b2a59bb Mon Sep 17 00:00:00 2001 From: James Lamont <958588+jylamont@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:38:47 -0500 Subject: [PATCH] bug: Fixes Sidekiq Worker issue w/ strict args --- lib/vero/senders/sidekiq.rb | 31 +++++++++++ spec/lib/senders/sidekiq_spec.rb | 88 ++++++++++++++++++++++++++++---- spec/spec_helper.rb | 1 + 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/lib/vero/senders/sidekiq.rb b/lib/vero/senders/sidekiq.rb index dcdacb9..4787676 100644 --- a/lib/vero/senders/sidekiq.rb +++ b/lib/vero/senders/sidekiq.rb @@ -1,9 +1,40 @@ class Vero::Senders::Sidekiq < Vero::Senders::Base def enqueue_work(api_class, domain, options) + options = deep_transform(options) ::Vero::SidekiqWorker.perform_async(api_class.to_s, domain, options) end def log_message "sidekiq job queued" end + + private + + # Sidekiq workers args must safely serialize to JSON. Therefore all symbols must be + # converted to strings. + # A future version of the gem should standardize on string keys throughout. + def deep_transform(hash) + hash.each_with_object({}) do |(key, value), new_hash| + key = transform_value(key) + new_hash[key] = transform_value(value) + end + rescue + # If there is an exception in this method, fallback to naive transform. + JSON.parse hash.to_json + end + + def transform_value(val) + case val + when Hash + deep_transform(val) + when Symbol + val.to_s + when Array, Set + val.map { transform_value(_1) } + when Time, Date, DateTime + val.iso8601 + else + val + end + end end diff --git a/spec/lib/senders/sidekiq_spec.rb b/spec/lib/senders/sidekiq_spec.rb index 2a0568b..6d3ba95 100644 --- a/spec/lib/senders/sidekiq_spec.rb +++ b/spec/lib/senders/sidekiq_spec.rb @@ -4,29 +4,97 @@ describe Vero::Senders::Sidekiq do subject { Vero::Senders::Sidekiq.new } - describe :call do + + let(:domain) { "https://api.getvero.com" } + let(:payload) { {event_name: "test"} } + + describe "call" do it "should perform_async a Vero::SidekiqWorker" do expect(Vero::SidekiqWorker).to( receive(:perform_async) - .with("Vero::Api::Workers::Events::TrackAPI", "abc", {test: "abc"}) + .with("Vero::Api::Workers::Events::TrackAPI", domain, payload.transform_keys(&:to_s)) + .and_call_original .once ) - subject.call(Vero::Api::Workers::Events::TrackAPI, "abc", {test: "abc"}) + + subject.call(Vero::Api::Workers::Events::TrackAPI, domain, payload) + end + + context "transforming options" do + it "transforms the payload to be JSON-safe" do + payload[:data] = { + items: [{item: :sku_1234, price: 50}, {item: :sku_5678, price: 50}], + total: 100.00 + } + + expected_payload = { + "event_name" => "test", + "data" => { + "items" => [ + {"item" => "sku_1234", "price" => 50}, + {"item" => "sku_5678", "price" => 50} + ], + "total" => 100.00 + } + } + + expect(Vero::SidekiqWorker).to receive(:perform_async) + .with("Vero::Api::Workers::Events::TrackAPI", domain, expected_payload) + .and_call_original + .once + + subject.enqueue_work(Vero::Api::Workers::Events::TrackAPI, domain, payload) + end + + it "handles times, sets, nested symbols" do + timestamp = Time.utc(2025, 1, 1, 12, 0, 0) + custom_payload = { + event_name: :test_event, + occurred_at: timestamp, + metadata: { + tags: Set.new([:premium, :new_user]), + nested: {level: :deep} + }, + some_array: [:alpha, :beta, 123] + } + + # Expected structure after transformation: + expected_payload = { + "event_name" => "test_event", + "occurred_at" => timestamp.iso8601, # "2025-01-01T12:00:00Z" + "metadata" => { + "tags" => ["premium", "new_user"], + "nested" => {"level" => "deep"} + }, + "some_array" => ["alpha", "beta", 123] + } + + expect(Vero::SidekiqWorker).to receive(:perform_async).with( + "Vero::Api::Workers::Events::TrackAPI", + domain, + expected_payload + ).and_call_original.once + + subject.enqueue_work(Vero::Api::Workers::Events::TrackAPI, domain, custom_payload) + end end end end describe Vero::SidekiqWorker do + let(:domain) { "https://api.getvero.com" } + let(:payload) { {"event_name" => "test"} } + subject { Vero::SidekiqWorker.new } - describe :perform do + describe "perform" do it "should call the api method" do - mock_api = double(Vero::Api::Workers::Events::TrackAPI) - expect(mock_api).to receive(:perform).once - - allow(Vero::Api::Workers::Events::TrackAPI).to receive(:new).and_return(mock_api) - expect(Vero::Api::Workers::Events::TrackAPI).to receive(:new).with("abc", {test: "abc"}).once + expect(Vero::Api::Workers::Events::TrackAPI).to( + receive(:perform) + .with(domain, payload) + .once + ) - subject.perform("Vero::Api::Workers::Events::TrackAPI", "abc", {test: "abc"}) + subject.perform("Vero::Api::Workers::Events::TrackAPI", domain, payload) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c193f16..dcfa4eb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ require "byebug" require "rspec" require "webmock/rspec" +require "sidekiq/testing" require "vero"