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,43 @@
1
+ require 'spec_helper'
2
+ require 'shared/entity'
3
+
4
+ RSpec.describe CleverTap::Event do
5
+ describe '.upload_limit' do
6
+ subject { described_class.upload_limit }
7
+ it { is_expected.to eq 1000 }
8
+ end
9
+
10
+ describe '#to_h' do
11
+ subject { described_class.new(**params).to_h }
12
+
13
+ describe 'choosing `identity`' do
14
+ it_behaves_like 'choosing identity for', 'event'
15
+ end
16
+
17
+ describe 'choosing timestamp' do
18
+ it_behaves_like 'choosing timestamp'
19
+ end
20
+
21
+ describe 'event name' do
22
+ let(:data) { { 'FBID' => '1414', 'Name' => 'John' } }
23
+ let(:params) { { data: data, identity: 'FBID' } }
24
+
25
+ context 'when `name` is not provided' do
26
+ it { expect { subject }.to raise_error(CleverTap::MissingEventNameError) }
27
+ end
28
+
29
+ context 'when `name` is provided' do
30
+ let!(:params_ext) { params.merge!(name: 'Web Event') }
31
+ it { is_expected.to include 'evtName' => 'Web Event' }
32
+ end
33
+ end
34
+
35
+ describe 'type' do
36
+ it_behaves_like 'proper type'
37
+ end
38
+
39
+ describe 'data' do
40
+ it_behaves_like 'constructing data for', 'event'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe CleverTap::FailedResponse do
4
+ let(:records) { [{ 'id' => 1 }, { 'id' => 2 }] }
5
+ let(:message) { 'LOL an error' }
6
+ let(:code) { 401 }
7
+
8
+ subject { described_class.new(records: records, message: message, code: code) }
9
+
10
+ describe '#status' do
11
+ it { expect(subject.status).to eq 'fail' }
12
+ end
13
+
14
+ describe '#success' do
15
+ it { expect(subject.success).to be false }
16
+ end
17
+
18
+ describe '#errors' do
19
+ it do
20
+ error = { 'status' => 'fail', 'code' => code, 'error' => message }
21
+
22
+ expect(subject.errors).to match_array(
23
+ records.map { |r| error.merge('record' => r) }
24
+ )
25
+ end
26
+ end
27
+
28
+ describe '#message' do
29
+ it { expect(subject.message).to eq message }
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'shared/entity'
3
+
4
+ RSpec.describe CleverTap::Profile do
5
+ describe '.upload_limit' do
6
+ subject { described_class.upload_limit }
7
+ it { is_expected.to eq 100 }
8
+ end
9
+
10
+ describe '#to_h' do
11
+ subject { described_class.new(**params).to_h }
12
+
13
+ describe 'choosing `identity`' do
14
+ it_behaves_like 'choosing identity for', 'profile'
15
+ end
16
+
17
+ describe 'choosing timestamp' do
18
+ it_behaves_like 'choosing timestamp'
19
+ end
20
+
21
+ describe 'type' do
22
+ it_behaves_like 'proper type'
23
+ end
24
+
25
+ describe 'data' do
26
+ it_behaves_like 'constructing data for', 'profile'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe CleverTap::Response do
4
+ let(:success) do
5
+ { 'status' => 'success', 'processed' => 1, 'unprocessed' => [] }
6
+ end
7
+
8
+ let(:partial) do
9
+ {
10
+ 'status' => 'success',
11
+ 'processed' => 1,
12
+ 'unprocessed' => [{ '{ "ID" => "5", "Name": "John"}' => 'Some error' }]
13
+ }
14
+ end
15
+
16
+ let(:failure) do
17
+ {
18
+ 'status' => 'fail',
19
+ 'error' => 'Account Id not valid',
20
+ 'code' => 401
21
+ }
22
+ end
23
+
24
+ describe '#new' do
25
+ subject { described_class.new(response) }
26
+
27
+ context 'when successful request' do
28
+ let(:response) { OpenStruct.new(body: success.to_json) }
29
+
30
+ it { expect(subject.success).to be true }
31
+ it { expect(subject.failures).to eq [] }
32
+ end
33
+
34
+ context 'when partially successful request' do
35
+ let(:response) { OpenStruct.new(body: partial.to_json) }
36
+
37
+ it { expect(subject.success).to be false }
38
+ it { expect(subject.failures).to eq partial['unprocessed'] }
39
+ end
40
+
41
+ context 'when failed request' do
42
+ let(:response) { OpenStruct.new(body: failure.to_json) }
43
+
44
+ it { expect(subject.success).to be false }
45
+ it { expect(subject.failures).to eq [failure] }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe CleverTap::SuccessfulResponse do
4
+ shared_context 'successful state' do
5
+ let(:raw_response) do
6
+ { 'status' => 'success', 'processed' => 2, 'unprocessed' => [] }
7
+ end
8
+ end
9
+
10
+ shared_context 'partial state' do
11
+ let(:records) { [{ 'id' => 1 }] }
12
+ let(:raw_response) do
13
+ {
14
+ 'status' => 'fail',
15
+ 'processed' => 1,
16
+ 'unprocessed' => records.map { |r| { 'record' => r.to_json } }
17
+ }
18
+ end
19
+ end
20
+
21
+ shared_context 'fail state' do
22
+ let(:records) { [{ 'id' => 1 }, { 'id' => 2 }] }
23
+ let(:raw_response) do
24
+ {
25
+ 'status' => 'fail',
26
+ 'processed' => 0,
27
+ 'unprocessed' => records.map { |r| { 'record' => r.to_json } }
28
+ }
29
+ end
30
+ end
31
+
32
+ subject { described_class.new(raw_response) }
33
+
34
+ describe '#status' do
35
+ context 'with successful status' do
36
+ include_context 'successful state'
37
+
38
+ it { expect(subject.status).to eq 'success' }
39
+ end
40
+
41
+ context 'with partial status' do
42
+ include_context 'partial state'
43
+
44
+ it { expect(subject.status).to eq 'partial' }
45
+ end
46
+
47
+ context 'with fail status' do
48
+ include_context 'fail state'
49
+
50
+ it { expect(subject.status).to eq 'fail' }
51
+ end
52
+ end
53
+
54
+ describe '#success' do
55
+ context 'with successful status' do
56
+ include_context 'successful state'
57
+
58
+ it { expect(subject.success).to be true }
59
+ end
60
+
61
+ context 'with partial status' do
62
+ include_context 'partial state'
63
+
64
+ it { expect(subject.success).to be false }
65
+ end
66
+
67
+ context 'with fail status' do
68
+ include_context 'fail state'
69
+
70
+ it { expect(subject.success).to be false }
71
+ end
72
+ end
73
+
74
+ describe '#errors' do
75
+ context 'with successful status' do
76
+ include_context 'successful state'
77
+
78
+ it { expect(subject.errors).to be_empty }
79
+ end
80
+
81
+ context 'with partial status' do
82
+ include_context 'partial state'
83
+ it { expect(subject.errors).to all(include('record')) }
84
+ end
85
+
86
+ context 'with fail status' do
87
+ include_context 'fail state'
88
+
89
+ it { expect(subject.errors).to all(include('record')) }
90
+ end
91
+ end
92
+
93
+ describe '#message' do
94
+ context 'with successful status' do
95
+ include_context 'successful state'
96
+
97
+ it { expect(subject.message).to eq '' }
98
+ end
99
+
100
+ context 'with partial status' do
101
+ include_context 'partial state'
102
+
103
+ it { expect(subject.message).to eq '' }
104
+ end
105
+
106
+ context 'with fail status' do
107
+ include_context 'fail state'
108
+
109
+ it { expect(subject.message).to eq '' }
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'validation failure' do |expected_code|
4
+ it 'failed to upload the profiles' do
5
+ result = subject.call(client)
6
+ body = JSON.parse(result.body)
7
+
8
+ aggregate_failures 'failed response' do
9
+ expect(result.success?).to be_truthy
10
+ expect(result.status).to eq(200)
11
+ expect(body).to include('processed' => 0,
12
+ 'status' => 'fail',
13
+ 'unprocessed' => contain_exactly(
14
+ a_hash_including('code' => expected_code),
15
+ a_hash_including('code' => expected_code)
16
+ ))
17
+ end
18
+ end
19
+ end
20
+
21
+ describe CleverTap::Uploader, vcr: true do
22
+ describe '#call' do
23
+ let(:profile_properties) { %i[id created_at full_name last_name bta] }
24
+ let(:client) { CleverTap::Client.new(AUTH_ACCOUNT_ID, AUTH_PASSCODE) }
25
+
26
+ context 'with valid data' do
27
+ let(:profiles) { [Profile.build_valid, Profile.build_valid] }
28
+
29
+ subject { described_class.new(profiles, identity_field: 'identity') }
30
+
31
+ it 'makes successful upload' do
32
+ result = subject.call(client)
33
+ body = JSON.parse(result.body)
34
+
35
+ aggregate_failures 'success response' do
36
+ expect(result.success?).to be_truthy
37
+ expect(result.status).to eq(200)
38
+ expect(body).to include('processed' => 2, 'unprocessed' => [], 'status' => 'success')
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'when email is invalid' do
44
+ let(:profiles) { [Profile.build_valid('Email' => '1234'), Profile.build_valid('Email' => '1234')] }
45
+
46
+ subject { described_class.new(profiles, identity_field: 'identity') }
47
+
48
+ it_behaves_like 'validation failure', 515
49
+ end
50
+
51
+ context 'when phone is invalid' do
52
+ let(:profiles) { [Profile.build_valid('Phone' => '223'), Profile.build_valid('Phone' => '123')] }
53
+
54
+ subject { described_class.new(profiles, identity_field: 'identity') }
55
+
56
+ it_behaves_like 'validation failure', 516
57
+ end
58
+
59
+ context 'when employment status is invalid' do
60
+ let(:profiles) { [Profile.build_valid('Employed' => '223'), Profile.build_valid('Employed' => '123')] }
61
+
62
+ subject { described_class.new(profiles, identity_field: 'identity') }
63
+
64
+ it_behaves_like 'validation failure', 517
65
+ end
66
+
67
+ context 'when education status is invalid' do
68
+ let(:profiles) { [Profile.build_valid('Education' => '223'), Profile.build_valid('Education' => '123')] }
69
+
70
+ subject { described_class.new(profiles, identity_field: 'identity') }
71
+
72
+ it_behaves_like 'validation failure', 518
73
+ end
74
+
75
+ context 'when marital status is invalid' do
76
+ let(:profiles) { [Profile.build_valid('Married' => '223'), Profile.build_valid('Married' => '123')] }
77
+
78
+ subject { described_class.new(profiles, identity_field: 'identity') }
79
+
80
+ it_behaves_like 'validation failure', 519
81
+ end
82
+
83
+ context 'when age is invalid' do
84
+ let(:profiles) { [Profile.build_valid('Age' => 'aa'), Profile.build_valid('Age' => 'aa')] }
85
+
86
+ subject { described_class.new(profiles, identity_field: 'identity') }
87
+
88
+ it_behaves_like 'validation failure', 520
89
+ end
90
+
91
+ context 'when the identity field is missing' do
92
+ let(:profiles) { [Profile.build_valid, Profile.build_valid] }
93
+
94
+ subject { described_class.new(profiles, identity_field: 'fake_id') }
95
+
96
+ it do
97
+ expect { subject.call(client) }.to raise_error(RuntimeError, /missing identity field/i)
98
+ end
99
+ end
100
+
101
+ context 'when the creation date field is missing' do
102
+ let(:profiles) { [Profile.build_valid, Profile.build_valid] }
103
+
104
+ subject do
105
+ described_class.new(profiles, identity_field: 'identity', date_field: 'fake_created_at')
106
+ end
107
+
108
+ it_behaves_like 'validation failure', 525
109
+ end
110
+
111
+ context 'with invalid credentials' do
112
+ let(:client) { CleverTap::Client.new('fake-id', 'fake-pass') }
113
+ subject { described_class.new([Profile.build_valid], identity_field: 'identity') }
114
+
115
+ it 'failed to upload the profiles' do
116
+ result = subject.call(client)
117
+ body = JSON.parse(result.body)
118
+
119
+ aggregate_failures 'failed response' do
120
+ expect(result.success?).to be_falsy
121
+ expect(result.status).to eq(401)
122
+ expect(body).to include('code' => 401,
123
+ 'status' => 'fail',
124
+ 'error' => matching(/account id/i))
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,42 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://api.clevertap.com/1/upload
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"d":[{"identity":"3","ts":1521649274,"type":"profile","profileData":{"identity":3,"created_at":"2018-03-21
9
+ 18:21:14 +0200","Name":"John Rush","Email":"example@gmail.com","Gender":"M","Phone":"+35922333232","Employed":"Y","Education":"Graduate","Married":"Y","Age":"18"}},{"identity":"4","ts":1521649274,"type":"profile","profileData":{"identity":4,"created_at":"2018-03-21
10
+ 18:21:14 +0200","Name":"John Rush","Email":"$$$$$","Gender":"M","Phone":"+35922333232","Employed":"Y","Education":"Graduate","Married":"Y","Age":"18"}}]}'
11
+ headers:
12
+ Content-Type:
13
+ - application/json
14
+ X-CleverTap-Account-Id:
15
+ - fake_account_id
16
+ X-CleverTap-Passcode:
17
+ - fake_passcode
18
+ User-Agent:
19
+ - Faraday v0.14.0
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ content-type:
26
+ - application/json;charset=utf-8
27
+ content-length:
28
+ - '505'
29
+ connection:
30
+ - Close
31
+ body:
32
+ encoding: UTF-8
33
+ string: '{ "status" : "partial" , "processed" : 1 , "unprocessed" : [ { "status"
34
+ : "fail" , "code" : 515 , "error" : "Profile data is incorrect. Email is not
35
+ valid.Skipped record number : 2" , "record" : { "identity" : "4" , "ts" :
36
+ 1521649274 , "type" : "profile" , "profileData" : { "identity" : 4 , "created_at"
37
+ : "2018-03-21 18:21:14 +0200" , "Name" : "John Rush" , "Email" : "$$$$$" ,
38
+ "Gender" : "M" , "Phone" : "+35922333232" , "Employed" : "Y" , "Education"
39
+ : "Graduate" , "Married" : "Y" , "Age" : "18"}}}]}'
40
+ http_version:
41
+ recorded_at: Wed, 21 Mar 2018 16:21:15 GMT
42
+ recorded_with: VCR 4.0.0
@@ -0,0 +1,41 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://api.clevertap.com/1/upload
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"d":[{"identity":"2","ts":1521649274,"type":"profile","profileData":{"identity":2,"created_at":"2018-03-21
9
+ 18:21:14 +0200","Name":"John Rush","Email":"$$$$$","Gender":"M","Phone":"+35922333232","Employed":"Y","Education":"Graduate","Married":"Y","Age":"18"}}]}'
10
+ headers:
11
+ Content-Type:
12
+ - application/json
13
+ X-CleverTap-Account-Id:
14
+ - fake_account_id
15
+ X-CleverTap-Passcode:
16
+ - fake_passcode
17
+ User-Agent:
18
+ - Faraday v0.14.0
19
+ response:
20
+ status:
21
+ code: 200
22
+ message: OK
23
+ headers:
24
+ content-type:
25
+ - application/json;charset=utf-8
26
+ content-length:
27
+ - '502'
28
+ connection:
29
+ - Close
30
+ body:
31
+ encoding: UTF-8
32
+ string: '{ "status" : "fail" , "processed" : 0 , "unprocessed" : [ { "status"
33
+ : "fail" , "code" : 515 , "error" : "Profile data is incorrect. Email is not
34
+ valid.Skipped record number : 1" , "record" : { "identity" : "2" , "ts" :
35
+ 1521649274 , "type" : "profile" , "profileData" : { "identity" : 2 , "created_at"
36
+ : "2018-03-21 18:21:14 +0200" , "Name" : "John Rush" , "Email" : "$$$$$" ,
37
+ "Gender" : "M" , "Phone" : "+35922333232" , "Employed" : "Y" , "Education"
38
+ : "Graduate" , "Married" : "Y" , "Age" : "18"}}}]}'
39
+ http_version:
40
+ recorded_at: Wed, 21 Mar 2018 16:21:14 GMT
41
+ recorded_with: VCR 4.0.0