Shopify / shopify-api-ruby

ShopifyAPI is a lightweight gem for accessing the Shopify admin REST and GraphQL web services.
MIT License
1.06k stars 473 forks source link

Bug in error handling in Shopify::Rest::Base#save #1202

Open martinsp opened 1 year ago

martinsp commented 1 year ago

Issue summary

Since v13.0.0 ShopifyAPI::DiscountCode#errors signature has been changed to Hash, and #errors is initialized as ~Hash~ nil for DiscountCode resources.

This causes exception in error handling in ShopifyAPI::Rest::Base#save if ShopifyAPI::Errors::HttpResponseError is raised during save operation for DiscountCode resource. Exception is raised because ~Hash~ nil does not implement #errors

Expected behavior

Rescuing from ShopifyAPI::Errors::HttpResponseError does not raise exception

Actual behavior

~#<NoMethodError: undefined method `errors' for {}:Hash> is raised~

<NoMethodError: undefined method `errors' for nil:NilClass> is raised

Edit: corrected details

martinsp commented 1 year ago

Please find the test case and test results below

test_case.rb, run with: ruby test_case.rb ```ruby # frozen_string_literal: true require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "minitest" gem "shopify_api", "~> 13.1.0" gem "webmock", "3.14" end require "minitest/autorun" require "webmock/minitest" require "shopify_api" class DiscountCode202204Test < Minitest::Test def setup ShopifyAPI::Context.setup( api_key: "API_KEY", api_secret_key: "API_SECRET_KEY", api_version: "2023-07", host: "https://app-address.com", scope: ["scope1", "scope2"], is_private: false, is_embedded: false, logger: ::Logger.new(T.let(StringIO.new, StringIO)), # comment line to see logging on stdout user_agent_prefix: nil, old_api_secret_key: nil, log_level: :off, ) test_session = ShopifyAPI::Auth::Session.new(id: "id", shop: "test-shop.myshopify.io", access_token: "this_is_a_test_token") ShopifyAPI::Context.activate_session(test_session) end def teardown ShopifyAPI::Context.deactivate_session end def test_exception_handling stub_request(:put, "https://test-shop.myshopify.io/admin/api/2023-07/price_rules/507328175/discount_codes/507328175.json") .with( headers: {"X-Shopify-Access-Token"=>"this_is_a_test_token", "Accept"=>"application/json", "Content-Type"=>"application/json"}, body: { "discount_code" => hash_including({"code" => "WINTERSALE20OFF"}) } ) .to_return(status: 404, body: { errors: "Not Found" }.to_json, headers: {}) discount_code = ShopifyAPI::DiscountCode.new discount_code.price_rule_id = 507328175 discount_code.id = 507328175 discount_code.code = "WINTERSALE20OFF" discount_code.save end end ```
Test output ``` ➜ $ ruby test_case.rb Fetching gem metadata from https://rubygems.org/........... Resolving dependencies... Run options: --seed 1435 # Running: E Finished in 0.028904s, 34.5973 runs/s, 0.0000 assertions/s. 1) Error: DiscountCode202204Test#test_exception_handling: NoMethodError: undefined method `errors' for nil:NilClass ~/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/shopify_api-13.1.0/lib/shopify_api/rest/base.rb:356:in `rescue in save' ~/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/shopify_api-13.1.0/lib/shopify_api/rest/base.rb:341:in `save' ~/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.10984/lib/types/private/methods/call_validation.rb:256:in `bind_call' ~/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.10984/lib/types/private/methods/call_validation.rb:256:in `validate_call' ~/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.10984/lib/types/private/methods/_methods.rb:275:in `block in _on_method_added' test_case.rb:57:in `test_exception_handling' 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips ```