-
Notifications
You must be signed in to change notification settings - Fork 1
Feature | Authenticator with cache #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
641df4c
4cd106a
80568f5
3a815d2
c6ebd4b
14df933
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,7 @@ gem "rubocop", "~> 1.21" | |
| group :development do | ||
| gem "dotenv" | ||
| end | ||
|
|
||
| group :test do | ||
| gem "webmock" | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "faraday" | ||
| require "json" | ||
|
|
||
| module Ruber | ||
| class Authenticator | ||
| OAUTH_URL = "https://auth.uber.com/oauth/v2/token" | ||
| GRANT_TYPE = "client_credentials" | ||
| SCOPE = "eats.deliveries" | ||
|
|
||
| class << self | ||
| def access_token | ||
| @access_token = cached_token || fetch_new_token | ||
|
|
||
| @access_token = refresh_access_token if token_expired? | ||
|
|
||
| @access_token | ||
| end | ||
|
|
||
| def refresh_access_token | ||
| Ruber.cache.delete(cache_key) | ||
|
|
||
| fetch_new_token | ||
| end | ||
|
|
||
| def cache_key | ||
| @cache_key ||= "#{Ruber.customer_id}_#{Ruber.client_id}_access_token" | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def token_expired? | ||
| cached_token[:expires_at] < Time.now | ||
| end | ||
|
|
||
| def cached_token | ||
| Ruber.cache.read(cache_key) | ||
| end | ||
|
|
||
| def fetch_new_token | ||
| response = Faraday.post( | ||
| OAUTH_URL, | ||
| { | ||
| client_id: Ruber.client_id, | ||
| client_secret: Ruber.client_secret, | ||
| grant_type: GRANT_TYPE, | ||
| scope: SCOPE | ||
| } | ||
| ) | ||
|
|
||
| data = JSON.parse(response.body.to_s) | ||
|
|
||
| expires_at = Time.now + data["expires_in"].to_i | ||
| Ruber.cache.write(cache_key, { token: data["access_token"], expires_at: expires_at }) | ||
|
|
||
| data["access_token"] | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Ruber | ||
| class NullCache | ||
| def read(key) = memory_store[key] | ||
| def write(key, value, _options = {}) = memory_store[key] = value | ||
| def clear = memory_store.clear | ||
| def delete(key) = memory_store.delete(key) | ||
| def memory_store = @memory_store ||= {} | ||
|
Comment on lines
+5
to
+9
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Está bien que el método por defecto sea cachear en memoria? No conviene que sea un FileStore? Porque si se usa memoria se va a pedir el token en cada vuelta y Uber podría penalizarte o que superes la quota. Entiendo que la idea es que los usuarios indiquen cual es su cache store de preferencia, pero me parece que estaría bueno que ya venga un store útil por defecto. Qué decís?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. en realidad primero había hecho uno que devolvía todo nil y listo, pero para los tests esto es conveniente porque funciona como si tuvieras algún caché. No estoy seguro de qué convenga hacer por defecto. |
||
| end | ||
|
|
||
| class << self | ||
| def cache=(store) | ||
| unless %i[read write clear delete].all? { |method| store.respond_to?(method) } | ||
| raise ArgumentError, "cache_store must respond to read, write, clear, and delete" | ||
| end | ||
|
|
||
| @cache = store | ||
| end | ||
|
|
||
| def cache | ||
| @cache ||= NullCache.new | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Ruber | ||
| class Configuration | ||
| class NullCache | ||
| def read(key) = memory_store[key] | ||
| def write(key, value, _options = {}) = memory_store[key] = value | ||
| def clear = memory_store.clear | ||
| def delete(key) = memory_store.delete(key) | ||
| def memory_store = @memory_store ||= {} | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "test_helper" | ||
| require "webmock/minitest" | ||
|
|
||
| module Ruber | ||
| class AuthenticatorTest < Minitest::Test | ||
| include WebMock::API | ||
|
|
||
| def setup | ||
| Ruber.cache.clear | ||
| end | ||
|
|
||
| def test_access_token_fetches_and_caches_token | ||
| stub_token_request(access_token: "new_token", expires_in: 3600) | ||
|
|
||
| token = Authenticator.access_token | ||
| cached_token = Ruber.cache.read(Authenticator.cache_key) | ||
|
|
||
| assert_equal "new_token", token | ||
| assert_equal "new_token", cached_token[:token] | ||
| end | ||
|
|
||
| def test_refresh_access_token_forces_new_token | ||
| Ruber.cache.write(Authenticator.cache_key, { token: "old_token", expires_at: Time.now + 3600 }) | ||
|
|
||
| stub_token_request(access_token: "refreshed_token", expires_in: 3600) | ||
|
|
||
| token = Authenticator.refresh_access_token | ||
| cached_token = Ruber.cache.read(Authenticator.cache_key) | ||
|
|
||
| assert_equal "refreshed_token", token | ||
| assert_equal "refreshed_token", cached_token[:token] | ||
| end | ||
|
|
||
| def test_refresh_access_token_if_expired | ||
| Ruber.cache.write(Authenticator.cache_key, { token: "expired_token", expires_at: Time.now - 3600 }) | ||
|
|
||
| stub_token_request(access_token: "refreshed_token", expires_in: 3600) | ||
|
|
||
| token = Authenticator.access_token | ||
| cached_token = Ruber.cache.read(Authenticator.cache_key) | ||
|
|
||
| assert_equal "refreshed_token", token | ||
| assert_equal "refreshed_token", cached_token[:token] | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def stub_token_request(access_token:, expires_in:) | ||
| stub_request(:post, Authenticator::OAUTH_URL) | ||
| .to_return( | ||
| status: 200, | ||
| body: { | ||
| access_token: access_token, | ||
| expires_in: expires_in | ||
| }.to_json | ||
| ) | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,4 @@ | |
| require "ruber" | ||
|
|
||
| require "minitest/autorun" | ||
| require "webmock/minitest" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
en qué contexto puede pasar que el token esté expirado acá?
se supone que cached_token guarda el token en caché por el tiempo de expiración que te devuelve la misma API, o no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ahí vi que también modificaste el write del caché para que el token quede guardado