kpm 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d38b77f40a5cdb50c139255664892b3f463a44bf
4
- data.tar.gz: 41019b6c0119626701a6a89823237585d7588bf6
3
+ metadata.gz: 4f1e196bb197de525d51d65751e802c910921afa
4
+ data.tar.gz: 64e8c82a289a508aa3bb76ff84671a5da4120ca9
5
5
  SHA512:
6
- metadata.gz: b0457be8ee32667355291e3b936ec9aeeaba943480a6006a5fe12f7cf7175d6445ca3663b8a4435b8cbabf0b9205f8ce8949f4dd5d535f8b64254e5cae04b4f4
7
- data.tar.gz: 0a14351c90df0618a2013a1a12b43001b4e483228a5af3e7f8fb098028b30914081e13077c9db4c5e497ca0bd4d6e7b3a83b04759fd2b140d23882c188ea93b1
6
+ metadata.gz: 8d95f6091a305f3c5c208a8e1aad22600762d3d7b5422ba0b94fa2193a4d309140ff03064a4c36ee94f1985833d7547710b334e0f50f05c77c14e6a1f7c16945
7
+ data.tar.gz: 149dc3e06978318594845dce2ae8f91025fe9a0b73f799d0655d18b74ff3af3c4b4ee782680b08a09bd31474df25fd88ee74d0310d823a8c1ea0d3cfdee4cf98
data/README.md CHANGED
@@ -147,6 +147,31 @@ ________________________________________________________________________________
147
147
  _______________________________________________________________________________________________________________________________________________________
148
148
 
149
149
  ```
150
+ ### Test required setups
151
+
152
+ There are 3 suites of tests for KPM (see `rake -T`):
153
+
154
+ * `rake test:spec` : Fast suite of unit tests
155
+ * `rake test:remote:spec` : Test suite that relies on maven artifacts
156
+ * `rake test:mysql:spec` : Test suite that requires an instance of Kill Bill server running and a properly setup database
157
+
158
+ #### KPM Unit test
159
+
160
+ Unit tests don't require any third party system or configuration.
161
+
162
+ #### KPM remote test
163
+
164
+ Test suite that verifies the following:\
165
+
166
+ * KPM `install` command by pulling artifacts from maven repository
167
+ * KPM `migration` command. This requires setting the `TOKEN` system property with a valid GITHUB api token.
168
+
169
+ #### KPM mysql test
170
+
171
+ Test suite that requires an instance of `mysql` running and verifies the following:
172
+
173
+ * KPM `account` command: The `account_spec.yml` file needs to be modified with correct credentials and user must have correct privileges; also the database schema must not exist.
174
+ In addition, one must start an instance of a Kill Bill server
150
175
 
151
176
  ## Internals
152
177
 
data/Rakefile CHANGED
@@ -20,6 +20,14 @@ namespace :test do
20
20
  task.pattern = './spec/*/remote/*_spec.rb'
21
21
  end
22
22
  end
23
+
24
+ namespace :mysql do
25
+ desc 'Run RSpec MySql related tests'
26
+ RSpec::Core::RakeTask.new do |task|
27
+ task.name = 'spec'
28
+ task.pattern = './spec/*/unit_mysql/*_spec.rb'
29
+ end
30
+ end
23
31
  end
24
32
 
25
33
  # Run tests by default
@@ -46,5 +46,6 @@ Gem::Specification.new do |s|
46
46
 
47
47
  s.add_development_dependency 'rake', '>= 10.0.0', '< 11.0.0'
48
48
  s.add_development_dependency 'rspec', '~> 2.12.0'
49
+ s.add_development_dependency 'killbill-client', '~> 1.0'
49
50
  end
50
51
 
data/lib/kpm.rb CHANGED
@@ -18,6 +18,8 @@ module KPM
18
18
  autoload :PluginsDirectory, 'kpm/plugins_directory'
19
19
  autoload :Migrations, 'kpm/migrations'
20
20
  autoload :System, 'kpm/system'
21
+ autoload :Account, 'kpm/account'
22
+ autoload :Database, 'kpm/database'
21
23
 
22
24
  class << self
23
25
  def root
@@ -0,0 +1,527 @@
1
+ require 'net/http'
2
+ require 'tmpdir'
3
+ require 'yaml'
4
+ require 'date'
5
+ require 'securerandom'
6
+
7
+ module KPM
8
+
9
+ class Account
10
+
11
+ # Killbill server
12
+ KILLBILL_HOST = ENV['KILLBILL_HOST'] || '127.0.0.1'
13
+ KILLBILL_URL = 'http://'.concat(KILLBILL_HOST).concat(':8080')
14
+ KILLBILL_API_VERSION = '1.0'
15
+
16
+ # USER/PWD
17
+ KILLBILL_USER = ENV['KILLBILL_USER'] || 'admin'
18
+ KILLBILL_PASSWORD = ENV['KILLBILL_PASSWORD'] || 'password'
19
+
20
+ # TENANT KEY
21
+ KILLBILL_API_KEY = ENV['KILLBILL_API_KEY'] || 'bob'
22
+ KILLBILL_API_SECRET = ENV['KILLBILL_API_SECRET'] || 'lazar'
23
+
24
+ # Temporary directory
25
+ TMP_DIR_PEFIX = 'killbill'
26
+ TMP_DIR = Dir.mktmpdir(TMP_DIR_PEFIX);
27
+
28
+ # Created By
29
+ WHO = 'kpm_export_import'
30
+
31
+ # safe payment method
32
+ SAFE_PAYMENT_METHOD = '__EXTERNAL_PAYMENT__'
33
+ PLUGIN_NAME_COLUMN = 'plugin_name'
34
+
35
+ # fields to remove from the export files
36
+ REMOVE_DATA_FROM = {:accounts => [:name, :address1, :address2, :city, :state_or_province, :phone, :email],
37
+ :account_history => [:name, :address1, :address2, :city, :state_or_province, :phone, :email]}
38
+
39
+ DATE_COLUMNS_TO_FIX = ['created_date','updated_date','processing_available_date','effective_date',
40
+ 'boot_date','start_timestamp','last_access_time','payment_date','original_created_date',
41
+ 'last_sys_update_date','charged_through_date','bundle_start_date','start_date']
42
+
43
+ # round trip constants duplicate record
44
+ ROUND_TRIP_EXPORT_IMPORT_MAP = {:accounts => {:id => :accounts_id, :external_key => :accounts_id}, :all => {:account_id => :accounts_id},
45
+ :account_history => {:id => :account_history_id, :external_key => :accounts_id, :payment_method_id => :payment_methods_id},
46
+ :account_emails => {:id => :account_emails_id}, :account_email_history => {:id => :account_email_history_id},
47
+ :subscription_events => {:id => :subscription_events_id},:subscriptions => {:id => :subscriptions_id},
48
+ :bundles => {:id => :bundles_id},:blocking_states => {:id => :blocking_states_id, :blockable_id => nil},
49
+ :invoice_items => {:id => :invoice_items_id, :child_account_id => nil, :invoice_id => :invoices_id, :bundle_id => :bundles_id, :subscription_id => :subscriptions_id },
50
+ :invoices => {:id => :invoices_id},
51
+ :invoice_payments => {:id => :invoice_payments_id, :invoice_id => :invoices_id, :payment_id => :payments_id},
52
+ :invoice_parent_children => {:id => :invoice_parent_children_id, :parent_invoice_id => nil, :child_invoice_id => nil, :child_account_id => nil},
53
+ :payment_attempts => {:id => :payment_attempts_id, :payment_method_id => :payment_methods_id, :transaction_id => :payment_transactions_id},
54
+ :payment_attempt_history => {:id => :payment_attempt_history_id, :payment_method_id => :payment_methods_id, :transaction_id => :payment_transactions_id},
55
+ :payment_methods => {:id => :payment_methods_id, :external_key => :generate},:payment_method_history => {:id => :payment_method_history_id},
56
+ :payments => {:id => :payments_id, :payment_method_id => :payment_methods_id},
57
+ :payment_history => {:id => :payment_history_id, :payment_method_id => :payment_methods_id},
58
+ :payment_transactions => {:id => :payment_transactions_id, :payment_id => :payments_id},
59
+ :payment_transaction_history => {:id => :payment_transaction_history_id, :payment_id => :payments_id},
60
+ :_invoice_payment_control_plugin_auto_pay_off => {:payment_method_id => :payment_methods_id, :payment_id => :payments_id},
61
+ :rolled_up_usage => {:id => :rolled_up_usage_id, :subscription_id => :subscriptions_id, :tracking_id => nil},
62
+ :custom_fields => {:id => :custom_fields_id},:custom_field_history => {:id => :custom_field_history_id},
63
+ :tag_definitions => {:id => :tag_definitions_id},:tag_definition_history => {:id => :tag_definition_history_id},
64
+ :tags => {:id => :tags_id, :object_id => nil},
65
+ :tag_history => {:id => :tag_history_id, :object_id => nil},
66
+ :audit_log => {:id => :audit_log_id}
67
+ }
68
+
69
+ #delimeters to sniff
70
+ DELIMITERS = [',','|']
71
+ DEFAULT_DELIMITER = "|"
72
+
73
+ def initialize(config_file = nil, killbill_api_credentials = nil, killbill_credentials = nil, killbill_url = nil,
74
+ database_name = nil, database_credentials = nil, data_delimiter = nil, logger = nil)
75
+ @killbill_api_key = KILLBILL_API_KEY
76
+ @killbill_api_secrets = KILLBILL_API_SECRET
77
+ @killbill_url = KILLBILL_URL
78
+ @killbill_user = KILLBILL_USER
79
+ @killbill_password = KILLBILL_PASSWORD
80
+ @delimiter = data_delimiter || DEFAULT_DELIMITER
81
+ @logger = logger
82
+ @tables_id = Hash.new
83
+
84
+
85
+ set_killbill_options(killbill_api_credentials,killbill_credentials,killbill_url)
86
+ set_database_options(database_name,database_credentials,logger)
87
+
88
+ load_config_from_file(config_file)
89
+
90
+ end
91
+
92
+ def export_data(account_id = nil)
93
+
94
+ if account_id === :export.to_s
95
+ raise Interrupt, 'Need to specify an account id'
96
+ end
97
+
98
+ export_data = fetch_export_data(account_id)
99
+ export_file = export(export_data)
100
+
101
+ if not File.exist?(export_file)
102
+ raise Interrupt, 'Account id not found'
103
+ else
104
+ @logger.info "\e[32mData exported under #{export_file}\e[0m"
105
+ end
106
+
107
+ export_file
108
+ end
109
+
110
+ def import_data(source_file,tenant_record_id, skip_payment_methods, round_trip_export_import = false, generate_record_id = false)
111
+
112
+ @generate_record_id = generate_record_id
113
+ @tenant_record_id = tenant_record_id
114
+ @round_trip_export_import = round_trip_export_import
115
+
116
+ if source_file === :import.to_s
117
+ raise Interrupt, 'Need to specify a file'
118
+ end
119
+
120
+ if not File.exist?(source_file)
121
+ raise Interrupt, 'Need to specify a valid file'
122
+ end
123
+
124
+ @delimiter = sniff_delimiter(source_file) || @delimiter
125
+
126
+ sanitize_and_import(source_file, skip_payment_methods)
127
+ end
128
+
129
+ private
130
+
131
+ # export helpers: fetch_export_data; export; process_export_data; remove_export_data;
132
+ def fetch_export_data(account_id)
133
+ uri = URI("#{@killbill_url}/#{KILLBILL_API_VERSION}/kb/export/#{account_id}")
134
+
135
+ request = Net::HTTP::Get.new(uri.request_uri)
136
+ request.basic_auth(@killbill_user,@killbill_password)
137
+ request['X-Killbill-ApiKey'] = @killbill_api_key;
138
+ request['X-Killbill-ApiSecret'] = @killbill_api_secrets;
139
+ request['X-Killbill-CreatedBy'] = WHO;
140
+
141
+ response = Net::HTTP.start(uri.host,uri.port) do |http|
142
+ http.request(request)
143
+ end
144
+
145
+ if response.to_s.include? 'HTTPUnauthorized'
146
+ raise Interrupt, "User is unauthorized -> \e[93mUser[#{@killbill_user}],password[#{@killbill_password}],api_key[#{@killbill_api_key}],api_secret[#{@killbill_api_secrets}]\e[0m"
147
+ end
148
+
149
+ if not response.is_a?(Net::HTTPSuccess)
150
+ raise Interrupt, 'Account id not found'
151
+ end
152
+
153
+ response.body
154
+ end
155
+
156
+ def export(export_data)
157
+ export_file = TMP_DIR + File::SEPARATOR + 'kbdump'
158
+
159
+ open (export_file), 'w' do |io|
160
+
161
+ table_name = nil
162
+ cols_names = nil
163
+ export_data.split("\n").each do |line|
164
+ words = line.strip.split(" ")
165
+ clean_line = line
166
+ if not /--/.match(words[0]).nil?
167
+ table_name = words[1]
168
+ cols_names = words[2].strip.split(@delimiter)
169
+ elsif not table_name.nil?
170
+ clean_line = process_export_data(line,table_name,cols_names)
171
+ end
172
+ io.puts clean_line
173
+
174
+ end
175
+
176
+ end
177
+
178
+ export_file
179
+ end
180
+
181
+ def process_export_data(line_to_process, table_name, cols_names)
182
+ clean_line = line_to_process
183
+
184
+ row = []
185
+ cols = clean_line.strip.split(@delimiter)
186
+ cols_names.each_with_index { |col_name, index|
187
+ sanitized_value = remove_export_data(table_name,col_name,cols[index])
188
+
189
+ row << sanitized_value
190
+
191
+ }
192
+
193
+ clean_line = row.join(@delimiter)
194
+
195
+ clean_line
196
+ end
197
+
198
+ def remove_export_data(table_name,col_name,value)
199
+
200
+ if not REMOVE_DATA_FROM[table_name.to_sym].nil?
201
+
202
+ if REMOVE_DATA_FROM[table_name.to_sym].include? col_name.to_sym
203
+ return nil
204
+ end
205
+
206
+ end
207
+
208
+ value
209
+ end
210
+
211
+ # import helpers: sanitize_and_import; import; sanitize; replace_tenant_record_id; replace_account_record_id; replace_boolean;
212
+ # fix_dates; fill_empty_column;
213
+ def sanitize_and_import(source_file, skip_payment_methods)
214
+ tables = Hash.new
215
+ error_importing_data = false
216
+
217
+ open (source_file), 'r' do |data|
218
+
219
+ rows = nil;
220
+ table_name = nil;
221
+ cols_names = nil;
222
+
223
+ data.each_line do |line|
224
+ words = line.strip.split(" ")
225
+
226
+ if /--/.match(words[0])
227
+ if not table_name.nil?
228
+ if @generate_record_id
229
+ cols_names.shift
230
+ end
231
+
232
+ tables[table_name] = { :col_names => cols_names, :rows => rows};
233
+ end
234
+
235
+ table_name = words[1]
236
+ cols_names = words[2].strip.split(@delimiter)
237
+
238
+ rows = []
239
+ elsif not table_name.nil?
240
+ row = process_import_data(line, table_name,cols_names, skip_payment_methods, rows)
241
+
242
+ next if row.nil?
243
+
244
+ rows.push(row)
245
+ else
246
+ error_importing_data = true
247
+ break
248
+ end
249
+ end
250
+
251
+ if not ( table_name.nil? || error_importing_data )
252
+ if @generate_record_id
253
+ cols_names.shift
254
+ end
255
+
256
+ tables[table_name] = { :col_names => cols_names, :rows => rows};
257
+ end
258
+
259
+ if tables.empty?
260
+ error_importing_data = true
261
+ end
262
+ end
263
+
264
+ if not error_importing_data
265
+ import(tables)
266
+ else
267
+ raise Interrupt, "Data on #{source_file} is invalid"
268
+ end
269
+
270
+ end
271
+
272
+ def process_import_data(line, table_name, cols_names, skip_payment_methods, rows)
273
+ cols = line.strip.split(@delimiter)
274
+
275
+ if cols_names.size != cols.size
276
+ return nil
277
+ end
278
+
279
+ row = []
280
+
281
+ cols_names.each_with_index do |col_name, index|
282
+ sanitized_value = sanitize(table_name,col_name,cols[index], skip_payment_methods)
283
+
284
+ if not sanitized_value.nil?
285
+ row << sanitized_value
286
+ end
287
+ end
288
+
289
+ return row
290
+ end
291
+
292
+ def import(tables)
293
+ record_id = nil;
294
+ statements = Database.generate_insert_statement(tables)
295
+ statements.each do |statement|
296
+ response = Database.execute_insert_statement(statement[:table_name],statement[:query], statement[:qty_to_insert], statement[:table_data],record_id)
297
+
298
+ if statement[:table_name] == 'accounts' && response.is_a?(String)
299
+ record_id = {:variable => '@account_record_id', :value => response}
300
+ end
301
+
302
+ if response === false
303
+ break
304
+ end
305
+ end
306
+
307
+ end
308
+
309
+ def sanitize(table_name,column_name,value,skip_payment_methods)
310
+ sanitized_value = replace_boolean(value)
311
+ sanitized_value = fill_empty_column(sanitized_value)
312
+
313
+ if table_name == 'payment_methods' && skip_payment_methods && column_name == PLUGIN_NAME_COLUMN
314
+ sanitized_value = SAFE_PAYMENT_METHOD
315
+ end
316
+
317
+ if DATE_COLUMNS_TO_FIX.include? column_name
318
+ sanitized_value = fix_dates(sanitized_value)
319
+ end
320
+
321
+ if not @tenant_record_id.nil?
322
+ sanitized_value = replace_tenant_record_id(table_name,column_name,sanitized_value)
323
+ end
324
+
325
+ if @generate_record_id
326
+ sanitized_value = replace_account_record_id(table_name,column_name,sanitized_value)
327
+ end
328
+
329
+ if @round_trip_export_import
330
+ sanitized_value = replace_uuid(table_name,column_name,sanitized_value)
331
+ end
332
+
333
+ sanitized_value
334
+ end
335
+
336
+ def replace_tenant_record_id(table_name,column_name,value)
337
+ return @tenant_record_id if column_name == 'tenant_record_id' || column_name == 'search_key2'
338
+ value
339
+ end
340
+
341
+ def replace_account_record_id(table_name,column_name,value)
342
+
343
+ if column_name == 'account_record_id'
344
+
345
+ return :@account_record_id
346
+ end
347
+
348
+ if column_name == 'record_id'
349
+ return nil
350
+ end
351
+
352
+ if column_name == 'target_record_id'
353
+
354
+ if table_name == 'account_history'
355
+ return :@account_record_id
356
+ end
357
+ end
358
+
359
+ if column_name == 'search_key1' && table_name == 'bus_ext_events_history'
360
+ return :@account_record_id
361
+ end
362
+
363
+ if column_name == 'search_key1' && table_name == 'bus_events_history'
364
+ return :@account_record_id
365
+ end
366
+
367
+ value
368
+
369
+ end
370
+
371
+ def replace_boolean(value)
372
+ if value.to_s === 'true'
373
+ return 1
374
+ elsif value.to_s === 'false'
375
+ return 0
376
+ else
377
+ return value
378
+ end
379
+ end
380
+
381
+ def fix_dates(value)
382
+ if !value.equal?(:DEFAULT)
383
+
384
+ dt = DateTime.parse(value)
385
+ return dt.strftime('%F %T').to_s
386
+
387
+ end
388
+
389
+ value
390
+ end
391
+
392
+ def fill_empty_column(value)
393
+ if value.to_s.strip.empty?
394
+ return :DEFAULT
395
+ else
396
+ return value
397
+ end
398
+ end
399
+
400
+ def replace_uuid(table_name,column_name,value)
401
+
402
+ if column_name == 'id'
403
+ @tables_id["#{table_name}_id"] = SecureRandom.uuid
404
+ end
405
+
406
+ if ROUND_TRIP_EXPORT_IMPORT_MAP[table_name.to_sym] && ROUND_TRIP_EXPORT_IMPORT_MAP[table_name.to_sym][column_name.to_sym]
407
+ key = ROUND_TRIP_EXPORT_IMPORT_MAP[table_name.to_sym][column_name.to_sym]
408
+
409
+ if key.equal?(:generate)
410
+ new_value = SecureRandom.uuid
411
+ else
412
+ new_value = @tables_id[key.to_s]
413
+ end
414
+
415
+ if new_value.nil?
416
+ new_value = SecureRandom.uuid
417
+ @tables_id[key.to_s] = new_value
418
+ end
419
+ return new_value
420
+ end
421
+
422
+ if not ROUND_TRIP_EXPORT_IMPORT_MAP[:all][column_name.to_sym].nil?
423
+ key = ROUND_TRIP_EXPORT_IMPORT_MAP[:all][column_name.to_sym]
424
+ new_value = @tables_id[key.to_s]
425
+
426
+ return new_value
427
+ end
428
+
429
+ value
430
+ end
431
+
432
+ def sniff_delimiter(file)
433
+
434
+ return nil if File.size?(file).nil?
435
+
436
+ first_line = File.open(file) {|f| f.readline}
437
+
438
+ return nil if first_line.nil?
439
+
440
+ sniff = {}
441
+
442
+ DELIMITERS.each do |delimiter|
443
+ sniff[delimiter] = first_line.count(delimiter)
444
+ end
445
+
446
+ sniff = sniff.sort {|a,b| b[1]<=>a[1]}
447
+ sniff.size > 0 ? sniff[0][0] : nil
448
+ end
449
+
450
+ # helper methods that set up killbill and database options: load_config_from_file; set_config; set_database_options;
451
+ # set_killbill_options;
452
+ def load_config_from_file(config_file)
453
+
454
+ set_config(config_file)
455
+
456
+ if not @config.nil?
457
+ config_killbill = @config['killbill']
458
+
459
+ if not config_killbill.nil?
460
+ set_killbill_options([config_killbill['api_key'],config_killbill['api_secret']],
461
+ [config_killbill['user'],config_killbill['password']],
462
+ "http://#{config_killbill['host']}:#{config_killbill['port']}")
463
+ end
464
+
465
+ config_db = @config['database']
466
+
467
+ if not config_db.nil?
468
+ set_database_options(config_db['database'],
469
+ [config_db['username'],config_db['password']],
470
+ @logger)
471
+
472
+ end
473
+ end
474
+ end
475
+
476
+ def set_config(config_file = nil)
477
+ @config = nil
478
+
479
+ if not config_file.nil?
480
+ if not Dir[config_file][0].nil?
481
+ @config = YAML::load_file(config_file)
482
+ end
483
+ end
484
+
485
+ end
486
+
487
+ def set_database_options(database_name = nil, database_credentials = nil, logger)
488
+
489
+ Database.set_logger(logger)
490
+
491
+ if not database_credentials.nil?
492
+ Database.set_credentials(database_credentials[0],database_credentials[1])
493
+ end
494
+
495
+ if not database_name.nil?
496
+ Database.set_database_name(database_name)
497
+ end
498
+
499
+ Database.set_mysql_command_line
500
+ end
501
+
502
+ def set_killbill_options(killbill_api_credentials, killbill_credentials, killbill_url)
503
+
504
+ if not killbill_api_credentials.nil?
505
+
506
+ @killbill_api_key = killbill_api_credentials[0]
507
+ @killbill_api_secrets = killbill_api_credentials[1]
508
+
509
+ end
510
+
511
+ if not killbill_credentials.nil?
512
+
513
+ @killbill_user = killbill_credentials[0]
514
+ @killbill_password = killbill_credentials[1]
515
+
516
+ end
517
+
518
+ if not killbill_url.nil?
519
+
520
+ @killbill_url = killbill_url
521
+
522
+ end
523
+ end
524
+
525
+ end
526
+
527
+ end