clever_tap 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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