clever_tap 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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +48 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +164 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +8 -0
  11. data/bin/setup +8 -0
  12. data/clever_tap.gemspec +33 -0
  13. data/lib/clever_tap.rb +79 -0
  14. data/lib/clever_tap/client.rb +113 -0
  15. data/lib/clever_tap/config.rb +25 -0
  16. data/lib/clever_tap/entity.rb +87 -0
  17. data/lib/clever_tap/event.rb +30 -0
  18. data/lib/clever_tap/failed_response.rb +28 -0
  19. data/lib/clever_tap/profile.rb +7 -0
  20. data/lib/clever_tap/response.rb +28 -0
  21. data/lib/clever_tap/successful_response.rb +30 -0
  22. data/lib/clever_tap/uploader.rb +72 -0
  23. data/lib/clever_tap/version.rb +3 -0
  24. data/lib/clevertap-ruby.rb +1 -0
  25. data/spec/factories/profile.rb +36 -0
  26. data/spec/integrations/clever_tap_spec.rb +81 -0
  27. data/spec/rubocop_spec.rb +12 -0
  28. data/spec/shared/clever_tap_client.rb +13 -0
  29. data/spec/shared/entity.rb +105 -0
  30. data/spec/spec_helper.rb +18 -0
  31. data/spec/units/clever_tap_client_spec.rb +279 -0
  32. data/spec/units/clever_tap_spec.rb +88 -0
  33. data/spec/units/event_spec.rb +43 -0
  34. data/spec/units/failed_response_spec.rb +31 -0
  35. data/spec/units/profile_spec.rb +29 -0
  36. data/spec/units/response_spec.rb +48 -0
  37. data/spec/units/successful_response_spec.rb +112 -0
  38. data/spec/units/uploader_spec.rb +129 -0
  39. data/spec/vcr_cassettes/CleverTap/uploading_a_many_profiles/when_only_some_are_valid/partially_succeds.yml +42 -0
  40. data/spec/vcr_cassettes/CleverTap/uploading_a_profile/when_is_invalid/fails.yml +41 -0
  41. data/spec/vcr_cassettes/CleverTap/uploading_a_profile/when_is_valid/succeed.yml +35 -0
  42. data/spec/vcr_cassettes/CleverTap/uploading_an_event/when_is_valid/succeed.yml +34 -0
  43. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_invalid_records/calls_on_failed_upload_once.yml +38 -0
  44. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_invalid_records/returns_an_array_with_one_failed_Response_object.yml +38 -0
  45. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_do_not_fit_upload_limit_/calls_on_successful_upload_proc_twice.yml +67 -0
  46. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_do_not_fit_upload_limit_/returns_an_array_with_two_successful_Response_objects.yml +67 -0
  47. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_fit_upload_limit_/calls_on_successful_upload_proc_once.yml +36 -0
  48. data/spec/vcr_cassettes/CleverTap_Client/_upload/when_upload_records_are_homogenous/and_valid_records/and_objects_fit_upload_limit_/returns_an_array_with_one_successful_Response_object.yml +36 -0
  49. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_age_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  50. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_education_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +49 -0
  51. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_email_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  52. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_employment_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  53. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_marital_status_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  54. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_phone_is_invalid/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  55. data/spec/vcr_cassettes/CleverTap_Uploader/_call/when_the_creation_date_field_is_missing/behaves_like_validation_failure/failed_to_upload_the_profiles.yml +48 -0
  56. data/spec/vcr_cassettes/CleverTap_Uploader/_call/with_invalid_credentials/failed_to_upload_the_profiles.yml +36 -0
  57. data/spec/vcr_cassettes/CleverTap_Uploader/_call/with_valid_data/makes_successful_upload.yml +36 -0
  58. data/spec/vcr_config.rb +13 -0
  59. metadata +199 -0
@@ -0,0 +1,13 @@
1
+ shared_examples 'configured `Client`' do
2
+ it 'preserves credentials in `Client`' do
3
+ expect(subject.account_id).to eq account_id
4
+ expect(subject.passcode).to eq account_passcode
5
+ end
6
+ end
7
+
8
+ shared_examples 'configured `Client`' do
9
+ it 'preserves credentials in `Client`' do
10
+ expect(subject.account_id).to eq account_id
11
+ expect(subject.passcode).to eq account_passcode
12
+ end
13
+ end
@@ -0,0 +1,105 @@
1
+ shared_examples_for 'setting allowed identities for' do |type|
2
+ type_data = type == 'event' ? 'evtData' : 'profileData'
3
+ described_class::ALLOWED_IDENTITIES.each do |id|
4
+ context "when `identity` set as `#{id}` in the event" do
5
+ let!(:params_ext) { params.merge!(identity: id) }
6
+ let!(:data_ext) { data.merge!(id => '1414') }
7
+
8
+ it { is_expected.to include(id => '1414') }
9
+ it { expect(subject[type_data]).not_to include(id => '1414') }
10
+ end
11
+ end
12
+ end
13
+
14
+ shared_examples_for 'choosing identity for' do |type|
15
+ evt_name = type == 'event' ? { name: 'Evt' } : {}
16
+ before { CleverTap.setup { |c| c.identity_field = 'ID' } }
17
+ let(:params) { { data: data }.merge!(evt_name) }
18
+ let(:data) { { 'ID' => 1, 'Name' => 'John' } }
19
+
20
+ context 'when custom `identity` from config' do
21
+ it { is_expected.to include 'identity' => '1' }
22
+ end
23
+
24
+ context 'when `identity` different from ALLOWED_IDENTITIES and config' do
25
+ let!(:params_ext) { params.merge!(identity: 'email') }
26
+ let!(:data_ext) { data.merge!('email' => 'example@email.com') }
27
+
28
+ it { is_expected.to include 'identity' => '1' }
29
+ end
30
+
31
+ context 'when `identity` missing from `data`' do
32
+ let(:data) { { 'Name' => 'John' } }
33
+
34
+ it { expect { subject }.to raise_error CleverTap::MissingIdentityError }
35
+ end
36
+
37
+ it_behaves_like 'setting allowed identities for', type
38
+ end
39
+
40
+ shared_examples_for 'choosing timestamp' do
41
+ let(:data) { { 'FBID' => 1, 'Name' => 'John' } }
42
+ let(:params) { { data: data, identity: 'FBID', name: 'evt' } }
43
+
44
+ context 'when no `timestamp_field`' do
45
+ it { is_expected.not_to include 'ts' }
46
+ end
47
+
48
+ context 'when specific `timestamp` field' do
49
+ let!(:data_ext) { data.merge!('Open Time' => open_time) }
50
+ let!(:params_ext) { params.merge!(timestamp_field: 'Open Time') }
51
+
52
+ context 'and `timestamp_field` is Unix timestamp' do
53
+ let(:open_time) { '1508241881' }
54
+ it { is_expected.to include('ts' => open_time.to_i) }
55
+ end
56
+
57
+ context 'and `timestamp_field` is `DateTime` timestamp' do
58
+ let(:open_time) { Time.now }
59
+ it { is_expected.to include('ts' => open_time.to_i) }
60
+ end
61
+ end
62
+
63
+ context 'when `custom_timestamp` specified' do
64
+ let!(:params_ext) { params.merge!(custom_timestamp: open_time) }
65
+
66
+ context 'and `custom_timestamp` is Unix timestamp' do
67
+ let(:open_time) { '1508241881' }
68
+ it { is_expected.to include('ts' => open_time.to_i) }
69
+ end
70
+
71
+ context 'and `custom_timestamp` is `DateTime` timestamp' do
72
+ let(:open_time) { Time.now }
73
+ it { is_expected.to include('ts' => open_time.to_i) }
74
+ end
75
+ end
76
+ end
77
+
78
+ shared_examples_for 'proper type' do
79
+ let(:data) { { 'FBID' => '1414', 'Name' => 'John' } }
80
+ let(:params) { { data: data, name: 'e', identity: 'FBID' } }
81
+
82
+ it { is_expected.to include described_class::TYPE_KEY_STRING => described_class::TYPE_VALUE_STRING }
83
+ end
84
+
85
+ shared_examples_for 'constructing data for' do |type|
86
+ obj_type = type == 'event' ? 'evtData' : 'profileData'
87
+ evt_name = type == 'event' ? { name: 'Evt' } : {}
88
+
89
+ let(:data) { { 'FBID' => '1414', 'Name' => 'John' } }
90
+ let(:params) { { data: data, identity: 'FBID' }.merge!(evt_name) }
91
+
92
+ context 'when no `data` param in `params` hash' do
93
+ let(:params) { {}.merge!(evt_name) }
94
+ it { expect { subject }.to raise_error CleverTap::NoDataError }
95
+ end
96
+
97
+ context 'when `data` empty hash in `params` hash' do
98
+ let(:params) { { data: {} }.merge!(evt_name) }
99
+ it { expect { subject }.to raise_error CleverTap::NoDataError }
100
+ end
101
+
102
+ context 'when `data` available in `params` hash' do
103
+ it { is_expected.to include(obj_type => { 'Name' => 'John' }) }
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ require 'clever_tap'
4
+
5
+ require 'vcr'
6
+ require 'vcr_config'
7
+
8
+ require 'factories/profile'
9
+ require 'pry-byebug'
10
+
11
+ # Use for recording VCR cassettes
12
+ AUTH_ACCOUNT_ID = ENV['CLEVER_TAP_ACCOUNT_ID'] || 'fake-id'
13
+ AUTH_PASSCODE = ENV['CLEVER_TAP_PASSCODE'] || 'fake-passcode'
14
+
15
+ RSpec.configure do |config|
16
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
17
+ config.mock_with(:rspec) { |c| c.syntax = :expect }
18
+ end
@@ -0,0 +1,279 @@
1
+ require 'spec_helper'
2
+ require 'shared/clever_tap_client'
3
+
4
+ describe CleverTap::Client, vcr: true do
5
+ subject { described_class.new('123456', 'passcode') }
6
+
7
+ describe 'authentication' do
8
+ it 'send right `account_id`' do
9
+ expect(subject.connection.headers['X-CleverTap-Account-Id']).to eq('123456')
10
+ end
11
+
12
+ it 'send right `passcode`' do
13
+ expect(subject.connection.headers['X-CleverTap-Passcode']).to eq('passcode')
14
+ end
15
+ end
16
+
17
+ describe 'customisations' do
18
+ context 'with different adapter' do
19
+ subject do
20
+ described_class.new('123456', 'passcode') { |config| config.adapter(:test) }
21
+ end
22
+
23
+ it 'override the default adapter' do
24
+ handlers = subject.connection.builder.handlers
25
+
26
+ expect(handlers.count).to eq(1)
27
+ expect(handlers.first).to eq(Faraday::Adapter::Test)
28
+ end
29
+ end
30
+
31
+ context 'without an adapter' do
32
+ subject { described_class.new('123456', 'passcode') }
33
+
34
+ it 'use Net::HTTP adapter' do
35
+ handlers = subject.connection.builder.handlers
36
+
37
+ expect(handlers.count).to eq(1)
38
+ expect(handlers.first).to eq(Faraday::Adapter::NetHttp)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#new' do
44
+ let(:identity_field) { 'ID' }
45
+ let(:account_id) { 'ABC1234' }
46
+ let(:account_passcode) { 'AcCPasScoDe123' }
47
+
48
+ context 'when credentials set in `CleverTap.setup`' do
49
+ subject { CleverTap::Client.new }
50
+
51
+ before do
52
+ CleverTap.setup do |config|
53
+ config.identity_field = identity_field
54
+ config.account_id = account_id
55
+ config.account_passcode = account_passcode
56
+ end
57
+ end
58
+
59
+ it_behaves_like 'configured `Client`'
60
+ end
61
+
62
+ context 'when credentials set in `CleverTap::Client.new`' do
63
+ subject { CleverTap::Client.new(account_id, account_passcode) }
64
+ it_behaves_like 'configured `Client`'
65
+ end
66
+ end
67
+
68
+ def event_factory(id, name)
69
+ CleverTap::Event.new(
70
+ data: { 'FBID' => id.to_s, 'Name' => name.to_s },
71
+ name: 'Web Event',
72
+ identity: 'FBID'
73
+ )
74
+ end
75
+
76
+ def profile_factory
77
+ CleverTap::Profile.new(
78
+ data: { 'ID' => '1414', 'Name' => 'John', 'Phone' => '+44+441234' }
79
+ )
80
+ end
81
+
82
+ describe '#upload' do
83
+ let(:success_proc) { proc { 'sample proc' } }
84
+ let(:response) { subject.upload([event1, event2]) }
85
+
86
+ subject do
87
+ client = described_class.new
88
+ client.on_successful_upload(&success_proc)
89
+ client
90
+ end
91
+
92
+ before do
93
+ CleverTap.setup do |c|
94
+ c.identity_field = 'ID'
95
+ c.account_id = AUTH_ACCOUNT_ID
96
+ c.account_passcode = AUTH_PASSCODE
97
+ end
98
+ end
99
+
100
+ context 'when upload records are homogenous' do
101
+ context 'and valid records' do
102
+ let(:event1) { event_factory('1414', 'John') }
103
+ let(:event2) { event_factory('1515', 'Jill') }
104
+
105
+ context 'and objects fit `upload_limit`' do
106
+ VCR.use_cassette('upload within Event limit') do
107
+ it 'returns an array with one successful `Response` object' do
108
+ expect(response.count).to eq 1
109
+ expect(response.first.success).to be true
110
+ expect(response.first).to be_a(CleverTap::Response)
111
+ end
112
+
113
+ it 'calls `on_successful_upload` proc once' do
114
+ expect(success_proc).to receive(:call).once
115
+ subject.upload([event1, event2])
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'and objects do not fit `upload_limit`' do
121
+ before do
122
+ allow(CleverTap::Event)
123
+ .to receive(:upload_limit).and_return(1)
124
+ end
125
+
126
+ VCR.use_cassette('upload out of Event limit') do
127
+ it 'returns an array with two successful `Response` objects' do
128
+ expect(response.count).to eq 2
129
+ expect(response.all?(&:success)).to be true
130
+ expect(
131
+ response.all? { |r| r.class == CleverTap::Response }
132
+ ).to be true
133
+ end
134
+
135
+ it 'calls `on_successful_upload` proc twice' do
136
+ expect(success_proc).to receive(:call).twice
137
+ subject.upload([event1, event2])
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ context 'and invalid records' do
144
+ let(:failure_proc) { proc { 'sample proc' } }
145
+ let(:response) { subject.upload([profile]) }
146
+
147
+ subject do
148
+ client = described_class.new
149
+ client.on_failed_upload(&failure_proc)
150
+ client
151
+ end
152
+
153
+ let(:profile) { profile_factory }
154
+
155
+ it 'returns an array with one failed `Response` object' do
156
+ expect(response.count).to eq 1
157
+ expect(response.first.class).to eq CleverTap::Response
158
+ expect(response.first.success).to be false
159
+ end
160
+
161
+ it 'calls `on_failed_upload` once' do
162
+ expect(failure_proc).to receive(:call).once
163
+ subject.upload([profile])
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'when objects are not homogenous' do
169
+ let(:event1) { event_factory('1414', 'John') }
170
+ let(:event2) { {} }
171
+
172
+ it 'raises `NotConsistentArrayError`' do
173
+ expect { subject.upload([event1, event2]) }.to raise_error CleverTap::NotConsistentArrayError
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '#request_body' do
179
+ subject { described_class.new('123456', 'passcode') }
180
+ let(:records) do
181
+ [
182
+ { 'ID' => '123', 'Name' => 'John' },
183
+ { 'ID' => '456', 'Name' => 'Jill' }
184
+ ]
185
+ end
186
+
187
+ it 'converts records hash to json' do
188
+ expect(subject.send(:request_body, records))
189
+ .to eq({ 'd' => records }.to_json)
190
+ end
191
+ end
192
+
193
+ describe '#determine_type' do
194
+ subject { described_class.new('123456', 'passcode') }
195
+
196
+ context 'when records are of the same type' do
197
+ let(:records) { [{}, {}] }
198
+
199
+ it 'returns the class of the elements' do
200
+ expect(subject.send(:determine_type, records)).to eq Hash
201
+ end
202
+ end
203
+
204
+ context 'when records are of different type' do
205
+ let(:records) { [{}, []] }
206
+
207
+ it 'raises `NotConsistentArrayError`' do
208
+ expect { subject.send(:determine_type, records) }.to raise_error CleverTap::NotConsistentArrayError
209
+ end
210
+ end
211
+ end
212
+
213
+ describe '#ensure_array' do
214
+ subject { described_class.new('123456', 'passcode') }
215
+ let(:records) { %w[sample sample2] }
216
+ let(:record) { 'sample' }
217
+
218
+ it 'returns an array when an array passed' do
219
+ expect(subject.send(:ensure_array, records)).to eq records
220
+ end
221
+
222
+ it 'returns an array when a single element passed' do
223
+ expect(subject.send(:ensure_array, record)).to eq [record]
224
+ end
225
+ end
226
+
227
+ describe 'setting `account_id` and `passcode`' do
228
+ subject { described_class.new(client_id, client_passcode) }
229
+ let(:client_id) { '123456' }
230
+ let(:client_passcode) { 'passcode' }
231
+ let(:config_id) { 'config_account_id' }
232
+ let(:config_passcode) { 'config_passcode' }
233
+
234
+ def config_client(account, pass)
235
+ CleverTap.setup do |c|
236
+ c.account_id = account
237
+ c.account_passcode = pass
238
+ end
239
+ end
240
+
241
+ context 'when credentials provided in configuration' do
242
+ before { config_client(config_id, config_passcode) }
243
+
244
+ context 'and in initialization as well' do
245
+ it 'has initialization values' do
246
+ expect(subject.send(:assign_account_id, client_id)).to eq client_id
247
+ expect(subject.send(:assign_passcode, client_passcode)).to eq client_passcode
248
+ end
249
+ end
250
+
251
+ context 'and not in initialization' do
252
+ it 'has initialization values' do
253
+ expect(subject.send(:assign_account_id, nil)).to eq config_id
254
+ expect(subject.send(:assign_passcode, nil)).to eq config_passcode
255
+ end
256
+ end
257
+ end
258
+
259
+ context 'when credentials not provided in configuration' do
260
+ before { config_client(nil, nil) }
261
+
262
+ context 'but provided in initialization' do
263
+ it 'has initialization values' do
264
+ expect(subject.send(:assign_account_id, client_id)).to eq client_id
265
+ expect(subject.send(:assign_passcode, client_passcode)).to eq client_passcode
266
+ end
267
+ end
268
+
269
+ context 'and not provided in initialization as well' do
270
+ before { config_client(nil, nil) }
271
+ subject { described_class.new }
272
+
273
+ it 'raises a `RuntimeError` error' do
274
+ expect { subject }.to raise_error RuntimeError
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CleverTap do
4
+ describe '#new' do
5
+ context 'with valid arguments' do
6
+ context 'with keyword arguments' do
7
+ subject(:clever_tap) do
8
+ CleverTap.new(account_id: 'foo',
9
+ passcode: 'passcode',
10
+ identity_field: 'ID',
11
+ configure_faraday: configure_faraday)
12
+ end
13
+
14
+ let(:configure_faraday) { proc {} }
15
+
16
+ it('persist the account_id') { expect(clever_tap.config.account_id).to eq('foo') }
17
+ it('persist the passcode') { expect(clever_tap.config.passcode).to eq('passcode') }
18
+ it('persist the identity field') { expect(clever_tap.config.identity_field).to eq('ID') }
19
+ it('persist the faraday config') { expect(clever_tap.config.configure_faraday).to eq(configure_faraday) }
20
+ end
21
+
22
+ context 'with block' do
23
+ subject(:clever_tap) do
24
+ CleverTap.new do |config|
25
+ config.account_id = 'foo'
26
+ config.passcode = 'passcode'
27
+ config.identity_field = 'ID'
28
+ config.configure_faraday(&configure_faraday)
29
+ end
30
+ end
31
+
32
+ let(:configure_faraday) { proc {} }
33
+
34
+ it('persist the account_id') { expect(clever_tap.config.account_id).to eq('foo') }
35
+ it('persist the passcode') { expect(clever_tap.config.passcode).to eq('passcode') }
36
+ it('persist the identity field') { expect(clever_tap.config.identity_field).to eq('ID') }
37
+ it('persist the faraday config') { expect(clever_tap.config.configure_faraday).to eq(configure_faraday) }
38
+ end
39
+ end
40
+
41
+ context 'with invalid arguments' do
42
+ it 'require `account_id`' do
43
+ expect { CleverTap.new(passcode: 'foo') }
44
+ .to raise_error(RuntimeError, /account_id/)
45
+ end
46
+
47
+ it 'require `passcode`' do
48
+ expect { CleverTap.new(account_id: 'bar') }
49
+ .to raise_error(RuntimeError, /passcode/)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#client' do
55
+ subject(:clever_tap) { CleverTap.new(account_id: 'foo', passcode: 'passcode', &configure_faraday) }
56
+ let(:configure_faraday) { proc { |_faraday| } }
57
+
58
+ it 'initialize client with a right auth data' do
59
+ expect(CleverTap::Client).to receive(:new).with('foo', 'passcode', &configure_faraday)
60
+
61
+ clever_tap.client
62
+ end
63
+
64
+ it 'cache the client between calls' do
65
+ id = clever_tap.client.object_id
66
+ expect(clever_tap.client.object_id).to eq(id)
67
+ end
68
+ end
69
+
70
+ describe '.config' do
71
+ let(:identity_field) { 'ID' }
72
+ let(:account_id) { 'ABC1234' }
73
+ let(:account_passcode) { 'AcCPasScoDe123' }
74
+ let(:remove_identity) { true }
75
+
76
+ it 'sets config variables' do
77
+ described_class.setup do |config|
78
+ config.identity_field = identity_field
79
+ config.account_id = account_id
80
+ config.account_passcode = account_passcode
81
+ end
82
+
83
+ expect(described_class.identity_field).to eq identity_field
84
+ expect(described_class.account_id).to eq account_id
85
+ expect(described_class.account_passcode).to eq account_passcode
86
+ end
87
+ end
88
+ end