diff --git a/lib/shopify-cli/commands/connect.rb b/lib/shopify-cli/commands/connect.rb index 9c53f0e5de..b00a783b81 100644 --- a/lib/shopify-cli/commands/connect.rb +++ b/lib/shopify-cli/commands/connect.rb @@ -21,7 +21,7 @@ def fetch_org org_id = if orgs.count == 1 orgs.first["id"] else - CLI::UI::Prompt.ask('Which organization does this project belong to?') do |handler| + CLI::UI::Prompt.ask('To which organization does this project belong?') do |handler| orgs.each { |org| handler.option(org["businessName"]) { org["id"] } } end end @@ -33,7 +33,7 @@ def get_app(apps) app_id = if apps.count == 1 apps.first["id"] else - CLI::UI::Prompt.ask('Which app does this project belong to?') do |handler| + CLI::UI::Prompt.ask('To which app does this project belong?') do |handler| apps.each { |app| handler.option(app["title"]) { app["id"] } } end end @@ -44,7 +44,7 @@ def get_shop(shops, id) if shops.count == 1 shops.first elsif shops.count == 0 - @ctx.puts('No developement shops available.') + @ctx.puts('No development stores available.') @ctx.puts("Visit {{underline:https://partners.shopify.com/#{id}/stores}} to create one") else shop = CLI::UI::Prompt.ask('Which development store would you like to use?') do |handler| diff --git a/lib/shopify-cli/helpers.rb b/lib/shopify-cli/helpers.rb index 74364f5048..024f78b9e6 100644 --- a/lib/shopify-cli/helpers.rb +++ b/lib/shopify-cli/helpers.rb @@ -8,7 +8,6 @@ module Helpers autoload :Haikunator, 'shopify-cli/helpers/haikunator' autoload :Node, 'shopify-cli/helpers/node' autoload :Organizations, 'shopify-cli/helpers/organizations' - autoload :PartnersAPI, 'shopify-cli/helpers/partners_api' autoload :PkceToken, 'shopify-cli/helpers/pkce_token' autoload :Store, 'shopify-cli/helpers/store' autoload :String, 'shopify-cli/helpers/string' diff --git a/lib/shopify-cli/helpers/organizations.rb b/lib/shopify-cli/helpers/organizations.rb index e568a73c74..e942c2d477 100644 --- a/lib/shopify-cli/helpers/organizations.rb +++ b/lib/shopify-cli/helpers/organizations.rb @@ -3,7 +3,7 @@ module Helpers class Organizations class << self def fetch_all(ctx) - resp = Helpers::PartnersAPI.query(ctx, 'all_organizations') + resp = ShopifyCli::PartnersAPI.query(ctx, 'all_organizations') resp['data']['organizations']['nodes'].map do |org| org['stores'] = org['stores']['nodes'] org @@ -11,7 +11,7 @@ def fetch_all(ctx) end def fetch(ctx, id:) - resp = Helpers::PartnersAPI.query(ctx, 'find_organization', id: id) + resp = ShopifyCli::PartnersAPI.query(ctx, 'find_organization', id: id) org = resp['data']['organizations']['nodes'].first return nil if org.nil? org['stores'] = org['stores']['nodes'] @@ -19,7 +19,7 @@ def fetch(ctx, id:) end def fetch_with_app(ctx) - resp = Helpers::PartnersAPI.query(ctx, 'all_orgs_with_apps') + resp = ShopifyCli::PartnersAPI.query(ctx, 'all_orgs_with_apps') resp['data']['organizations']['nodes'].map do |org| org['stores'] = org['stores']['nodes'] org['apps'] = org['apps']['nodes'] diff --git a/lib/shopify-cli/helpers/partners_api.rb b/lib/shopify-cli/helpers/partners_api.rb deleted file mode 100644 index dd408dedfc..0000000000 --- a/lib/shopify-cli/helpers/partners_api.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'shopify_cli' - -module ShopifyCli - module Helpers - class PartnersAPI < API - ENV_VAR = 'SHOPIFY_APP_CLI_LOCAL_PARTNERS' - AUTH_PROD_URI = 'https://accounts.shopify.com' - AUTH_DEV_URI = 'https://identity.myshopify.io' - PROD_URI = 'https://partners.shopify.com' - DEV_URI = 'https://partners.myshopify.io/' - PROD_ID = '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' - DEV_ID = 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' - PROD_CLI_ID = 'fbdb2649-e327-4907-8f67-908d24cfd7e3' - DEV_CLI_ID = 'e5380e02-312a-7408-5718-e07017e9cf52' - - class << self - def id - ENV[ENV_VAR].nil? ? PROD_ID : DEV_ID - end - - def cli_id - ENV[ENV_VAR].nil? ? PROD_CLI_ID : DEV_CLI_ID - end - - def auth_endpoint - ENV[ENV_VAR].nil? ? AUTH_PROD_URI : AUTH_DEV_URI - end - - def endpoint - ENV[ENV_VAR].nil? ? PROD_URI : DEV_URI - end - - def query(ctx, body, **variables) - authenticated_req(ctx) do - api_client(ctx).query(body, variables: variables) - end - end - - private - - def authenticated_req(ctx) - yield - rescue API::APIRequestUnauthorizedError - Tasks::AuthenticateIdentity.call(ctx) - retry - rescue API::APIRequestNotFoundError - ctx.abort("error: Your account was not found. Please sign up at https://partners.shopify.com/signup") - end - - def api_client(ctx) - new( - ctx: ctx, - token: Helpers::PkceToken.read(ctx), - url: "#{endpoint}/api/cli/graphql", - ) - end - end - - def auth_headers(token) - { Authorization: "Bearer #{token}" } - end - end - end -end diff --git a/lib/shopify-cli/partners_api.rb b/lib/shopify-cli/partners_api.rb new file mode 100644 index 0000000000..089db78747 --- /dev/null +++ b/lib/shopify-cli/partners_api.rb @@ -0,0 +1,62 @@ +require 'shopify_cli' + +module ShopifyCli + class PartnersAPI < API + ENV_VAR = 'SHOPIFY_APP_CLI_LOCAL_PARTNERS' + AUTH_PROD_URI = 'https://accounts.shopify.com' + AUTH_DEV_URI = 'https://identity.myshopify.io' + PROD_URI = 'https://partners.shopify.com' + DEV_URI = 'https://partners.myshopify.io/' + PROD_ID = '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' + DEV_ID = 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' + PROD_CLI_ID = 'fbdb2649-e327-4907-8f67-908d24cfd7e3' + DEV_CLI_ID = 'e5380e02-312a-7408-5718-e07017e9cf52' + + class << self + def id + ENV[ENV_VAR].nil? ? PROD_ID : DEV_ID + end + + def cli_id + ENV[ENV_VAR].nil? ? PROD_CLI_ID : DEV_CLI_ID + end + + def auth_endpoint + ENV[ENV_VAR].nil? ? AUTH_PROD_URI : AUTH_DEV_URI + end + + def endpoint + ENV[ENV_VAR].nil? ? PROD_URI : DEV_URI + end + + def query(ctx, body, **variables) + authenticated_req(ctx) do + api_client(ctx).query(body, variables: variables) + end + end + + private + + def authenticated_req(ctx) + yield + rescue API::APIRequestUnauthorizedError + Tasks::AuthenticateIdentity.call(ctx) + retry + rescue API::APIRequestNotFoundError + ctx.puts("{{x}} error: Your account was not found. Please sign up at https://partners.shopify.com/signup") + end + + def api_client(ctx) + new( + ctx: ctx, + token: Helpers::PkceToken.read(ctx), + url: "#{endpoint}/api/cli/graphql", + ) + end + end + + def auth_headers(token) + { Authorization: "Bearer #{token}" } + end + end +end diff --git a/lib/shopify-cli/tasks/authenticate_identity.rb b/lib/shopify-cli/tasks/authenticate_identity.rb index 751a6d49de..220e88b641 100644 --- a/lib/shopify-cli/tasks/authenticate_identity.rb +++ b/lib/shopify-cli/tasks/authenticate_identity.rb @@ -9,10 +9,10 @@ def call(ctx) OAuth.new( ctx: ctx, service: 'identity', - client_id: Helpers::PartnersAPI.cli_id, + client_id: ShopifyCli::PartnersAPI.cli_id, scopes: SCOPES, - request_exchange: Helpers::PartnersAPI.id, - ).authenticate("#{Helpers::PartnersAPI.auth_endpoint}/oauth") + request_exchange: ShopifyCli::PartnersAPI.id, + ).authenticate("#{ShopifyCli::PartnersAPI.auth_endpoint}/oauth") end end end diff --git a/lib/shopify-cli/tasks/create_api_client.rb b/lib/shopify-cli/tasks/create_api_client.rb index 177fc8a7c8..7a5cfe3821 100644 --- a/lib/shopify-cli/tasks/create_api_client.rb +++ b/lib/shopify-cli/tasks/create_api_client.rb @@ -6,7 +6,7 @@ class CreateApiClient < ShopifyCli::Task VALID_APP_TYPES = %w(public custom) def call(ctx, org_id:, title:, app_url:, type:) - resp = Helpers::PartnersAPI.query( + resp = ShopifyCli::PartnersAPI.query( ctx, 'create_app', org: org_id.to_i, diff --git a/lib/shopify-cli/tasks/ensure_loopback_url.rb b/lib/shopify-cli/tasks/ensure_loopback_url.rb index 3d212cf5c9..a9d1fd40cb 100644 --- a/lib/shopify-cli/tasks/ensure_loopback_url.rb +++ b/lib/shopify-cli/tasks/ensure_loopback_url.rb @@ -4,13 +4,13 @@ class EnsureLoopbackURL < ShopifyCli::Task def call(ctx) @ctx = ctx api_key = Project.current.env.api_key - result = Helpers::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) + result = ShopifyCli::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) loopback = OAuth::REDIRECT_HOST app = result['data']['app'] urls = app['redirectUrlWhitelist'] if urls.grep(/#{loopback}/).empty? with_loopback = urls.push(loopback) - ShopifyCli::Helpers::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { + ShopifyCli::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { redirectUrlWhitelist: with_loopback, apiKey: api_key }) end diff --git a/lib/shopify-cli/tasks/ensure_test_shop.rb b/lib/shopify-cli/tasks/ensure_test_shop.rb index 0a9130860c..19aaddfd95 100644 --- a/lib/shopify-cli/tasks/ensure_test_shop.rb +++ b/lib/shopify-cli/tasks/ensure_test_shop.rb @@ -9,7 +9,7 @@ def call(ctx) return if shop['transferDisabled'] == true return unless CLI::UI::Prompt.confirm("Do you want to convert #{project.env.shop} to a test shop."\ " This will enable you to install your app on this store.") - ShopifyCli::Helpers::PartnersAPI.query(ctx, 'convert_dev_to_test_store', input: { + ShopifyCli::PartnersAPI.query(ctx, 'convert_dev_to_test_store', input: { organizationID: shop['orgID'].to_i, shopId: shop['shopId'], }) diff --git a/lib/shopify-cli/tasks/update_dashboard_urls.rb b/lib/shopify-cli/tasks/update_dashboard_urls.rb index 71fd41a4f0..4544aa8ff1 100644 --- a/lib/shopify-cli/tasks/update_dashboard_urls.rb +++ b/lib/shopify-cli/tasks/update_dashboard_urls.rb @@ -7,12 +7,12 @@ def call(ctx, url:, callback_url:) @ctx = ctx project = ShopifyCli::Project.current api_key = project.env.api_key - result = Helpers::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) + result = ShopifyCli::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) app = result['data']['app'] consent = check_application_url(app['applicationUrl'], url) constructed_urls = construct_redirect_urls(app['redirectUrlWhitelist'], url, callback_url) return if url == app['applicationUrl'] - ShopifyCli::Helpers::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { + ShopifyCli::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { applicationUrl: consent ? url : app['applicationUrl'], redirectUrlWhitelist: constructed_urls, apiKey: api_key }) diff --git a/lib/shopify_cli.rb b/lib/shopify_cli.rb index d19b3f63c1..d9166a92f6 100644 --- a/lib/shopify_cli.rb +++ b/lib/shopify_cli.rb @@ -108,6 +108,7 @@ module ShopifyCli autoload :Log, 'shopify-cli/log' autoload :OAuth, 'shopify-cli/oauth' autoload :Options, 'shopify-cli/options' + autoload :PartnersAPI, 'shopify-cli/partners_api' autoload :ProcessSupervision, 'shopify-cli/process_supervision' autoload :Project, 'shopify-cli/project' autoload :ProjectType, 'shopify-cli/project_type' diff --git a/test/shopify-cli/commands/connect_test.rb b/test/shopify-cli/commands/connect_test.rb index baaf1b06bc..9bf30c8772 100644 --- a/test/shopify-cli/commands/connect_test.rb +++ b/test/shopify-cli/commands/connect_test.rb @@ -35,7 +35,7 @@ def test_run }], }] ShopifyCli::Helpers::Organizations.stubs(:fetch_with_app).returns(response) - CLI::UI::Prompt.expects(:ask).with('Which organization does this project belong to?').returns(422) + CLI::UI::Prompt.expects(:ask).with('To which organization does this project belong?').returns(422) CLI::UI::Prompt.expects(:ask).with( 'Which development store would you like to use?' ).returns('store.myshopify.com') diff --git a/test/shopify-cli/helpers/partners_api_test.rb b/test/shopify-cli/helpers/partners_api_test.rb deleted file mode 100644 index e85defaaaa..0000000000 --- a/test/shopify-cli/helpers/partners_api_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'test_helper' - -module ShopifyCli - module Helpers - class PartnersAPITest < MiniTest::Test - include TestHelpers::Project - - def setup - super - Helpers::PkceToken.stubs(:read).returns('token123') - end - - def test_id - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_ID, PartnersAPI.id - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_ID, PartnersAPI.id - end - - def test_cli_id - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_CLI_ID, PartnersAPI.cli_id - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_CLI_ID, PartnersAPI.cli_id - end - - def test_auth_endpoint - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::AUTH_DEV_URI, PartnersAPI.auth_endpoint - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::AUTH_PROD_URI, PartnersAPI.auth_endpoint - end - - def test_endpoint - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_URI, PartnersAPI.endpoint - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_URI, PartnersAPI.endpoint - end - - def test_query_calls_partners_api - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).with('query', variables: {}).returns('response') - assert_equal 'response', PartnersAPI.query(@context, 'query') - end - - def test_query_can_reauth - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub).twice - api_stub.expects(:query).with('query', variables: {}).returns('response') - api_stub.expects(:query).raises(API::APIRequestUnauthorizedError) - Tasks::AuthenticateIdentity.expects(:call) - PartnersAPI.query(@context, 'query') - end - - def test_query_aborts_without_partners_account - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).raises(API::APIRequestNotFoundError) - e = assert_raises ShopifyCli::Abort do - PartnersAPI.query(@context, 'query') - end - assert_match( - 'error: Your account was not found. Please sign up at https://partners.shopify.com/signup', - e.message - ) - end - - def test_query - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).with('query', variables: {}).returns('response') - assert_equal 'response', PartnersAPI.query(@context, 'query') - end - end - end -end diff --git a/test/shopify-cli/partners_api_test.rb b/test/shopify-cli/partners_api_test.rb new file mode 100644 index 0000000000..d7009b19dd --- /dev/null +++ b/test/shopify-cli/partners_api_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +module ShopifyCli + class PartnersAPITest < MiniTest::Test + include TestHelpers::Project + + def setup + super + Helpers::PkceToken.stubs(:read).returns('token123') + end + + def test_id + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_ID, PartnersAPI.id + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_ID, PartnersAPI.id + end + + def test_cli_id + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_CLI_ID, PartnersAPI.cli_id + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_CLI_ID, PartnersAPI.cli_id + end + + def test_auth_endpoint + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::AUTH_DEV_URI, PartnersAPI.auth_endpoint + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::AUTH_PROD_URI, PartnersAPI.auth_endpoint + end + + def test_endpoint + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_URI, PartnersAPI.endpoint + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_URI, PartnersAPI.endpoint + end + + def test_query_calls_partners_api + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).with('query', variables: {}).returns('response') + assert_equal 'response', PartnersAPI.query(@context, 'query') + end + + def test_query_can_reauth + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub).twice + api_stub.expects(:query).with('query', variables: {}).returns('response') + api_stub.expects(:query).raises(API::APIRequestUnauthorizedError) + Tasks::AuthenticateIdentity.expects(:call) + PartnersAPI.query(@context, 'query') + end + + def test_query_fails_gracefully_without_partners_account + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).raises(API::APIRequestNotFoundError) + @context.expects(:puts).with( + "{{x}} error: Your account was not found. Please sign up at https://partners.shopify.com/signup", + ) + PartnersAPI.query(@context, 'query') + end + + def test_query + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).with('query', variables: {}).returns('response') + assert_equal 'response', PartnersAPI.query(@context, 'query') + end + end +end diff --git a/test/task/authenticate_identity_test.rb b/test/task/authenticate_identity_test.rb index e30826a748..61989dad11 100644 --- a/test/task/authenticate_identity_test.rb +++ b/test/task/authenticate_identity_test.rb @@ -12,9 +12,9 @@ def test_negotiate_oauth_and_store_token .with( ctx: @context, service: 'identity', - client_id: Helpers::PartnersAPI.cli_id, + client_id: PartnersAPI.cli_id, scopes: AuthenticateIdentity::SCOPES, - request_exchange: Helpers::PartnersAPI.id, + request_exchange: PartnersAPI.id, ).returns(@oauth_client) @oauth_client .expects(:authenticate)