amorail 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
[](https://rubygems.org/gems/amorail)
|
1
|
+
[](https://rubygems.org/gems/amorail) 
|
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
|