vkontakte_api 1.2 → 1.3
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/.gitignore +1 -0
- data/CHANGELOG.md +5 -0
- data/Guardfile +3 -1
- data/README.md +9 -3
- data/lib/vkontakte_api/client.rb +10 -0
- data/lib/vkontakte_api/namespace.rb +30 -1
- data/lib/vkontakte_api/resolvable.rb +1 -1
- data/lib/vkontakte_api/resolver.rb +13 -28
- data/lib/vkontakte_api/uploading.rb +3 -3
- data/lib/vkontakte_api/version.rb +1 -1
- data/spec/integration_spec.rb +24 -27
- data/spec/vkontakte_api/api_spec.rb +13 -13
- data/spec/vkontakte_api/authorization_spec.rb +27 -24
- data/spec/vkontakte_api/client_spec.rb +74 -38
- data/spec/vkontakte_api/configuration_spec.rb +12 -11
- data/spec/vkontakte_api/error_spec.rb +10 -12
- data/spec/vkontakte_api/logger_spec.rb +31 -28
- data/spec/vkontakte_api/method_spec.rb +21 -18
- data/spec/vkontakte_api/namespace_spec.rb +47 -1
- data/spec/vkontakte_api/resolvable_spec.rb +5 -7
- data/spec/vkontakte_api/resolver_spec.rb +6 -66
- data/spec/vkontakte_api/result_spec.rb +43 -34
- data/spec/vkontakte_api/uploading_spec.rb +20 -16
- data/spec/vkontakte_api/utils_spec.rb +15 -18
- data/spec/vkontakte_api_spec.rb +9 -7
- data/vkontakte_api.gemspec +7 -2
- metadata +34 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60dffad7bbac6d779ff73eaeb39221648bd06b81
|
4
|
+
data.tar.gz: c702f943689840e842d49859f2bcc87b10fce869
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc49252ddc7b95434e0f2f5ac59cb4ea4f749c1a30a7026b1bbefa5f570ae836f3b25125f70c9f8026aee2322b5cd4f878e19d90bc9a90f26948951f250d14b8
|
7
|
+
data.tar.gz: 90a2c6d2da5ac5e7e08851575373c7bc5e53684a88d5111e7a128026d3aa2accfb8b5c41bd2bedc373b3759009cf35d68c1132eb2f55691da93f37438fc9a1b0
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 1.3 (01.11.2013)
|
2
|
+
|
3
|
+
* Корректная работа API-методов `*.search` (спасибо [leonko](https://github.com/leonko))
|
4
|
+
* Поддержка IO-объектов в методе `upload` (спасибо [undr](https://github.com/undr))
|
5
|
+
|
1
6
|
## 1.2 (14.07.2013)
|
2
7
|
|
3
8
|
* Повтор запроса при определенных ошибках
|
data/Guardfile
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
guard 'rspec' do
|
1
|
+
guard 'rspec', all_on_start: true, all_after_pass: true do
|
2
2
|
watch(%r{^spec/.+_spec\.rb$})
|
3
3
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
4
4
|
watch('spec/spec_helper.rb') { 'spec' }
|
5
|
+
|
6
|
+
watch('spec/support/mechanized_authorization.rb') { 'spec/integration_spec.rb' }
|
5
7
|
end
|
6
8
|
|
7
9
|
notification :terminal_notifier, activate: 'com.googlecode.iTerm2'
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
``` ruby
|
8
8
|
# Gemfile
|
9
|
-
gem 'vkontakte_api', '~> 1.
|
9
|
+
gem 'vkontakte_api', '~> 1.3'
|
10
10
|
```
|
11
11
|
|
12
12
|
или просто
|
@@ -47,7 +47,7 @@ users.first.last_name # => "Дуров"
|
|
47
47
|
# то блок будет выполнен для каждого элемента,
|
48
48
|
# и метод вернет обработанный массив
|
49
49
|
fields = [:first_name, :last_name, :screen_name]
|
50
|
-
@vk.friends.get(fields: fields) do |friend|
|
50
|
+
@vk.friends.get(uid: 2, fields: fields) do |friend|
|
51
51
|
"#{friend.first_name} '#{friend.screen_name}' #{friend.last_name}"
|
52
52
|
end
|
53
53
|
# => ["Павел 'durov' Дуров"]
|
@@ -64,6 +64,13 @@ url = 'http://cs303110.vkontakte.ru/upload.php?act=do_add'
|
|
64
64
|
VkontakteApi.upload(url: url, photo: ['/path/to/file.jpg', 'image/jpeg'])
|
65
65
|
```
|
66
66
|
|
67
|
+
Если загружаемый файл доступен как открытый IO-объект, его можно передать альтернативным синтаксисом - IO-объект, MIME-тип и путь к файлу:
|
68
|
+
|
69
|
+
``` ruby
|
70
|
+
url = 'http://cs303110.vkontakte.ru/upload.php?act=do_add'
|
71
|
+
VkontakteApi.upload(url: url, photo: [file_io, 'image/jpeg', '/path/to/file.jpg'])
|
72
|
+
```
|
73
|
+
|
67
74
|
Метод вернет ответ сервера ВКонтакте, преобразованный в `Hashie::Mash`; его можно использовать при вызове метода API на последнем этапе процесса загрузки.
|
68
75
|
|
69
76
|
### Авторизация
|
@@ -262,7 +269,6 @@ $ rails generate vkontakte_api:install
|
|
262
269
|
|
263
270
|
## Roadmap
|
264
271
|
|
265
|
-
* настраиваемый retry запроса при стандартных исключениях
|
266
272
|
* CLI-интерфейс с автоматической авторизацией
|
267
273
|
|
268
274
|
## Участие в разработке
|
data/lib/vkontakte_api/client.rb
CHANGED
@@ -69,6 +69,16 @@ module VkontakteApi
|
|
69
69
|
end.keys
|
70
70
|
end
|
71
71
|
|
72
|
+
# If the called method is a namespace, it creates and returns a new `VkontakteApi::Namespace` instance.
|
73
|
+
# Otherwise it creates a `VkontakteApi::Method` instance and calls it passing the arguments and a block.
|
74
|
+
def method_missing(*args, &block)
|
75
|
+
if Namespace.exists?(args.first)
|
76
|
+
create_namespace(args.first)
|
77
|
+
else
|
78
|
+
call_method(args, &block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
72
82
|
private
|
73
83
|
def settings
|
74
84
|
@settings ||= self.get_user_settings
|
@@ -1,7 +1,36 @@
|
|
1
1
|
module VkontakteApi
|
2
|
-
# An API method namespace (such as `users` or `friends`).
|
2
|
+
# An API method namespace (such as `users` or `friends`).
|
3
|
+
#
|
4
|
+
# It includes `Resolvable` and `Resolver` and calls API methods via `Resolver#call_method`.
|
5
|
+
# It also holds the list of all known namespaces.
|
3
6
|
class Namespace
|
4
7
|
include Resolvable
|
5
8
|
include Resolver
|
9
|
+
|
10
|
+
# Creates and calls the `VkontakteApi::Method` using `VkontakteApi::Resolver#call_method`.
|
11
|
+
def method_missing(*args, &block)
|
12
|
+
call_method(args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# An array of all method namespaces.
|
17
|
+
#
|
18
|
+
# Lazily loads the list from `namespaces.yml` and caches it.
|
19
|
+
# @return [Array] An array of strings
|
20
|
+
def names
|
21
|
+
if @names.nil?
|
22
|
+
filename = File.expand_path('../namespaces.yml', __FILE__)
|
23
|
+
@names = YAML.load_file(filename)
|
24
|
+
end
|
25
|
+
|
26
|
+
@names
|
27
|
+
end
|
28
|
+
|
29
|
+
# Does a given namespace exist?
|
30
|
+
# @param [String, Symbol] name
|
31
|
+
def exists?(name)
|
32
|
+
names.include?(name.to_s)
|
33
|
+
end
|
34
|
+
end
|
6
35
|
end
|
7
36
|
end
|
@@ -7,7 +7,7 @@ module VkontakteApi
|
|
7
7
|
# @param [String] name The name of this resolvable.
|
8
8
|
# @option options [Hashie::Mash] :resolver A mash holding information about the previous resolver.
|
9
9
|
def initialize(name, options = {})
|
10
|
-
@name = name
|
10
|
+
@name = name.to_s
|
11
11
|
@previous_resolver = options.delete(:resolver)
|
12
12
|
end
|
13
13
|
|
@@ -1,41 +1,26 @@
|
|
1
1
|
module VkontakteApi
|
2
2
|
# A mixin for classes that will resolve other classes' objects via `#method_missing`.
|
3
3
|
module Resolver
|
4
|
-
# Main methods dispatch.
|
5
|
-
#
|
6
|
-
# If the called method is a namespace, it creates and returns a new `VkontakteApi::Namespace` instance.
|
7
|
-
# Otherwise it creates a `VkontakteApi::Method` instance and invokes it's `#call` method passing it the arguments and a block.
|
8
|
-
def method_missing(method_name, *args, &block)
|
9
|
-
method_name = method_name.to_s
|
10
|
-
|
11
|
-
if Resolver.namespaces.include?(method_name)
|
12
|
-
# called from Client
|
13
|
-
Namespace.new(method_name, resolver: resolver)
|
14
|
-
else
|
15
|
-
# called from Namespace or one-level method
|
16
|
-
Method.new(method_name, resolver: resolver).call(args.first || {}, &block)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
4
|
# A `Hashie::Mash` structure holding the name and token of current instance.
|
21
5
|
# @return [Hashie::Mash]
|
22
6
|
def resolver
|
23
7
|
@resolver ||= Hashie::Mash.new(name: @name, token: token)
|
24
8
|
end
|
25
9
|
|
10
|
+
private
|
11
|
+
def create_namespace(name)
|
12
|
+
Namespace.new(name, resolver: resolver)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_method(name)
|
16
|
+
Method.new(name, resolver: resolver)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call_method(args, &block)
|
20
|
+
create_method(args.shift).call(args.first || {}, &block)
|
21
|
+
end
|
22
|
+
|
26
23
|
class << self
|
27
|
-
# An array of method namespaces.
|
28
|
-
# Lazily loads the list from `namespaces.yml` and caches it.
|
29
|
-
# @return [Array]
|
30
|
-
def namespaces
|
31
|
-
if @namespaces.nil?
|
32
|
-
filename = File.expand_path('../namespaces.yml', __FILE__)
|
33
|
-
@namespaces = YAML.load_file(filename)
|
34
|
-
end
|
35
|
-
|
36
|
-
@namespaces
|
37
|
-
end
|
38
|
-
|
39
24
|
# When this module is included, it undefines the `:send` instance method in the `base_class`
|
40
25
|
# so it can be resolved via `method_missing`.
|
41
26
|
def included(base_class)
|
@@ -12,15 +12,15 @@ module VkontakteApi
|
|
12
12
|
# VkontakteApi.upload(
|
13
13
|
# url: 'http://example.com/upload',
|
14
14
|
# file1: ['/path/to/file1.jpg', 'image/jpeg'],
|
15
|
-
# file2: ['/path/to/file2.png'
|
15
|
+
# file2: [io_object, 'image/png', '/path/to/file2.png']
|
16
16
|
# )
|
17
17
|
def upload(params = {})
|
18
18
|
url = params.delete(:url)
|
19
19
|
raise ArgumentError, 'You should pass :url parameter' unless url
|
20
20
|
|
21
21
|
files = {}
|
22
|
-
params.each do |param_name, (
|
23
|
-
files[param_name] = Faraday::UploadIO.new(
|
22
|
+
params.each do |param_name, (file_path_or_io, file_type, file_path)|
|
23
|
+
files[param_name] = Faraday::UploadIO.new(file_path_or_io, file_type, file_path)
|
24
24
|
end
|
25
25
|
|
26
26
|
API.connection.post(url, files).body
|
data/spec/integration_spec.rb
CHANGED
@@ -13,70 +13,67 @@ describe "Integration" do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe "unauthorized requests" do
|
16
|
-
|
17
|
-
@vk = VK::Client.new
|
18
|
-
end
|
16
|
+
let(:vk) { VK::Client.new }
|
19
17
|
|
20
18
|
it "get users" do
|
21
|
-
user =
|
22
|
-
user.uid.
|
23
|
-
user.last_name.
|
24
|
-
user.first_name.
|
19
|
+
user = vk.users.get(uid: 1).first
|
20
|
+
expect(user.uid).to eq(1)
|
21
|
+
expect(user.last_name).not_to be_empty
|
22
|
+
expect(user.first_name).not_to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
it "search newsfeed" do
|
26
|
+
news = vk.newsfeed.search(q: 'vk', count: 1)
|
27
|
+
expect(news).to be_a(Enumerable)
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
31
|
if MechanizedAuthorization.on?
|
29
32
|
describe "authorized requests" do
|
30
|
-
|
31
|
-
@vk = MechanizedAuthorization.client
|
32
|
-
end
|
33
|
+
let(:vk) { MechanizedAuthorization.client }
|
33
34
|
|
34
35
|
it "get groups" do
|
35
|
-
|
36
|
-
groups.
|
36
|
+
pending 'MechanizedAuthorization not working'
|
37
|
+
expect(vk.groups.get).to include(1)
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
41
|
describe "requests with camelCase and predicate methods" do
|
41
|
-
|
42
|
-
@vk = MechanizedAuthorization.client
|
43
|
-
end
|
42
|
+
let(:vk) { MechanizedAuthorization.client }
|
44
43
|
|
45
44
|
it "convert method names to vk.com format" do
|
46
|
-
|
45
|
+
pending 'MechanizedAuthorization not working'
|
46
|
+
expect(vk.is_app_user?).to be_true
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
describe "requests with array arguments" do
|
52
|
-
|
53
|
-
@vk = VK::Client.new
|
54
|
-
end
|
52
|
+
let(:vk) { VK::Client.new }
|
55
53
|
|
56
54
|
it "join arrays with a comma" do
|
57
|
-
users =
|
58
|
-
users.first.screen_name.
|
55
|
+
users = vk.users.get(uids: [1, 2, 3], fields: %w[first_name last_name screen_name])
|
56
|
+
expect(users.first.screen_name).not_to be_empty
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
62
60
|
describe "requests with blocks" do
|
63
|
-
|
64
|
-
@vk = VK::Client.new
|
65
|
-
end
|
61
|
+
let(:vk) { VK::Client.new }
|
66
62
|
|
67
63
|
it "map the result with a block" do
|
68
|
-
users =
|
64
|
+
users = vk.users.get(uid: 1) do |user|
|
69
65
|
"#{user.last_name} #{user.first_name}"
|
70
66
|
end
|
71
67
|
|
72
|
-
users.first.
|
68
|
+
expect(users.first).not_to be_empty
|
73
69
|
end
|
74
70
|
end
|
75
71
|
|
76
72
|
describe "authorization" do
|
77
73
|
context "with a scope" do
|
78
74
|
it "returns a correct url" do
|
79
|
-
VK.authorization_url(scope: %w[friends groups])
|
75
|
+
url = VK.authorization_url(scope: %w[friends groups])
|
76
|
+
expect(url).to include('scope=friends%2Cgroups')
|
80
77
|
end
|
81
78
|
end
|
82
79
|
end
|
@@ -23,13 +23,13 @@ describe VkontakteApi::API do
|
|
23
23
|
|
24
24
|
context "called with a token parameter" do
|
25
25
|
it "sends it to .connection" do
|
26
|
-
subject.
|
26
|
+
expect(subject).to receive(:connection).with(url: VkontakteApi::API::URL_PREFIX, token: 'token')
|
27
27
|
subject.call('apiMethod', { some: :params }, 'token')
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
it "returns the response body" do
|
32
|
-
subject.call('apiMethod').
|
32
|
+
expect(subject.call('apiMethod')).to eq(@result)
|
33
33
|
end
|
34
34
|
|
35
35
|
it "uses an HTTP verb from VkontakteApi.http_verb" do
|
@@ -37,7 +37,7 @@ describe VkontakteApi::API do
|
|
37
37
|
VkontakteApi.http_verb = http_verb
|
38
38
|
|
39
39
|
response = double("Response", body: double)
|
40
|
-
@connection.
|
40
|
+
expect(@connection).to receive(:send).with(http_verb, 'apiMethod', {}).and_return(response)
|
41
41
|
subject.call('apiMethod')
|
42
42
|
end
|
43
43
|
|
@@ -51,27 +51,27 @@ describe VkontakteApi::API do
|
|
51
51
|
faraday_options = double("Faraday options")
|
52
52
|
VkontakteApi.stub(:faraday_options).and_return(faraday_options)
|
53
53
|
url = double("URL")
|
54
|
-
|
55
|
-
|
54
|
+
|
55
|
+
expect(Faraday).to receive(:new).with(url, faraday_options)
|
56
|
+
subject.connection(url: url)
|
56
57
|
end
|
57
58
|
|
58
59
|
context "without a token" do
|
59
60
|
it "creates a connection without an oauth2 middleware" do
|
60
|
-
|
61
|
-
|
61
|
+
handler_names = subject.connection.builder.handlers.map(&:name)
|
62
|
+
expect(handler_names).not_to include('FaradayMiddleware::OAuth2')
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
65
66
|
context "with a token" do
|
66
|
-
|
67
|
-
@token = double("Token")
|
68
|
-
end
|
67
|
+
let(:token) { double("Token") }
|
69
68
|
|
70
69
|
it "creates a connection with an oauth2 middleware" do
|
71
|
-
connection = subject.connection(token:
|
70
|
+
connection = subject.connection(token: token)
|
72
71
|
handler = connection.builder.handlers.first
|
73
|
-
|
74
|
-
handler.
|
72
|
+
|
73
|
+
expect(handler.name).to eq('FaradayMiddleware::OAuth2')
|
74
|
+
expect(handler.instance_variable_get(:@args)).to eq([token])
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -18,35 +18,38 @@ describe VkontakteApi::Authorization do
|
|
18
18
|
|
19
19
|
@client = double("OAuth2::Client instance", auth_code: @auth_code, implicit: @implicit, client_credentials: @client_credentials)
|
20
20
|
OAuth2::Client.stub(:new).and_return(@client)
|
21
|
-
|
22
|
-
@auth = Object.new
|
23
|
-
@auth.extend VkontakteApi::Authorization
|
24
21
|
end
|
25
22
|
|
23
|
+
let(:auth) do
|
24
|
+
Object.new.tap do |object|
|
25
|
+
object.extend VkontakteApi::Authorization
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
26
29
|
describe "#authorization_url" do
|
27
30
|
before(:each) do
|
28
|
-
|
31
|
+
auth.stub(:client).and_return(@client)
|
29
32
|
end
|
30
33
|
|
31
34
|
context "with a site type" do
|
32
35
|
it "returns a valid authorization url" do
|
33
|
-
@auth_code.
|
34
|
-
|
36
|
+
expect(@auth_code).to receive(:authorize_url).with(redirect_uri: @redirect_uri)
|
37
|
+
expect(auth.authorization_url(type: :site)).to eq(@url)
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
38
41
|
context "with a client type" do
|
39
42
|
it "returns a valid authorization url" do
|
40
|
-
@implicit.
|
41
|
-
|
43
|
+
expect(@implicit).to receive(:authorize_url).with(redirect_uri: @redirect_uri)
|
44
|
+
expect(auth.authorization_url(type: :client)).to eq(@url)
|
42
45
|
end
|
43
46
|
end
|
44
47
|
|
45
48
|
context "given a redirect_uri" do
|
46
49
|
it "prefers the given uri over VkontakteApi.redirect_uri" do
|
47
50
|
redirect_uri = 'http://example.com/oauth/callback'
|
48
|
-
@auth_code.
|
49
|
-
|
51
|
+
expect(@auth_code).to receive(:authorize_url).with(redirect_uri: redirect_uri)
|
52
|
+
auth.authorization_url(redirect_uri: redirect_uri)
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
@@ -55,9 +58,9 @@ describe VkontakteApi::Authorization do
|
|
55
58
|
scope = double("Scope")
|
56
59
|
flat_scope = double("Flat scope")
|
57
60
|
|
58
|
-
VkontakteApi::Utils.
|
59
|
-
@auth_code.
|
60
|
-
|
61
|
+
expect(VkontakteApi::Utils).to receive(:flatten_argument).with(scope).and_return(flat_scope)
|
62
|
+
expect(@auth_code).to receive(:authorize_url).with(redirect_uri: @redirect_uri, scope: flat_scope)
|
63
|
+
auth.authorization_url(scope: scope)
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
@@ -70,42 +73,42 @@ describe VkontakteApi::Authorization do
|
|
70
73
|
end
|
71
74
|
|
72
75
|
it "gets the token" do
|
73
|
-
@auth_code.
|
74
|
-
|
76
|
+
expect(@auth_code).to receive(:get_token).with(@code, redirect_uri: @redirect_uri)
|
77
|
+
auth.authorize(type: :site, code: @code)
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
78
81
|
context "with an app_server type" do
|
79
82
|
it "gets the token" do
|
80
|
-
@client_credentials.
|
81
|
-
|
83
|
+
expect(@client_credentials).to receive(:get_token).with({ redirect_uri: @redirect_uri }, subject::OPTIONS[:client_credentials])
|
84
|
+
auth.authorize(type: :app_server)
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
88
|
context "with an unknown type" do
|
86
89
|
it "raises an ArgumentError" do
|
87
90
|
expect {
|
88
|
-
|
91
|
+
auth.authorize(type: :unknown)
|
89
92
|
}.to raise_error(ArgumentError)
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
93
96
|
it "builds a VkontakteApi::Client instance with the received token" do
|
94
97
|
client = double("VkontakteApi::Client instance")
|
95
|
-
VkontakteApi::Client.
|
96
|
-
|
98
|
+
expect(VkontakteApi::Client).to receive(:new).with(@token).and_return(client)
|
99
|
+
expect(auth.authorize).to eq(client)
|
97
100
|
end
|
98
101
|
end
|
99
102
|
|
100
103
|
describe "#client" do
|
101
104
|
it "creates and returns an OAuth2::Client instance" do
|
102
|
-
OAuth2::Client.
|
103
|
-
|
105
|
+
expect(OAuth2::Client).to receive(:new).with(@app_id, @app_secret, subject::OPTIONS[:client])
|
106
|
+
expect(auth.send(:client)).to eq(@client)
|
104
107
|
end
|
105
108
|
|
106
109
|
it "caches the result" do
|
107
|
-
OAuth2::Client.
|
108
|
-
5.times {
|
110
|
+
expect(OAuth2::Client).to receive(:new).once
|
111
|
+
5.times { auth.send(:client) }
|
109
112
|
end
|
110
113
|
end
|
111
114
|
end
|