shopify_dashboard_plus 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +16 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile.lock +99 -35
  5. data/README.md +16 -7
  6. data/Rakefile +13 -0
  7. data/bin/shopify_dashboard_plus.rb +1 -1
  8. data/config.ru +4 -0
  9. data/lib/shopify_dashboard_plus.rb +40 -52
  10. data/lib/shopify_dashboard_plus/currency.rb +31 -0
  11. data/lib/shopify_dashboard_plus/discount_report.rb +36 -0
  12. data/lib/shopify_dashboard_plus/helpers.rb +65 -51
  13. data/lib/shopify_dashboard_plus/report.rb +4 -196
  14. data/lib/shopify_dashboard_plus/revenue_report.rb +55 -0
  15. data/lib/shopify_dashboard_plus/sales_report.rb +86 -0
  16. data/lib/shopify_dashboard_plus/traffic_report.rb +65 -0
  17. data/lib/shopify_dashboard_plus/version.rb +3 -1
  18. data/shopify_dashboard_plus.gemspec +17 -7
  19. data/test/fixtures/vcr_cassettes/.gitkeep +0 -0
  20. data/test/fixtures/vcr_cassettes/authenticate.yml +88 -0
  21. data/test/fixtures/vcr_cassettes/multiple_pages_orders.yml +1544 -0
  22. data/test/fixtures/vcr_cassettes/orders_from_2010_01_01.yml +815 -0
  23. data/test/fixtures/vcr_cassettes/orders_from_2010_01_01_to_2015_01_01.yml +566 -0
  24. data/test/fixtures/vcr_cassettes/orders_no_paramaters.yml +81 -0
  25. data/test/fixtures/vcr_cassettes/orders_none.yml +77 -0
  26. data/test/fixtures/vcr_cassettes/orders_to_2015-06-26.yml +81 -0
  27. data/test/resources/anonymizer.rb +125 -0
  28. data/test/resources/modify_data.rb +110 -0
  29. data/test/strip_sensitive_data.rb +79 -0
  30. data/test/test_app.rb +60 -0
  31. data/test/test_frontend.rb +76 -0
  32. data/test/test_mockdata.rb +237 -0
  33. data/views/connect.erb +6 -6
  34. data/views/layout.erb +2 -3
  35. data/views/report.erb +1 -1
  36. metadata +190 -59
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'YAML'
4
+ require 'json'
5
+ require 'faker'
6
+ require_relative 'resources/modify_data'
7
+ require_relative 'resources/anonymizer'
8
+
9
+
10
+ def strip_shop_details(cassette_name:)
11
+ include ModifyData
12
+ include Anonymizer
13
+
14
+ # Load shop details authentication VCR cassette
15
+ request_data = YAML.load_file "test/fixtures/vcr_cassettes/#{cassette_name}"
16
+ response_payloads = request_data['http_interactions']
17
+
18
+ # Iterate through all requests saved in cassette
19
+ # Replace sensitive shop information with randomly generated information
20
+ response_payloads.each_with_index do |payload, index|
21
+ anonymous_shop = ModifyData.anonymize_shop(payload['response']['body']['string'])
22
+ response_payloads[index]['response']['body']['string'] = anonymous_shop
23
+ end
24
+
25
+ # Propagate new information to full request payload in YAML file
26
+ request_data['http_interactions'] = response_payloads
27
+ File.open("test/fixtures/vcr_cassettes/#{cassette_name}", 'w') { |f| YAML.dump(request_data, f) }
28
+ end
29
+
30
+
31
+ def strip_order_details(cassette_name:)
32
+ include ModifyData
33
+ include Anonymizer
34
+
35
+ # Load orders
36
+ request_data = YAML.load_file "test/fixtures/vcr_cassettes/#{cassette_name}"
37
+ response_payloads = request_data['http_interactions']
38
+
39
+ # Iterate through all requests saved in cassette
40
+ # Replace all sensitive order data with randomly generated information
41
+ response_payloads.each_with_index do |payload, index|
42
+ anonymous_order = ModifyData.anonymize_orders(payload['response']['body']['string'])
43
+ response_payloads[index]['response']['body']['string'] = anonymous_order
44
+ end
45
+
46
+ # Propagate new information to full request payload in YAML file
47
+ request_data['http_interactions'] = response_payloads
48
+ File.open("test/fixtures/vcr_cassettes/#{cassette_name}", 'w') { |f| YAML.dump(request_data, f) }
49
+ end
50
+
51
+
52
+ def duplicate_orders(cassette_name:, multiplier:, output_cassette:)
53
+ include ModifyData
54
+ include Anonymizer
55
+
56
+ # Load orders
57
+ request_data = YAML.load_file "test/fixtures/vcr_cassettes/#{cassette_name}"
58
+ response_payloads = request_data['http_interactions']
59
+
60
+ # Iterate through all requests saved in cassette
61
+ # Replace all sensitive order data with randomly generated information
62
+ response_payloads.each_with_index do |payload, index|
63
+ duplicated_order_list = ModifyData.duplicate_orders(payload['response']['body']['string'], multiplier_constant: multiplier)
64
+ response_payloads[index]['response']['body']['string'] = duplicated_order_list
65
+ end
66
+
67
+ # Propagate new information to full request payload in YAML file
68
+ request_data['http_interactions'] = response_payloads
69
+ File.open("test/fixtures/vcr_cassettes/#{output_cassette}", 'w') { |f| YAML.dump(request_data, f) }
70
+ end
71
+
72
+
73
+ strip_shop_details(cassette_name: 'authenticate.yml')
74
+ strip_order_details(cassette_name: 'orders_from_2010_01_01_to_2015_01_01.yml')
75
+ strip_order_details(cassette_name: 'orders_from_2010_01_01.yml')
76
+ strip_order_details(cassette_name: 'orders_no_paramaters.yml')
77
+ strip_order_details(cassette_name: 'orders_to_2015-06-26.yml')
78
+ duplicate_orders(cassette_name: 'orders_from_2010_01_01_to_2015_01_01.yml', multiplier: 3, output_cassette: 'multiple_pages_orders.yml')
79
+
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'rack/test'
5
+ require 'tilt/erb'
6
+ require 'vcr'
7
+ require 'webmock'
8
+ require './lib/shopify_dashboard_plus.rb'
9
+ require './lib/shopify_dashboard_plus/version'
10
+ require './lib/shopify_dashboard_plus/helpers'
11
+ require './lib/shopify_dashboard_plus/discount_report'
12
+ require './lib/shopify_dashboard_plus/revenue_report'
13
+ require './lib/shopify_dashboard_plus/sales_report'
14
+ require './lib/shopify_dashboard_plus/traffic_report'
15
+
16
+ ENV['RACK_ENV'] = 'test'
17
+
18
+ ## Application tests related to:
19
+ ## Launching application, File Structure, and Version
20
+
21
+
22
+ class TestShopifyDashboardPlus < MiniTest::Test
23
+ include Rack::Test::Methods
24
+
25
+ # VCR from test_mockdata test suite should not intercept these HTTP requests
26
+ VCR.turned_off do
27
+
28
+ # Allow real HTTP Requests
29
+ WebMock.allow_net_connect!
30
+
31
+ def app
32
+ Capybara.app = Sinatra::Application
33
+ end
34
+
35
+ def test_version_exists
36
+ # Should be formatted as A.B.C (A, B, C all integers) Ex. => 2.1.3
37
+ # Ensure the portion before the last decimal converts to an integer (A.B). Ex => 2.1.to_i
38
+ @version = ShopifyDashboardPlus::VERSION
39
+ assert @version.gsub(/.\d\Z/, "").to_i, "#{@version} does not seem to be formated as X.Y.Z where X, Y, Z are integers"
40
+ end
41
+
42
+ def test_directories_exist
43
+ assert_equal(true, File.directory?("./bin"), "bin directory does not exist!")
44
+ assert_equal(true, File.directory?("./lib"), "lib directory does not exist!")
45
+ assert_equal(true, File.directory?("./lib/shopify_dashboard_plus"), "lib/shopify_dashboard_plus directory does not exist!")
46
+ assert_equal(true, File.directory?("./public"), "public directory does not exist!")
47
+ assert_equal(true, File.directory?("./public/css"), "public/css directory does not exist!")
48
+ assert_equal(true, File.directory?("./public/js"), "public/js directory does not exist!")
49
+ assert_equal(true, File.directory?("./test"), "test directory does not exist!")
50
+ assert_equal(true, File.directory?("./test/fixtures"), "test/fixtures directory does not exist!")
51
+ assert_equal(true, File.directory?("./views"), "views directory does not exist!")
52
+ end
53
+
54
+ def test_launch_sinatra
55
+ # Script returns true for zero exit status, false for non-zero
56
+ assert true, `ruby "./bin/shopify_dashboard_plus.rb"`
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'rack/test'
5
+ require 'capybara'
6
+ require 'capybara/dsl'
7
+ require 'capybara/webkit'
8
+ require 'tilt/erb'
9
+ require 'vcr'
10
+ require 'webmock'
11
+ require './lib/shopify_dashboard_plus.rb'
12
+ require './lib/shopify_dashboard_plus/version'
13
+ require './lib/shopify_dashboard_plus/helpers'
14
+ require './lib/shopify_dashboard_plus/discount_report'
15
+ require './lib/shopify_dashboard_plus/revenue_report'
16
+ require './lib/shopify_dashboard_plus/sales_report'
17
+ require './lib/shopify_dashboard_plus/traffic_report'
18
+
19
+ ENV['RACK_ENV'] = 'test'
20
+
21
+ ## Application tests related to:
22
+ ## Front-end testing (flash errors messages, etc) using Capybara-Webkit
23
+
24
+
25
+ class TestShopifyDashboardPlus < MiniTest::Test
26
+ include Rack::Test::Methods
27
+ include Capybara::DSL
28
+
29
+ # VCR from test_mockdata test suite should not intercept these HTTP requests
30
+ VCR.turned_off do
31
+
32
+ # Allow real HTTP Requests
33
+ WebMock.allow_net_connect!
34
+
35
+ # Necessary to use capybara with sinatra applicaiton
36
+ Capybara.app = Sinatra::Application
37
+
38
+
39
+ def setup
40
+ Capybara.configure do |config|
41
+ config.run_server = false
42
+ config.current_driver = :webkit
43
+ config.default_driver = :webkit
44
+ config.javascript_driver = :webkit
45
+ config.page.driver.browser.ignore_ssl_errors
46
+ config.default_wait_time = 10
47
+ config.always_include_port = true
48
+ config.server_port = 31_337
49
+ config.app_host = "http://127.0.0.1"
50
+ end
51
+ end
52
+
53
+ def invalid_connection(api_key: nil, api_pwd: nil, shop_name: nil)
54
+ assert_equal("/connect", page.current_path)
55
+ within '#connect-box' do
56
+ fill_in 'api_key', :with => api_key if api_key
57
+ fill_in 'api_pwd', :with => api_pwd if api_pwd
58
+ fill_in 'shop_name', :with => shop_name if shop_name
59
+ click_button 'connect'
60
+ end
61
+ assert_equal 'Failed to Connect...', find('p#flash').text
62
+ end
63
+
64
+ def test_invalid_credentials_flash_message
65
+ page.visit '/connect'
66
+ invalid_connection(api_key: "bad_api_key")
67
+ page.visit '/connect'
68
+ invalid_connection(api_pwd: "bad_api_pwd")
69
+ page.visit '/connect'
70
+ invalid_connection(shop_name: "bad_shop_name")
71
+ page.visit '/connect'
72
+ invalid_connection
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'rack/test'
5
+ require 'tilt/erb'
6
+ require 'capybara'
7
+ require 'vcr'
8
+ require 'byebug'
9
+ require './lib/shopify_dashboard_plus.rb'
10
+ require './lib/shopify_dashboard_plus/version'
11
+ require './lib/shopify_dashboard_plus/helpers'
12
+ require './lib/shopify_dashboard_plus/discount_report'
13
+ require './lib/shopify_dashboard_plus/revenue_report'
14
+ require './lib/shopify_dashboard_plus/sales_report'
15
+ require './lib/shopify_dashboard_plus/traffic_report'
16
+
17
+ ENV['RACK_ENV'] = 'test'
18
+
19
+ VCR.configure do |config|
20
+ config.cassette_library_dir = 'test/fixtures/vcr_cassettes'
21
+
22
+ # Shopify Gem uses Net::HTTP
23
+ # Requests can be captured using webmock
24
+ config.hook_into :webmock
25
+ config.ignore_hosts '127.0.0.1', 'localhost', '0.0.0.0', 'example.com'
26
+ # config.debug_logger = File.open('request_log.log', 'w')
27
+
28
+ # Allow other test suites to send real HTTP requests
29
+ config.allow_http_connections_when_no_cassette = true
30
+
31
+ # Show response payload in body as plaintext and hide API credentials
32
+ config.before_record { |cassette| cassette.response.body.force_encoding('UTF-8') }
33
+ config.filter_sensitive_data('<API_KEY>') { ENV['API_KEY'] || "testkey" }
34
+ config.filter_sensitive_data('<API_PWD>') { ENV['API_PWD'] || "testpwd" }
35
+ config.filter_sensitive_data('<SHOP_NAME>') { ENV['SHOP_NAME'] || "testshop" }
36
+
37
+ # Match certain requests by date in the payload
38
+ config.register_request_matcher :port do |req1, req2|
39
+ @today == VCR::HTTPInteraction
40
+ end
41
+ end
42
+
43
+
44
+ class TestShopifyDashboardPlus < MiniTest::Test
45
+ include Rack::Test::Methods
46
+
47
+ attr_accessor :authenticated
48
+
49
+ def app
50
+ Sinatra::Application
51
+ end
52
+
53
+ def setup
54
+ @today = DateTime.now.strftime('%Y-%m-%d')
55
+ @hardcoded_day = "2015-06-26"
56
+ end
57
+
58
+
59
+ #######################
60
+ ## Common Methods
61
+ #######################
62
+
63
+ def env_set?
64
+ true if ENV['API_KEY'] && ENV['API_PWD'] && ENV['SHOP_NAME']
65
+ end
66
+
67
+ def authenticate
68
+ return if authenticated
69
+
70
+ VCR.use_cassette('authenticate', :match_requests_on => [:path]) do
71
+
72
+ # Use environment variables if specified, otherwise use fake information
73
+ # Shopify URL will appear as <api_key>:<api_pwd>@<storename>.myshopify.com/<path>
74
+ # VCR casettes should match on the URI path, not the host information
75
+ payload = if env_set?
76
+ "api_key=#{ENV['API_KEY']}&api_pwd=#{ENV['API_PWD']}&shop_name=#{ENV['SHOP_NAME']}"
77
+ else
78
+ "api_key=testkey&api_pwd=testpwd&shop_name=testshop"
79
+ end
80
+
81
+ post('/connect', payload, "Content-Type" => "application/x-www-form-urlencoded")
82
+ end
83
+ end
84
+
85
+ def build_url(from: nil, to: nil)
86
+ if from && to
87
+ "/?from=#{from}&to=#{to}"
88
+ elsif from
89
+ "/?from=#{from}"
90
+ elsif to
91
+ "/?to=#{to}"
92
+ else
93
+ "/"
94
+ end
95
+ end
96
+
97
+ def validate_body(returned_body)
98
+ # No Three digit precision decimal points
99
+ assert_equal 0, returned_body.scan(/[0-9]{1,}[.][0-9]{3,}/).length
100
+
101
+ # Time / Times is pluralized correctly
102
+ assert_equal 0, returned_body.scan(/1 Times/).length
103
+ assert_equal 0, returned_body.scan(/[02-9] Time^[s]/).length
104
+
105
+ # Referral / Referrals is pluralized correctly
106
+ assert_equal 0, returned_body.scan(/1 Referrals/).length
107
+ assert_equal 0, returned_body.scan(/[02-9] Referral^[s]/).length
108
+
109
+ # Valid Response
110
+ assert_match(/Retrieve metrics over the following period/, returned_body)
111
+ assert_equal 0, returned_body.scan(/Invalid Dates. Please use format YYYY-MM-DD/).length
112
+ end
113
+
114
+
115
+ #######################
116
+ ## Test Cases
117
+ #######################
118
+
119
+ def test_unauthorized_redirect
120
+ VCR.turned_off do
121
+ get '/?from=2013-01-01&to=2015-01-01'
122
+ follow_redirect!
123
+ assert_match(/connect/, last_request.fullpath)
124
+ end
125
+ end
126
+
127
+ def test_no_parameters
128
+ # Validate results with no start date or end date parameter set
129
+ # Results should default to today's results
130
+ return unless env_set?
131
+
132
+ authenticate
133
+ url = build_url
134
+
135
+ # Will reuse cassette for tests run the same day (in which the URL paramater created_at_min=YYYY-MM-DD will be identical)
136
+ # Will append a new entry on a new day
137
+ VCR.use_cassette(:orders_no_paramaters, :erb => { :today => @today }, :record => :once, :match_requests_on => [:method]) do
138
+ r = get url
139
+ assert_equal last_request.fullpath, '/'
140
+
141
+ # Ensure default start and end date are today's date
142
+ assert_equal 2, r.body.scan(/placeholder=\"#{@today}\"/).length
143
+ validate_body(r.body)
144
+ end
145
+ end
146
+
147
+
148
+ def test_only_start_date_parameter
149
+ # Validate results with only start date parameter set
150
+ # End date should default to today
151
+ authenticate
152
+ url = build_url(:from => "2010-01-01")
153
+
154
+ VCR.use_cassette(:orders_from_2010_01_01, :record => :once, :match_requests_on => [:path]) do
155
+ r = get url
156
+ assert_equal '/?from=2010-01-01', last_request.fullpath
157
+ validate_body(r.body)
158
+ end
159
+ end
160
+
161
+
162
+ def test_only_end_date_parameter
163
+ # Validate results with only the end date parameter set
164
+ authenticate
165
+ url = build_url(:to => @hardcoded_day)
166
+
167
+ VCR.use_cassette("orders_to_#{@hardcoded_day}", :match_requests_on => [:path]) do
168
+ r = get url
169
+ assert_equal "/?to=#{@hardcoded_day}", last_request.fullpath
170
+ validate_body(r.body)
171
+ end
172
+ end
173
+
174
+
175
+ def test_valid_date_range
176
+ # Validate results over a valid start date and end date
177
+ authenticate
178
+ url = build_url(:from => "2010-01-01", :to => "2015-01-01")
179
+
180
+ VCR.use_cassette(:orders_from_2010_01_01_to_2015_01_01, :match_requests_on => [:path]) do
181
+ r = get url
182
+
183
+ assert_equal '/?from=2010-01-01&to=2015-01-01', last_request.fullpath
184
+ validate_body(r.body)
185
+ end
186
+ end
187
+
188
+
189
+ def test_pagination
190
+ # Validate that more than 250 results can be returned (the limit)
191
+ authenticate
192
+ url = build_url(:from => "2010-01-01", :to => "2015-01-01")
193
+
194
+ VCR.use_cassette(:multiple_pages_orders, :match_requests_on => [:path]) do
195
+ r = get url
196
+
197
+ sales_regexp = r.body.scan(/(Number of Sales[<][\/]h5>)\n(.*[<]h3[ ]class=["]money["][>][0-9]*)/).first[1]
198
+ number_of_sales = sales_regexp.scan(/\d+$/).first
199
+
200
+ assert number_of_sales.to_i > 250, "number of sales (#{number_of_sales}) does not encompass at least two pages (> 250 entries)"
201
+ end
202
+ end
203
+
204
+
205
+ def test_empty_order_set
206
+ # Validate an empty order is rendered correctly
207
+ authenticate
208
+ url = build_url
209
+
210
+ VCR.use_cassette(:orders_none, :match_requests_on => [:path]) do
211
+ r = get url
212
+
213
+ validate_body(r.body)
214
+ end
215
+ end
216
+
217
+
218
+ #######################
219
+ # Negative Test Cases
220
+ #######################
221
+
222
+ def test_end_date_before_start_date
223
+ authenticate
224
+ url = build_url(:from => "2015-01-01", :to => "2010-01-01")
225
+
226
+ get url
227
+ assert_match(/Invalid Dates. Please use format YYYY-MM-DD/, last_response.body)
228
+ end
229
+
230
+ def test_unsupported_date_characters
231
+ authenticate
232
+ url = build_url(:from => "abcd", :to => "fghijk")
233
+
234
+ get url
235
+ assert_match(/Invalid Dates. Please use format YYYY-MM-DD/, last_response.body)
236
+ end
237
+ end