shopify_app 6.4.2 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
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