amorail 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +23 -0
- data/.gitignore +2 -1
- data/CHANGELOG.md +19 -0
- data/README.md +49 -8
- data/RELEASING.md +43 -0
- data/amorail.gemspec +4 -2
- data/lib/amorail.rb +25 -6
- data/lib/amorail/access_token.rb +44 -0
- data/lib/amorail/client.rb +82 -23
- data/lib/amorail/config.rb +12 -8
- data/lib/amorail/store_adapters.rb +15 -0
- data/lib/amorail/store_adapters/abstract_store_adapter.rb +23 -0
- data/lib/amorail/store_adapters/memory_store_adapter.rb +50 -0
- data/lib/amorail/store_adapters/redis_store_adapter.rb +83 -0
- data/lib/amorail/version.rb +1 -1
- data/spec/access_token_spec.rb +59 -0
- data/spec/client_spec.rb +34 -24
- data/spec/fixtures/amorail_test.yml +5 -3
- data/spec/fixtures/authorize.json +6 -0
- data/spec/helpers/webmock_helpers.rb +48 -13
- data/spec/store_adapters/memory_store_adapter_spec.rb +56 -0
- data/spec/store_adapters/redis_store_adapter_spec.rb +67 -0
- metadata +37 -8
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b18605e272eb7a0c72af69e2d017f54753ff715d474d79cc9071791a036cd6d
|
4
|
+
data.tar.gz: d77fee259b877786fd18a044aaa52aff432be6936834095b5d8ab2d857d0df11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab1e1221b40f406909ed76f4b444b7f7f7805d8f04c1e386030563471172175149ff718438bb4ee97d9bee28f2a3d9b9a78733828f8ab2d7b62edca6f631f56e
|
7
|
+
data.tar.gz: 4034fbcc9fea9004e159e62ac910ce9bfbe301ea76013f7d62d05a26cae8df2084c2836a4e4c65707cc4451fc8380fc163c5afe580068bdb8c4a40f79f632ff3
|
@@ -0,0 +1,23 @@
|
|
1
|
+
name: Build
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v2
|
16
|
+
- name: Set up Ruby 2.5
|
17
|
+
uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: 2.5
|
20
|
+
bundler-cache: true
|
21
|
+
bundler: 1.13.6
|
22
|
+
- name: Build and test
|
23
|
+
run: bundle exec rspec
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## master (unreleased)
|
4
|
+
|
5
|
+
## 0.7.0 (2021-07-16)
|
6
|
+
|
7
|
+
### Features
|
8
|
+
|
9
|
+
- Introduce [#53](https://github.com/teachbase/amorail/issues/48) Implement OAuth authentication ([@lHydra][])
|
10
|
+
|
11
|
+
### Changes
|
12
|
+
|
13
|
+
- increased ruby version to >= 2.5.0
|
14
|
+
- updates dependencies versions
|
15
|
+
|
16
|
+
|
17
|
+
[@palkan]: https://github.com/palkan
|
18
|
+
[@AlexanderShvaykin]: https://github.com/AlexanderShvaykin
|
19
|
+
[@lHydra]: https://github.com/lHydra
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail)
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) ![Build](https://github.com/teachbase/amorail/workflows/Build/badge.svg)
|
2
2
|
|
3
3
|
# Amorail
|
4
4
|
|
@@ -25,12 +25,48 @@ Or install it yourself as:
|
|
25
25
|
With Amorail you can manipulate the following AmoCRM entities: Companies, Contacts, Leads and Tasks.
|
26
26
|
We're triying to build simple AR-like interface.
|
27
27
|
|
28
|
+
### Store configuration
|
29
|
+
|
30
|
+
In order to configure a token store you should set up a store adapter in a following way: `Amorail.token_store = :redis, { redis_url: 'redis://127.0.0.1:6379/0' }` (options can be omitted). Currently supported stores are `:redis` and `:memory`. Memory adapter is used **by default**.
|
31
|
+
|
32
|
+
Here is a default configuration for Redis:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Amorail.token_store = :redis, {
|
36
|
+
redis_host: "127.0.0.1",
|
37
|
+
redis_port: "6379",
|
38
|
+
redis_db_name: "0"
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
You can also provide a Redis URL instead:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Amorail.token_store = :redis, { redis_url: "redis://localhost:6397" }
|
46
|
+
```
|
47
|
+
|
48
|
+
**NOTE**: if `REDIS_URL` environment variable is set it is used automatically.
|
49
|
+
|
50
|
+
### Add custom store
|
51
|
+
|
52
|
+
To add custom store you need declare a class that implements the interface `AbstractStoreAdapter`.
|
53
|
+
For example `class FileStoreAdapter < Amorail::StoreAdapters::AbstractStoreAdapter`
|
54
|
+
|
55
|
+
The class must contain constructor `initialize(**options)` and **4 required methods**:
|
56
|
+
|
57
|
+
1. `fetch_access` — method that should return Hash with token data (**required keys:** `token`, `refresh_token` and `expiration`) or empty Hash (`{}`) if no value was received
|
58
|
+
2. `persist_access` — method that stores data in storage
|
59
|
+
3. `update_access` — method that updates existed token data in storage
|
60
|
+
4. `access_expired?` — method that returns `true` if token was expired otherwise `false`
|
61
|
+
|
28
62
|
### Auth configuration
|
29
63
|
|
30
64
|
Amorail uses [anyway_config](https://github.com/palkan/anyway_config) for configuration, so you
|
31
65
|
can provide configuration parameters through env vars, seperate config file (`config/amorail.yml`) or `secrets.yml`.
|
32
66
|
|
33
|
-
Required params: **
|
67
|
+
Required params: **client_id**, **client_secret** **code**, **redirect_uri** and **api_endpoint**.
|
68
|
+
|
69
|
+
An authorization **code** is required for the initial obtaining of a pair of access and refresh tokens. You can see it in the interface or through a Redirect URI if the authorization was run from the modal window for permissions. The lifespan of the code is 20 minutes. [More details](https://www.amocrm.com/developers/content/oauth/oauth/)
|
34
70
|
|
35
71
|
Example:
|
36
72
|
|
@@ -39,9 +75,14 @@ Example:
|
|
39
75
|
development:
|
40
76
|
...
|
41
77
|
amorail:
|
42
|
-
|
43
|
-
|
44
|
-
|
78
|
+
client_id: c0df457d-eacc-47cc-behb-3d8f962g4lbf
|
79
|
+
client_secret: a36b564b64398d3e53004c12e4997eb340e32b18ee185389ddb409292ebc5ebae297a3eab96be4a9d38ecbf274d90bbb54a7e8f282f40d1b29e5c9b2e2e357a6
|
80
|
+
code: a911ff963f58ea6c846901056114d37a14d2efa4d05ffb6ef0a8d60d32e5d6dae785bd317cbc9b0bd04261cb0cf9905af0cc32b5567c1eb84433328d08888f5c613608b822c1928272769ffd284b
|
81
|
+
redirect_uri: https://example.ru
|
82
|
+
api_endpoint: https://test.amocrm.ru
|
83
|
+
redis_host: 127.0.0.1
|
84
|
+
redis_port: 6379
|
85
|
+
redis_db_name: 0
|
45
86
|
```
|
46
87
|
|
47
88
|
### Running from console
|
@@ -50,7 +91,7 @@ You can try amorail in action from console ([PRY](https://github.com/pry/pry) is
|
|
50
91
|
|
51
92
|
```shell
|
52
93
|
# amorail gem directory
|
53
|
-
|
94
|
+
AMORAIL_CLIENT_ID=integration_id AMORAIL_CLIENT_SECRET=secret_key AMORAIL_CODE=my_code AMORAIL_REDIRECT_URI=https://example.com AMORAIL_API_ENDPOINT=https://test.amocrm.ru bundle exec rake console
|
54
95
|
pry> Amorail.properties
|
55
96
|
# ... prints properties (custom_fields) data
|
56
97
|
pry> Amorail::Contact.find_by_query("test_contact")
|
@@ -223,12 +264,12 @@ It is possible to use Amorail with multiple AmoCRM accounts. To do so use `Amora
|
|
223
264
|
which receive client params or client instance and a block to execute within custom context:
|
224
265
|
|
225
266
|
```ruby
|
226
|
-
Amorail.with_client(
|
267
|
+
Amorail.with_client(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com") do
|
227
268
|
# Client specific code here
|
228
269
|
end
|
229
270
|
|
230
271
|
# or using client instance
|
231
|
-
my_client = Amorail::Client.new(
|
272
|
+
my_client = Amorail::Client.new(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com")
|
232
273
|
|
233
274
|
Amorail.with_client(client) do
|
234
275
|
...
|
data/RELEASING.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# How to release a gem
|
2
|
+
|
3
|
+
This document describes a process of releasing a new version of a gem.
|
4
|
+
|
5
|
+
1. Bump version.
|
6
|
+
|
7
|
+
```sh
|
8
|
+
git commit -m "Bump 1.<x>.<y>"
|
9
|
+
```
|
10
|
+
|
11
|
+
We're (kinda) using semantic versioning:
|
12
|
+
|
13
|
+
- Bugfixes should be released as fast as possible as patch versions.
|
14
|
+
- New features could be combined and released as minor or patch version upgrades (depending on the _size of the feature_—it's up to maintainers to decide).
|
15
|
+
- Breaking API changes should be avoided in minor and patch releases.
|
16
|
+
- Breaking dependencies changes (e.g., dropping older Ruby support) could be released in minor versions.
|
17
|
+
|
18
|
+
How to bump a version:
|
19
|
+
|
20
|
+
- Change the version number in `lib/amorail/version.rb` file.
|
21
|
+
- Update the changelog (add new heading with the version name and date).
|
22
|
+
- Update the installation documentation if necessary (e.g., during minor and major updates).
|
23
|
+
|
24
|
+
2. Push code to GitHub and make sure CI passes.
|
25
|
+
|
26
|
+
```sh
|
27
|
+
git push
|
28
|
+
```
|
29
|
+
|
30
|
+
3. Release a gem.
|
31
|
+
|
32
|
+
```sh
|
33
|
+
gem release -t
|
34
|
+
git push --tags
|
35
|
+
```
|
36
|
+
|
37
|
+
We use [gem-release](https://github.com/svenfuchs/gem-release) for publishing gems with a single command:
|
38
|
+
|
39
|
+
```sh
|
40
|
+
gem release -t
|
41
|
+
```
|
42
|
+
|
43
|
+
Don't forget to push tags and write release notes on GitHub (if necessary).
|
data/amorail.gemspec
CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = %q{Ruby API client for AmoCRM. You can integrate your system with it.}
|
13
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
15
16
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0")
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -28,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
28
29
|
spec.add_dependency "anyway_config", ">= 1.0"
|
29
30
|
spec.add_dependency "faraday"
|
30
31
|
spec.add_dependency "faraday_middleware"
|
31
|
-
spec.add_dependency
|
32
|
-
spec.add_dependency
|
32
|
+
spec.add_dependency "activemodel"
|
33
|
+
spec.add_dependency "json"
|
34
|
+
spec.add_dependency "redis"
|
33
35
|
end
|
data/lib/amorail.rb
CHANGED
@@ -6,34 +6,38 @@ require 'amorail/client'
|
|
6
6
|
require 'amorail/exceptions'
|
7
7
|
require 'amorail/entity'
|
8
8
|
require 'amorail/property'
|
9
|
+
require 'amorail/access_token'
|
10
|
+
require 'amorail/store_adapters'
|
9
11
|
|
10
12
|
Gem.find_files('amorail/entities/*.rb').each { |path| require path }
|
11
13
|
|
12
14
|
# AmoCRM API integration.
|
13
15
|
# https://www.amocrm.com/
|
14
16
|
module Amorail
|
15
|
-
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def config
|
16
20
|
@config ||= Config.new
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
23
|
+
def properties
|
20
24
|
client.properties
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
27
|
+
def configure
|
24
28
|
yield(config) if block_given?
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
31
|
+
def client
|
28
32
|
ClientRegistry.client || (@client ||= Client.new)
|
29
33
|
end
|
30
34
|
|
31
|
-
def
|
35
|
+
def reset
|
32
36
|
@config = nil
|
33
37
|
@client = nil
|
34
38
|
end
|
35
39
|
|
36
|
-
def
|
40
|
+
def with_client(client)
|
37
41
|
client = Client.new(client) unless client.is_a?(Client)
|
38
42
|
ClientRegistry.client = client
|
39
43
|
yield
|
@@ -41,6 +45,21 @@ module Amorail
|
|
41
45
|
ClientRegistry.client = nil
|
42
46
|
end
|
43
47
|
|
48
|
+
def token_store=(args)
|
49
|
+
adapter, options = Array(args)
|
50
|
+
@token_store = StoreAdapters.build_by_name(adapter, options)
|
51
|
+
rescue NameError => e
|
52
|
+
raise e.class, "Token store adapter for :#{adapter} haven't been found", e.backtrace
|
53
|
+
end
|
54
|
+
|
55
|
+
def token_store
|
56
|
+
unless instance_variable_defined?(:@token_store)
|
57
|
+
self.token_store = :memory
|
58
|
+
end
|
59
|
+
|
60
|
+
@token_store
|
61
|
+
end
|
62
|
+
|
44
63
|
class ClientRegistry # :nodoc:
|
45
64
|
extend ActiveSupport::PerThreadRegistry
|
46
65
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Amorail
|
4
|
+
class AccessToken
|
5
|
+
attr_reader :token, :secret, :expiration, :refresh_token, :store
|
6
|
+
|
7
|
+
def initialize(secret, token, refresh_token, expiration, store)
|
8
|
+
@secret = secret
|
9
|
+
@token = token
|
10
|
+
@refresh_token = refresh_token
|
11
|
+
@expiration = expiration
|
12
|
+
@store = store
|
13
|
+
end
|
14
|
+
|
15
|
+
def expired?
|
16
|
+
store.access_expired?(secret)
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def create(secret, token, refresh_token, expiration, store)
|
21
|
+
new(secret, token, refresh_token, expiration, store).tap do |access_token|
|
22
|
+
store.persist_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(secret, store)
|
27
|
+
token_attrs = store.fetch_access(secret)
|
28
|
+
build_with_token_attrs(store, secret, token_attrs)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refresh(secret, token, refresh_token, expiration, store)
|
32
|
+
new(secret, token, refresh_token, expiration, store).tap do |access_token|
|
33
|
+
store.update_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def build_with_token_attrs(store, secret, token_attrs)
|
40
|
+
new(secret, token_attrs[:token], token_attrs[:refresh_token], token_attrs[:expiration], store)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/amorail/client.rb
CHANGED
@@ -10,14 +10,31 @@ module Amorail
|
|
10
10
|
class Client
|
11
11
|
SUCCESS_STATUS_CODES = [200, 204].freeze
|
12
12
|
|
13
|
-
|
13
|
+
attr_accessor :store
|
14
|
+
attr_reader :api_endpoint,
|
15
|
+
:access_token,
|
16
|
+
:refresh_token,
|
17
|
+
:access,
|
18
|
+
:client_id,
|
19
|
+
:client_secret,
|
20
|
+
:code,
|
21
|
+
:redirect_uri
|
14
22
|
|
15
23
|
def initialize(api_endpoint: Amorail.config.api_endpoint,
|
16
|
-
|
17
|
-
|
24
|
+
client_id: Amorail.config.client_id,
|
25
|
+
client_secret: Amorail.config.client_secret,
|
26
|
+
code: Amorail.config.code,
|
27
|
+
redirect_uri: Amorail.config.redirect_uri)
|
28
|
+
@store = Amorail.token_store
|
18
29
|
@api_endpoint = api_endpoint
|
19
|
-
@
|
20
|
-
@
|
30
|
+
@client_id = client_id
|
31
|
+
@client_secret = client_secret
|
32
|
+
@code = code
|
33
|
+
@redirect_uri = redirect_uri
|
34
|
+
@access = AccessToken.find(@client_secret, store)
|
35
|
+
@access_token = @access.token
|
36
|
+
@refresh_token = @access.refresh_token
|
37
|
+
|
21
38
|
@connect = Faraday.new(url: api_endpoint) do |faraday|
|
22
39
|
faraday.response :json, content_type: /\bjson$/
|
23
40
|
faraday.use :instrumentation
|
@@ -34,33 +51,53 @@ module Amorail
|
|
34
51
|
end
|
35
52
|
|
36
53
|
def authorize
|
37
|
-
|
38
|
-
response
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
54
|
+
response = post(Amorail.config.auth_url, auth_params)
|
55
|
+
create_access_token(response)
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
def refresh_token!
|
60
|
+
response = post(Amorail.config.auth_url, refresh_params)
|
61
|
+
update_access_token(response)
|
44
62
|
response
|
45
63
|
end
|
46
64
|
|
65
|
+
def auth_params
|
66
|
+
{
|
67
|
+
client_id: client_id,
|
68
|
+
client_secret: client_secret,
|
69
|
+
grant_type: 'authorization_code',
|
70
|
+
code: @code,
|
71
|
+
redirect_uri: redirect_uri
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def refresh_params
|
76
|
+
{
|
77
|
+
client_id: client_id,
|
78
|
+
client_secret: client_secret,
|
79
|
+
grant_type: 'refresh_token',
|
80
|
+
refresh_token: refresh_token,
|
81
|
+
redirect_uri: redirect_uri
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
47
85
|
def safe_request(method, url, params = {})
|
48
|
-
|
49
|
-
|
50
|
-
authorize
|
86
|
+
authorize if access_token.blank?
|
87
|
+
refresh_token! if access.expired?
|
51
88
|
public_send(method, url, params)
|
52
89
|
end
|
53
90
|
|
54
91
|
def get(url, params = {})
|
55
92
|
response = connect.get(url, params) do |request|
|
56
|
-
request.headers['
|
93
|
+
request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
|
57
94
|
end
|
58
95
|
handle_response(response)
|
59
96
|
end
|
60
97
|
|
61
98
|
def post(url, params = {})
|
62
99
|
response = connect.post(url) do |request|
|
63
|
-
request.headers['
|
100
|
+
request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
|
64
101
|
request.headers['Content-Type'] = 'application/json'
|
65
102
|
request.body = params.to_json
|
66
103
|
end
|
@@ -69,12 +106,6 @@ module Amorail
|
|
69
106
|
|
70
107
|
private
|
71
108
|
|
72
|
-
attr_accessor :cookies
|
73
|
-
|
74
|
-
def cookie_handler(response)
|
75
|
-
self.cookies = response.headers['set-cookie'].split('; ')[0]
|
76
|
-
end
|
77
|
-
|
78
109
|
def handle_response(response) # rubocop:disable all
|
79
110
|
return response if SUCCESS_STATUS_CODES.include?(response.status)
|
80
111
|
|
@@ -99,5 +130,33 @@ module Amorail
|
|
99
130
|
fail ::Amorail::AmoUnknownError, response.body
|
100
131
|
end
|
101
132
|
end
|
133
|
+
|
134
|
+
def create_access_token(response)
|
135
|
+
_access = AccessToken.create(
|
136
|
+
client_secret,
|
137
|
+
response.body['access_token'],
|
138
|
+
response.body['refresh_token'],
|
139
|
+
expiration(response.body['expires_in']),
|
140
|
+
store
|
141
|
+
)
|
142
|
+
@access_token = _access.token
|
143
|
+
@refresh_token = _access.refresh_token
|
144
|
+
end
|
145
|
+
|
146
|
+
def update_access_token(response)
|
147
|
+
_access = AccessToken.refresh(
|
148
|
+
client_secret,
|
149
|
+
response.body['access_token'],
|
150
|
+
response.body['refresh_token'],
|
151
|
+
expiration(response.body['expires_in']),
|
152
|
+
store
|
153
|
+
)
|
154
|
+
@access_token = _access.token
|
155
|
+
@refresh_token = _access.refresh_token
|
156
|
+
end
|
157
|
+
|
158
|
+
def expiration(expires_in)
|
159
|
+
Time.now.to_i + expires_in.to_i
|
160
|
+
end
|
102
161
|
end
|
103
162
|
end
|
data/lib/amorail/config.rb
CHANGED
@@ -4,16 +4,20 @@ require 'anyway'
|
|
4
4
|
|
5
5
|
module Amorail
|
6
6
|
# Amorail config contains:
|
7
|
-
# - usermail ("user@gmail.com")
|
8
|
-
# - api_key ("13601bbac84727df")
|
9
7
|
# - api_endpoint ("http://you_company.amocrm.com")
|
10
8
|
# - api_path (default: "/private/api/v2/json/")
|
11
|
-
# - auth_url (default: "/
|
9
|
+
# - auth_url (default: "/oauth2/access_token")
|
12
10
|
class Config < Anyway::Config
|
13
|
-
attr_config :
|
14
|
-
:
|
15
|
-
:
|
16
|
-
|
17
|
-
|
11
|
+
attr_config :api_endpoint,
|
12
|
+
:client_id,
|
13
|
+
:client_secret,
|
14
|
+
:code,
|
15
|
+
:redirect_uri,
|
16
|
+
:redis_url,
|
17
|
+
api_path: '/private/api/v2/json/',
|
18
|
+
auth_url: '/oauth2/access_token',
|
19
|
+
redis_host: '127.0.0.1',
|
20
|
+
redis_port: '6379',
|
21
|
+
redis_db_name: '0'
|
18
22
|
end
|
19
23
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'amorail/store_adapters/abstract_store_adapter'
|
4
|
+
require 'amorail/store_adapters/redis_store_adapter'
|
5
|
+
require 'amorail/store_adapters/memory_store_adapter'
|
6
|
+
|
7
|
+
module Amorail
|
8
|
+
module StoreAdapters
|
9
|
+
def self.build_by_name(adapter, options = nil)
|
10
|
+
camelized_adapter = adapter.to_s.split('_').map(&:capitalize).join
|
11
|
+
adapter_class_name = "#{camelized_adapter}StoreAdapter"
|
12
|
+
StoreAdapters.const_get(adapter_class_name).new(**(options || {}))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Amorail
|
4
|
+
module StoreAdapters
|
5
|
+
class AbstractStoreAdapter
|
6
|
+
def fetch_access(_secret)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def persist_access(_secret, _token, _refresh_token, _expiration)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_refresh(_secret, _token, _refresh_token, _expiration)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def access_expired?(_key)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Amorail
|
4
|
+
module StoreAdapters
|
5
|
+
class MemoryStoreAdapter < AbstractStoreAdapter
|
6
|
+
attr_reader :storage
|
7
|
+
|
8
|
+
def initialize(**options)
|
9
|
+
raise ArgumentError, 'Memory store doesn\'t support any options' if options.any?
|
10
|
+
@storage = Hash.new { |hh, kk| hh[kk] = {} }
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch_access(secret)
|
14
|
+
value_if_not_expired(secret)
|
15
|
+
end
|
16
|
+
|
17
|
+
def persist_access(secret, token, refresh_token, expiration)
|
18
|
+
access_token = { token: token, refresh_token: refresh_token, expiration: expiration }
|
19
|
+
storage.store(secret, access_token)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_access(secret, token, refresh_token, expiration)
|
23
|
+
update_access_fields(
|
24
|
+
secret,
|
25
|
+
token: token,
|
26
|
+
refresh_token: refresh_token,
|
27
|
+
expiration: expiration
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def access_expired?(key)
|
32
|
+
storage[key][:expiration] && Time.now.to_i >= storage[key][:expiration]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def value_if_not_expired(key)
|
38
|
+
if !access_expired?(key)
|
39
|
+
storage[key]
|
40
|
+
else
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_access_fields(key, fields)
|
46
|
+
storage[key].merge!(fields)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Amorail
|
4
|
+
module StoreAdapters
|
5
|
+
class RedisStoreAdapter < AbstractStoreAdapter
|
6
|
+
attr_reader :storage
|
7
|
+
|
8
|
+
def initialize(**options)
|
9
|
+
begin
|
10
|
+
require 'redis'
|
11
|
+
@storage = configure_redis_client(**options)
|
12
|
+
rescue LoadError => e
|
13
|
+
msg = 'Could not load the \'redis\' gem, please add it to your gemfile or ' \
|
14
|
+
'configure a different adapter (e.g. Amorail.store_adapter = :memory)'
|
15
|
+
raise e.class, msg, e.backtrace
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch_access(secret)
|
20
|
+
token = storage.get(access_key(secret))
|
21
|
+
refresh_token = storage.get(refresh_key(secret))
|
22
|
+
token.nil? ? {} : { token: token, refresh_token: refresh_token }
|
23
|
+
end
|
24
|
+
|
25
|
+
def persist_access(secret, token, refresh_token, expiration)
|
26
|
+
update_data(secret, token, refresh_token, expiration)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_access(secret, token, refresh_token, expiration)
|
30
|
+
update_data(secret, token, refresh_token, expiration)
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_expired?(secret)
|
34
|
+
access_key = access_key(secret)
|
35
|
+
refresh_key = refresh_key(secret)
|
36
|
+
storage.get(refresh_key) && storage.get(access_key).nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def update_data(secret, token, refresh_token, expiration)
|
42
|
+
access_key = access_key(secret)
|
43
|
+
refresh_key = refresh_key(secret)
|
44
|
+
storage.set(access_key, token)
|
45
|
+
storage.set(refresh_key, refresh_token)
|
46
|
+
storage.expireat(access_key, expiration)
|
47
|
+
end
|
48
|
+
|
49
|
+
def configure_redis_client(redis_url: nil, redis_host: nil, redis_port: nil, redis_db_name: nil)
|
50
|
+
if redis_url && (redis_host || redis_port || redis_db_name)
|
51
|
+
raise ArgumentError, 'redis_url cannot be passed along with redis_host, redis_port or redis_db_name options'
|
52
|
+
end
|
53
|
+
|
54
|
+
redis_url ||= build_redis_url(
|
55
|
+
redis_host: redis_host,
|
56
|
+
redis_port: redis_port,
|
57
|
+
redis_db_name: redis_db_name
|
58
|
+
)
|
59
|
+
|
60
|
+
Redis.new(url: redis_url)
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_redis_url(redis_host: nil, redis_port: nil, redis_db_name: nil)
|
64
|
+
redis_db_name ||= Amorail.config.redis_db_name
|
65
|
+
return URI.join(Amorail.config.redis_url, redis_db_name).to_s if Amorail.config.redis_url
|
66
|
+
|
67
|
+
redis_host ||= Amorail.config.redis_host
|
68
|
+
redis_port ||= Amorail.config.redis_port
|
69
|
+
|
70
|
+
redis_base_url = ENV['REDIS_URL'] || "redis://#{redis_host}:#{redis_port}"
|
71
|
+
URI.join(redis_base_url, redis_db_name).to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def access_key(secret)
|
75
|
+
"access_#{secret}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def refresh_key(secret)
|
79
|
+
"refresh_#{secret}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/amorail/version.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Amorail::AccessToken do
|
6
|
+
let(:store) { Amorail.token_store }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Amorail::AccessToken.create(
|
10
|
+
'secret',
|
11
|
+
'token',
|
12
|
+
'refresh_token',
|
13
|
+
Time.now.to_i + 86_000,
|
14
|
+
store
|
15
|
+
)
|
16
|
+
|
17
|
+
Amorail::AccessToken.create(
|
18
|
+
'secret_expired',
|
19
|
+
'token',
|
20
|
+
'refresh_token',
|
21
|
+
Time.now.to_i - 92_000,
|
22
|
+
store
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#find' do
|
27
|
+
context 'when token is not expired' do
|
28
|
+
subject { Amorail::AccessToken.find('secret', store).token }
|
29
|
+
|
30
|
+
it 'should return token' do
|
31
|
+
expect(subject).to eq('token')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when token is expired' do
|
36
|
+
subject { Amorail::AccessToken.find('secret_expired', store).token }
|
37
|
+
|
38
|
+
it 'should return nil' do
|
39
|
+
expect(subject).to be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#refresh' do
|
45
|
+
it 'updates token data' do
|
46
|
+
upd_expiration = Time.now.to_i + 96_000
|
47
|
+
access_token = Amorail::AccessToken.refresh('secret',
|
48
|
+
'upd_token',
|
49
|
+
'upd_refresh',
|
50
|
+
upd_expiration,
|
51
|
+
Amorail.token_store)
|
52
|
+
aggregate_failures do
|
53
|
+
expect(access_token.token).to eq('upd_token')
|
54
|
+
expect(access_token.refresh_token).to eq('upd_refresh')
|
55
|
+
expect(access_token.expiration).to eq(upd_expiration)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/client_spec.rb
CHANGED
@@ -9,8 +9,10 @@ describe Amorail::Client do
|
|
9
9
|
|
10
10
|
context "default client" do
|
11
11
|
it "should create client", :aggregate_failures do
|
12
|
-
expect(subject.
|
13
|
-
expect(subject.
|
12
|
+
expect(subject.client_id).to eq "some_id"
|
13
|
+
expect(subject.client_secret).to eq "some_secret"
|
14
|
+
expect(subject.code).to eq "some_code"
|
15
|
+
expect(subject.redirect_uri).to eq "https://example.ru/redirect/uri"
|
14
16
|
expect(subject.api_endpoint).to eq "https://test.amocrm.ru"
|
15
17
|
end
|
16
18
|
|
@@ -26,40 +28,48 @@ describe Amorail::Client do
|
|
26
28
|
end
|
27
29
|
|
28
30
|
describe "#with_client" do
|
29
|
-
before { mock_custom_api("https://custom.amo.com", "
|
31
|
+
before { mock_custom_api("https://custom.amo.com", "custom_client_id", "custom_client_secret", "custom_code", "https://custom-site.ru/redirecto/uri") }
|
30
32
|
|
31
33
|
let(:new_client) do
|
32
34
|
described_class.new(
|
33
35
|
api_endpoint: "https://custom.amo.com",
|
34
|
-
|
35
|
-
|
36
|
+
client_secret: "custom_client_secret",
|
37
|
+
client_id: "custom_client_id",
|
38
|
+
code: "custom_code",
|
39
|
+
redirect_uri: "https://custom-site.ru/redirecto/uri"
|
36
40
|
)
|
37
41
|
end
|
38
42
|
|
39
43
|
it "use custom client as instance", :aggregate_failures do
|
40
|
-
expect(Amorail.client.
|
44
|
+
expect(Amorail.client.client_id).to eq "some_id"
|
41
45
|
Amorail.with_client(new_client) do
|
42
|
-
expect(Amorail.client.
|
46
|
+
expect(Amorail.client.client_secret).to eq "custom_client_secret"
|
47
|
+
expect(Amorail.client.client_id).to eq "custom_client_id"
|
43
48
|
expect(Amorail.client.api_endpoint).to eq "https://custom.amo.com"
|
44
|
-
expect(Amorail.client.
|
49
|
+
expect(Amorail.client.code).to eq "custom_code"
|
50
|
+
expect(Amorail.client.redirect_uri).to eq "https://custom-site.ru/redirecto/uri"
|
45
51
|
end
|
46
52
|
|
47
|
-
expect(Amorail.client.
|
53
|
+
expect(Amorail.client.client_id).to eq "some_id"
|
48
54
|
end
|
49
55
|
|
50
56
|
it "use custom client as options", :aggregate_failures do
|
51
|
-
expect(Amorail.client.
|
57
|
+
expect(Amorail.client.client_id).to eq "some_id"
|
52
58
|
Amorail.with_client(
|
53
59
|
api_endpoint: "https://custom.amo.com",
|
54
|
-
|
55
|
-
|
60
|
+
client_secret: "custom_client_secret",
|
61
|
+
client_id: "custom_client_id",
|
62
|
+
code: "custom_code",
|
63
|
+
redirect_uri: "https://custom-site.ru/redirecto/uri"
|
56
64
|
) do
|
57
|
-
expect(Amorail.client.
|
65
|
+
expect(Amorail.client.client_secret).to eq "custom_client_secret"
|
66
|
+
expect(Amorail.client.client_id).to eq "custom_client_id"
|
58
67
|
expect(Amorail.client.api_endpoint).to eq "https://custom.amo.com"
|
59
|
-
expect(Amorail.client.
|
68
|
+
expect(Amorail.client.code).to eq "custom_code"
|
69
|
+
expect(Amorail.client.redirect_uri).to eq "https://custom-site.ru/redirecto/uri"
|
60
70
|
end
|
61
71
|
|
62
|
-
expect(Amorail.client.
|
72
|
+
expect(Amorail.client.client_id).to eq "some_id"
|
63
73
|
end
|
64
74
|
|
65
75
|
it "loads custom properties", :aggregate_failures do
|
@@ -83,10 +93,10 @@ describe Amorail::Client do
|
|
83
93
|
# only after the second thread enters block
|
84
94
|
threads << Thread.new do
|
85
95
|
q1.pop
|
86
|
-
Amorail.with_client(
|
96
|
+
Amorail.with_client(client_id: 'some_id_1') do
|
87
97
|
q2 << 1
|
88
98
|
q1.pop
|
89
|
-
results << Amorail.client.
|
99
|
+
results << Amorail.client.client_id
|
90
100
|
q2 << 1
|
91
101
|
end
|
92
102
|
q3 << 1
|
@@ -96,10 +106,10 @@ describe Amorail::Client do
|
|
96
106
|
# after the first block
|
97
107
|
threads << Thread.new do
|
98
108
|
q2.pop
|
99
|
-
Amorail.with_client(
|
109
|
+
Amorail.with_client(client_id: 'some_id_2') do
|
100
110
|
q1 << 1
|
101
111
|
q2.pop
|
102
|
-
results << Amorail.client.
|
112
|
+
results << Amorail.client.client_id
|
103
113
|
end
|
104
114
|
q3 << 1
|
105
115
|
end
|
@@ -107,19 +117,19 @@ describe Amorail::Client do
|
|
107
117
|
# This thread enters block third and commits
|
108
118
|
# after all other threads left blocks
|
109
119
|
threads << Thread.new do
|
110
|
-
Amorail.with_client(
|
120
|
+
Amorail.with_client(client_id: 'some_id_3') do
|
111
121
|
q3.pop
|
112
122
|
q3.pop
|
113
|
-
results << Amorail.client.
|
123
|
+
results << Amorail.client.client_id
|
114
124
|
end
|
115
125
|
end
|
116
126
|
|
117
127
|
q1 << 1
|
118
128
|
threads.each(&:join)
|
119
129
|
|
120
|
-
expect(results[0]).to eq '
|
121
|
-
expect(results[1]).to eq '
|
122
|
-
expect(results[2]).to eq '
|
130
|
+
expect(results[0]).to eq 'some_id_1'
|
131
|
+
expect(results[1]).to eq 'some_id_2'
|
132
|
+
expect(results[2]).to eq 'some_id_3'
|
123
133
|
end
|
124
134
|
end
|
125
135
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
client_id: 'some_id'
|
2
|
+
client_secret: 'some_secret'
|
3
|
+
code: 'some_code'
|
4
|
+
redirect_uri: 'https://example.ru/redirect/uri'
|
5
|
+
api_endpoint: 'https://test.amocrm.ru'
|
@@ -0,0 +1,6 @@
|
|
1
|
+
{
|
2
|
+
"token_type": "Bearer",
|
3
|
+
"expires_in": 86400,
|
4
|
+
"access_token": "eyJ0eXAiOiJKf2QihCJhbGciOiJSUzI1NiIsImp0aSI6IjMxMTY3MGY3ZDVjY2IwODRjYTUyOTc5OGVjMzM0ZGU2ZDIzMmYzZDUyMjE0NTFhMTYzZDFlMTM2ZGY3NjA4MGVmMjViZTMwODQwNzFhY2Y0In0.eyJhdWQiOiIwNDA4M2FjZC1iY2ZmLTRiOWItOTJkYS05OTFiOTc0MDBlNzMiLCJqdGkiOiIzMTE2NzBmN2Q1Y2NiMDg0Y2E1Mjk3OThlYzMzNGRlNmQyMzJmM2Q1MjIxNDUxYTE2M2QxZTEzNmRmNzYwODBlZjI1YmUzMDg0MDcxYWNmNCIsImlhdCI6MTYxMzM4NzQ2MiwibmJmIDoxNjEzMzg3NDYyLCJleHAiOjE2MTM0NzM4NyIsInN1YiI6IjYyNjU1OTEiLCJhY2NvdW50X2lkIjoyOTAxMTIzMSwic2NvcGVzIjpbInB1c2hfbm90aWZpY2F0aW9ucyIsImNybSIsIm5vdGlmaWNhdGlvbnMiXX0.HRnJJXSK5KrlkcxHVBQBvE8TS34Rwru4Ba_lBlgzEtfU6FjaOCuBA0VLKJ24lLHz2w9B3lgMn9-OG9ayXAkDqrNPZByOimJ5cdwQExlNws-gvO9Hj27QiWK5JMTvbDE4EjVGvKuH7uXRsFTcsmuP6MA-5hjZ5h2DmNr5gzfZVS2Onh_9vrX75yh5FjFohfoO8izohtQYzZDVNYzek13EBdEK2BiNgLCoGhyGeMRtmTpwdSueD72qFGL80RMkQAGV8mElkZiXeUuGckTvEdIUodlgH6fk_KYG8OF3jozbEpzFKKfgPMUjE3vyCLVPWCEolQZjZPhSd6KbatK-f0oPWA",
|
5
|
+
"refresh_token": "def50200f4151ec4424843abc377eec488c6e8dceeda93b3f0b33d8f02207911533d98b408b97e2b2688acd1d956a44368f51263c7a55253efb543b256b90a2a1dc0ac71bf546374f23ab3d2b07f3ced5ad31e500e16b4d99deb6c735a5001c43c70156feac0a9c94dd24fdc6238ee59603997614ccb28773e4bce14d01e69fa7c04f869088fb1c9c27fea2f68f70E46d615a3982bdcd352Dcd05e3ae70e1d1ba707dcb354e7b611eba6790039a6c4bf2909210a85bdc34a254e9a10f8637aff405938ddcdc8bf88fb4ed8c8f15d7fa1e0e787c624b26c270b43e5877e1b6c87292bd95bb90305f50886b595d5a83c5b9768a8a61c63cdc7b082312ecc32619577253856058e8761c6f1e338e58824d8bed2a227bbe43dd12a69bf347657aa4f41f5d7609b6c5e088a0a0d2b2af9877bf8642c7d44fc6caa3c7dfa342d8b38e37d8a68dd26e814f0bfcdee707bf44ce7ef9e4d57ff5ac6aa52f2259388e5a3b7e5c6689366f43bf9197b2bf0e00470a3031581031b82ade8d8c0f8c01eabb61c312f47a2cfdc2943b10bc1f3d0b7791e1b1487832d3db75908611af90659f721cd3f831dcc76d8da12044611910722c9dc8bc09a5aff82923a2551a5bd5c5c57e4273993e655da7881e3"
|
6
|
+
}
|
@@ -5,34 +5,69 @@ module AmoWebMock
|
|
5
5
|
def mock_api
|
6
6
|
authorize_stub(
|
7
7
|
Amorail.config.api_endpoint,
|
8
|
-
Amorail.config.
|
9
|
-
Amorail.config.
|
8
|
+
Amorail.config.client_id,
|
9
|
+
Amorail.config.client_secret,
|
10
|
+
'authorization_code',
|
11
|
+
Amorail.config.code,
|
12
|
+
Amorail.config.redirect_uri
|
13
|
+
)
|
14
|
+
|
15
|
+
resresh_token_stub(
|
16
|
+
Amorail.config.api_endpoint,
|
17
|
+
Amorail.config.client_id,
|
18
|
+
Amorail.config.client_secret,
|
19
|
+
'refresh_token',
|
20
|
+
'refresh_token',
|
21
|
+
Amorail.config.redirect_uri
|
22
|
+
)
|
10
23
|
|
11
24
|
account_info_stub(Amorail.config.api_endpoint)
|
12
25
|
end
|
13
26
|
|
14
|
-
def mock_custom_api(endpoint,
|
27
|
+
def mock_custom_api(endpoint, client_id, client_secret, code, redirect_uri, properties = 'response_2.json')
|
15
28
|
authorize_stub(
|
16
29
|
endpoint,
|
17
|
-
|
18
|
-
|
30
|
+
client_id,
|
31
|
+
client_secret,
|
32
|
+
'authorization_code',
|
33
|
+
code,
|
34
|
+
redirect_uri
|
35
|
+
)
|
36
|
+
|
37
|
+
resresh_token_stub(
|
38
|
+
endpoint,
|
39
|
+
client_id,
|
40
|
+
client_secret,
|
41
|
+
'refresh_token',
|
42
|
+
'refresh_token',
|
43
|
+
redirect_uri
|
19
44
|
)
|
20
45
|
|
21
46
|
account_info_stub(endpoint, properties)
|
22
47
|
end
|
23
48
|
|
24
|
-
def authorize_stub(endpoint,
|
25
|
-
|
26
|
-
stub_request(:post, "#{endpoint}/private/api/auth.php?type=json")
|
49
|
+
def authorize_stub(endpoint, client_id, client_secret, grant_type, code, redirect_uri)
|
50
|
+
stub_request(:post, "#{endpoint}/oauth2/access_token")
|
27
51
|
.with(
|
28
|
-
body: "{\"
|
52
|
+
body: "{\"client_id\":\"#{client_id}\",\"client_secret\":\"#{client_secret}\",\"grant_type\":\"#{grant_type}\",\"code\":\"#{code}\",\"redirect_uri\":\"#{redirect_uri}\"}"
|
29
53
|
)
|
30
54
|
.to_return(
|
31
55
|
status: 200,
|
32
|
-
body:
|
33
|
-
headers: {
|
34
|
-
|
35
|
-
|
56
|
+
body: File.read('./spec/fixtures/authorize.json'),
|
57
|
+
headers: {}
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def resresh_token_stub(endpoint, client_id, client_secret, grant_type, refresh_token, redirect_uri)
|
62
|
+
stub_request(:post, "#{endpoint}/oauth2/access_token")
|
63
|
+
.with(
|
64
|
+
body: "{\"client_id\":\"#{client_id}\",\"client_secret\":\"#{client_secret}\",\"grant_type\":\"#{grant_type}\",\"refresh_token\":\"#{refresh_token}\",\"redirect_uri\":\"#{redirect_uri}\"}"
|
65
|
+
)
|
66
|
+
.to_return(
|
67
|
+
status: 200,
|
68
|
+
body: File.read('./spec/fixtures/authorize.json'),
|
69
|
+
headers: {}
|
70
|
+
)
|
36
71
|
end
|
37
72
|
|
38
73
|
def account_info_stub(endpoint, properties = 'response_1.json')
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Amorail::StoreAdapters::MemoryStoreAdapter do
|
6
|
+
let!(:expiration) { Time.now.to_i + 86_000 }
|
7
|
+
let(:store) { Amorail::StoreAdapters.build_by_name(:memory) }
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
it 'raises error on unknow option' do
|
11
|
+
expect { Amorail::StoreAdapters::MemoryStoreAdapter.new(something: 'something') }.to raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#persist_access' do
|
16
|
+
subject { store.persist_access('secret', 'token', 'refresh_token', expiration) }
|
17
|
+
|
18
|
+
it 'save record to memory' do
|
19
|
+
expect { subject }.to change { store.fetch_access('secret') }.from(
|
20
|
+
{}
|
21
|
+
).to(
|
22
|
+
{ token: 'token', refresh_token: 'refresh_token', expiration: expiration }
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#fetch_access' do
|
28
|
+
context 'when token not expired' do
|
29
|
+
it 'returns valid data' do
|
30
|
+
store.persist_access('secret', 'token', 'refresh_token', expiration)
|
31
|
+
expect(store.fetch_access('secret')).to eq({ token: 'token', refresh_token: 'refresh_token', expiration: expiration })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when token is expired' do
|
36
|
+
it 'returns blank hash' do
|
37
|
+
store.persist_access('secret', 'token', 'refresh_token', Time.now.to_i - 10_000)
|
38
|
+
expect(store.fetch_access('secret')).to eq({})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#update_access' do
|
44
|
+
let!(:upd_expiration) { Time.now.to_i + 92_000 }
|
45
|
+
subject { store.update_access('secret', 'upd_token', 'upd_refresh', upd_expiration) }
|
46
|
+
|
47
|
+
it 'refresh token data' do
|
48
|
+
store.persist_access('secret', 'token', 'refresh_token', expiration)
|
49
|
+
expect { subject }.to change { store.fetch_access('secret') }.from(
|
50
|
+
{ token: 'token', refresh_token: 'refresh_token', expiration: expiration }
|
51
|
+
).to(
|
52
|
+
{ token: 'upd_token', refresh_token: 'upd_refresh', expiration: upd_expiration }
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Amorail::StoreAdapters::RedisStoreAdapter do
|
6
|
+
before :each do
|
7
|
+
Amorail.config.reload
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:store) { Amorail::StoreAdapters.build_by_name(:redis) }
|
11
|
+
|
12
|
+
describe '#initialize' do
|
13
|
+
it 'raises error on mixed redis options' do
|
14
|
+
expect { Amorail::StoreAdapters::RedisStoreAdapter.new(redis_url: 'redis://127.0.0.1:6379/0',
|
15
|
+
redis_port: '8082') }.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises error on unknown options' do
|
19
|
+
expect { Amorail::StoreAdapters::RedisStoreAdapter.new(redis_url: 'redis://127.0.0.1:6379/0',
|
20
|
+
something: 'smth') }.to raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'configuration' do
|
25
|
+
let(:adapter) { Amorail::StoreAdapters::RedisStoreAdapter.new }
|
26
|
+
|
27
|
+
it 'set default url' do
|
28
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.1:6379/0')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'set url from env variable' do
|
32
|
+
ENV['REDIS_URL'] = 'redis://localhost:2020/'
|
33
|
+
adapter = Amorail::StoreAdapters::RedisStoreAdapter.new
|
34
|
+
expect(adapter.storage.id).to eq('redis://localhost:2020/0')
|
35
|
+
|
36
|
+
ENV.delete('REDIS_URL')
|
37
|
+
adapter = Amorail::StoreAdapters::RedisStoreAdapter.new
|
38
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.1:6379/0')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'configuration via host post and db' do
|
42
|
+
adapter = Amorail::StoreAdapters::RedisStoreAdapter.new(
|
43
|
+
redis_host: '127.0.0.2',
|
44
|
+
redis_port: '6372',
|
45
|
+
redis_db_name: '2'
|
46
|
+
)
|
47
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.2:6372/2')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'configuration via host port and db in module' do
|
51
|
+
Amorail.config.redis_host = '127.0.0.2'
|
52
|
+
Amorail.config.redis_port = '6372'
|
53
|
+
Amorail.config.redis_db_name = '2'
|
54
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.2:6372/2')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'configuration via redis url' do
|
58
|
+
adapter = Amorail::StoreAdapters::RedisStoreAdapter.new(redis_url: 'redis://127.0.0.2:6322')
|
59
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.2:6322/0')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'configuration via redis url in module' do
|
63
|
+
Amorail.config.redis_url = 'redis://127.0.0.2:6322'
|
64
|
+
expect(adapter.storage.id).to eq('redis://127.0.0.2:6322/0')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amorail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alekseenkoss
|
8
8
|
- palkan
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-07-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -179,6 +179,20 @@ dependencies:
|
|
179
179
|
- - ">="
|
180
180
|
- !ruby/object:Gem::Version
|
181
181
|
version: '0'
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: redis
|
184
|
+
requirement: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
type: :runtime
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
182
196
|
description: Ruby API client for AmoCRM. You can integrate your system with it.
|
183
197
|
email:
|
184
198
|
- alekseenkoss@gmail.com
|
@@ -187,15 +201,18 @@ executables: []
|
|
187
201
|
extensions: []
|
188
202
|
extra_rdoc_files: []
|
189
203
|
files:
|
204
|
+
- ".github/workflows/rspec.yml"
|
190
205
|
- ".gitignore"
|
191
206
|
- ".rubocop.yml"
|
192
|
-
-
|
207
|
+
- CHANGELOG.md
|
193
208
|
- Gemfile
|
194
209
|
- LICENSE.txt
|
195
210
|
- README.md
|
211
|
+
- RELEASING.md
|
196
212
|
- Rakefile
|
197
213
|
- amorail.gemspec
|
198
214
|
- lib/amorail.rb
|
215
|
+
- lib/amorail/access_token.rb
|
199
216
|
- lib/amorail/client.rb
|
200
217
|
- lib/amorail/config.rb
|
201
218
|
- lib/amorail/entities/company.rb
|
@@ -214,8 +231,13 @@ files:
|
|
214
231
|
- lib/amorail/exceptions.rb
|
215
232
|
- lib/amorail/property.rb
|
216
233
|
- lib/amorail/railtie.rb
|
234
|
+
- lib/amorail/store_adapters.rb
|
235
|
+
- lib/amorail/store_adapters/abstract_store_adapter.rb
|
236
|
+
- lib/amorail/store_adapters/memory_store_adapter.rb
|
237
|
+
- lib/amorail/store_adapters/redis_store_adapter.rb
|
217
238
|
- lib/amorail/version.rb
|
218
239
|
- lib/tasks/amorail.rake
|
240
|
+
- spec/access_token_spec.rb
|
219
241
|
- spec/client_spec.rb
|
220
242
|
- spec/company_spec.rb
|
221
243
|
- spec/contact_link_spec.rb
|
@@ -224,6 +246,7 @@ files:
|
|
224
246
|
- spec/fixtures/accounts/response_1.json
|
225
247
|
- spec/fixtures/accounts/response_2.json
|
226
248
|
- spec/fixtures/amorail_test.yml
|
249
|
+
- spec/fixtures/authorize.json
|
227
250
|
- spec/fixtures/contacts/create.json
|
228
251
|
- spec/fixtures/contacts/find_many.json
|
229
252
|
- spec/fixtures/contacts/find_one.json
|
@@ -244,6 +267,8 @@ files:
|
|
244
267
|
- spec/note_spec.rb
|
245
268
|
- spec/property_spec.rb
|
246
269
|
- spec/spec_helper.rb
|
270
|
+
- spec/store_adapters/memory_store_adapter_spec.rb
|
271
|
+
- spec/store_adapters/redis_store_adapter_spec.rb
|
247
272
|
- spec/support/elementable_example.rb
|
248
273
|
- spec/support/entity_class_example.rb
|
249
274
|
- spec/support/leadable_example.rb
|
@@ -255,7 +280,7 @@ homepage: ''
|
|
255
280
|
licenses:
|
256
281
|
- MIT
|
257
282
|
metadata: {}
|
258
|
-
post_install_message:
|
283
|
+
post_install_message:
|
259
284
|
rdoc_options: []
|
260
285
|
require_paths:
|
261
286
|
- lib
|
@@ -263,18 +288,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
288
|
requirements:
|
264
289
|
- - ">="
|
265
290
|
- !ruby/object:Gem::Version
|
266
|
-
version:
|
291
|
+
version: 2.5.0
|
267
292
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
268
293
|
requirements:
|
269
294
|
- - ">="
|
270
295
|
- !ruby/object:Gem::Version
|
271
296
|
version: '0'
|
272
297
|
requirements: []
|
273
|
-
rubygems_version: 3.0.
|
274
|
-
signing_key:
|
298
|
+
rubygems_version: 3.0.8
|
299
|
+
signing_key:
|
275
300
|
specification_version: 4
|
276
301
|
summary: Ruby API client for AmoCRM
|
277
302
|
test_files:
|
303
|
+
- spec/access_token_spec.rb
|
278
304
|
- spec/client_spec.rb
|
279
305
|
- spec/company_spec.rb
|
280
306
|
- spec/contact_link_spec.rb
|
@@ -283,6 +309,7 @@ test_files:
|
|
283
309
|
- spec/fixtures/accounts/response_1.json
|
284
310
|
- spec/fixtures/accounts/response_2.json
|
285
311
|
- spec/fixtures/amorail_test.yml
|
312
|
+
- spec/fixtures/authorize.json
|
286
313
|
- spec/fixtures/contacts/create.json
|
287
314
|
- spec/fixtures/contacts/find_many.json
|
288
315
|
- spec/fixtures/contacts/find_one.json
|
@@ -303,6 +330,8 @@ test_files:
|
|
303
330
|
- spec/note_spec.rb
|
304
331
|
- spec/property_spec.rb
|
305
332
|
- spec/spec_helper.rb
|
333
|
+
- spec/store_adapters/memory_store_adapter_spec.rb
|
334
|
+
- spec/store_adapters/redis_store_adapter_spec.rb
|
306
335
|
- spec/support/elementable_example.rb
|
307
336
|
- spec/support/entity_class_example.rb
|
308
337
|
- spec/support/leadable_example.rb
|