diff --git a/CHANGELOG.md b/CHANGELOG.md index a454d327f..eca842ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api ## Unreleased +- [#1344](https://github.com/Shopify/shopify-api-ruby/pull/1344) Allow ShopifyAPI::Webhooks::Registry to update a webhook when fields or metafield_namespaces are changed. - [#1343](https://github.com/Shopify/shopify-api-ruby/pull/1343) Make ShopifyAPI::Context::scope parameter optional. `scope` defaults to empty list `[]`. - [#1348](https://github.com/Shopify/shopify-api-ruby/pull/1348) Add config option that will disable the REST API client and REST resources. New apps should use the GraphQL Admin API diff --git a/lib/shopify_api/webhooks/registration.rb b/lib/shopify_api/webhooks/registration.rb index eb786b70e..8cd18d543 100644 --- a/lib/shopify_api/webhooks/registration.rb +++ b/lib/shopify_api/webhooks/registration.rb @@ -48,7 +48,14 @@ def mutation_name(webhook_id); end sig { abstract.returns(String) } def build_check_query; end - sig { abstract.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) } + sig do + abstract.params(body: T::Hash[String, T.untyped]).returns({ + webhook_id: T.nilable(String), + current_address: T.nilable(String), + fields: T::Array[String], + metafield_namespaces: T::Array[String], + }) + end def parse_check_result(body); end sig { params(webhook_id: T.nilable(String)).returns(String) } diff --git a/lib/shopify_api/webhooks/registrations/event_bridge.rb b/lib/shopify_api/webhooks/registrations/event_bridge.rb index 51076861b..8a3653f4b 100644 --- a/lib/shopify_api/webhooks/registrations/event_bridge.rb +++ b/lib/shopify_api/webhooks/registrations/event_bridge.rb @@ -30,6 +30,8 @@ def build_check_query edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookEventBridgeEndpoint { @@ -43,17 +45,29 @@ def build_check_query QUERY end - sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) } + sig do + override.params(body: T::Hash[String, T.untyped]).returns({ + webhook_id: T.nilable(String), + current_address: T.nilable(String), + fields: T::Array[String], + metafield_namespaces: T::Array[String], + }) + end def parse_check_result(body) edges = body.dig("data", "webhookSubscriptions", "edges") || {} webhook_id = nil + fields = [] + metafield_namespaces = [] current_address = nil unless edges.empty? node = edges[0]["node"] webhook_id = node["id"].to_s current_address = node["endpoint"]["arn"].to_s + fields = node["includeFields"] || [] + metafield_namespaces = node["metafieldNamespaces"] || [] end - { webhook_id: webhook_id, current_address: current_address } + { webhook_id: webhook_id, current_address: current_address, fields: fields, + metafield_namespaces: metafield_namespaces, } end end end diff --git a/lib/shopify_api/webhooks/registrations/http.rb b/lib/shopify_api/webhooks/registrations/http.rb index a8446ac20..a3c0cf48a 100644 --- a/lib/shopify_api/webhooks/registrations/http.rb +++ b/lib/shopify_api/webhooks/registrations/http.rb @@ -36,6 +36,8 @@ def build_check_query edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookHttpEndpoint { @@ -49,10 +51,19 @@ def build_check_query QUERY end - sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) } + sig do + override.params(body: T::Hash[String, T.untyped]).returns({ + webhook_id: T.nilable(String), + current_address: T.nilable(String), + fields: T::Array[String], + metafield_namespaces: T::Array[String], + }) + end def parse_check_result(body) edges = body.dig("data", "webhookSubscriptions", "edges") || {} webhook_id = nil + fields = [] + metafield_namespaces = [] current_address = nil unless edges.empty? node = edges[0]["node"] @@ -63,8 +74,11 @@ def parse_check_result(body) else node["callbackUrl"].to_s end + fields = node["includeFields"] || [] + metafield_namespaces = node["metafieldNamespaces"] || [] end - { webhook_id: webhook_id, current_address: current_address } + { webhook_id: webhook_id, current_address: current_address, fields: fields, + metafield_namespaces: metafield_namespaces, } end end end diff --git a/lib/shopify_api/webhooks/registrations/pub_sub.rb b/lib/shopify_api/webhooks/registrations/pub_sub.rb index c1a229341..183ba890b 100644 --- a/lib/shopify_api/webhooks/registrations/pub_sub.rb +++ b/lib/shopify_api/webhooks/registrations/pub_sub.rb @@ -34,6 +34,8 @@ def build_check_query edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookPubSubEndpoint { @@ -48,17 +50,30 @@ def build_check_query QUERY end - sig { override.params(body: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, String]) } + sig do + override.params(body: T::Hash[String, T.untyped]).returns({ + webhook_id: T.nilable(String), + current_address: T.nilable(String), + fields: T::Array[String], + metafield_namespaces: T::Array[String], + }) + end def parse_check_result(body) edges = body.dig("data", "webhookSubscriptions", "edges") || {} webhook_id = nil + fields = [] + metafield_namespaces = [] current_address = nil unless edges.empty? node = edges[0]["node"] webhook_id = node["id"].to_s - current_address = "pubsub://#{node["endpoint"]["pubSubProject"]}:#{node["endpoint"]["pubSubTopic"]}" + current_address = + "pubsub://#{node["endpoint"]["pubSubProject"]}:#{node["endpoint"]["pubSubTopic"]}" + fields = node["includeFields"] || [] + metafield_namespaces = node["metafieldNamespaces"] || [] end - { webhook_id: webhook_id, current_address: current_address } + { webhook_id: webhook_id, current_address: current_address, fields: fields, + metafield_namespaces: metafield_namespaces, } end end end diff --git a/lib/shopify_api/webhooks/registry.rb b/lib/shopify_api/webhooks/registry.rb index 4ffa6640d..d6abb8d44 100644 --- a/lib/shopify_api/webhooks/registry.rb +++ b/lib/shopify_api/webhooks/registry.rb @@ -219,8 +219,14 @@ def webhook_registration_needed?(client, registration) check_response = client.query(query: registration.build_check_query, response_as_struct: false) raise Errors::WebhookRegistrationError, "Failed to check if webhook was already registered" unless check_response.ok? + parsed_check_result = registration.parse_check_result(T.cast(check_response.body, T::Hash[String, T.untyped])) - must_register = parsed_check_result[:current_address] != registration.callback_address + registration_fields = registration.fields || [] + registration_metafield_namespaces = registration.metafield_namespaces || [] + + must_register = parsed_check_result[:current_address] != registration.callback_address || + parsed_check_result[:fields].sort != registration_fields.sort || + parsed_check_result[:metafield_namespaces].sort != registration_metafield_namespaces.sort { webhook_id: parsed_check_result[:webhook_id], must_register: must_register } end diff --git a/test/webhooks/registry_test.rb b/test/webhooks/registry_test.rb index 6b855a16f..6a852a9d6 100644 --- a/test/webhooks/registry_test.rb +++ b/test/webhooks/registry_test.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true require_relative "../test_helper.rb" -require_relative "webhook_registration_queries.rb" +require_relative "registry_test_queries.rb" module ShopifyAPITest module Webhooks @@ -32,6 +32,160 @@ def setup @url = "#{ShopifyAPI::Context.host}/admin/api/#{ShopifyAPI::Context.api_version}/graphql.json" end + VALID_PROTOCOL_ADDRESSES = { + http: ["test-webhooks", "https://app-address.com/test-webhooks", "app-address.com/test-webhooks"], + pub_sub: ["pubsub://my-project-id:my-topic-id"], + event_bridge: ["test-webhooks"], + } + + VALID_PROTOCOL_ADDRESSES.each do |protocol, addresses| + addresses.each do |address| + define_method("test_#{protocol}_no_registration_if_identical_webhook_exists_with_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query]], + [queries[protocol][:check_existing_response_with_attributes]], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address, + fields: "field1, field2", + metafield_namespaces: ["namespace1", "namespace2"], + ) + + # Then + assert_nil(update_registration_response.body) + end + + define_method("test_#{protocol}_add_registration_with_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query], queries[protocol][:register_add_query]], + [queries[protocol][:check_empty_response], queries[protocol][:register_add_response]], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address, + fields: ["field1", "field2"], + metafield_namespaces: ["namespace1", "namespace2"], + ) + + # Then + assert(update_registration_response.success) + assert_equal(queries[protocol][:register_add_response], update_registration_response.body) + end + + define_method("test_#{protocol}_update_registration_address_with_original_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query], queries[protocol][:register_update_query]], + [queries[protocol][:check_existing_response], queries[protocol][:register_update_response]], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address + "-updated", + ) + + # Then + assert(update_registration_response.success) + assert_equal(queries[protocol][:register_update_response], update_registration_response.body) + end + + define_method("test_#{protocol}_update_registration_fields_with_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query], queries[protocol][:register_update_query_with_fields]], + [queries[protocol][:check_existing_response], queries[protocol][:register_update_with_fields_response]], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address, + fields: "field1, field2, field3", + ) + + # Then + assert(update_registration_response.success) + assert_equal(queries[protocol][:register_update_with_fields_response], update_registration_response.body) + end + + define_method("test_#{protocol}_update_registration_fields_array_with_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query], queries[protocol][:register_update_query_with_fields]], + [queries[protocol][:check_existing_response], queries[protocol][:register_update_with_fields_response]], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address, + fields: ["field1", "field2", "field3"], + ) + + # Then + assert(update_registration_response.success) + assert_equal(queries[protocol][:register_update_with_fields_response], update_registration_response.body) + end + + define_method("test_#{protocol}_update_registration_metafield_namespaces_with_address_#{address}") do + # Given + setup_queries_and_responses( + [queries[protocol][:check_query], queries[protocol][:register_update_query_with_metafield_namespaces]], + [queries[protocol][:check_existing_response], + queries[protocol][:register_update_with_metafield_namespaces_response],], + ) + + # When + update_registration_response = add_and_register_webhook( + protocol, + address, + metafield_namespaces: ["namespace1", "namespace2", "namespace3"], + ) + + # Then + assert(update_registration_response.success) + assert_equal(queries[protocol][:register_update_with_metafield_namespaces_response], + update_registration_response.body) + end + + define_method("test_raises_on_#{protocol}_registration_check_error_with_address_#{address}") do + # Given + ShopifyAPI::Webhooks::Registry.clear + body = { query: queries[protocol][:check_query], variables: nil } + + stub_request(:post, @url) + .with(body: JSON.dump(body)) + .to_return(status: 304) + + # When + ShopifyAPI::Webhooks::Registry.add_registration( + topic: @topic, + delivery_method: protocol, + path: address, + handler: TestHelpers::FakeWebhookHandler.new( + lambda do |topic, shop, body| + end, + ), + ) + + # Then + assert_raises(StandardError) do + ShopifyAPI::Webhooks::Registry.register_all( + session: @session, + ) + end + end + end + end + def test_add_http_registration_without_handler assert_raises(ShopifyAPI::Errors::InvalidWebhookRegistrationError) do ShopifyAPI::Webhooks::Registry.add_registration(topic: @topic, path: "path", delivery_method: :http) @@ -121,80 +275,10 @@ def test_process_no_handler @headers["x-shopify-topic"] = "non_registered_topic" assert_raises(ShopifyAPI::Errors::NoWebhookHandler) do - ShopifyAPI::Webhooks::Registry.process(ShopifyAPI::Webhooks::Request.new(raw_body: "{}", - headers: @headers)) + ShopifyAPI::Webhooks::Registry.process(ShopifyAPI::Webhooks::Request.new(raw_body: "{}", headers: @headers)) end end - def test_http_registration_add_and_update - do_registration_test(:http, "test-webhooks") - end - - def test_http_registration_add_and_update_with_full_url - do_registration_test(:http, "https://app-address.com/test-webhooks") - end - - def test_http_registration_add_and_update_with_schemeless_url - do_registration_test(:http, "app-address.com/test-webhooks") - end - - def test_http_registration_with_fields_add_and_update - do_registration_test(:http, "test-webhooks", fields: "field1, field2") - end - - def test_http_registration_with_fields_array_add_and_update - do_registration_test(:http, "test-webhooks", fields: ["field1", "field2"]) - end - - def test_http_registration_with_metafield_namespaces_add_and_update - do_registration_test(:http, "test-webhooks", metafield_namespaces: ["namespace1", "namespace2"]) - end - - def test_raises_on_http_registration_check_error - do_registration_check_error_test(:http, "test-webhooks") - end - - def test_pubsub_registration_add_and_update - do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id") - end - - def test_pubsub_registration_with_fields_add_and_update - do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id", fields: "field1, field2") - end - - def test_pubsub_registration_with_fields_array_add_and_update - do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id", fields: ["field1", "field2"]) - end - - def test_pubsub_registration_with_metafield_namespaces_add_and_update - do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id", - metafield_namespaces: ["namespace1", "namespace2"]) - end - - def test_raises_on_pubsub_registration_check_error - do_registration_check_error_test(:pub_sub, "pubsub://my-project-id:my-topic-id") - end - - def test_eventbridge_registration_add_and_update - do_registration_test(:event_bridge, "test-webhooks") - end - - def test_eventbridge_registration_with_fields_add_and_update - do_registration_test(:event_bridge, "test-webhooks", fields: "field1, field2") - end - - def test_eventbridge_registration_with_fields_array_add_and_update - do_registration_test(:event_bridge, "test-webhooks", fields: ["field1", "field2"]) - end - - def test_eventbridge_registration_with_metafield_namespaces_add_and_update - do_registration_test(:event_bridge, "test-webhooks", metafield_namespaces: ["namespace1", "namespace2"]) - end - - def test_raises_on_eventbridge_registration_check_error - do_registration_check_error_test(:event_bridge, "test-webhooks") - end - def test_register_topic_not_not_registry assert_raises(ShopifyAPI::Errors::InvalidWebhookRegistrationError) do ShopifyAPI::Webhooks::Registry.register(topic: "not-registered", session: @session) @@ -202,13 +286,10 @@ def test_register_topic_not_not_registry end def test_unregister_success - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response]) }) - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:delete_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:delete_response]) }) + setup_queries_and_responses( + [queries[:fetch_id_query], queries[:delete_query]], + [queries[:fetch_id_response], queries[:delete_response]], + ) delete_response = ShopifyAPI::Webhooks::Registry.unregister( topic: "some/topic", @@ -219,13 +300,10 @@ def test_unregister_success end def test_unregister_fail_with_errors - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response]) }) - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:delete_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:delete_response_with_errors]) }) + setup_queries_and_responses( + [queries[:fetch_id_query], queries[:delete_query]], + [queries[:fetch_id_response], queries[:delete_response_with_errors]], + ) exception = assert_raises(ShopifyAPI::Errors::WebhookRegistrationError) do ShopifyAPI::Webhooks::Registry.unregister( @@ -237,13 +315,10 @@ def test_unregister_fail_with_errors end def test_unregister_fail_with_user_errors - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response]) }) - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:delete_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:delete_response_with_user_errors]) }) + setup_queries_and_responses( + [queries[:fetch_id_query], queries[:delete_query]], + [queries[:fetch_id_response], queries[:delete_response_with_user_errors]], + ) exception = assert_raises(ShopifyAPI::Errors::WebhookRegistrationError) do ShopifyAPI::Webhooks::Registry.unregister( @@ -266,9 +341,7 @@ def test_unregister_to_mandatory_topics_are_skipped end def test_get_webhook_id_success - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response]) }) + setup_queries_and_responses([queries[:fetch_id_query]], [queries[:fetch_id_response]]) webhook_id_response = ShopifyAPI::Webhooks::Registry.get_webhook_id( topic: "some/topic", @@ -281,9 +354,7 @@ def test_get_webhook_id_success end def test_get_webhook_id_success_for_event - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_event_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response]) }) + setup_queries_and_responses([queries[:fetch_id_event_query]], [queries[:fetch_id_response]]) webhook_id_response = ShopifyAPI::Webhooks::Registry.get_webhook_id( topic: "domain.sub_domain.something_happened", @@ -296,9 +367,7 @@ def test_get_webhook_id_success_for_event end def test_get_webhook_id_not_found - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response_not_found]) }) + setup_queries_and_responses([queries[:fetch_id_query]], [queries[:fetch_id_response_not_found]]) webhook_id_response = ShopifyAPI::Webhooks::Registry.get_webhook_id( topic: "some/topic", @@ -308,9 +377,7 @@ def test_get_webhook_id_not_found end def test_get_webhook_id_with_graphql_errors - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[:fetch_id_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[:fetch_id_response_with_errors]) }) + setup_queries_and_responses([queries[:fetch_id_query]], [queries[:fetch_id_response_with_errors]]) exception = assert_raises(ShopifyAPI::Errors::WebhookRegistrationError) do ShopifyAPI::Webhooks::Registry.get_webhook_id( @@ -336,196 +403,32 @@ def test_registrations_to_mandatory_topics_are_ignored private - def do_registration_test(delivery_method, path, fields: nil, metafield_namespaces: nil) + def setup_queries_and_responses(queries, responses) ShopifyAPI::Webhooks::Registry.clear - - check_query_body = { query: queries[delivery_method][:check_query], variables: nil } - - stub_request(:post, @url) - .with(body: JSON.dump(check_query_body)) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_empty_response]) }) - - add_query_type = if fields - :register_add_query_with_fields - elsif metafield_namespaces - :register_add_query_with_metafield_namespaces - else - :register_add_query - end - add_response_type = if fields - :register_add_with_fields_response - elsif metafield_namespaces - :register_add_with_metafield_namespaces_response - else - :register_add_response + queries.zip(responses).each do |query, response| + stub_request(:post, @url) + .with(body: JSON.dump({ query: query, variables: nil })) + .to_return({ status: 200, body: JSON.dump(response) }) end + end - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[delivery_method][add_query_type], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][add_response_type]) }) - - ShopifyAPI::Webhooks::Registry.add_registration( - topic: @topic, - delivery_method: delivery_method, - path: path, - handler: TestHelpers::FakeWebhookHandler.new( - lambda do |topic, shop, body| - end, - ), - fields: fields, - metafield_namespaces: metafield_namespaces, - ) - registration_response = ShopifyAPI::Webhooks::Registry.register_all( - session: @session, - )[0] - - assert(registration_response.success) - assert_equal(queries[delivery_method][add_response_type], registration_response.body) - - stub_request(:post, @url) - .with(body: JSON.dump(check_query_body)) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_existing_response]) }) - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[delivery_method][:register_update_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:register_update_response]) }) - + def add_and_register_webhook(protocol, address, fields: nil, metafield_namespaces: nil) ShopifyAPI::Webhooks::Registry.add_registration( topic: @topic, - delivery_method: delivery_method, - path: "#{path}-updated", + delivery_method: protocol, + path: address, handler: TestHelpers::FakeWebhookHandler.new( lambda do |topic, shop, body| end, ), - ) - update_registration_response = ShopifyAPI::Webhooks::Registry.register_all( - session: @session, - )[0] - - assert(update_registration_response.success) - assert_equal(queries[delivery_method][:register_update_response], update_registration_response.body) - end - - def do_registration_new_handler_test(delivery_method, path, fields: nil, metafield_namespaces: nil) - ShopifyAPI::Webhooks::Registry.clear - - check_query_body = { query: queries[delivery_method][:check_query], variables: nil } - - stub_request(:post, @url) - .with(body: JSON.dump(check_query_body)) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_empty_response]) }) - - add_query_type = if fields - :register_add_query_with_fields - elsif metafield_namespaces - :register_add_query_with_metafield_namespaces - else - :register_add_query - end - add_response_type = if fields - :register_add_with_fields_response - elsif metafield_namespaces - :register_add_with_metafield_namespaces_response - else - :register_add_response - end - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[delivery_method][add_query_type], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][add_response_type]) }) - - ShopifyAPI::Webhooks::Registry.add_registration( - topic: @topic, - delivery_method: delivery_method, - path: path, - handler: TestHelpers::NewFakeWebhookHandler.new( - lambda do |data| - end, - ), fields: fields, metafield_namespaces: metafield_namespaces, ) - registration_response = ShopifyAPI::Webhooks::Registry.register_all( - session: @session, - )[0] - - assert(registration_response.success) - assert_equal(queries[delivery_method][add_response_type], registration_response.body) - - stub_request(:post, @url) - .with(body: JSON.dump(check_query_body)) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_existing_response]) }) - - stub_request(:post, @url) - .with(body: JSON.dump({ query: queries[delivery_method][:register_update_query], variables: nil })) - .to_return({ status: 200, body: JSON.dump(queries[delivery_method][:register_update_response]) }) - - ShopifyAPI::Webhooks::Registry.add_registration( - topic: @topic, - delivery_method: delivery_method, - path: "#{path}-updated", - handler: TestHelpers::NewFakeWebhookHandler.new( - lambda do |data| - end, - ), - ) update_registration_response = ShopifyAPI::Webhooks::Registry.register_all( session: @session, )[0] - assert(update_registration_response.success) - assert_equal(queries[delivery_method][:register_update_response], update_registration_response.body) - end - - def do_registration_check_error_test(delivery_method, path) - ShopifyAPI::Webhooks::Registry.clear - body = { query: queries[delivery_method][:check_query], variables: nil } - - stub_request(:post, @url) - .with(body: JSON.dump(body)) - .to_return(status: 304) - - ShopifyAPI::Webhooks::Registry.add_registration( - topic: @topic, - delivery_method: delivery_method, - path: path, - handler: TestHelpers::FakeWebhookHandler.new( - lambda do |topic, shop, body| - end, - ), - ) - - assert_raises(StandardError) do - ShopifyAPI::Webhooks::Registry.register_all( - session: @session, - ) - end - end - - def do_registration_check_error_test_new_handler(delivery_method, path) - ShopifyAPI::Webhooks::Registry.clear - body = { query: queries[delivery_method][:check_query], variables: nil } - - stub_request(:post, @url) - .with(body: JSON.dump(body)) - .to_return(status: 304) - - ShopifyAPI::Webhooks::Registry.add_registration( - topic: @topic, - delivery_method: delivery_method, - path: path, - handler: TestHelpers::NewFakeWebhookHandler.new( - lambda do |data| - end, - ), - ) - - assert_raises(StandardError) do - ShopifyAPI::Webhooks::Registry.register_all( - session: @session, - ) - end + update_registration_response end end end diff --git a/test/webhooks/webhook_registration_queries.rb b/test/webhooks/registry_test_queries.rb similarity index 62% rename from test/webhooks/webhook_registration_queries.rb rename to test/webhooks/registry_test_queries.rb index 8d4659ae8..ddb186310 100644 --- a/test/webhooks/webhook_registration_queries.rb +++ b/test/webhooks/registry_test_queries.rb @@ -14,6 +14,8 @@ def queries edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookHttpEndpoint { @@ -36,13 +38,15 @@ def queries register_add_query: <<~QUERY, mutation webhookSubscription { - webhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {callbackUrl: "https://app-address.com/test-webhooks"}) { + webhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {callbackUrl: "https://app-address.com/test-webhooks", includeFields: ["field1", "field2"], metafieldNamespaces: ["namespace1", "namespace2"]}) { userErrors { field message } webhookSubscription { id + includeFields + metafieldNamespaces } } } @@ -82,7 +86,11 @@ def queries "data" => { "webhookSubscriptionCreate" => { "userErrors" => [], - "webhookSubscription" => { "id" => "gid://shopify/WebhookSubscription/12345" }, + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2"], + "metafieldNamespaces" => ["namespace1", "namespace2"], + }, }, }, }, @@ -123,6 +131,23 @@ def queries }, }, }, + check_existing_response_with_attributes: { + "data" => { + "webhookSubscriptions" => { + "edges" => [ + "node" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2"], + "metafieldNamespaces" => ["namespace1", "namespace2"], + "endpoint" => { + "typename" => "WebhookHttpEndpoint", + "callbackUrl" => "https://app-address.com/test-webhooks", + }, + }, + ], + }, + }, + }, register_update_query: <<~QUERY, mutation webhookSubscription { @@ -137,6 +162,36 @@ def queries } } QUERY + register_update_query_with_fields: + <<~QUERY, + mutation webhookSubscription { + webhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {callbackUrl: "https://app-address.com/test-webhooks", includeFields: ["field1", "field2", "field3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + includeFields + } + } + } + QUERY + register_update_query_with_metafield_namespaces: + <<~QUERY, + mutation webhookSubscription { + webhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {callbackUrl: "https://app-address.com/test-webhooks", metafieldNamespaces: ["namespace1", "namespace2", "namespace3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + metafieldNamespaces + } + } + } + QUERY register_update_response: { "data" => { "webhookSubscriptionUpdate" => { @@ -145,6 +200,29 @@ def queries }, }, }, + + register_update_with_fields_response: { + "data" => { + "webhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2", "field3"], + }, + }, + }, + }, + register_update_with_metafield_namespaces_response: { + "data" => { + "webhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "metafieldNamespaces" => ["namespace1", "namespace2", "namespace3"], + }, + }, + }, + }, }, event_bridge: { check_query: @@ -154,6 +232,8 @@ def queries edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookEventBridgeEndpoint { @@ -175,13 +255,15 @@ def queries register_add_query: <<~QUERY, mutation webhookSubscription { - eventBridgeWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {arn: "test-webhooks"}) { + eventBridgeWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {arn: "test-webhooks", includeFields: ["field1", "field2"], metafieldNamespaces: ["namespace1", "namespace2"]}) { userErrors { field message } webhookSubscription { id + includeFields + metafieldNamespaces } } } @@ -220,7 +302,11 @@ def queries "data" => { "eventBridgeWebhookSubscriptionCreate" => { "userErrors" => [], - "webhookSubscription" => { "id" => "gid://shopify/WebhookSubscription/12345" }, + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2"], + "metafieldNamespaces" => ["namespace1", "namespace2"], + }, }, }, }, @@ -261,6 +347,23 @@ def queries }, }, }, + check_existing_response_with_attributes: { + "data" => { + "webhookSubscriptions" => { + "edges" => [ + "node" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "endpoint" => { + "typename" => "WebhookEventBridgeEndpoint", + "arn" => "test-webhooks", + }, + "includeFields" => ["field2", "field1"], + "metafieldNamespaces" => ["namespace2", "namespace1"], + }, + ], + }, + }, + }, register_update_query: <<~QUERY, mutation webhookSubscription { @@ -275,6 +378,36 @@ def queries } } QUERY + register_update_query_with_fields: + <<~QUERY, + mutation webhookSubscription { + eventBridgeWebhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {arn: "test-webhooks", includeFields: ["field1", "field2", "field3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + includeFields + } + } + } + QUERY + register_update_query_with_metafield_namespaces: + <<~QUERY, + mutation webhookSubscription { + eventBridgeWebhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {arn: "test-webhooks", metafieldNamespaces: ["namespace1", "namespace2", "namespace3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + metafieldNamespaces + } + } + } + QUERY register_update_response: { "data" => { "eventBridgeWebhookSubscriptionUpdate" => { @@ -283,6 +416,28 @@ def queries }, }, }, + register_update_with_fields_response: { + "data" => { + "eventBridgeWebhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2", "field3"], + }, + }, + }, + }, + register_update_with_metafield_namespaces_response: { + "data" => { + "eventBridgeWebhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "metafieldNamespaces" => ["namespace1", "namespace2", "namespace3"], + }, + }, + }, + }, }, pub_sub: { check_query: @@ -292,6 +447,8 @@ def queries edges { node { id + includeFields + metafieldNamespaces endpoint { __typename ... on WebhookPubSubEndpoint { @@ -314,13 +471,15 @@ def queries register_add_query: <<~QUERY, mutation webhookSubscription { - pubSubWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {pubSubProject: "my-project-id", pubSubTopic: "my-topic-id"}) { + pubSubWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {pubSubProject: "my-project-id", pubSubTopic: "my-topic-id", includeFields: ["field1", "field2"], metafieldNamespaces: ["namespace1", "namespace2"]}) { userErrors { field message } webhookSubscription { id + includeFields + metafieldNamespaces } } } @@ -359,7 +518,11 @@ def queries "data" => { "pubSubWebhookSubscriptionCreate" => { "userErrors" => [], - "webhookSubscription" => { "id" => "gid://shopify/WebhookSubscription/12345" }, + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2"], + "metafieldNamespaces" => ["namespace1", "namespace2"], + }, }, }, }, @@ -401,6 +564,24 @@ def queries }, }, }, + check_existing_response_with_attributes: { + "data" => { + "webhookSubscriptions" => { + "edges" => [ + "node" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "endpoint" => { + "typename" => "WebhookPubSubEndpoint", + "pubSubProject" => "my-project-id", + "pubSubTopic" => "my-topic-id", + }, + "includeFields" => ["field1", "field2"], + "metafieldNamespaces" => ["namespace1", "namespace2"], + }, + ], + }, + }, + }, register_update_query: <<~QUERY, mutation webhookSubscription { @@ -415,6 +596,37 @@ def queries } } QUERY + register_update_query_with_fields: + <<~QUERY, + mutation webhookSubscription { + pubSubWebhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {pubSubProject: "my-project-id", pubSubTopic: "my-topic-id", includeFields: ["field1", "field2", "field3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + includeFields + } + } + } + QUERY + register_update_query_with_metafield_namespaces: + <<~QUERY, + mutation webhookSubscription { + pubSubWebhookSubscriptionUpdate(id: "gid://shopify/WebhookSubscription/12345", webhookSubscription: {pubSubProject: "my-project-id", pubSubTopic: "my-topic-id", metafieldNamespaces: ["namespace1", "namespace2", "namespace3"]}) { + userErrors { + field + message + } + webhookSubscription { + id + metafieldNamespaces + } + } + } + QUERY + register_update_response: { "data" => { "pubSubWebhookSubscriptionUpdate" => { @@ -423,6 +635,28 @@ def queries }, }, }, + register_update_with_fields_response: { + "data" => { + "pubSubWebhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "includeFields" => ["field1", "field2", "field3"], + }, + }, + }, + }, + register_update_with_metafield_namespaces_response: { + "data" => { + "pubSubWebhookSubscriptionUpdate" => { + "userErrors" => [], + "webhookSubscription" => { + "id" => "gid://shopify/WebhookSubscription/12345", + "metafieldNamespaces" => ["namespace1", "namespace2", "namespace3"], + }, + }, + }, + }, }, fetch_id_query: <<~QUERY,