shopify_app 6.4.2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +13 -0
  3. data/ISSUE_TEMPLATE.md +14 -0
  4. data/QUICKSTART.md +6 -7
  5. data/README.md +38 -16
  6. data/Rakefile +2 -6
  7. data/app/controllers/shopify_app/authenticated_controller.rb +7 -0
  8. data/app/controllers/shopify_app/sessions_controller.rb +5 -0
  9. data/app/controllers/shopify_app/webhooks_controller.rb +27 -0
  10. data/app/views/{sessions → shopify_app/sessions}/new.html.erb +0 -0
  11. data/config/routes.rb +4 -0
  12. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +61 -0
  13. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb +8 -0
  14. data/lib/generators/shopify_app/controllers/controllers_generator.rb +1 -1
  15. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +0 -1
  16. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +1 -1
  17. data/lib/generators/shopify_app/install/install_generator.rb +13 -19
  18. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +2 -2
  19. data/lib/generators/shopify_app/install/templates/shopify_app.rb +3 -4
  20. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +0 -1
  21. data/lib/generators/shopify_app/routes/templates/routes.rb +4 -0
  22. data/lib/generators/shopify_app/shopify_app_generator.rb +1 -7
  23. data/lib/shopify_app.rb +2 -3
  24. data/lib/shopify_app/configuration.rb +0 -1
  25. data/lib/shopify_app/engine.rb +1 -0
  26. data/lib/shopify_app/login_protection.rb +53 -5
  27. data/lib/shopify_app/{sessions_controller.rb → sessions_concern.rb} +5 -5
  28. data/lib/shopify_app/version.rb +1 -1
  29. data/lib/shopify_app/webhook_verification.rb +35 -0
  30. data/shopify_app.gemspec +1 -1
  31. metadata +11 -43
  32. data/CONTRIBUTING.md +0 -14
  33. data/app/controllers/authenticated_controller.rb +0 -5
  34. data/app/controllers/sessions_controller.rb +0 -3
  35. data/lib/shopify_app/controller.rb +0 -18
  36. data/lib/shopify_app/webhooks_controller.rb +0 -35
  37. data/test/app_templates/app/controllers/application_controller.rb +0 -2
  38. data/test/app_templates/config/application.rb +0 -24
  39. data/test/app_templates/config/initializers/shopify_app.rb +0 -6
  40. data/test/app_templates/config/routes.rb +0 -4
  41. data/test/controllers/sessions_controller_test.rb +0 -122
  42. data/test/controllers/sessions_routes_test.rb +0 -27
  43. data/test/dummy/Rakefile +0 -6
  44. data/test/dummy/app/controllers/application_controller.rb +0 -3
  45. data/test/dummy/app/controllers/home_controller.rb +0 -7
  46. data/test/dummy/config.ru +0 -4
  47. data/test/dummy/config/application.rb +0 -26
  48. data/test/dummy/config/boot.rb +0 -5
  49. data/test/dummy/config/database.yml +0 -25
  50. data/test/dummy/config/environment.rb +0 -5
  51. data/test/dummy/config/environments/test.rb +0 -42
  52. data/test/dummy/config/initializers/shopify_app.rb +0 -6
  53. data/test/dummy/config/routes.rb +0 -10
  54. data/test/dummy/config/secrets.yml +0 -17
  55. data/test/generators/controllers_generator_test.rb +0 -16
  56. data/test/generators/home_controller_generator_test.rb +0 -50
  57. data/test/generators/install_generator_test.rb +0 -87
  58. data/test/generators/routes_generator_test.rb +0 -26
  59. data/test/generators/shop_model_generator_test.rb +0 -39
  60. data/test/generators/shopify_app_generator.rb +0 -15
  61. data/test/generators/views_generator_test.rb +0 -15
  62. data/test/shopify_app/configuration_test.rb +0 -49
  63. data/test/shopify_app/in_memory_session_store_test.rb +0 -35
  64. data/test/shopify_app/login_protection_test.rb +0 -100
  65. data/test/shopify_app/shopify_session_repository_test.rb +0 -73
  66. data/test/shopify_app/utils_test.rb +0 -30
  67. data/test/shopify_app/webhooks_controller_test.rb +0 -50
  68. data/test/shopify_app/webhooks_manager_test.rb +0 -86
  69. data/test/support/generator_test_helpers.rb +0 -29
  70. data/test/test_helper.rb +0 -17
@@ -1,26 +0,0 @@
1
- require 'test_helper'
2
- require 'generators/shopify_app/routes/routes_generator'
3
-
4
- class ControllerGeneratorTest < Rails::Generators::TestCase
5
- tests ShopifyApp::Generators::RoutesGenerator
6
- destination File.expand_path("../tmp", File.dirname(__FILE__))
7
-
8
- setup do
9
- prepare_destination
10
- provide_existing_routes_file
11
- provide_existing_initializer_file
12
- end
13
-
14
- test "copies ShopifyApp routes to the host application" do
15
- run_generator
16
-
17
- assert_file "config/routes.rb" do |routes|
18
- assert_match "get 'login' => :new, :as => :login", routes
19
- assert_match "post 'login' => :create, :as => :authenticate", routes
20
- assert_match "get 'auth/shopify/callback' => :callback", routes
21
- assert_match "get 'logout' => :destroy, :as => :logout", routes
22
- refute_match "mount ShopifyApp::Engine, at: '/'", routes
23
- end
24
- end
25
-
26
- end
@@ -1,39 +0,0 @@
1
- require 'test_helper'
2
- require 'generators/shopify_app/shop_model/shop_model_generator'
3
-
4
- class ShopModelGeneratorTest < Rails::Generators::TestCase
5
- tests ShopifyApp::Generators::ShopModelGenerator
6
- destination File.expand_path("../tmp", File.dirname(__FILE__))
7
- setup :prepare_destination
8
-
9
- test "create the shop model" do
10
- run_generator
11
- assert_file "app/models/shop.rb" do |shop|
12
- assert_match "class Shop < ActiveRecord::Base", shop
13
- assert_match "include ShopifyApp::Shop", shop
14
- assert_match "include ShopifyApp::SessionStorage", shop
15
- end
16
- end
17
-
18
- test "creates ShopModel migration" do
19
- run_generator
20
- assert_migration "db/migrate/create_shops.rb" do |migration|
21
- assert_match "create_table :shops do |t|", migration
22
- end
23
- end
24
-
25
- test "adds the shopify_session_repository initializer" do
26
- run_generator
27
- assert_file "config/initializers/shopify_session_repository.rb" do |file|
28
- assert_match "ShopifyApp::SessionRepository.storage = Shop", file
29
- end
30
- end
31
-
32
- test "creates default shop fixtures" do
33
- run_generator
34
- assert_file "test/fixtures/shops.yml" do |file|
35
- assert_match "regular_shop:", file
36
- end
37
- end
38
-
39
- end
@@ -1,15 +0,0 @@
1
- require 'test_helper'
2
- require 'generators/shopify_app/shopify_app_generator'
3
-
4
- class ViewsGeneratorTest < Rails::Generators::TestCase
5
- tests ShopifyApp::Generators::ViewsGenerator
6
- destination File.expand_path("../tmp", File.dirname(__FILE__))
7
- setup :prepare_destination
8
-
9
- test "copies ShopifyApp views to the host application" do
10
- run_generator
11
- assert_directory "app/views"
12
- assert_file "app/views/sessions/new.html.erb"
13
- end
14
-
15
- end
@@ -1,15 +0,0 @@
1
- require 'test_helper'
2
- require 'generators/shopify_app/views/views_generator'
3
-
4
- class ViewsGeneratorTest < Rails::Generators::TestCase
5
- tests ShopifyApp::Generators::ViewsGenerator
6
- destination File.expand_path("../tmp", File.dirname(__FILE__))
7
- setup :prepare_destination
8
-
9
- test "copies ShopifyApp views to the host application" do
10
- run_generator
11
- assert_directory "app/views"
12
- assert_file "app/views/sessions/new.html.erb"
13
- end
14
-
15
- end
@@ -1,49 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ConfigurationTest < ActiveSupport::TestCase
4
-
5
- setup do
6
- ShopifyApp.configuration = nil
7
- end
8
-
9
- test "configure" do
10
- ShopifyApp.configure do |config|
11
- config.embedded_app = true
12
- end
13
-
14
- assert_equal true, ShopifyApp.configuration.embedded_app
15
- end
16
-
17
- test "defaults to myshopify_domain" do
18
- assert_equal "myshopify.com", ShopifyApp.configuration.myshopify_domain
19
- end
20
-
21
- test "can set myshopify_domain" do
22
- ShopifyApp.configure do |config|
23
- config.myshopify_domain = 'myshopify.io'
24
- end
25
-
26
- assert_equal "myshopify.io", ShopifyApp.configuration.myshopify_domain
27
- end
28
-
29
- test "can configure webhooks for creation" do
30
- webhook = {topic: 'carts/update', address: 'example-app.com/webhooks', format: 'json'}
31
-
32
- ShopifyApp.configure do |config|
33
- config.webhooks = [webhook]
34
- end
35
-
36
- assert_equal webhook, ShopifyApp.configuration.webhooks.first
37
- end
38
-
39
- test "has_webhooks? is true if webhooks have been configured" do
40
- refute ShopifyApp.configuration.has_webhooks?
41
-
42
- ShopifyApp.configure do |config|
43
- config.webhooks = [{topic: 'carts/update', address: 'example-app.com/webhooks', format: 'json'}]
44
- end
45
-
46
- assert ShopifyApp.configuration.has_webhooks?
47
- end
48
-
49
- end
@@ -1,35 +0,0 @@
1
- require 'test_helper'
2
-
3
- class InMemorySessionStoreTest < ActiveSupport::TestCase
4
- teardown do
5
- InMemorySessionStore.clear
6
- end
7
-
8
- test "storing a session" do
9
- uuid = InMemorySessionStore.store('something')
10
- assert_equal 'something', InMemorySessionStore.repo[uuid]
11
- end
12
-
13
- test "retrieving a session" do
14
- InMemorySessionStore.repo['abra'] = 'something'
15
- assert_equal 'something', InMemorySessionStore.retrieve('abra')
16
- end
17
-
18
- test "clearing the store" do
19
- uuid = InMemorySessionStore.store('data')
20
- assert_equal 'data', InMemorySessionStore.retrieve(uuid)
21
- InMemorySessionStore.clear
22
- assert !InMemorySessionStore.retrieve(uuid), 'The sessions should have been removed'
23
- end
24
-
25
- test "it should raise when the environment is not valid" do
26
- Rails.env.stubs(:production?).returns(true)
27
- assert_raises InMemorySessionStore::EnvironmentError do
28
- InMemorySessionStore.store('data')
29
- end
30
-
31
- assert_raises InMemorySessionStore::EnvironmentError do
32
- InMemorySessionStore.retrieve('abracadabra')
33
- end
34
- end
35
- end
@@ -1,100 +0,0 @@
1
- require 'test_helper'
2
- require 'action_controller'
3
- require 'action_controller/base'
4
-
5
- class LoginProtectionController < ActionController::Base
6
- include ShopifyApp::LoginProtection
7
- helper_method :shop_session
8
-
9
- around_action :shopify_session, only: [:index]
10
- before_action :login_again_if_different_shop, only: [:second_login]
11
-
12
- def index
13
- render nothing: true
14
- end
15
-
16
- def second_login
17
- render nothing: true
18
- end
19
- end
20
-
21
- class LoginProtectionTest < ActionController::TestCase
22
- tests LoginProtectionController
23
-
24
- setup do
25
- ShopifyApp::SessionRepository.storage = InMemorySessionStore
26
- end
27
-
28
- test "calling shop session returns nil when session is nil" do
29
- with_application_test_routes do
30
- session[:shopify] = nil
31
- get :index
32
- assert_nil @controller.shop_session
33
- end
34
- end
35
-
36
- test "calling shop session retreives session from storage" do
37
- with_application_test_routes do
38
- session[:shopify] = "foobar"
39
- get :index
40
- ShopifyApp::SessionRepository.expects(:retrieve).returns(session).once
41
- assert @controller.shop_session
42
- end
43
- end
44
-
45
- test "shop session is memoized and does not retreive session twice" do
46
- with_application_test_routes do
47
- session[:shopify] = "foobar"
48
- get :index
49
- ShopifyApp::SessionRepository.expects(:retrieve).returns(session).once
50
- assert @controller.shop_session
51
- assert @controller.shop_session
52
- end
53
- end
54
-
55
- test "login_again_if_different_shop removes current session and redirects to login path" do
56
- with_application_test_routes do
57
- session[:shopify] = "foobar"
58
- session[:shopify_domain] = "foobar"
59
- sess = stub(url: 'https://foobar.myshopify.com')
60
- ShopifyApp::SessionRepository.expects(:retrieve).returns(sess).once
61
- get :second_login, shop: 'other_shop'
62
- assert_redirected_to @controller.send(:login_path, shop: 'other_shop')
63
- assert_nil session[:shopify]
64
- assert_nil session[:shopify_domain]
65
- end
66
- end
67
-
68
- test '#shopify_session with no Shopify session, redirects to the login path' do
69
- with_application_test_routes do
70
- get :index, shop: 'foobar'
71
- assert_redirected_to @controller.send(:login_path, shop: 'foobar')
72
- end
73
- end
74
-
75
- test '#shopify_session with no Shopify session, sets session[:return_to]' do
76
- with_application_test_routes do
77
- get :index, shop: 'foobar'
78
- assert_equal '/?shop=foobar', session[:return_to]
79
- end
80
- end
81
-
82
- test '#shopify_session with no Shopify session, when the request is an XHR, returns an HTTP 401' do
83
- with_application_test_routes do
84
- xhr :get, :index, shop: 'foobar'
85
- assert_equal 401, response.status
86
- end
87
- end
88
-
89
- private
90
-
91
- def with_application_test_routes
92
- with_routing do |set|
93
- set.draw do
94
- get '/' => 'login_protection#index'
95
- get '/second_login' => 'login_protection#second_login'
96
- end
97
- yield
98
- end
99
- end
100
- end
@@ -1,73 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TestSessionStore
4
- attr_reader :storage
5
-
6
- def initialize
7
- @storage = []
8
- end
9
-
10
- def retrieve(id)
11
- storage[id]
12
- end
13
-
14
- def store(session)
15
- id = storage.length
16
- storage[id] = session
17
- id
18
- end
19
- end
20
-
21
- class TestSessionStoreClass
22
- def self.store(session)
23
- end
24
-
25
- def self.retrieve(id)
26
- end
27
- end
28
-
29
- class ShopifySessionRepositoryTest < ActiveSupport::TestCase
30
- attr_reader :session_store, :session
31
-
32
- setup do
33
- @session_store = TestSessionStore.new
34
- @session = ShopifyAPI::Session.new('shop.myshopify.com', 'abracadabra')
35
- ShopifyApp::SessionRepository.storage = session_store
36
- end
37
-
38
- teardown do
39
- ShopifyApp::SessionRepository.storage = nil
40
- end
41
-
42
- test "adding a session to the repository" do
43
- assert_equal 0, ShopifyApp::SessionRepository.store(session)
44
- assert_equal session, session_store.retrieve(0)
45
- end
46
-
47
- test "retrieving a session from the repository" do
48
- session_store.storage[9] = session
49
- assert_equal session, ShopifyApp::SessionRepository.retrieve(9)
50
- end
51
-
52
- test "retrieving a session for an id that does not exist" do
53
- ShopifyApp::SessionRepository.store(session)
54
- assert !ShopifyApp::SessionRepository.retrieve(100), "The session with id 100 should not exist in the Repository"
55
- end
56
-
57
- test "retrieving a session for a misconfigured shops repository" do
58
- ShopifyApp::SessionRepository.storage = nil
59
- assert_raises ShopifyApp::SessionRepository::ConfigurationError do
60
- ShopifyApp::SessionRepository.retrieve(0)
61
- end
62
-
63
- assert_raises ShopifyApp::SessionRepository::ConfigurationError do
64
- ShopifyApp::SessionRepository.store(session)
65
- end
66
- end
67
-
68
- test "accepts a string and constantizes it" do
69
- ShopifyApp::SessionRepository.storage = 'TestSessionStoreClass'
70
- assert_equal TestSessionStoreClass, ShopifyApp::SessionRepository.storage
71
- end
72
-
73
- end
@@ -1,30 +0,0 @@
1
- require 'test_helper'
2
-
3
- class UtilsTest < ActiveSupport::TestCase
4
-
5
- setup do
6
- ShopifyApp.configuration = nil
7
- end
8
-
9
- ['my-shop', 'my-shop.myshopify.com', 'https://my-shop.myshopify.com', 'http://my-shop.myshopify.com'].each do |good_url|
10
- test "sanitize_shop_domain for (#{good_url})" do
11
- ShopifyApp.configuration.embedded_app = true
12
- assert ShopifyApp::Utils.sanitize_shop_domain(good_url)
13
- end
14
- end
15
-
16
- ['my-shop', 'my-shop.myshopify.io', 'https://my-shop.myshopify.io', 'http://my-shop.myshopify.io'].each do |good_url|
17
- test "sanitize_shop_domain URL (#{good_url}) with custom myshopify_domain" do
18
- ShopifyApp.configuration.embedded_app = true
19
- ShopifyApp.configuration.myshopify_domain = 'myshopify.io'
20
- assert ShopifyApp::Utils.sanitize_shop_domain(good_url)
21
- end
22
- end
23
-
24
- ['myshop.com', 'myshopify.com', 'shopify.com', 'two words', 'store.myshopify.com.evil.com', '/foo/bar'].each do |bad_url|
25
- test "sanitize_shop_domain for a non-myshopify URL (#{bad_url})" do
26
- assert_nil ShopifyApp::Utils.sanitize_shop_domain(bad_url)
27
- end
28
- end
29
-
30
- end
@@ -1,50 +0,0 @@
1
- require 'test_helper'
2
- require 'action_controller'
3
- require 'action_controller/base'
4
-
5
- class WebhooksController < ActionController::Base
6
- include ShopifyApp::WebhooksController
7
- def carts_update
8
- head :ok
9
- end
10
- end
11
-
12
- class WebhooksControllerTest < ActionController::TestCase
13
- tests WebhooksController
14
-
15
- setup do
16
- ShopifyApp::SessionRepository.storage = InMemorySessionStore
17
- ShopifyApp.configure do |config|
18
- config.secret = 'secret'
19
- end
20
- end
21
-
22
- test "#carts_update should verify request" do
23
- with_application_test_routes do
24
- data = {foo: :bar}.to_json
25
- @controller.expects(:validate_hmac).with('secret', data.to_s).returns(true)
26
- post :carts_update, data
27
- assert_response :ok
28
- end
29
- end
30
-
31
- test "un-verified request returns unauthorized" do
32
- with_application_test_routes do
33
- data = {foo: :bar}.to_json
34
- @controller.expects(:validate_hmac).with('secret', data.to_s).returns(false)
35
- post :carts_update, data
36
- assert_response :unauthorized
37
- end
38
- end
39
-
40
- private
41
-
42
- def with_application_test_routes
43
- with_routing do |set|
44
- set.draw do
45
- get '/carts_update' => 'webhooks#carts_update'
46
- end
47
- yield
48
- end
49
- end
50
- end
@@ -1,86 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ShopifyApp::WebhooksManagerTest < ActiveSupport::TestCase
4
-
5
- setup do
6
- ShopifyApp.configure do |config|
7
- config.webhooks = [
8
- {topic: 'app/uninstalled', address: "https://example-app.com/webhooks/app_uninstalled"},
9
- {topic: 'orders/create', address: "https://example-app.com/webhooks/order_create"},
10
- {topic: 'orders/updated', address: "https://example-app.com/webhooks/order_updated"},
11
- ]
12
- end
13
-
14
- @manager = ShopifyApp::WebhooksManager.new("regular-shop.myshopify.com", "token")
15
- end
16
-
17
- test "#create_webhooks makes calls to create webhooks" do
18
- ShopifyAPI::Webhook.stubs(all: [])
19
-
20
- expect_webhook_creation('app/uninstalled', "https://example-app.com/webhooks/app_uninstalled")
21
- expect_webhook_creation('orders/create', "https://example-app.com/webhooks/order_create")
22
- expect_webhook_creation('orders/updated', "https://example-app.com/webhooks/order_updated")
23
-
24
- @manager.create_webhooks
25
- end
26
-
27
- test "#create_webhooks when creating a webhook fails, raises an error" do
28
- ShopifyAPI::Webhook.stubs(all: [])
29
- webhook = stub(persisted?: false)
30
- ShopifyAPI::Webhook.stubs(create: webhook)
31
-
32
- assert_raise ShopifyApp::WebhooksManager::CreationFailed do
33
- @manager.create_webhooks
34
- end
35
- end
36
-
37
- test "#create_webhooks when creating a webhook fails and the webhook exists, do not raise an error" do
38
- webhook = stub(persisted?: false)
39
- webhooks = all_webhook_topics.map{|t| stub(topic: t)}
40
- ShopifyAPI::Webhook.stubs(create: webhook, all: webhooks)
41
-
42
- assert_nothing_raised ShopifyApp::WebhooksManager::CreationFailed do
43
- @manager.create_webhooks
44
- end
45
- end
46
-
47
- test "#recreate_webhooks! destroys all webhooks and recreates" do
48
- @manager.expects(:destroy_webhooks)
49
- @manager.expects(:create_webhooks)
50
-
51
- @manager.recreate_webhooks!
52
- end
53
-
54
- test "#destroy_webhooks makes calls to destroy webhooks" do
55
- ShopifyAPI::Webhook.stubs(:all).returns(Array.wrap(all_mock_webhooks.first))
56
- ShopifyAPI::Webhook.expects(:delete).with(all_mock_webhooks.first.id)
57
-
58
- @manager.destroy_webhooks
59
- end
60
-
61
- test "#destroy_webhooks does not destroy webhooks that do not have a matching address" do
62
- ShopifyAPI::Webhook.stubs(:all).returns([stub(address: 'http://something-or-the-other.com/webhooks/product_update', id: 7214109)])
63
- ShopifyAPI::Webhook.expects(:delete).never
64
-
65
- @manager.destroy_webhooks
66
- end
67
-
68
- private
69
-
70
- def expect_webhook_creation(topic, address)
71
- stub_webhook = stub(persisted?: true)
72
- ShopifyAPI::Webhook.expects(:create).with(topic: topic, address: address, format: 'json').returns(stub_webhook)
73
- end
74
-
75
- def all_webhook_topics
76
- @webhooks ||= ['app/uninstalled', 'orders/create', 'orders/updated']
77
- end
78
-
79
- def all_mock_webhooks
80
- [
81
- stub(id: 1, address: "https://example-app.com/webhooks/app_uninstalled", topic: 'app/uninstalled'),
82
- stub(id: 2, address: "https://example-app.com/webhooks/order_create", topic: 'orders/create'),
83
- stub(id: 3, address: "https://example-app.com/webhooks/order_updated", topic: 'orders/updated'),
84
- ]
85
- end
86
- end