agilecrm-wrapper 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +89 -0
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/Rakefile +10 -0
- data/agilecrm.gemspec +35 -0
- data/lib/agilecrm-wrapper.rb +47 -0
- data/lib/agilecrm-wrapper/configuration.rb +11 -0
- data/lib/agilecrm-wrapper/contact.rb +119 -0
- data/lib/agilecrm-wrapper/error.rb +75 -0
- data/lib/agilecrm-wrapper/note.rb +31 -0
- data/lib/agilecrm-wrapper/response/raise_error.rb +21 -0
- data/lib/agilecrm-wrapper/version.rb +3 -0
- data/spec/agilecrm-wrapper/agilecrm_wrapper_spec.rb +45 -0
- data/spec/agilecrm-wrapper/contact_spec.rb +108 -0
- data/spec/agilecrm-wrapper/note_spec.rb +11 -0
- data/spec/fixtures/contacts/create_contact.json +45 -0
- data/spec/fixtures/contacts/get_contact.json +45 -0
- data/spec/fixtures/contacts/get_contact_notes.json +52 -0
- data/spec/fixtures/contacts/list_contacts.json +100 -0
- data/spec/fixtures/contacts/search_by_email.json +45 -0
- data/spec/fixtures/contacts/search_by_email_no_results.json +1 -0
- data/spec/fixtures/contacts/updated_contact.json +45 -0
- data/spec/fixtures/notes/create_note.json +52 -0
- data/spec/fixtures/notes/create_without_contact.json +8 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/fake_agilecrm.rb +61 -0
- metadata +270 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module AgileCRMWrapper
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :response
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def from_response(response, message = '')
|
7
|
+
new(response, message)
|
8
|
+
end
|
9
|
+
|
10
|
+
def errors
|
11
|
+
@errors ||= {
|
12
|
+
400 => AgileCRMWrapper::BadRequest,
|
13
|
+
401 => AgileCRMWrapper::Unauthorized,
|
14
|
+
404 => AgileCRMWrapper::NotFound,
|
15
|
+
405 => AgileCRMWrapper::MethodNotAllowed,
|
16
|
+
415 => AgileCRMWrapper::MediaTypeMismatch,
|
17
|
+
500 => AgileCRMWrapper::InternalServerError
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(response, message = '')
|
23
|
+
super(message)
|
24
|
+
@response = response
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Raised when Twitter returns a 4xx HTTP status code
|
29
|
+
class ClientError < Error; end
|
30
|
+
|
31
|
+
# Raised when AgileCRMWrapper returns a 400 HTTP status code
|
32
|
+
class BadRequest < ClientError
|
33
|
+
def initialize(response, message = 'The request was formatted incorrectly')
|
34
|
+
super(response, message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Raised when AgileCRMWrapper returns a 401 HTTP status code
|
39
|
+
class Unauthorized < ClientError
|
40
|
+
def initialize(response, message = 'Invalid API Key')
|
41
|
+
super(response, message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raised when AgileCRMWrapper returns a 404 HTTP status code
|
46
|
+
class NotFound < ClientError
|
47
|
+
def initialize(response, message = 'Resource not found')
|
48
|
+
super(response, message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Raised when AgileCRMWrapper returns a 405 HTTP status code
|
53
|
+
class MethodNotAllowed < ClientError
|
54
|
+
def initialize(response, message = 'Invalid method type')
|
55
|
+
super(response, message)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Raised when AgileCRMWrapper returns a 415 HTTP status code
|
60
|
+
class MediaTypeMismatch < ClientError
|
61
|
+
def initialize(response, message = 'Unsupported Media type')
|
62
|
+
super(response, message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Raised when AgileCRMWrapper returns a 5xx HTTP status code
|
67
|
+
class ServerError < Error; end
|
68
|
+
|
69
|
+
# Raised when AgileCRMWrapper returns a 500 HTTP status code
|
70
|
+
class InternalServerError < ServerError
|
71
|
+
def initialize(response, message = 'Server error')
|
72
|
+
super(response, message)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'agilecrm-wrapper/error'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module AgileCRMWrapper
|
5
|
+
class Note < Hashie::Mash
|
6
|
+
class << self
|
7
|
+
def create(*contacts, subject: '', description: '')
|
8
|
+
contacts = contacts.flatten.uniq.map(&:to_s)
|
9
|
+
payload = {
|
10
|
+
'subject' => subject,
|
11
|
+
'description' => description,
|
12
|
+
'contact_ids' => contacts
|
13
|
+
}
|
14
|
+
response = AgileCRMWrapper.connection.post('notes', payload)
|
15
|
+
new(response.body)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_by_email(email: '', subject: '', description: '')
|
19
|
+
payload = {
|
20
|
+
'subject' => subject,
|
21
|
+
'description' => description
|
22
|
+
}
|
23
|
+
query = "email=#{email}¬e=#{payload.to_json}"
|
24
|
+
AgileCRMWrapper.connection.post(
|
25
|
+
'contacts/email/note/add', query,
|
26
|
+
'content-type' => 'application/x-www-form-urlencoded'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'agilecrm-wrapper/error'
|
3
|
+
|
4
|
+
module AgileCRMWrapper
|
5
|
+
module Response
|
6
|
+
class RaiseError < Faraday::Response::Middleware
|
7
|
+
private
|
8
|
+
|
9
|
+
def on_complete(response)
|
10
|
+
status_code = response.status.to_i
|
11
|
+
klass = AgileCRMWrapper::Error.errors[status_code]
|
12
|
+
return unless klass
|
13
|
+
fail(klass.from_response(response))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Faraday::Response.register_middleware(
|
20
|
+
agilecrm_error: AgileCRMWrapper::Response::RaiseError
|
21
|
+
)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AgileCRMWrapper do
|
4
|
+
let(:domain) { 'mydomain' }
|
5
|
+
let(:api_key) { 'xxx' }
|
6
|
+
let(:email) { 'email@example.com' }
|
7
|
+
|
8
|
+
describe 'configuration' do
|
9
|
+
before(:each) do
|
10
|
+
AgileCRMWrapper.configure do |config|
|
11
|
+
config.domain = domain
|
12
|
+
config.api_key = api_key
|
13
|
+
config.email = email
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
after :each do
|
18
|
+
AgileCRMWrapper.reset
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.configure' do
|
22
|
+
it 'has a custom configuration' do
|
23
|
+
expect(AgileCRMWrapper.configuration.domain).to match domain
|
24
|
+
expect(AgileCRMWrapper.configuration.api_key).to match api_key
|
25
|
+
expect(AgileCRMWrapper.configuration.email).to match email
|
26
|
+
end
|
27
|
+
|
28
|
+
its(:endpoint) { should eq 'https://mydomain.agilecrm.com/dev/api' }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.reset' do
|
32
|
+
before(:each) do
|
33
|
+
AgileCRMWrapper.configure do |config|
|
34
|
+
config.email = email
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'resets the configuration' do
|
39
|
+
AgileCRMWrapper.reset
|
40
|
+
config = AgileCRMWrapper.configuration
|
41
|
+
expect(config.email).to eq ''
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AgileCRMWrapper::Contact do
|
4
|
+
let(:contact) { AgileCRMWrapper::Contact.find(123) }
|
5
|
+
|
6
|
+
describe '.all' do
|
7
|
+
subject { AgileCRMWrapper::Contact.all }
|
8
|
+
|
9
|
+
it 'should return an array of Contacts' do
|
10
|
+
expect(subject.map(&:class).uniq).to eq([AgileCRMWrapper::Contact])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.find' do
|
15
|
+
let(:id) { 123 }
|
16
|
+
subject { AgileCRMWrapper::Contact.find(id) }
|
17
|
+
|
18
|
+
context 'given an existing contact ID' do
|
19
|
+
it { should be_kind_of(AgileCRMWrapper::Contact) }
|
20
|
+
|
21
|
+
its(:id) { should eq id }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'given an unknown contact ID' do
|
25
|
+
let(:id) { 0 }
|
26
|
+
it { expect { is_expected.to raise_error(AgileCRMWrapper::NotFound) } }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.delete' do
|
31
|
+
context 'given a single ID' do
|
32
|
+
subject { AgileCRMWrapper::Contact.delete(123) }
|
33
|
+
|
34
|
+
its(:status) { should eq 204 }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.search_by_email' do
|
39
|
+
let(:email) { 'anitadrink@example.com' }
|
40
|
+
subject { AgileCRMWrapper::Contact.search_by_email(email) }
|
41
|
+
|
42
|
+
context 'given an existing email' do
|
43
|
+
it 'should return a contact with the corresponding email' do
|
44
|
+
expect(subject.get_property('email')).to eq email
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'given an non-existing email' do
|
49
|
+
let(:email) { 'idontexist@example.com' }
|
50
|
+
|
51
|
+
it 'should return an empty array' do
|
52
|
+
expect(subject).to eq nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '.create' do
|
58
|
+
subject do
|
59
|
+
AgileCRMWrapper::Contact.create(
|
60
|
+
tags: %w(sales, rspec), first_name: 'Anita',
|
61
|
+
last_name: 'Drink', email: 'anitadrink@example.com',
|
62
|
+
custom_field: 'Im a custom field!'
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
its(:status) { should eq 201 }
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#notes' do
|
70
|
+
it 'returns the associated notes' do
|
71
|
+
expect(contact.notes.map(&:class).uniq).to eq [AgileCRMWrapper::Note]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#update' do
|
76
|
+
|
77
|
+
it 'updates the receiving contact with the supplied key-value pair(s)' do
|
78
|
+
expect do
|
79
|
+
contact.update(first_name: 'Foo!')
|
80
|
+
end.to change{
|
81
|
+
contact.get_property('first_name')
|
82
|
+
}.from('Anita').to('Foo!')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#get_property' do
|
87
|
+
let(:contact) { AgileCRMWrapper::Contact.find(123) }
|
88
|
+
|
89
|
+
context 'supplied an existing property name' do
|
90
|
+
it 'returns the value' do
|
91
|
+
expect(contact.get_property('email')).to_not be_nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'supplied a non-existing property name' do
|
96
|
+
it 'returns nil' do
|
97
|
+
expect(contact.get_property('nil-propety')).to be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#destroy' do
|
103
|
+
let(:contact) { AgileCRMWrapper::Contact.find(123) }
|
104
|
+
subject { contact.destroy }
|
105
|
+
|
106
|
+
its(:status) { should eq 204 }
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"id": 123,
|
3
|
+
"type": "PERSON",
|
4
|
+
"created_time": 1412704793,
|
5
|
+
"updated_time": 0,
|
6
|
+
"viewed_time": 0,
|
7
|
+
"viewed": {
|
8
|
+
"viewed_time": 0
|
9
|
+
},
|
10
|
+
"star_value": 0,
|
11
|
+
"lead_score": 0,
|
12
|
+
"tags": ["sales", "rspec"],
|
13
|
+
"tagsWithTime": [{
|
14
|
+
"tag": "sales",
|
15
|
+
"createdTime": 1412704793874,
|
16
|
+
"availableCount": 0,
|
17
|
+
"entity_type": "tag"
|
18
|
+
}, {
|
19
|
+
"tag": "rspec",
|
20
|
+
"createdTime": 1412704793874,
|
21
|
+
"availableCount": 0,
|
22
|
+
"entity_type": "tag"
|
23
|
+
}],
|
24
|
+
"properties": [{
|
25
|
+
"type": "SYSTEM",
|
26
|
+
"name": "first_name",
|
27
|
+
"value": "Anita"
|
28
|
+
}, {
|
29
|
+
"type": "SYSTEM",
|
30
|
+
"name": "last_name",
|
31
|
+
"value": "Drink"
|
32
|
+
}, {
|
33
|
+
"type": "SYSTEM",
|
34
|
+
"name": "email",
|
35
|
+
"value": "anitadrink@example.com"
|
36
|
+
}, {
|
37
|
+
"type": "CUSTOM",
|
38
|
+
"name": "custom_field",
|
39
|
+
"value": "Im a custom field!"
|
40
|
+
}],
|
41
|
+
"campaignStatus": [],
|
42
|
+
"entity_type": "contact_entity",
|
43
|
+
"unsubscribeStatus": [],
|
44
|
+
"emailBounceStatus": []
|
45
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"id": 123,
|
3
|
+
"type": "PERSON",
|
4
|
+
"created_time": 1412704793,
|
5
|
+
"updated_time": 0,
|
6
|
+
"viewed_time": 0,
|
7
|
+
"viewed": {
|
8
|
+
"viewed_time": 0
|
9
|
+
},
|
10
|
+
"star_value": 0,
|
11
|
+
"lead_score": 0,
|
12
|
+
"tags": ["sales", "rspec"],
|
13
|
+
"tagsWithTime": [{
|
14
|
+
"tag": "sales",
|
15
|
+
"createdTime": 1412704793874,
|
16
|
+
"availableCount": 0,
|
17
|
+
"entity_type": "tag"
|
18
|
+
}, {
|
19
|
+
"tag": "rspec",
|
20
|
+
"createdTime": 1412704793874,
|
21
|
+
"availableCount": 0,
|
22
|
+
"entity_type": "tag"
|
23
|
+
}],
|
24
|
+
"properties": [{
|
25
|
+
"type": "SYSTEM",
|
26
|
+
"name": "first_name",
|
27
|
+
"value": "Anita"
|
28
|
+
}, {
|
29
|
+
"type": "SYSTEM",
|
30
|
+
"name": "last_name",
|
31
|
+
"value": "Drink"
|
32
|
+
}, {
|
33
|
+
"type": "SYSTEM",
|
34
|
+
"name": "email",
|
35
|
+
"value": "anitadrink@example.com"
|
36
|
+
}, {
|
37
|
+
"type": "CUSTOM",
|
38
|
+
"name": "custom_field",
|
39
|
+
"value": "Im a custom field!"
|
40
|
+
}],
|
41
|
+
"campaignStatus": [],
|
42
|
+
"entity_type": "contact_entity",
|
43
|
+
"unsubscribeStatus": [],
|
44
|
+
"emailBounceStatus": []
|
45
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
[{
|
2
|
+
"id": 80001,
|
3
|
+
"created_time": 1360561958,
|
4
|
+
"subject": "Note subject",
|
5
|
+
"description": "Note description",
|
6
|
+
"contacts": [{
|
7
|
+
"id": 123,
|
8
|
+
"type": "PERSON",
|
9
|
+
"created_time": 1412704793,
|
10
|
+
"updated_time": 0,
|
11
|
+
"viewed_time": 0,
|
12
|
+
"viewed": {
|
13
|
+
"viewed_time": 0
|
14
|
+
},
|
15
|
+
"star_value": 0,
|
16
|
+
"lead_score": 0,
|
17
|
+
"tags": ["sales", "rspec"],
|
18
|
+
"tagsWithTime": [{
|
19
|
+
"tag": "sales",
|
20
|
+
"createdTime": 1412704793874,
|
21
|
+
"availableCount": 0,
|
22
|
+
"entity_type": "tag"
|
23
|
+
}, {
|
24
|
+
"tag": "rspec",
|
25
|
+
"createdTime": 1412704793874,
|
26
|
+
"availableCount": 0,
|
27
|
+
"entity_type": "tag"
|
28
|
+
}],
|
29
|
+
"properties": [{
|
30
|
+
"type": "SYSTEM",
|
31
|
+
"name": "first_name",
|
32
|
+
"value": "Anita"
|
33
|
+
}, {
|
34
|
+
"type": "SYSTEM",
|
35
|
+
"name": "last_name",
|
36
|
+
"value": "Drink"
|
37
|
+
}, {
|
38
|
+
"type": "SYSTEM",
|
39
|
+
"name": "email",
|
40
|
+
"value": "anitadrink@example.com"
|
41
|
+
}, {
|
42
|
+
"type": "CUSTOM",
|
43
|
+
"name": "custom_field",
|
44
|
+
"value": "Im a custom field!"
|
45
|
+
}],
|
46
|
+
"campaignStatus": [],
|
47
|
+
"entity_type": "contact_entity",
|
48
|
+
"unsubscribeStatus": [],
|
49
|
+
"emailBounceStatus": []
|
50
|
+
}],
|
51
|
+
"entity_type": "note"
|
52
|
+
}]
|