automation-api 0.3.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.
- checksums.yaml +7 -0
- data/.dependabot/config.yml +8 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELONG.md +13 -0
- data/Dockerfile +16 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +149 -0
- data/Jenkinsfile +31 -0
- data/README.md +176 -0
- data/Rakefile +44 -0
- data/automation-api.gemspec +42 -0
- data/bin/console +17 -0
- data/bin/setup +8 -0
- data/config/basic.yml +16 -0
- data/config/essential.yml +21 -0
- data/config/loyalty.yml +7 -0
- data/config/online.yml +17 -0
- data/config/profile.yml.example +231 -0
- data/config/restrictions.yml +55 -0
- data/config/starter.yml +11 -0
- data/docker-compose.yml +12 -0
- data/exe/store-gen +6 -0
- data/lib/automation/api.rb +17 -0
- data/lib/automation/api/cli.rb +320 -0
- data/lib/automation/api/client.rb +133 -0
- data/lib/automation/api/requests.rb +26 -0
- data/lib/automation/api/requests/account_owner.rb +22 -0
- data/lib/automation/api/requests/app_sync.rb +93 -0
- data/lib/automation/api/requests/button_pages.rb +34 -0
- data/lib/automation/api/requests/buttons.rb +34 -0
- data/lib/automation/api/requests/categories.rb +34 -0
- data/lib/automation/api/requests/collection.rb +32 -0
- data/lib/automation/api/requests/customers.rb +34 -0
- data/lib/automation/api/requests/departments.rb +34 -0
- data/lib/automation/api/requests/employees.rb +34 -0
- data/lib/automation/api/requests/entitlements.rb +33 -0
- data/lib/automation/api/requests/features.rb +33 -0
- data/lib/automation/api/requests/helper.rb +59 -0
- data/lib/automation/api/requests/location_preferences.rb +22 -0
- data/lib/automation/api/requests/loyalty_provisioning.rb +22 -0
- data/lib/automation/api/requests/matrix_products.rb +34 -0
- data/lib/automation/api/requests/modifiers.rb +34 -0
- data/lib/automation/api/requests/named_discounts.rb +34 -0
- data/lib/automation/api/requests/online_orders.rb +18 -0
- data/lib/automation/api/requests/registers.rb +30 -0
- data/lib/automation/api/requests/sales.rb +22 -0
- data/lib/automation/api/requests/sales_restrictions.rb +34 -0
- data/lib/automation/api/requests/settings.rb +22 -0
- data/lib/automation/api/requests/stock_items.rb +34 -0
- data/lib/automation/api/requests/tax_rate_service_taxes.rb +34 -0
- data/lib/automation/api/requests/taxes.rb +34 -0
- data/lib/automation/api/requests/tenders.rb +38 -0
- data/lib/automation/api/version.rb +7 -0
- metadata +321 -0
data/config/starter.yml
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
version: '3'
|
2
|
+
|
3
|
+
services:
|
4
|
+
test:
|
5
|
+
build: .
|
6
|
+
environment:
|
7
|
+
- CODECLIMATE_REPO_TOKEN
|
8
|
+
- COVERAGE
|
9
|
+
- GIT_COMMIT
|
10
|
+
- GIT_BRANCH
|
11
|
+
command: >
|
12
|
+
bash -c "rspec --format progress --format RspecJunitFormatter --out /usr/artifacts/rspec.xml && bundle exec codeclimate-test-reporter && bundle exec rake pact:publish"
|
data/exe/store-gen
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'automation/api/cli'
|
4
|
+
require 'automation/api/client'
|
5
|
+
require 'automation/api/version'
|
6
|
+
|
7
|
+
module Automation
|
8
|
+
# Automation::API
|
9
|
+
module API
|
10
|
+
# Your code here
|
11
|
+
ROOT = File.expand_path('../../', __dir__)
|
12
|
+
|
13
|
+
def self.root
|
14
|
+
ROOT
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'thor'
|
5
|
+
require 'yaml'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module Automation
|
9
|
+
module API
|
10
|
+
# Automation::API::CLI
|
11
|
+
class CLI < Thor
|
12
|
+
DEFAULT_PROFILE = 'starter'
|
13
|
+
PROFILE_PATH = "#{File.expand_path('../../../', __dir__)}/config"
|
14
|
+
PROFILE_EXAMPLE = "#{PROFILE_PATH}/profile.yml.example"
|
15
|
+
|
16
|
+
ARCHETYPES = %w[
|
17
|
+
complex
|
18
|
+
coffee_shop
|
19
|
+
empty
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
PROCESSORS = %w[
|
23
|
+
tgate_dev
|
24
|
+
tgate_prod
|
25
|
+
tgate_mock
|
26
|
+
clover
|
27
|
+
freedompay_uat
|
28
|
+
tgate_emulator_uat
|
29
|
+
broken
|
30
|
+
none
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
desc 'create [PROFILE]', 'Create store of using specified profile'
|
34
|
+
|
35
|
+
method_option :archetype,
|
36
|
+
enum: ARCHETYPES,
|
37
|
+
aliases: '-a',
|
38
|
+
default: 'empty',
|
39
|
+
desc: 'Store archetype - the base upon which the profile is built'
|
40
|
+
|
41
|
+
method_option :license_count,
|
42
|
+
type: :numeric,
|
43
|
+
aliases: '-l',
|
44
|
+
default: 100,
|
45
|
+
desc: 'Number of register license available'
|
46
|
+
|
47
|
+
method_option :subdomain,
|
48
|
+
aliases: '-s',
|
49
|
+
default: "test-auto-thor-#{SecureRandom.hex(4)}",
|
50
|
+
desc: 'Store subdomain - randomly generated if not given'
|
51
|
+
|
52
|
+
method_option :email,
|
53
|
+
aliases: '-e',
|
54
|
+
desc: 'Email address associated with account - randomly generated if not given'
|
55
|
+
|
56
|
+
method_option :password,
|
57
|
+
aliases: '-p',
|
58
|
+
default: 'password',
|
59
|
+
desc: 'Password for account owner'
|
60
|
+
|
61
|
+
method_option :time_zone,
|
62
|
+
aliases: '-t',
|
63
|
+
default: 'Eastern Time (US & Canada)',
|
64
|
+
desc: 'Store time zone'
|
65
|
+
|
66
|
+
method_option :sk_expert,
|
67
|
+
aliases: '-x',
|
68
|
+
desc: "I'm not entirly sure what this is"
|
69
|
+
|
70
|
+
method_option :sk_employee,
|
71
|
+
aliases: '-e',
|
72
|
+
desc: "I'm not entirly sure what this is"
|
73
|
+
|
74
|
+
method_option :processor,
|
75
|
+
enum: PROCESSORS,
|
76
|
+
aliases: '-c',
|
77
|
+
default: 'tgate_dev',
|
78
|
+
desc: 'Credit card processor'
|
79
|
+
|
80
|
+
method_option :profile_path,
|
81
|
+
aliases: '--path',
|
82
|
+
default: PROFILE_PATH,
|
83
|
+
desc: 'Specify a path to the directory containing the profile configuration'
|
84
|
+
|
85
|
+
def create(profile = DEFAULT_PROFILE)
|
86
|
+
@saved = {}
|
87
|
+
@client = Automation::API::Client.create(options)
|
88
|
+
|
89
|
+
file_path = File.expand_path("#{profile}.yml", options[:profile_path])
|
90
|
+
|
91
|
+
raise 'Store profile not found' unless File.exist?(file_path)
|
92
|
+
|
93
|
+
configuration = YAML.load_file(file_path)
|
94
|
+
|
95
|
+
configuration.each do |key, value|
|
96
|
+
next unless private_methods.include?(key)
|
97
|
+
|
98
|
+
send(key, value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'list', 'List of predifined store profiles'
|
103
|
+
|
104
|
+
method_option :profile_path,
|
105
|
+
aliases: '--path',
|
106
|
+
default: PROFILE_PATH,
|
107
|
+
desc: 'Specify a path to the directory containing the profile configuration'
|
108
|
+
|
109
|
+
def list
|
110
|
+
regex = %r{([^\/]*).yml$}x
|
111
|
+
file_paths = Dir["#{options[:profile_path]}/*"]
|
112
|
+
|
113
|
+
file_paths.map { |file| file.match(regex) }
|
114
|
+
.compact
|
115
|
+
.map { |match| match[1] }
|
116
|
+
.sort
|
117
|
+
.each { |profile| puts profile }
|
118
|
+
end
|
119
|
+
|
120
|
+
desc 'view [PROFILE]', 'View config file for requested profile'
|
121
|
+
|
122
|
+
method_option :config_path,
|
123
|
+
aliases: '--path',
|
124
|
+
default: PROFILE_PATH,
|
125
|
+
desc: 'Specify a path to the directory containing the profile configuration'
|
126
|
+
|
127
|
+
def view(store_type = DEFAULT_STORE_TYPE)
|
128
|
+
path = "#{options[:config_path]}/#{store_type}.yml"
|
129
|
+
|
130
|
+
if File.exist?(path)
|
131
|
+
puts File.read(path)
|
132
|
+
else
|
133
|
+
puts 'No configuration file found'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
desc 'new_profile [NAME]', 'Start creating a new custom profile'
|
138
|
+
|
139
|
+
method_option :profile_path,
|
140
|
+
aliases: '--path',
|
141
|
+
default: Dir.pwd,
|
142
|
+
desc: 'Specify a path to the directory where profile configuration will be saved'
|
143
|
+
|
144
|
+
def new_profile(name = 'custom_profile')
|
145
|
+
dest = File.expand_path("#{name}.yml", options[:profile_path])
|
146
|
+
|
147
|
+
FileUtils.cp(PROFILE_EXAMPLE, dest)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def account_owner(config)
|
153
|
+
print 'Updating account owner information'
|
154
|
+
|
155
|
+
@client.update_account_owner(config)
|
156
|
+
|
157
|
+
puts '...done'
|
158
|
+
end
|
159
|
+
|
160
|
+
def loyalty_provisioned(provision)
|
161
|
+
print "#{provision ? 'P' : 'Dep'}rovisioning loyalty"
|
162
|
+
|
163
|
+
provision ? @client.provision : @client.deprovison
|
164
|
+
|
165
|
+
puts '...done'
|
166
|
+
end
|
167
|
+
|
168
|
+
def location_preferences(config)
|
169
|
+
print 'Updating location preferences'
|
170
|
+
|
171
|
+
@client.update_location_preferences(config)
|
172
|
+
|
173
|
+
puts '...done'
|
174
|
+
end
|
175
|
+
|
176
|
+
def entitlements(params)
|
177
|
+
print 'Updating entitlements'
|
178
|
+
|
179
|
+
@client.update_entitlements(params)
|
180
|
+
|
181
|
+
puts '...done'
|
182
|
+
end
|
183
|
+
|
184
|
+
def features(params)
|
185
|
+
print 'Updating features'
|
186
|
+
|
187
|
+
@client.update_features(params)
|
188
|
+
|
189
|
+
puts '...done'
|
190
|
+
end
|
191
|
+
|
192
|
+
def settings(config)
|
193
|
+
print 'Updating settings'
|
194
|
+
|
195
|
+
@client.update_settings(config)
|
196
|
+
|
197
|
+
puts '...done'
|
198
|
+
end
|
199
|
+
|
200
|
+
def burn_first_register(burn)
|
201
|
+
return unless burn
|
202
|
+
|
203
|
+
print 'Burning first register'
|
204
|
+
|
205
|
+
@client.create_register
|
206
|
+
|
207
|
+
puts '...done'
|
208
|
+
end
|
209
|
+
|
210
|
+
def departments(config)
|
211
|
+
@saved[:departments] = []
|
212
|
+
|
213
|
+
config.each do |dept|
|
214
|
+
print "Creating department: #{dept[:name]}"
|
215
|
+
|
216
|
+
@saved[:departments] << @client.create_department(department: dept)
|
217
|
+
|
218
|
+
puts '...done'
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def categories(config)
|
223
|
+
@saved[:categories] = []
|
224
|
+
|
225
|
+
config.each do |category|
|
226
|
+
print "Creating category: #{category[:name]}"
|
227
|
+
|
228
|
+
dept = @saved[:departments].find { |d| d.name == category[:department] }
|
229
|
+
|
230
|
+
@saved[:categories] << @client.create_category(department_id: dept.id, category: category)
|
231
|
+
|
232
|
+
puts '...done'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def sales_restrictions(config)
|
237
|
+
@saved[:sales_restrictions] = []
|
238
|
+
|
239
|
+
config.each do |restriction|
|
240
|
+
print "Creating sales restriction: #{restriction[:name]}"
|
241
|
+
|
242
|
+
dept = @saved[:departments].find { |d| d.name == restriction[:department] }
|
243
|
+
ctgy = @saved[:categories].find { |c| c.name == restriction[:category] }
|
244
|
+
|
245
|
+
@saved[:sales_restrictions] << @client.create_sales_restriction(department_id: dept.id, category_id: ctgy.id, sales_restriction: restriction)
|
246
|
+
|
247
|
+
puts '...done'
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def stock_items(config)
|
252
|
+
@saved[:stock_items] = []
|
253
|
+
|
254
|
+
config.each do |item|
|
255
|
+
print "Creating stock item: #{item[:description]}"
|
256
|
+
|
257
|
+
dept = @saved[:departments].find { |d| d.name == item[:department] }
|
258
|
+
ctgy = @saved[:categories].find { |c| c.name == item[:category] }
|
259
|
+
|
260
|
+
item_params = item.merge(department_id: dept.id, category_id: ctgy.id)
|
261
|
+
|
262
|
+
@saved[:stock_items] << @client.create_stock_item(stock_item: item_params)
|
263
|
+
|
264
|
+
puts '...done'
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def button_pages(pages)
|
269
|
+
@saved[:button_pages] = []
|
270
|
+
|
271
|
+
pages.each do |page|
|
272
|
+
print "Creating button page: #{page[:name]}"
|
273
|
+
|
274
|
+
@saved[:button_pages] << @client.create_button_page(button_page: page)
|
275
|
+
|
276
|
+
puts '...done'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def buttons(config)
|
281
|
+
@saved[:buttons] = []
|
282
|
+
|
283
|
+
config.each do |button|
|
284
|
+
print "Creating button for: #{button[:item]}"
|
285
|
+
|
286
|
+
page = @saved[:button_pages].find { |pg| pg.name == button[:page] }
|
287
|
+
item = @saved[:stock_items].find { |i| i.description == button[:item] }
|
288
|
+
|
289
|
+
butt = @client.create_button(button_page_id: page.id,
|
290
|
+
button: button.merge(item_id: item.id))
|
291
|
+
|
292
|
+
@saved[:buttons] << butt
|
293
|
+
|
294
|
+
puts '...done'
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def online_ordering(_thing)
|
299
|
+
puts 'Online Ordering requires a active register. Please activate a register before continuing'
|
300
|
+
|
301
|
+
@client.enable_online_ordering if user_confirmation
|
302
|
+
end
|
303
|
+
|
304
|
+
def user_confirmation
|
305
|
+
puts '[Enter] to continue or [S] to skip'
|
306
|
+
|
307
|
+
answer = STDIN.gets.chomp.upcase
|
308
|
+
|
309
|
+
case answer
|
310
|
+
when ''
|
311
|
+
true
|
312
|
+
when 'S'
|
313
|
+
false
|
314
|
+
else
|
315
|
+
user_confirmation
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'automation/api/requests'
|
4
|
+
require 'json'
|
5
|
+
require 'pp'
|
6
|
+
require 'rest-client'
|
7
|
+
require 'stringio'
|
8
|
+
require 'zlib'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
module Automation
|
12
|
+
module API
|
13
|
+
# Automation::API::Client
|
14
|
+
class Client
|
15
|
+
include Requests::AccountOwner
|
16
|
+
include Requests::AppSync
|
17
|
+
include Requests::ButtonPages
|
18
|
+
include Requests::Buttons
|
19
|
+
include Requests::Categories
|
20
|
+
include Requests::Customers
|
21
|
+
include Requests::Departments
|
22
|
+
include Requests::Employees
|
23
|
+
include Requests::Entitlements
|
24
|
+
include Requests::Features
|
25
|
+
include Requests::LocationPreferences
|
26
|
+
include Requests::LoyaltyProvisioning
|
27
|
+
include Requests::MatrixProducts
|
28
|
+
include Requests::Modifiers
|
29
|
+
include Requests::NamedDiscounts
|
30
|
+
include Requests::OnlineOrders
|
31
|
+
include Requests::Registers
|
32
|
+
include Requests::Sales
|
33
|
+
include Requests::SalesRestrictions
|
34
|
+
include Requests::Settings
|
35
|
+
include Requests::StockItems
|
36
|
+
include Requests::Taxes
|
37
|
+
include Requests::TaxRateServiceTaxes
|
38
|
+
include Requests::Tenders
|
39
|
+
|
40
|
+
LOCAL_URL = 'http://admin.127.0.0.1.xip.io:3000'
|
41
|
+
MOCK_URL = 'http://localhost:1234'
|
42
|
+
URL = 'https://api.getshopkeep.com'
|
43
|
+
USERNAME = '8ec7225ad7aead3ef09265456f8de02c'
|
44
|
+
PASSWORD = 'X'
|
45
|
+
AUTH_TOKEN = 'OGVjNzIyNWFkN2FlYWQzZWYwOTI2NTQ1NmY4ZGUwMmM6WA=='
|
46
|
+
|
47
|
+
CONTENT_HEADERS = { content_type: :json, accept: :json }.freeze
|
48
|
+
|
49
|
+
DEFAULT_ARCHETYPE = 'complex'
|
50
|
+
DEFAULT_PROCESSOR = 'tgate_dev'
|
51
|
+
|
52
|
+
DEFAULT_OPTIONS = { archetype: DEFAULT_ARCHETYPE,
|
53
|
+
processor: DEFAULT_PROCESSOR }.freeze
|
54
|
+
|
55
|
+
def self.create(options = DEFAULT_OPTIONS)
|
56
|
+
store_data = generate_store(options)
|
57
|
+
|
58
|
+
pp store_data
|
59
|
+
|
60
|
+
store_settings(subdomain: store_data['subdomain'],
|
61
|
+
processor: options['processor'])
|
62
|
+
|
63
|
+
new(uuid: store_data['uuid'])
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.generate_store(options)
|
67
|
+
response = RestClient.post("#{URL}/dev/test/generate_store",
|
68
|
+
options.to_json,
|
69
|
+
content_type: :json,
|
70
|
+
accept: :json,
|
71
|
+
authorization: "Basic #{AUTH_TOKEN}")
|
72
|
+
|
73
|
+
JSON.parse(response.body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.store_settings(settings)
|
77
|
+
response = RestClient.post("#{URL}/dev/test/store_settings",
|
78
|
+
settings.to_json,
|
79
|
+
content_type: :json,
|
80
|
+
accept: :json,
|
81
|
+
authorization: "Basic #{AUTH_TOKEN}")
|
82
|
+
|
83
|
+
JSON.parse(response.body)
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :uuid, :store, :login, :password
|
87
|
+
|
88
|
+
def initialize(uuid:, store: '', login: '', password: '', url: URL)
|
89
|
+
@uuid = uuid
|
90
|
+
@store = store
|
91
|
+
@login = login
|
92
|
+
@password = password
|
93
|
+
@base_uri = "#{url}/dev/test/store/#{uuid}"
|
94
|
+
@trs_uri = "#{url}/tax-rates-service/stores/#{uuid}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def dev_headers
|
98
|
+
@dev_headers ||= CONTENT_HEADERS.merge(authorization: "Basic #{AUTH_TOKEN}")
|
99
|
+
end
|
100
|
+
|
101
|
+
def jwt_headers
|
102
|
+
@jwt_headers ||= CONTENT_HEADERS.merge(authorization: "Token token=#{jwt_token}")
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def jwt_token
|
108
|
+
header, payload, sig = *decompress_token
|
109
|
+
|
110
|
+
encoded_header = Base64.strict_encode64(header).match(/^[^=]*/)
|
111
|
+
encoded_payload = Base64.strict_encode64(payload).match(/^[^=]*/)
|
112
|
+
|
113
|
+
"#{encoded_header}.#{encoded_payload}.#{sig}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def decompress_token
|
117
|
+
raw = Base64.decode64(auth_token)
|
118
|
+
gz = Zlib::GzipReader.new(StringIO.new(raw))
|
119
|
+
|
120
|
+
JSON.parse(gz.read)
|
121
|
+
end
|
122
|
+
|
123
|
+
def auth_token
|
124
|
+
url = 'https://api.getshopkeep.com/auth'
|
125
|
+
data = { store_name: store, password: password, login: login }.to_json
|
126
|
+
|
127
|
+
RestClient.post(url, data, content_type: :json) do |response, _, _|
|
128
|
+
response.cookies.fetch('_sk_oidc_auth_token')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|