stp_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f762d76df1ac396cf8b3deac83f0735bec22d06e541883276225809bc43ca59c
4
+ data.tar.gz: b2d8660f373cd6a84fdca4599b30bd310f9ddd4df7185f7cbd1119eced4e3f66
5
+ SHA512:
6
+ metadata.gz: 4e2a979387cbc82f6a55722ec6e3f15e3129bc9eeb7a66f3729d19abadc105e5c42536e242f47511c240dfe342daa71cd0f8d826f78bd81172d463884f1f3c44
7
+ data.tar.gz: 4e232fc32b0f086c9089d1712209aefb2ecd06dc075b0e77e21a1e04522a03edbba7163c5dbbadb86375b40d0a437f811bbf64cd1313168bacbac22e26e6af9a
data/.gitignore ADDED
@@ -0,0 +1,51 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
+
4
+ # Declare your gem's dependencies in commons.gemspec.
5
+ # Bundler will treat runtime dependencies like base dependencies, and
6
+ # development dependencies will be added by default to the :development group.
7
+ gemspec
8
+
9
+ # Declare any dependencies that are still in development here instead of in
10
+ # your gemspec. These might include edge Rails or gems from your path or
11
+ # Git. Remember to move these dependencies to your gemspec before releasing
12
+ # your gem to rubygems.org.
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stp_client (0.1.0)
5
+ crypto_yellowme (~> 0.3)
6
+ faraday (~> 1.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (5.2.4.1)
12
+ activesupport (= 5.2.4.1)
13
+ activesupport (5.2.4.1)
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ i18n (>= 0.7, < 2)
16
+ minitest (~> 5.1)
17
+ tzinfo (~> 1.1)
18
+ bcrypt (3.1.13)
19
+ concurrent-ruby (1.1.5)
20
+ crypto_yellowme (0.3.0)
21
+ activemodel (~> 5.2)
22
+ activesupport (~> 5.0)
23
+ bcrypt (~> 3.1.7)
24
+ diff-lcs (1.3)
25
+ docile (1.3.2)
26
+ faker (2.10.0)
27
+ i18n (>= 1.6, < 1.8)
28
+ faraday (1.0.0)
29
+ multipart-post (>= 1.2, < 3)
30
+ i18n (1.7.0)
31
+ concurrent-ruby (~> 1.0)
32
+ json (2.3.0)
33
+ minitest (5.13.0)
34
+ multipart-post (2.1.1)
35
+ rspec (3.9.0)
36
+ rspec-core (~> 3.9.0)
37
+ rspec-expectations (~> 3.9.0)
38
+ rspec-mocks (~> 3.9.0)
39
+ rspec-core (3.9.1)
40
+ rspec-support (~> 3.9.1)
41
+ rspec-expectations (3.9.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.9.0)
44
+ rspec-mocks (3.9.1)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.9.0)
47
+ rspec-support (3.9.2)
48
+ simplecov (0.17.1)
49
+ docile (~> 1.1)
50
+ json (>= 1.8, < 3)
51
+ simplecov-html (~> 0.10.0)
52
+ simplecov-html (0.10.2)
53
+ thread_safe (0.3.6)
54
+ tzinfo (1.2.6)
55
+ thread_safe (~> 0.1)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ faker (~> 2.0)
62
+ rspec (~> 3.8)
63
+ simplecov (~> 0.17)
64
+ stp_client!
65
+
66
+ BUNDLED WITH
67
+ 2.0.2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Juan Ku
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # stp-client
2
+ stp_client is a ruby integration to STP services
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/spec`
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/lib/stp.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'crypto'
2
+ require 'faraday'
3
+
4
+ require 'stp/config'
5
+
6
+ require 'stp/structs/account'
7
+ require 'stp/structs/payment_order'
8
+ require 'stp/account'
9
+ require 'stp/payment_order'
10
+
11
+ module STP
12
+ end
@@ -0,0 +1,27 @@
1
+ module STP
2
+ module Account
3
+ def self.create(account)
4
+ account.signature = Crypto::Commons.rsa_seal(STP.private_key, STP.private_key_password, account.original_chain)
5
+ connection = Faraday.new(url: "#{STP.api_uri}/cuentaModule/fisica")
6
+ response = connection.put do |req|
7
+ req.url ''
8
+ req.headers['Content-Type'] = 'application/json'
9
+ req.body = account.to_json
10
+ end
11
+ hash = JSON.parse(response.body)
12
+ hash
13
+ end
14
+
15
+ def self.delete(account)
16
+ account.signature = Crypto::Commons.rsa_seal(STP.private_key, STP.private_key_password, account.original_chain)
17
+ connection = Faraday.new(url: "#{STP.api_uri}/cuentaModule/fisica")
18
+ response = connection.delete do |req|
19
+ req.url ''
20
+ req.headers['Content-Type'] = 'application/json'
21
+ req.body = account.to_json
22
+ end
23
+ hash = JSON.parse(response.body)
24
+ hash
25
+ end
26
+ end
27
+ end
data/lib/stp/config.rb ADDED
@@ -0,0 +1,35 @@
1
+ module STP
2
+ class << self
3
+ def api_uri=(api_uri)
4
+ @api_uri = api_uri
5
+ end
6
+
7
+ def api_uri
8
+ @api_uri
9
+ end
10
+
11
+ def private_key=(private_key)
12
+ @private_key = private_key
13
+ end
14
+
15
+ def private_key
16
+ @private_key
17
+ end
18
+
19
+ def private_key_password=(private_key_password)
20
+ @private_key_password = private_key_password
21
+ end
22
+
23
+ def private_key_password
24
+ @private_key_password
25
+ end
26
+
27
+ def company=(company)
28
+ @company = company
29
+ end
30
+
31
+ def company
32
+ @company
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module STP
2
+ module PaymentOrder
3
+ def self.create(payment_order)
4
+ payment_order.signature = Crypto::Commons.rsa_seal(STP.private_key, STP.private_key_password, payment_order.original_chain)
5
+ connection = Faraday.new(url: "#{STP.api_uri}/ordenPago/registra")
6
+ response = connection.put do |req|
7
+ req.url ''
8
+ req.headers['Content-Type'] = 'application/json'
9
+ req.body = payment_order.to_json
10
+ end
11
+ hash = JSON.parse(response.body)
12
+ hash['resultado']
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ module STP
2
+ module Structs
3
+ class Account
4
+ attr_accessor :account, # cuenta
5
+ :company, # empresa
6
+ :name, # nombre
7
+ :paternal_surname, # apellidoPaterno
8
+ :maternal_surname, # apellidoMaterno
9
+ :rfc, # rfcCurp
10
+ :birthdate, # fechaNacimiento
11
+ :sex, # genero
12
+ :state, # entidadFederativa
13
+ :economic_activity, # actividadEconomica
14
+ :street, # calle
15
+ :exterior_number, # numeroExterior
16
+ :interior_nunmber, # numeroInterior
17
+ :neighborhood, # colonia
18
+ :municipality, # alcaldiaMunicipio
19
+ :zip_code, # cp
20
+ :country, # pais
21
+ :email, # email
22
+ :identification_id, # idIdentificacion: credencial para votar (INE/IFE)
23
+ :phone, # telefono
24
+ :signature
25
+
26
+ def original_chain
27
+ "||#{@company}|"\
28
+ "#{@account}|"\
29
+ "#{@rfc}||"
30
+ end
31
+
32
+ def as_json(_options = {})
33
+ {
34
+ cuenta: @account,
35
+ empresa: @company,
36
+ nombre: @name,
37
+ apellidoPaterno: @paternal_surname,
38
+ apellidoMaterno: @maternal_surname,
39
+ rfcCurp: @rfc,
40
+ fechaNacimiento: @birthdate,
41
+ genero: @sex,
42
+ entidadFederativa: @state,
43
+ actividadEconomica: @economic_activity,
44
+ calle: @street,
45
+ numeroExterior: @exterior_number,
46
+ numeroInterior: @interior_nunmber,
47
+ colonia: @neighborhood,
48
+ alcaldiaMunicipio: @municipality,
49
+ cp: @zip_code,
50
+ pais: @country,
51
+ email: @email,
52
+ idIdentificacion: @identification_id,
53
+ telefono: @phone,
54
+ firma: @signature,
55
+ }.compact
56
+ end
57
+
58
+ def to_json(*options)
59
+ as_json(*options).to_json(*options)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,107 @@
1
+ module STP
2
+ module Structs
3
+ class PaymentOrder
4
+ attr_accessor :counterpart_institution, # a - Institución contraparte
5
+ :company, # b - Empresa
6
+ :operation_date, # c - Fecha de operación
7
+ :folio, # d - Folio origen
8
+ :tracking_key, # e - Clave de rastreo
9
+ :operant_institution, # f - Institución operante
10
+ :amount, # g - Monto del pago
11
+ :payment_type, # h - Tipo del pago
12
+ :payer_account_type, # i - Tipo de la cuenta del ordenante
13
+ :payer_name, # j - Nombre del ordenante
14
+ :payer_account, # k - Cuenta del ordenante
15
+ :payer_rfc, # l - RFC / Curp del ordenante
16
+ :beneficiary_account_type, # m - Tipo de cuenta del beneficiario
17
+ :beneficiary_name, # n - Nombre del beneficiario
18
+ :beneficiary_account, # o - Cuenta del beneficiario
19
+ :beneficiary_rfc, # p - RFC / Curp del beneficiario
20
+ :beneficiary_email, # q - Email del beneficiario
21
+ :secondary_beneficiary_account_type, # r - Tipo de cuenta del beneficiario 2
22
+ :secondary_beneficiary_name, # s - Nombre del beneficiario 2
23
+ :secondary_beneficiary_account, # t - Cuenta del beneficiario 2
24
+ :secondary_beneficiary_rfc, # u - RFC / Curp del beneficiario 2
25
+ :payment_concept, # v - Concepto del pago
26
+ :payment_secondary_concept, # w - Conceptodelpago2
27
+ :user_catalog_key, # x - Clave del catálogo de usuario 1
28
+ :secondary_user_catalog_key, # y - Clave del catálogo de usuario 2
29
+ :payment_key, # z - Clave del pago
30
+ :collection_reference, # aa - Referencia de cobranza
31
+ :number_reference, # bb - Referencia numérica
32
+ :operation_type, # cc - Tipo de operación
33
+ :topology, # dd - Topología
34
+ :user, # ee - Usuario
35
+ :delivery_method, # ff - Medio de entrega
36
+ :priority, # gg - Prioridad
37
+ :iva, # hh - IVA
38
+ :original_chain,
39
+ :signature
40
+
41
+ def original_chain
42
+ "||#{@counterpart_institution}|"\
43
+ "#{@company}|"\
44
+ "#{@operation_date}|"\
45
+ "#{@folio}|"\
46
+ "#{@tracking_key}|"\
47
+ "#{@operant_institution}|"\
48
+ "#{@amount.nil? ? '' : format('%.2f', @amount.to_f)}|"\
49
+ "#{@payment_type}|"\
50
+ "#{@payer_account_type}|"\
51
+ "#{@payer_name}|"\
52
+ "#{@payer_account}|"\
53
+ "#{@payer_rfc}|"\
54
+ "#{@beneficiary_account_type}|"\
55
+ "#{@beneficiary_name}|"\
56
+ "#{@beneficiary_account}|"\
57
+ "#{@beneficiary_rfc}|"\
58
+ "#{@beneficiary_email}|"\
59
+ "#{@secondary_beneficiary_account_type}|"\
60
+ "#{@secondary_beneficiary_name}|"\
61
+ "#{@secondary_beneficiary_account}|"\
62
+ "#{@secondary_beneficiary_rfc}|"\
63
+ "#{@payment_concept}|"\
64
+ "#{@payment_secondary_concept}|"\
65
+ "#{@user_catalog_key}|"\
66
+ "#{@secondary_user_catalog_key}|"\
67
+ "#{@payment_key}|"\
68
+ "#{@collection_reference}|"\
69
+ "#{@number_reference}|"\
70
+ "#{@operation_type}|"\
71
+ "#{@topology}|"\
72
+ "#{@user}|"\
73
+ "#{@delivery_method}|"\
74
+ "#{@priority}|"\
75
+ "#{@iva.nil? ? '' : format('%.2f', @iva.to_f)}||"
76
+ end
77
+
78
+ def as_json(_options = {})
79
+ {
80
+ claveRastreo: @tracking_key,
81
+ conceptoPago: @payment_concept,
82
+ cuentaBeneficiario: @beneficiary_account,
83
+ cuentaOrdenante: @payer_account,
84
+ empresa: @company,
85
+ fechaOperacion: @operation_date,
86
+ firma: @signature,
87
+ folioOrigen: @folio,
88
+ institucionContraparte: @counterpart_institution,
89
+ institucionOperante: @operant_institution,
90
+ monto: @amount,
91
+ nombreBeneficiario: @beneficiary_name,
92
+ nombreOrdenante: @payer_name,
93
+ referenciaNumerica: @number_reference,
94
+ rfcCurpBeneficiario: @beneficiary_rfc,
95
+ rfcCurpOrdenante: @payer_rfc,
96
+ tipoCuentaBeneficiario: @beneficiary_account_type,
97
+ tipoCuentaOrdenante: @payer_account_type,
98
+ tipoPago: @payment_type,
99
+ }.compact
100
+ end
101
+
102
+ def to_json(*options)
103
+ as_json(*options).to_json(*options)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module STP
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'rspec'
2
+ require 'simplecov'
3
+ require 'faker'
4
+
5
+ require 'stp'
6
+
7
+ STP.api_uri = 'xxxxx'
8
+ STP.private_key = %q(
9
+ -----BEGIN RSA PRIVATE KEY-----
10
+ MIICXgIBAAKBgQDO6dcS4k8Sz//09ISLLCIqHjCYNpHUbtauyu4+N3bvTOxgyX9R
11
+ fC9hQ0HyL6VjcAoY67BNF1gvLPTRB0ijwMPpl2uEyv2y1pPwxmDoH4dH0aoRrWUW
12
+ zSejnfVn4NoHIchiu8umIofDFuGb/cIDnVBikKNFBTf6HxPePUW2p35Q9wIDAQAB
13
+ AoGBAJm4jqmf6mEumJkyw/nlauhhj3a2K/dn9STc7MzaRgkY3BA4AtfV7BlVb3vv
14
+ O+85QLcs+sj7S++YdbWJtMS7pI/jYQPEiR2pQ9aHx/Hacor92Oi7zEzn6QLr/VKA
15
+ K2qPAiRicPItrY4zWkrEwmwX0SBUPjAD3Ffjf8T6cETE8puxAkEA7aijTnkK8FzM
16
+ TxXDl6UMxSwjcJeP/vJcN+im+aphliIGJykL1kcXT5B9R3OUyqsbySgmjsG2yNEm
17
+ psCEd/rZKQJBAN7hxnVGAeVl1q2NWgL+lCgvgwaoPqE2NzcFsIxs5EOpKXn0fZuR
18
+ oH7l09Giv61NmdNAJT1oMxdLVQ9QJ1UNfR8CQQC9ACr3Yk2vv6z/i+hjte/E8ogw
19
+ p2ftsaJjGBOKY9R9yAsqo3r1as4ACYGIDEQdNRzAybx4NVf+tk5NuLbgj86ZAkEA
20
+ h3BpmgA1zMHK5+H6rdEoFRdyJsx8api4it4RP/Q37gnQ44Q4BB5Find89WpR0i1S
21
+ 6bWUK7GzQleL0+dgT2YH/wJAByKN5rEvP/XLiwSu68lQtloDAJxQfD4CSHlC97Mw
22
+ 9bIAmsKrvYVL4B40Nb3jPNXCKm8zRqFu//WX/G1uy5Q6nQ==
23
+ -----END RSA PRIVATE KEY-----
24
+ )
25
+ STP.private_key_password = '12345678'
26
+ STP.company = 'xxxxx'
27
+
28
+ SimpleCov.start
@@ -0,0 +1,110 @@
1
+ require 'support/stp_test_helpers'
2
+
3
+ RSpec.describe STP::Account do
4
+ subject do
5
+ response = STP::Account.create(account_obj)
6
+ response
7
+ end
8
+
9
+ describe 'account create' do
10
+ context 'works ok! ' do
11
+ context 'with valid data' do
12
+ let(:account_obj) do
13
+ account = STP::Structs::Account.new
14
+ account.account = '01010101'
15
+ account.company = STP.company
16
+ account.name = Faker::Name.first_name
17
+ account.paternal_surname = Faker::Name.last_name
18
+ account.maternal_surname = Faker::Name.last_name
19
+ account.rfc = 'RFC'
20
+ account.birthdate = Faker::Date.birthday(min_age: 6, max_age: 65)
21
+ account.sex = 'male'
22
+ account.state = 'Mérida'
23
+ account.economic_activity = 'programador'
24
+ account.street = Faker::Address.street_name
25
+ account.exterior_number = Faker::Address.building_number
26
+ account.interior_nunmber = Faker::Address.building_number
27
+ account.neighborhood = Faker::Address.community
28
+ account.municipality = Faker::Address.community
29
+ account.zip_code = Faker::Number.number(digits: 5)
30
+ account.country = 'México'
31
+ account.email = Faker::Internet.email
32
+ account.identification_id = 'id'
33
+ account.phone = Faker::PhoneNumber.phone_number
34
+ account
35
+ end
36
+
37
+ before do
38
+ allow(STP::Account).to receive(:create).
39
+ and_return(STPTestHelpers::ACCOUNT_CREATE_MOCK_SUCCESS())
40
+ end
41
+
42
+ it do
43
+ response = subject
44
+ expect(response['id']).not_to be_nil
45
+ expect(response['id']).to be 0
46
+ expect(response['descripcionError']).to be_nil
47
+ end
48
+ end
49
+
50
+ context 'with minimal data' do
51
+ let(:account_obj) do
52
+ account = STP::Structs::Account.new
53
+ account.company = STP.company
54
+ account.account = '846180000400000001'
55
+ account.rfc = 'RFCURP'
56
+ account
57
+ end
58
+
59
+ before do
60
+ allow(STP::Account).to receive(:create).
61
+ and_return(STPTestHelpers::ACCOUNT_CREATE_MOCK_SUCCESS())
62
+ end
63
+
64
+ it do
65
+ response = subject
66
+ expect(response['id']).not_to be_nil
67
+ expect(response['id']).to be 0
68
+ expect(response['descripcionError']).to be_nil
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'fails when' do
74
+ context 'repeating account and rfcCurp' do
75
+ let(:account_obj) do
76
+ account = STP::Structs::Account.new
77
+ account.account = '01010101'
78
+ account.company = STP.company
79
+ account.name = Faker::Name.first_name
80
+ account.paternal_surname = Faker::Name.last_name
81
+ account.maternal_surname = Faker::Name.last_name
82
+ account.rfc = 'RFC'
83
+ account.birthdate = Faker::Date.birthday(min_age: 6, max_age: 65)
84
+ account.sex = 'male'
85
+ account.state = 'Mérida'
86
+ account.economic_activity = 'programador'
87
+ account.street = Faker::Address.street_name
88
+ account.exterior_number = Faker::Address.building_number
89
+ account.interior_nunmber = Faker::Address.building_number
90
+ account.neighborhood = Faker::Address.community
91
+ account.municipality = Faker::Address.community
92
+ account.zip_code = Faker::Number.number(digits: 5)
93
+ account.country = 'México'
94
+ account.email = Faker::Internet.email
95
+ account.identification_id = 'id'
96
+ account.phone = Faker::PhoneNumber.phone_number
97
+ account
98
+ end
99
+
100
+ before do
101
+ allow(STP::Account).to receive(:create).
102
+ and_return(STPTestHelpers::ACCOUNT_CREATE_MOCK_ERROR(-2))
103
+ end
104
+
105
+ it { expect(subject['id']).to eq -2 }
106
+ it { expect(subject['descripcionError']).not_to be_nil }
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,110 @@
1
+ require 'support/stp_test_helpers'
2
+
3
+ RSpec.describe STP::Account do
4
+ subject do
5
+ response = STP::Account.delete(account_obj)
6
+ response
7
+ end
8
+
9
+ describe 'account delete' do
10
+ context 'works ok! ' do
11
+ context 'with valid data' do
12
+ let(:account_obj) do
13
+ account = STP::Structs::Account.new
14
+ account.account = '01010101'
15
+ account.company = STP.company
16
+ account.name = Faker::Name.first_name
17
+ account.paternal_surname = Faker::Name.last_name
18
+ account.maternal_surname = Faker::Name.last_name
19
+ account.rfc = 'RFC'
20
+ account.birthdate = Faker::Date.birthday(min_age: 6, max_age: 65)
21
+ account.sex = 'male'
22
+ account.state = 'Mérida'
23
+ account.economic_activity = 'programador'
24
+ account.street = Faker::Address.street_name
25
+ account.exterior_number = Faker::Address.building_number
26
+ account.interior_nunmber = Faker::Address.building_number
27
+ account.neighborhood = Faker::Address.community
28
+ account.municipality = Faker::Address.community
29
+ account.zip_code = Faker::Number.number(digits: 5)
30
+ account.country = 'México'
31
+ account.email = Faker::Internet.email
32
+ account.identification_id = 'id'
33
+ account.phone = Faker::PhoneNumber.phone_number
34
+ account
35
+ end
36
+
37
+ before do
38
+ allow(STP::Account).to receive(:delete).
39
+ and_return(STPTestHelpers::ACCOUNT_DELETE_MOCK_SUCCESS())
40
+ end
41
+
42
+ it do
43
+ response = subject
44
+ expect(response['id']).not_to be_nil
45
+ expect(response['id']).to be 0
46
+ expect(response['descripcionError']).to be_nil
47
+ end
48
+ end
49
+
50
+ context 'with minimal data' do
51
+ let(:account_obj) do
52
+ account = STP::Structs::Account.new
53
+ account.company = STP.company
54
+ account.account = '846180000400000001'
55
+ account.rfc = 'RFCURP'
56
+ account
57
+ end
58
+
59
+ before do
60
+ allow(STP::Account).to receive(:delete).
61
+ and_return(STPTestHelpers::ACCOUNT_DELETE_MOCK_SUCCESS())
62
+ end
63
+
64
+ it do
65
+ response = subject
66
+ expect(response['id']).not_to be_nil
67
+ expect(response['id']).to be 0
68
+ expect(response['descripcionError']).to be_nil
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'fails when' do
74
+ context 'repeating account and rfcCurp' do
75
+ let(:account_obj) do
76
+ account = STP::Structs::Account.new
77
+ account.account = '01010101'
78
+ account.company = STP.company
79
+ account.name = Faker::Name.first_name
80
+ account.paternal_surname = Faker::Name.last_name
81
+ account.maternal_surname = Faker::Name.last_name
82
+ account.rfc = 'RFC'
83
+ account.birthdate = Faker::Date.birthday(min_age: 6, max_age: 65)
84
+ account.sex = 'male'
85
+ account.state = 'Mérida'
86
+ account.economic_activity = 'programador'
87
+ account.street = Faker::Address.street_name
88
+ account.exterior_number = Faker::Address.building_number
89
+ account.interior_nunmber = Faker::Address.building_number
90
+ account.neighborhood = Faker::Address.community
91
+ account.municipality = Faker::Address.community
92
+ account.zip_code = Faker::Number.number(digits: 5)
93
+ account.country = 'México'
94
+ account.email = Faker::Internet.email
95
+ account.identification_id = 'id'
96
+ account.phone = Faker::PhoneNumber.phone_number
97
+ account
98
+ end
99
+
100
+ before do
101
+ allow(STP::Account).to receive(:delete).
102
+ and_return(STPTestHelpers::ACCOUNT_DELETE_MOCK_ERROR(-2))
103
+ end
104
+
105
+ it { expect(subject['id']).to eq -2 }
106
+ it { expect(subject['descripcionError']).not_to be_nil }
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,106 @@
1
+ require 'support/stp_test_helpers'
2
+
3
+ RSpec.describe STP::PaymentOrder do
4
+ subject do
5
+ response = STP::PaymentOrder.create(payment_order_obj)
6
+ response
7
+ end
8
+
9
+ describe 'create' do
10
+ context 'works ok! ' do
11
+ context 'with valid data' do
12
+ let(:withdrawal_id) { "3#{rand.to_s[2..3]}d#{rand.to_s[2..3]}c6-1d#{rand.to_s[2..3]}-40a6-afef-70d6b3a9336f" }
13
+ let(:payment_order_obj) do
14
+ payment_order = STP::Structs::PaymentOrder.new
15
+ payment_order.payment_concept = 'prueba veinte'
16
+ payment_order.tracking_key = "#{Time.now.strftime '%j%y'}#{withdrawal_id.tr('-', '')[0, 24]}"
17
+ payment_order.company = 'FINVE'
18
+ payment_order.beneficiary_account = '846180000000000016'
19
+ payment_order.counterpart_institution = '846'
20
+ payment_order.operant_institution = '90646'
21
+ payment_order.amount = '10.01'
22
+ payment_order.beneficiary_name = 'prueba'
23
+ payment_order.number_reference = "#{Time.now.strftime '%y%m%d'}"
24
+ payment_order.payment_type = '1'
25
+ payment_order.beneficiary_account_type = '40'
26
+ payment_order.beneficiary_rfc = 'ND'
27
+ payment_order
28
+ end
29
+
30
+ before do
31
+ allow(STP::PaymentOrder).to receive(:create).
32
+ and_return(STPTestHelpers::PAYMENT_ORDER_MOCK_SUCCESS())
33
+ end
34
+
35
+ it do
36
+ response = subject
37
+ expect(response['id']).not_to be_nil
38
+ expect(response['id']).not_to be '0'
39
+ expect(response['descripcionError']).to be_nil
40
+ end
41
+ end
42
+
43
+ context 'with valid data' do
44
+ let(:withdrawal_id) { "3#{rand.to_s[2..3]}d#{rand.to_s[2..3]}c6-1d#{rand.to_s[2..3]}-40a6-afef-70d6b3a9336f" }
45
+ let(:payment_order_obj) do
46
+ payment_order = STP::Structs::PaymentOrder.new
47
+ payment_order.payment_concept = 'prueba veinte'
48
+ payment_order.tracking_key = "#{Time.now.strftime '%j%y'}#{withdrawal_id.tr('-', '')[0, 24]}"
49
+ payment_order.company = STP.company
50
+ payment_order.folio = withdrawal_id
51
+ payment_order.beneficiary_account = '846180000400000001'
52
+ payment_order.counterpart_institution = '846'
53
+ payment_order.operant_institution = '90646'
54
+ payment_order.amount = '10.01'
55
+ payment_order.beneficiary_name = 'STP'
56
+ payment_order.number_reference = "#{Time.now.strftime '%y%m%d'}"
57
+ payment_order.payment_type = '1'
58
+ payment_order.beneficiary_account_type = '40'
59
+ payment_order.beneficiary_rfc = 'ND'
60
+ payment_order
61
+ end
62
+
63
+ before do
64
+ allow(STP::PaymentOrder).to receive(:create).
65
+ and_return(STPTestHelpers::PAYMENT_ORDER_MOCK_SUCCESS())
66
+ end
67
+
68
+ it do
69
+ response = subject
70
+ expect(response['id']).not_to be_nil
71
+ expect(response['id']).not_to be '0'
72
+ expect(response['descripcionError']).to be_nil
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'fails when' do
78
+ context 'repeating tracking_key' do
79
+ let(:payment_order_obj) do
80
+ payment_order = STP::Structs::PaymentOrder.new
81
+ payment_order.payment_concept = 'prueba veinte'
82
+ payment_order.tracking_key = 'pruebaFinve002'
83
+ payment_order.company = 'FINVE'
84
+ payment_order.beneficiary_account = '846180000000000016'
85
+ payment_order.counterpart_institution = '846'
86
+ payment_order.operant_institution = '90646'
87
+ payment_order.amount = '10.01'
88
+ payment_order.beneficiary_name = 'prueba'
89
+ payment_order.number_reference = '123456'
90
+ payment_order.payment_type = '1'
91
+ payment_order.beneficiary_account_type = '40'
92
+ payment_order.beneficiary_rfc = 'ND'
93
+ payment_order
94
+ end
95
+
96
+ before do
97
+ allow(STP::PaymentOrder).to receive(:create).
98
+ and_return(STPTestHelpers::PAYMENT_ORDER_MOCK_ERROR(-2))
99
+ end
100
+
101
+ it { expect(subject['id']).to eq -2 }
102
+ it { expect(subject['descripcionError']).not_to be_nil }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,23 @@
1
+ RSpec.describe STP::Structs::Account do
2
+ describe 'original_chain works ok' do
3
+ context 'with full attributes' do
4
+ let(:rfc) { 'RFCCURP' }
5
+ let(:account) { '20111111' }
6
+ let(:company) { 'EMPRESA' }
7
+ let(:original_chain) { "||#{company}|#{account}|#{rfc}||" }
8
+
9
+ before do
10
+ @account = STP::Structs::Account.new
11
+ @account.company = company
12
+ @account.account = account
13
+ @account.rfc = rfc
14
+ end
15
+
16
+ subject do
17
+ @account.original_chain
18
+ end
19
+
20
+ it { expect(subject).to eq original_chain }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,135 @@
1
+ RSpec.describe STP::Structs::PaymentOrder do
2
+ describe 'original_chain works ok' do
3
+ context 'with given example attributes' do
4
+ let(:original_chain) { '||40072|EMPRESA|20111111||RAS|90646|9999.99||||1234|||Beneficiario|5678||||||||||||REFCOB|7777||||||||' }
5
+
6
+ before do
7
+ @payment_order = STP::Structs::PaymentOrder.new
8
+ @payment_order.counterpart_institution = '40072'
9
+ @payment_order.company = 'EMPRESA'
10
+ @payment_order.operation_date = '20111111'
11
+ @payment_order.tracking_key = 'RAS'
12
+ @payment_order.operant_institution = '90646'
13
+ @payment_order.amount = '9999.99'
14
+ @payment_order.payer_account = '1234'
15
+ @payment_order.beneficiary_name = 'Beneficiario'
16
+ @payment_order.beneficiary_account = '5678'
17
+ @payment_order.collection_reference = 'REFCOB'
18
+ @payment_order.number_reference = '7777'
19
+ end
20
+
21
+ subject do
22
+ @payment_order.original_chain
23
+ end
24
+
25
+ it { expect(subject).to eq original_chain }
26
+ end
27
+
28
+ context 'with full attributes' do
29
+ let(:original_chain) do
30
+ '||40072|EMPRESA|20111111|123|RAS|90646|9999.99|payment_type|'\
31
+ 'payer_account_type|payer_name|1234|payer_rfc|beneficiary_account_type|Beneficiario|'\
32
+ '5678|beneficiary_rfc|beneficiary_email|secondary_beneficiary_account_type|secondary_beneficiary_name|secondary_beneficiary_account'\
33
+ '|secondary_beneficiary_rfc|payment_concept|payment_secondary_concept|user_catalog_key|secondary_user_catalog_key|payment_key|REFCOB|7777|'\
34
+ 'operation_type|topology|user|delivery_method|priority|12.00||'
35
+ end
36
+
37
+ before do
38
+ @payment_order = STP::Structs::PaymentOrder.new
39
+ @payment_order.counterpart_institution = '40072'
40
+ @payment_order.company = 'EMPRESA'
41
+ @payment_order.operation_date = '20111111'
42
+ @payment_order.folio = '123'
43
+ @payment_order.tracking_key = 'RAS'
44
+ @payment_order.operant_institution = '90646'
45
+ @payment_order.amount = '9999.99'
46
+ @payment_order.payment_type = 'payment_type'
47
+ @payment_order.payer_account_type = 'payer_account_type'
48
+ @payment_order.payer_name = 'payer_name'
49
+ @payment_order.payer_account = '1234'
50
+ @payment_order.payer_rfc = 'payer_rfc'
51
+ @payment_order.beneficiary_account_type = 'beneficiary_account_type'
52
+ @payment_order.beneficiary_name = 'Beneficiario'
53
+ @payment_order.beneficiary_account = '5678'
54
+ @payment_order.beneficiary_rfc = 'beneficiary_rfc'
55
+ @payment_order.beneficiary_email = 'beneficiary_email'
56
+ @payment_order.secondary_beneficiary_account_type = 'secondary_beneficiary_account_type'
57
+ @payment_order.secondary_beneficiary_name = 'secondary_beneficiary_name'
58
+ @payment_order.secondary_beneficiary_account = 'secondary_beneficiary_account'
59
+ @payment_order.secondary_beneficiary_rfc = 'secondary_beneficiary_rfc'
60
+ @payment_order.payment_concept = 'payment_concept'
61
+ @payment_order.payment_secondary_concept = 'payment_secondary_concept'
62
+ @payment_order.user_catalog_key = 'user_catalog_key'
63
+ @payment_order.secondary_user_catalog_key = 'secondary_user_catalog_key'
64
+ @payment_order.payment_key = 'payment_key'
65
+ @payment_order.collection_reference = 'REFCOB'
66
+ @payment_order.number_reference = '7777'
67
+ @payment_order.operation_type = 'operation_type'
68
+ @payment_order.topology = 'topology'
69
+ @payment_order.user = 'user'
70
+ @payment_order.delivery_method = 'delivery_method'
71
+ @payment_order.priority = 'priority'
72
+ @payment_order.iva = '12'
73
+ end
74
+
75
+ subject do
76
+ @payment_order.original_chain
77
+ end
78
+
79
+ it { expect(subject).to eq original_chain }
80
+ end
81
+
82
+ context 'with some attributes' do
83
+ let(:original_chain) { '||846|XXXXXX|||123456789|90646|11.35|1|40|||||S.A. de C.V.|846180000000000016|||||||Prueba REST||||||123456||T||3||||' }
84
+
85
+ before do
86
+ @payment_order = STP::Structs::PaymentOrder.new
87
+ @payment_order.counterpart_institution = '846'
88
+ @payment_order.company = 'XXXXXX'
89
+ @payment_order.tracking_key = '123456789'
90
+ @payment_order.operant_institution = '90646'
91
+ @payment_order.amount = '11.35'
92
+ @payment_order.payment_type = '1'
93
+ @payment_order.payer_account_type = '40'
94
+ @payment_order.beneficiary_name = 'S.A. de C.V.'
95
+ @payment_order.beneficiary_account = '846180000000000016'
96
+ @payment_order.payment_concept = 'Prueba REST'
97
+ @payment_order.number_reference = '123456'
98
+ @payment_order.topology = 'T'
99
+ @payment_order.delivery_method = '3'
100
+ end
101
+
102
+ subject do
103
+ @payment_order.original_chain
104
+ end
105
+
106
+ it { expect(subject).to eq original_chain }
107
+ end
108
+
109
+ context 'example number 4' do
110
+ let(:original_chain) { '||846|FINVE|||pruebaFinve000|90646|0.01|1|||||40|prueba|846180000000000016|ND||||||prueba||||||123456||||||||' }
111
+
112
+ before do
113
+ @payment_order = STP::Structs::PaymentOrder.new
114
+ @payment_order.payment_concept = 'prueba'
115
+ @payment_order.tracking_key = 'pruebaFinve000'
116
+ @payment_order.company = 'FINVE'
117
+ @payment_order.beneficiary_account = '846180000000000016'
118
+ @payment_order.counterpart_institution = '846'
119
+ @payment_order.operant_institution = '90646'
120
+ @payment_order.amount = '0.01'
121
+ @payment_order.beneficiary_name = 'prueba'
122
+ @payment_order.number_reference = '123456'
123
+ @payment_order.payment_type = '1'
124
+ @payment_order.beneficiary_account_type = '40'
125
+ @payment_order.beneficiary_rfc = 'ND'
126
+ end
127
+
128
+ subject do
129
+ @payment_order.original_chain
130
+ end
131
+
132
+ it { expect(subject).to eq original_chain }
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,46 @@
1
+ module STPTestHelpers
2
+ def self.PAYMENT_ORDER_MOCK_ERROR(error_code)
3
+ {
4
+ 'descripcionError' => 'regular error msg',
5
+ 'id' => error_code,
6
+ }
7
+ end
8
+
9
+ def self.PAYMENT_ORDER_MOCK_SUCCESS
10
+ {
11
+ 'id' => 5_840_086,
12
+ 'empresa' => 'FINVE',
13
+ 'folioOrigen' => '5840086',
14
+ 'estado' => 'Exito',
15
+ 'causaDevolucion' => '',
16
+ }
17
+ end
18
+
19
+ def self.ACCOUNT_CREATE_MOCK_ERROR(error_code)
20
+ {
21
+ 'descripcionError' => 'regular error msg',
22
+ 'id' => error_code,
23
+ }
24
+ end
25
+
26
+ def self.ACCOUNT_CREATE_MOCK_SUCCESS
27
+ {
28
+ 'id' => 0,
29
+ 'descripcion' => '',
30
+ }
31
+ end
32
+
33
+ def self.ACCOUNT_DELETE_MOCK_ERROR(error_code)
34
+ {
35
+ 'descripcionError' => 'regular error msg',
36
+ 'id' => error_code,
37
+ }
38
+ end
39
+
40
+ def self.ACCOUNT_DELETE_MOCK_SUCCESS
41
+ {
42
+ 'id' => 0,
43
+ 'descripcion' => '',
44
+ }
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ $:.push File.expand_path("lib", __dir__)
2
+
3
+ # Maintain your gem's version:
4
+ require "stp/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "stp_client"
9
+ spec.version = STP::VERSION
10
+ spec.date = '2019-12-10'
11
+ spec.summary = "STP Client is a ruby integration to STP services"
12
+ spec.description = "STP Client is a lib for consuming STP services"
13
+ spec.files = `git ls-files`.split($/)
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ spec.authors = ["Yellowme"]
17
+ spec.email = 'hola@yellowme.mx'
18
+ spec.homepage = 'https://github.com/yellowme/stp-client'
19
+ spec.license = 'MIT'
20
+
21
+ spec.add_dependency "faraday", "~> 1.0"
22
+ spec.add_dependency "crypto_yellowme", "~> 0.3"
23
+
24
+ spec.add_development_dependency "rspec", "~> 3.8"
25
+ spec.add_development_dependency "faker", "~> 2.0"
26
+ spec.add_development_dependency "simplecov", "~> 0.17"
27
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stp_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yellowme
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: crypto_yellowme
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faker
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.17'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.17'
83
+ description: STP Client is a lib for consuming STP services
84
+ email: hola@yellowme.mx
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".gitignore"
90
+ - ".rspec"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - MIT-LICENSE
94
+ - README.md
95
+ - Rakefile
96
+ - lib/stp.rb
97
+ - lib/stp/account.rb
98
+ - lib/stp/config.rb
99
+ - lib/stp/payment_order.rb
100
+ - lib/stp/structs/account.rb
101
+ - lib/stp/structs/payment_order.rb
102
+ - lib/stp/version.rb
103
+ - spec/spec_helper.rb
104
+ - spec/stp/account_create_spec.rb
105
+ - spec/stp/account_delete_spec.rb
106
+ - spec/stp/payment_order_spec.rb
107
+ - spec/stp/structs/account_spec.rb
108
+ - spec/stp/structs/payment_order_spec.rb
109
+ - spec/support/stp_test_helpers.rb
110
+ - stp_client.gemspec
111
+ homepage: https://github.com/yellowme/stp-client
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.7.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: STP Client is a ruby integration to STP services
135
+ test_files:
136
+ - spec/spec_helper.rb
137
+ - spec/stp/account_create_spec.rb
138
+ - spec/stp/account_delete_spec.rb
139
+ - spec/stp/payment_order_spec.rb
140
+ - spec/stp/structs/account_spec.rb
141
+ - spec/stp/structs/payment_order_spec.rb
142
+ - spec/support/stp_test_helpers.rb