amorail 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,263 @@
1
+ require 'active_model'
2
+
3
+ module Amorail
4
+ class AmoEntity
5
+ include ActiveModel::Model
6
+ include ActiveModel::AttributeMethods
7
+ include ActiveModel::Validations
8
+
9
+ class InvalidRecord < ::Amorail::Error; end
10
+ class NotPersisted < ::Amorail::Error; end
11
+ class RecordNotFound < ::Amorail::Error; end
12
+
13
+ class << self
14
+ attr_reader :amo_name, :amo_response_name, :attributes, :properties
15
+
16
+ def amo_names(name, response_name = nil)
17
+ @amo_name = @amo_response_name = name
18
+ @amo_response_name = response_name unless response_name.nil?
19
+ end
20
+
21
+ def amo_field(*vars, **hargs)
22
+ @attributes ||= {}
23
+ vars.each { |v| @attributes[v] = :default }
24
+ hargs.each { |k, v| @attributes[k] = v }
25
+ attr_accessor(*@attributes.keys)
26
+ end
27
+
28
+ def amo_property(name, options = {})
29
+ @properties ||= {}
30
+ @properties[name] = options
31
+ attr_accessor(name)
32
+ end
33
+
34
+ def find(id)
35
+ new.load_record(id)
36
+ end
37
+
38
+ def find!(id)
39
+ rec = find(id)
40
+ fail RecordNotFound unless rec
41
+ rec
42
+ end
43
+ end
44
+
45
+ amo_names 'entity'
46
+
47
+ amo_field :id, :request_id, :responsible_user_id,
48
+ date_create: :timestamp, last_modified: :timestamp
49
+
50
+ delegate :client, :properties, to: Amorail
51
+
52
+ def initialize(attributes = {})
53
+ super(attributes)
54
+ self.last_modified = Time.now.to_i if last_modified.nil?
55
+ end
56
+
57
+ def new_record?
58
+ id.blank?
59
+ end
60
+
61
+ def persisted?
62
+ !new_record?
63
+ end
64
+
65
+ def load_record(id)
66
+ response = client.safe_request(
67
+ :get,
68
+ remote_url('list'),
69
+ id: id
70
+ )
71
+ handle_response(response, 'load')
72
+ end
73
+
74
+ def save
75
+ return false unless valid?
76
+ new_record? ? push('add') : push('update')
77
+ end
78
+
79
+ def save!
80
+ if save
81
+ true
82
+ else
83
+ fail InvalidRecord
84
+ end
85
+ end
86
+
87
+ def update(attrs = {})
88
+ return false if new_record?
89
+ merge_params(attrs)
90
+ push('update')
91
+ end
92
+
93
+ def update!(attrs = {})
94
+ if update(attrs)
95
+ true
96
+ else
97
+ fail NotPersisted
98
+ end
99
+ end
100
+
101
+ def params
102
+ data = {}
103
+ self.class.attributes.each do |k, v|
104
+ data[k] = send("to_#{v}", send(k))
105
+ end
106
+
107
+ data[:custom_fields] = custom_fields
108
+
109
+ normalize_params(data)
110
+ end
111
+
112
+ protected
113
+
114
+ def custom_fields
115
+ return unless properties.respond_to?(self.class.amo_name)
116
+
117
+ return if self.class.properties.nil?
118
+
119
+ props = properties.send(self.class.amo_name)
120
+
121
+ custom_fields = []
122
+
123
+ self.class.properties.each do |k, v|
124
+ prop_id = props.send(k).id
125
+ prop_val = { value: send(k) }.merge(v)
126
+ custom_fields << { id: prop_id, values: [prop_val] }
127
+ end
128
+
129
+ custom_fields
130
+ end
131
+
132
+ def create_params(method)
133
+ {
134
+ request: {
135
+ self.class.amo_response_name => {
136
+ method => [
137
+ params
138
+ ]
139
+ }
140
+ }
141
+ }
142
+ end
143
+
144
+ def normalize_custom_fields(val)
145
+ val.reject do |field|
146
+ field[:values].all? { |item| !item[:value] }
147
+ end
148
+ end
149
+
150
+ # this method removes nil values and empty arrays from params hash (deep)
151
+ def normalize_params(data)
152
+ return data unless data.is_a?(Hash)
153
+
154
+ compacted = {}
155
+ data.each do |key, val|
156
+ case val
157
+ when Numeric, String
158
+ compacted[key] = val
159
+ when Array
160
+ val.compact!
161
+ # handle custom keys
162
+ val = normalize_custom_fields(val) if key == :custom_fields
163
+ unless val.empty?
164
+ compacted[key] = val.map { |el| normalize_params(el) }
165
+ end
166
+ else
167
+ params = normalize_params(val)
168
+ compacted[key] = params unless params.nil?
169
+ end
170
+ end
171
+ compacted.with_indifferent_access
172
+ end
173
+
174
+ def to_timestamp(val)
175
+ return if val.nil?
176
+
177
+ case val
178
+ when String
179
+ (date = Time.parse(val)) && date.to_i
180
+ when Date
181
+ val.to_time.to_i
182
+ else
183
+ val.to_i
184
+ end
185
+ end
186
+
187
+ def to_default(val)
188
+ val
189
+ end
190
+
191
+ def reload_model(info)
192
+ merge_params(info)
193
+ merge_custom_fields(info['custom_fields'])
194
+ self
195
+ end
196
+
197
+ private
198
+
199
+ def merge_params(attrs)
200
+ attrs.each do |k, v|
201
+ action = "#{k}="
202
+ next unless respond_to?(action)
203
+ send(action, v)
204
+ end
205
+ self
206
+ end
207
+
208
+ def merge_custom_fields(fields)
209
+ return if fields.nil?
210
+ fields.each do |f|
211
+ fname = "#{f.fetch('code').downcase}="
212
+ fval = f.fetch('values').first.fetch('value')
213
+ send(fname, fval) if respond_to?(fname)
214
+ end
215
+ end
216
+
217
+ def attributes_list
218
+ self.class.attributes
219
+ end
220
+
221
+ def remote_url(action)
222
+ File.join(Amorail.config.api_path, self.class.amo_name, action)
223
+ end
224
+
225
+ # call safe method <safe_request>. safe_request call authorize
226
+ # if current session undefined or expires.
227
+ def push(method)
228
+ response = commit_request(create_params(method))
229
+ handle_response(response, method)
230
+ end
231
+
232
+ def commit_request(attrs)
233
+ client.safe_request(
234
+ :post,
235
+ remote_url('set'),
236
+ normalize_params(attrs)
237
+ )
238
+ end
239
+
240
+ def handle_response(response, method)
241
+ if response.status == 200
242
+ extract_method = "extract_data_#{method}"
243
+ reload_model(
244
+ send(
245
+ extract_method,
246
+ response.body['response'][self.class.amo_response_name]
247
+ )
248
+ ) if respond_to?(extract_method, true)
249
+ self
250
+ else
251
+ false
252
+ end
253
+ end
254
+
255
+ def extract_data_load(response)
256
+ response.first
257
+ end
258
+
259
+ def extract_data_add(response)
260
+ response.fetch('add').first
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,26 @@
1
+ # Classes for Amorail Exceptions
2
+ # Every class is name of HTTP response error code(status)
3
+
4
+ module Amorail
5
+ class Error < ::StandardError; end
6
+
7
+ class APIError < Error; end
8
+
9
+ class AmoBadRequestError < APIError; end
10
+
11
+ class AmoMovedPermanentlyError < APIError; end
12
+
13
+ class AmoUnauthorizedError < APIError; end
14
+
15
+ class AmoForbiddenError < APIError; end
16
+
17
+ class AmoNotFoudError < APIError; end
18
+
19
+ class AmoInternalError < APIError; end
20
+
21
+ class AmoBadGatewayError < APIError; end
22
+
23
+ class AmoServiceUnaviableError < APIError; end
24
+
25
+ class AmoUnknownError < APIError; end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Amorail
2
+ VERSION = "0.1.4"
3
+ end
data/lib/amorail.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'amorail/version'
2
+ require 'amorail/config'
3
+ require 'amorail/client'
4
+ require 'amorail/exceptions'
5
+ require 'amorail/entity'
6
+ require 'amorail/custom_fields'
7
+
8
+ require 'amorail/entities/leadable'
9
+
10
+ Gem.find_files('amorail/entities/*.rb').each { |path| require path }
11
+
12
+ module Amorail
13
+ def self.config
14
+ @config ||= Config.new
15
+ end
16
+
17
+ def self.properties
18
+ @properties ||= Property.new(client)
19
+ end
20
+
21
+ def self.configure
22
+ yield(config) if block_given?
23
+ end
24
+
25
+ def self.client
26
+ @client ||= Client.new
27
+ end
28
+
29
+ def self.reset
30
+ @config = nil
31
+ @client = nil
32
+ end
33
+
34
+ require 'amorail/engine' if defined?(Rails)
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'amorail'
2
+
3
+ namespace :amorail do
4
+ desc "Check Amorails configuration. Authorize and get custom fields information"
5
+ task :check do
6
+ p Amorail.properties
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::Client do
4
+ let(:client) { Amorail.client }
5
+
6
+ before(:each) { mock_api }
7
+
8
+ it "it should create client" do
9
+ expect(Amorail.config.usermail).to eq "amorail@test.com"
10
+ expect(Amorail.config.api_key).to eq "75742b166417fe32ae132282ce178cf6"
11
+ expect(Amorail.config.api_endpoint).to eq "https://test.amocrm.ru"
12
+ end
13
+
14
+ it "should #authorize method call" do
15
+ res = client.authorize
16
+ expect(res.status).to eq 200
17
+ end
18
+
19
+ it "should #authorize and set cookie" do
20
+ res = client.get("/private/api/v2/json/accounts/current")
21
+ expect(res.status).to eq 200
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoCompany do
4
+ before { mock_api }
5
+
6
+ describe "validations" do
7
+ it { should validate_presence_of(:name) }
8
+ end
9
+
10
+ describe "#params" do
11
+ let(:company) do
12
+ Amorail::AmoCompany.new(
13
+ name: 'Test inc',
14
+ phone: '12345678',
15
+ email: 'test@mala.ru',
16
+ address: '10, State st',
17
+ web: 'hoohle.com'
18
+ )
19
+ end
20
+
21
+ subject { company.params }
22
+
23
+ specify { is_expected.to include(name: 'Test inc') }
24
+ specify { is_expected.to include(type: 'contact') }
25
+
26
+ it "contains email property" do
27
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460591" }
28
+ expect(prop).not_to be_nil
29
+ expect(prop[:values].first[:value]).to eq 'test@mala.ru'
30
+ expect(prop[:values].first[:enum]).to eq 'WORK'
31
+ end
32
+
33
+ it "contains phone property" do
34
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460589" }
35
+ expect(prop).not_to be_nil
36
+ expect(prop[:values].first[:value]).to eq '12345678'
37
+ expect(prop[:values].first[:enum]).to eq 'WORK'
38
+ end
39
+
40
+ it "contains address property" do
41
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460597" }
42
+ expect(prop).not_to be_nil
43
+ expect(prop[:values].first[:value]).to eq '10, State st'
44
+ end
45
+
46
+ it "contains web property" do
47
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460593" }
48
+ expect(prop).not_to be_nil
49
+ expect(prop[:values].first[:value]).to eq 'hoohle.com'
50
+ end
51
+ end
52
+
53
+ describe "#save" do
54
+ let(:company) { Amorail::AmoCompany.new(name: "test") }
55
+
56
+ before { company_create_stub(Amorail.config.api_endpoint) }
57
+
58
+ it "should set id after create" do
59
+ company.save!
60
+ expect(company.id).to eq 101
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,104 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoContact do
4
+ before { mock_api }
5
+
6
+ let(:contact) { Amorail::AmoContact.new(name: "test") }
7
+
8
+ describe "validations" do
9
+ it { should validate_presence_of(:name)}
10
+ end
11
+
12
+ describe "#params" do
13
+ let(:contact) do
14
+ Amorail::AmoContact.new(
15
+ name: 'Tester',
16
+ company_name: 'Test inc',
17
+ phone: '12345678',
18
+ email: 'test@mala.ru',
19
+ position: 'CEO'
20
+ )
21
+ end
22
+
23
+ subject { contact.params }
24
+
25
+ specify { is_expected.to include(name: 'Tester') }
26
+ specify { is_expected.to include(company_name: 'Test inc') }
27
+
28
+ it "contains email property" do
29
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460591" }
30
+ expect(prop).not_to be_nil
31
+ expect(prop[:values].first[:value]).to eq 'test@mala.ru'
32
+ expect(prop[:values].first[:enum]).to eq 'MOB'
33
+ end
34
+
35
+ it "contains phone property" do
36
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460589" }
37
+ expect(prop).not_to be_nil
38
+ expect(prop[:values].first[:value]).to eq '12345678'
39
+ expect(prop[:values].first[:enum]).to eq 'WORK'
40
+ end
41
+
42
+ it "contains position property" do
43
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460587" }
44
+ expect(prop).not_to be_nil
45
+ expect(prop[:values].first[:value]).to eq 'CEO'
46
+ end
47
+ end
48
+
49
+ describe ".find" do
50
+ before { contact_find_stub(Amorail.config.api_endpoint, 101) }
51
+ before { contact_find_stub(Amorail.config.api_endpoint, 102, false) }
52
+
53
+ it "loads entity" do
54
+ obj = Amorail::AmoContact.find(101)
55
+ expect(obj.id).to eq 101
56
+ expect(obj.company_name).to eq "Foo Inc."
57
+ expect(obj.email).to eq "foo@tb.com"
58
+ expect(obj.phone).to eq "1111 111 111"
59
+ end
60
+
61
+ it "returns nil" do
62
+ obj = Amorail::AmoContact.find(102)
63
+ expect(obj).to be_falsey
64
+ end
65
+
66
+ it "raise error" do
67
+ expect { Amorail::AmoContact.find!(102) }
68
+ .to raise_error(Amorail::AmoEntity::RecordNotFound)
69
+ end
70
+ end
71
+
72
+ describe "#save" do
73
+ before { contact_create_stub(Amorail.config.api_endpoint) }
74
+
75
+ it "set id after create" do
76
+ contact.save!
77
+ expect(contact.id).to eq 101
78
+ end
79
+ end
80
+
81
+ describe "#update" do
82
+ before { contact_create_stub(Amorail.config.api_endpoint) }
83
+
84
+ it "update params" do
85
+ contact.save!
86
+ contact.name = "foo"
87
+
88
+ contact_update_stub(Amorail.config.api_endpoint)
89
+ expect(contact.save!).to be_truthy
90
+ expect(contact.name).to eq "foo"
91
+ end
92
+
93
+ it "raise error if id is blank?" do
94
+ obj = Amorail::AmoContact.new
95
+ expect { obj.update!(name: 'Igor') }.to raise_error
96
+ end
97
+
98
+ it "raise error" do
99
+ obj = Amorail::AmoContact.new
100
+ expect { obj.update!(id: 101, name: "Igor") }
101
+ .to(raise_error)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+ require "webmock/rspec"
3
+
4
+ describe "spec for Amorail Properties" do
5
+ before(:each) { mock_api }
6
+
7
+ let(:prop) {Amorail.properties}
8
+
9
+ it "should parse companies hash" do
10
+ expect(prop.company.phone.present?).to be_truthy
11
+ expect(prop.company.phone.is_a?(Amorail::Property::PropertyItem)).to be_truthy
12
+ expect(prop.company.phone.id.present?).to be_truthy
13
+ expect(prop.company.data["phone"].data["id"]).to eq prop.company.phone.id
14
+
15
+ expect(prop.company.phone.id).to eq "1460589"
16
+ expect(prop.company.address.id).to eq "1460597"
17
+ expect(prop.company.email.id).to eq "1460591"
18
+ expect(prop.company.web.id).to eq "1460593"
19
+ end
20
+
21
+ it "should parse contacts hash" do
22
+ expect(prop.contacts.email.present?).to be_truthy
23
+ expect(prop.contacts.im.is_a?(Amorail::Property::PropertyItem)).to be_truthy
24
+ expect(prop.contacts.im.id.present?).to be_truthy
25
+ expect(prop.contacts.data["im"].data["id"]).to eq prop.contacts.im.id
26
+
27
+ expect(prop.contacts.im.id).to eq "1460595"
28
+ expect(prop.contacts.position.id).to eq "1460587"
29
+ expect(prop.contacts.phone.id).to eq "1460589"
30
+ expect(prop.contacts.email.id).to eq "1460591"
31
+ end
32
+
33
+ it "should parse leads hash" do
34
+ expect(prop.leads.statuses["Первичный контакт"].id).to eq "8195972"
35
+ expect(prop.leads.statuses["Успешно реализовано"].id).to eq "142"
36
+ end
37
+
38
+ it "should parse task types" do
39
+ expect(prop.tasks.follow_up.id).to eq 1
40
+ expect(prop.tasks["CALL"].id).to eq 1
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoEntity do
4
+ before { mock_api }
5
+
6
+ let(:entity) { Amorail::AmoEntity.new }
7
+
8
+ describe "#params" do
9
+ let(:now) { Time.now }
10
+
11
+ subject { entity.params }
12
+
13
+ specify { is_expected.to include(:last_modified) }
14
+ specify { is_expected.not_to include(
15
+ :id, :request_id, :responsible_user_id, :date_create)
16
+ }
17
+
18
+ context "with some values" do
19
+ let(:entity) do
20
+ Amorail::AmoEntity.new(
21
+ responsible_user_id: 2,
22
+ last_modified: now
23
+ )
24
+ end
25
+
26
+ specify { is_expected.to include(responsible_user_id: 2) }
27
+ specify { is_expected.to include(last_modified: now.to_i) }
28
+ specify { is_expected.not_to include(
29
+ :id, :request_id, :date_create)
30
+ }
31
+ end
32
+
33
+ context "with all values" do
34
+ let(:entity) do
35
+ Amorail::AmoEntity.new(
36
+ id: 100,
37
+ request_id: 1,
38
+ responsible_user_id: 2,
39
+ date_create: now,
40
+ last_modified: now
41
+ )
42
+ end
43
+
44
+ specify { is_expected.to include(id: 100) }
45
+ specify { is_expected.to include(request_id: 1) }
46
+ specify { is_expected.to include(responsible_user_id: 2) }
47
+ specify { is_expected.to include(date_create: now.to_i) }
48
+ specify { is_expected.to include(last_modified: now.to_i) }
49
+ end
50
+ end
51
+ end