From ed07fffe9fca2fb37c5f62e7d3531dbd28f2394d Mon Sep 17 00:00:00 2001 From: Tom Turner Date: Tue, 1 Sep 2026 10:25:13 -0400 Subject: [PATCH 1/2] Expose rate limit info on Management API responses (v6) Wraps the RawClient#send return value in a RawResponse that delegates #code, #body, and header access to the underlying response (so the generated callers are unaffected) and adds a #rate_limit built from the x-ratelimit-* headers. - Add Auth0::Internal::Http::RateLimit (limit/remaining/reset; blank and non-numeric header values become nil rather than 0) - Add Auth0::Internal::Http::RawResponse wrapper - RawClient#send returns the wrapper (retry logic still operates on the raw response inside the loop; only the final response is wrapped) - Unit tests for RateLimit, RawResponse, and RawClient#send All changes live in fernignored files (lib/auth0/internal/**), so they survive regeneration. Refs #606. --- lib/auth0.rb | 2 + lib/auth0/internal/http/rate_limit.rb | 50 ++++++++++++++ lib/auth0/internal/http/raw_client.rb | 9 ++- lib/auth0/internal/http/raw_response.rb | 48 ++++++++++++++ test/unit/internal/http/test_rate_limit.rb | 38 +++++++++++ test/unit/internal/http/test_raw_client.rb | 69 ++++++++++++++++++++ test/unit/internal/http/test_raw_response.rb | 67 +++++++++++++++++++ 7 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 lib/auth0/internal/http/rate_limit.rb create mode 100644 lib/auth0/internal/http/raw_response.rb create mode 100644 test/unit/internal/http/test_rate_limit.rb create mode 100644 test/unit/internal/http/test_raw_client.rb create mode 100644 test/unit/internal/http/test_raw_response.rb diff --git a/lib/auth0.rb b/lib/auth0.rb index dc3213d6..474639f0 100644 --- a/lib/auth0.rb +++ b/lib/auth0.rb @@ -12,6 +12,8 @@ require_relative "auth0/internal/errors/type_error" require_relative "auth0/internal/http/base_request" require_relative "auth0/internal/json/request" +require_relative "auth0/internal/http/rate_limit" +require_relative "auth0/internal/http/raw_response" require_relative "auth0/internal/http/raw_client" require_relative "auth0/internal/multipart/multipart_encoder" require_relative "auth0/internal/multipart/multipart_form_data_part" diff --git a/lib/auth0/internal/http/rate_limit.rb b/lib/auth0/internal/http/rate_limit.rb new file mode 100644 index 00000000..6030bab4 --- /dev/null +++ b/lib/auth0/internal/http/rate_limit.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Auth0 + module Internal + module Http + # Rate limit information parsed from the `x-ratelimit-*` headers Auth0 + # returns on Management API responses. + # + # @see https://auth0.com/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy + class RateLimit + # @return [Integer, nil] the maximum number of requests allowed in the current window + attr_reader :limit + # @return [Integer, nil] the number of requests remaining in the current window + attr_reader :remaining + # @return [Time, nil] the UTC time at which the current window resets + attr_reader :reset + + # @param limit [Integer, nil] + # @param remaining [Integer, nil] + # @param reset [Time, nil] + def initialize(limit:, remaining:, reset:) + @limit = limit + @remaining = remaining + @reset = reset + end + + # Build a RateLimit from an HTTP response. Header lookups are + # case-insensitive (delegated to the response), and missing or + # non-numeric values become nil rather than a misleading 0. + # + # @param response [Net::HTTPResponse] anything responding to `[]` with header access + # @return [Auth0::Internal::Http::RateLimit] + def self.from_response(response) + reset = to_integer(response["x-ratelimit-reset"]) + + new( + limit: to_integer(response["x-ratelimit-limit"]), + remaining: to_integer(response["x-ratelimit-remaining"]), + reset: reset.nil? ? nil : Time.at(reset).utc + ) + end + + def self.to_integer(value) + Integer(value.to_s.strip, exception: false) + end + private_class_method :to_integer + end + end + end +end diff --git a/lib/auth0/internal/http/raw_client.rb b/lib/auth0/internal/http/raw_client.rb index 0de6d27c..797ed4ee 100644 --- a/lib/auth0/internal/http/raw_client.rb +++ b/lib/auth0/internal/http/raw_client.rb @@ -45,7 +45,9 @@ def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) end # @param request [Auth0::Internal::Http::BaseRequest] The HTTP request. - # @return [HTTP::Response] The HTTP response. + # @return [Auth0::Internal::Http::RawResponse] The HTTP response, wrapped + # to expose rate limit information via #rate_limit while delegating + # #code / #body / header access to the underlying response. def send(request) url = build_url(request) attempt = 0 @@ -74,7 +76,10 @@ def send(request) attempt += 1 end - response + # Wrap only the final response (retry logic above operates on the raw + # Net::HTTPResponse). Delegates #code/#body so existing callers are + # unaffected, and adds #rate_limit from the response headers. + RawResponse.new(response) end # Determines if a request should be retried based on the response status code. diff --git a/lib/auth0/internal/http/raw_response.rb b/lib/auth0/internal/http/raw_response.rb new file mode 100644 index 00000000..718b9f0d --- /dev/null +++ b/lib/auth0/internal/http/raw_response.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Auth0 + module Internal + module Http + # Thin wrapper around the underlying HTTP response that adds rate limit + # information while delegating everything else (e.g. `#code`, `#body`, + # header access via `#[]`) to the wrapped response. Existing callers that + # use `.code`/`.body` are unaffected. + class RawResponse + # @return [Auth0::Internal::Http::RateLimit] rate limit parsed from the response headers + attr_reader :rate_limit + + # @param response [Net::HTTPResponse] the wrapped response + def initialize(response) + @response = response + @rate_limit = RateLimit.from_response(response) + end + + # @return [String] the HTTP status code + def code + @response.code + end + + # @return [String, nil] the response body + def body + @response.body + end + + # @return [String, nil] header access, delegated to the wrapped response + def [](name) + @response[name] + end + + # Delegate anything else to the wrapped response. + def method_missing(name, *, &) + return @response.send(name, *, &) if @response.respond_to?(name) + + super + end + + def respond_to_missing?(name, include_private = false) + @response.respond_to?(name, include_private) || super + end + end + end + end +end diff --git a/test/unit/internal/http/test_rate_limit.rb b/test/unit/internal/http/test_rate_limit.rb new file mode 100644 index 00000000..eacfdaa2 --- /dev/null +++ b/test/unit/internal/http/test_rate_limit.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Auth0::Internal::Http::RateLimit do + RateLimit = Auth0::Internal::Http::RateLimit + + describe ".from_response" do + it "parses the x-ratelimit-* headers" do + response = { + "x-ratelimit-limit" => "100", + "x-ratelimit-remaining" => "42", + "x-ratelimit-reset" => "1724000000" + } + + rate_limit = RateLimit.from_response(response) + + _(rate_limit.limit).must_equal 100 + _(rate_limit.remaining).must_equal 42 + _(rate_limit.reset).must_equal Time.at(1_724_000_000).utc + end + + it "reports a remaining of 0 as an integer, not nil" do + _(RateLimit.from_response("x-ratelimit-remaining" => "0").remaining).must_equal 0 + end + + it "returns nil for missing or non-numeric values instead of a misleading 0" do + rate_limit = RateLimit.from_response( + "x-ratelimit-limit" => "", + "x-ratelimit-remaining" => "not-a-number" + ) + + _(rate_limit.limit).must_be_nil + _(rate_limit.remaining).must_be_nil + _(rate_limit.reset).must_be_nil + end + end +end diff --git a/test/unit/internal/http/test_raw_client.rb b/test/unit/internal/http/test_raw_client.rb new file mode 100644 index 00000000..7a085396 --- /dev/null +++ b/test/unit/internal/http/test_raw_client.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Auth0::Internal::Http::RawClient do + module TestRawClient + # Minimal stand-in for a Net::HTTPResponse. + class FakeHttpResponse + def initialize(code:, body:, headers:) + @code = code + @body = body + @headers = headers + end + + attr_reader :code, :body + + def [](name) + @headers[name] + end + end + + # Minimal stand-in for the Net::HTTP connection. + class FakeConnection + def initialize(response) + @response = response + end + + def open_timeout=(_); end + def read_timeout=(_); end + def write_timeout=(_); end + def continue_timeout=(_); end + + def request(_http_request) + @response + end + end + end + + it "returns a RawResponse that delegates code/body and exposes rate_limit" do + client = Auth0::Internal::Http::RawClient.new(base_url: "https://tenant.auth0.com", max_retries: 0) + http_response = TestRawClient::FakeHttpResponse.new( + code: "200", + body: "{}", + headers: { + "x-ratelimit-limit" => "100", + "x-ratelimit-remaining" => "12", + "x-ratelimit-reset" => "1724000000" + } + ) + request = Auth0::Internal::JSON::Request.new( + base_url: nil, + method: "GET", + path: "users", + query: {}, + request_options: {} + ) + + result = client.stub(:connect, TestRawClient::FakeConnection.new(http_response)) do + client.send(request) + end + + _(result).must_be_instance_of Auth0::Internal::Http::RawResponse + _(result.code).must_equal "200" + _(result.body).must_equal "{}" + _(result.rate_limit.limit).must_equal 100 + _(result.rate_limit.remaining).must_equal 12 + _(result.rate_limit.reset).must_equal Time.at(1_724_000_000).utc + end +end diff --git a/test/unit/internal/http/test_raw_response.rb b/test/unit/internal/http/test_raw_response.rb new file mode 100644 index 00000000..ffd22c58 --- /dev/null +++ b/test/unit/internal/http/test_raw_response.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Auth0::Internal::Http::RawResponse do + module TestRawResponse + # Minimal stand-in for a Net::HTTPResponse. + class FakeHttpResponse + def initialize(code:, body:, headers: {}) + @code = code + @body = body + @headers = headers + end + + attr_reader :code, :body + + def [](name) + @headers[name] + end + + def message + "OK" + end + end + + def self.response(headers: {}) + FakeHttpResponse.new( + code: "200", + body: "{\"ok\":true}", + headers: { + "x-ratelimit-limit" => "100", + "x-ratelimit-remaining" => "7", + "x-ratelimit-reset" => "1724000000" + }.merge(headers) + ) + end + end + + it "delegates #code and #body to the wrapped response" do + wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) + + _(wrapped.code).must_equal "200" + _(wrapped.body).must_equal "{\"ok\":true}" + end + + it "delegates header access via #[]" do + wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) + + _(wrapped["x-ratelimit-remaining"]).must_equal "7" + end + + it "delegates unknown methods to the wrapped response" do + wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) + + _(wrapped.message).must_equal "OK" + _(wrapped.respond_to?(:message)).must_equal true + end + + it "exposes rate limit information parsed from the response headers" do + wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) + + _(wrapped.rate_limit).must_be_instance_of Auth0::Internal::Http::RateLimit + _(wrapped.rate_limit.limit).must_equal 100 + _(wrapped.rate_limit.remaining).must_equal 7 + _(wrapped.rate_limit.reset).must_equal Time.at(1_724_000_000).utc + end +end From f71555e5266ff55165de6f7ffd609367ed56d62a Mon Sep 17 00:00:00 2001 From: Tom Turner Date: Tue, 1 Sep 2026 10:37:13 -0400 Subject: [PATCH 2/2] Expose rate limit info via a management client callback (v6) Adds an opt-in callback, invoked with the rate limit parsed from every Management API response, so callers can monitor how close they are to the limit. Chosen over changing endpoint return values because the response is dropped in generated endpoint code that a third-party change can't durably alter; the callback lives entirely in fernignored files. - Add Auth0::Internal::Http::RateLimit (limit/remaining/reset; blank and non-numeric header values become nil rather than 0) - RawClient gains a rate_limit_handler, invoked in #send after retries on every response; handler errors are swallowed so they can't break a request - Wire it through the custom client: Auth0::Client.new(management_rate_limit_handler:) attaches the handler to the management raw client - Unit tests for RateLimit, RawClient#send handler behavior, and client wiring Refs #606. --- lib/auth0.rb | 1 - lib/auth0/auth_client.rb | 15 ++++ lib/auth0/internal/http/raw_client.rb | 32 ++++++-- lib/auth0/internal/http/raw_response.rb | 48 ----------- lib/auth0/mixins/initializer.rb | 1 + test/unit/internal/http/test_raw_client.rb | 85 ++++++++++++++------ test/unit/internal/http/test_raw_response.rb | 67 --------------- test/unit/test_auth_client_rate_limit.rb | 24 ++++++ 8 files changed, 123 insertions(+), 150 deletions(-) delete mode 100644 lib/auth0/internal/http/raw_response.rb delete mode 100644 test/unit/internal/http/test_raw_response.rb create mode 100644 test/unit/test_auth_client_rate_limit.rb diff --git a/lib/auth0.rb b/lib/auth0.rb index 474639f0..cfed8439 100644 --- a/lib/auth0.rb +++ b/lib/auth0.rb @@ -13,7 +13,6 @@ require_relative "auth0/internal/http/base_request" require_relative "auth0/internal/json/request" require_relative "auth0/internal/http/rate_limit" -require_relative "auth0/internal/http/raw_response" require_relative "auth0/internal/http/raw_client" require_relative "auth0/internal/multipart/multipart_encoder" require_relative "auth0/internal/multipart/multipart_form_data_part" diff --git a/lib/auth0/auth_client.rb b/lib/auth0/auth_client.rb index 9f3cbde6..dde11ea8 100644 --- a/lib/auth0/auth_client.rb +++ b/lib/auth0/auth_client.rb @@ -91,8 +91,23 @@ def management opts[:max_retries] = @management_max_retries if @management_max_retries opts[:headers] = @management_additional_headers if @management_additional_headers @_management = Auth0::Management.new(**opts) + attach_rate_limit_handler(@_management) end @_management end + + private + + # Attaches the configured rate limit handler to the management client's + # underlying raw client. Management is generated and builds its own raw + # client, so we set the handler on it after construction. + # @param management [Auth0::Management] + # @return [void] + def attach_rate_limit_handler(management) + return if @management_rate_limit_handler.nil? + + raw_client = management.instance_variable_get(:@raw_client) + raw_client.rate_limit_handler = @management_rate_limit_handler if raw_client + end end end diff --git a/lib/auth0/internal/http/raw_client.rb b/lib/auth0/internal/http/raw_client.rb index 797ed4ee..8a029f4a 100644 --- a/lib/auth0/internal/http/raw_client.rb +++ b/lib/auth0/internal/http/raw_client.rb @@ -20,14 +20,21 @@ class RawClient # @return [String] The base URL for requests attr_reader :base_url + # @return [#call, nil] Optional callback invoked with an + # {Auth0::Internal::Http::RateLimit} after every response. + attr_accessor :rate_limit_handler + # @param base_url [String] The base url for the request. # @param max_retries [Integer] The number of times to retry a failed request, defaults to 2. # @param timeout [Float] The timeout for the request, defaults to 60.0 seconds. # @param headers [Hash] The headers for the request. - def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) + # @param rate_limit_handler [#call, nil] Optional callback invoked with the + # parsed rate limit (from the `x-ratelimit-*` headers) after every response. + def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}, rate_limit_handler: nil) @base_url = base_url @max_retries = max_retries @timeout = timeout + @rate_limit_handler = rate_limit_handler # Auth0 telemetry in standard format telemetry = { @@ -45,9 +52,7 @@ def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) end # @param request [Auth0::Internal::Http::BaseRequest] The HTTP request. - # @return [Auth0::Internal::Http::RawResponse] The HTTP response, wrapped - # to expose rate limit information via #rate_limit while delegating - # #code / #body / header access to the underlying response. + # @return [Net::HTTPResponse] The HTTP response. def send(request) url = build_url(request) attempt = 0 @@ -76,10 +81,21 @@ def send(request) attempt += 1 end - # Wrap only the final response (retry logic above operates on the raw - # Net::HTTPResponse). Delegates #code/#body so existing callers are - # unaffected, and adds #rate_limit from the response headers. - RawResponse.new(response) + notify_rate_limit(response) + response + end + + # Invokes the rate limit handler with the rate limit parsed from the + # response headers. Runs after retries, on every response. A handler + # error must never break the request, so it is swallowed. + # @param response [Net::HTTPResponse] The HTTP response. + # @return [void] + def notify_rate_limit(response) + return if @rate_limit_handler.nil? + + @rate_limit_handler.call(RateLimit.from_response(response)) + rescue StandardError + nil end # Determines if a request should be retried based on the response status code. diff --git a/lib/auth0/internal/http/raw_response.rb b/lib/auth0/internal/http/raw_response.rb deleted file mode 100644 index 718b9f0d..00000000 --- a/lib/auth0/internal/http/raw_response.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module Auth0 - module Internal - module Http - # Thin wrapper around the underlying HTTP response that adds rate limit - # information while delegating everything else (e.g. `#code`, `#body`, - # header access via `#[]`) to the wrapped response. Existing callers that - # use `.code`/`.body` are unaffected. - class RawResponse - # @return [Auth0::Internal::Http::RateLimit] rate limit parsed from the response headers - attr_reader :rate_limit - - # @param response [Net::HTTPResponse] the wrapped response - def initialize(response) - @response = response - @rate_limit = RateLimit.from_response(response) - end - - # @return [String] the HTTP status code - def code - @response.code - end - - # @return [String, nil] the response body - def body - @response.body - end - - # @return [String, nil] header access, delegated to the wrapped response - def [](name) - @response[name] - end - - # Delegate anything else to the wrapped response. - def method_missing(name, *, &) - return @response.send(name, *, &) if @response.respond_to?(name) - - super - end - - def respond_to_missing?(name, include_private = false) - @response.respond_to?(name, include_private) || super - end - end - end - end -end diff --git a/lib/auth0/mixins/initializer.rb b/lib/auth0/mixins/initializer.rb index a37ac88a..9885738e 100644 --- a/lib/auth0/mixins/initializer.rb +++ b/lib/auth0/mixins/initializer.rb @@ -21,6 +21,7 @@ def initialize(config) @management_timeout = options[:management_timeout] @management_max_retries = options[:management_max_retries] @management_additional_headers = options[:management_additional_headers] + @management_rate_limit_handler = options[:management_rate_limit_handler] extend Auth0::Api::AuthenticationEndpoints @client_id = options[:client_id] diff --git a/test/unit/internal/http/test_raw_client.rb b/test/unit/internal/http/test_raw_client.rb index 7a085396..e107887c 100644 --- a/test/unit/internal/http/test_raw_client.rb +++ b/test/unit/internal/http/test_raw_client.rb @@ -34,36 +34,69 @@ def request(_http_request) @response end end + + def self.build_request + Auth0::Internal::JSON::Request.new( + base_url: nil, + method: "GET", + path: "users", + query: {}, + request_options: {} + ) + end + + def self.build_response + FakeHttpResponse.new( + code: "200", + body: "{}", + headers: { + "x-ratelimit-limit" => "100", + "x-ratelimit-remaining" => "12", + "x-ratelimit-reset" => "1724000000" + } + ) + end end - it "returns a RawResponse that delegates code/body and exposes rate_limit" do - client = Auth0::Internal::Http::RawClient.new(base_url: "https://tenant.auth0.com", max_retries: 0) - http_response = TestRawClient::FakeHttpResponse.new( - code: "200", - body: "{}", - headers: { - "x-ratelimit-limit" => "100", - "x-ratelimit-remaining" => "12", - "x-ratelimit-reset" => "1724000000" - } - ) - request = Auth0::Internal::JSON::Request.new( - base_url: nil, - method: "GET", - path: "users", - query: {}, - request_options: {} + def send_with(client, response) + client.stub(:connect, TestRawClient::FakeConnection.new(response)) do + client.send(TestRawClient.build_request) + end + end + + it "invokes the rate limit handler with the parsed rate limit and returns the response unchanged" do + captured = nil + client = Auth0::Internal::Http::RawClient.new( + base_url: "https://tenant.auth0.com", + max_retries: 0, + rate_limit_handler: ->(rate_limit) { captured = rate_limit } ) + response = TestRawClient.build_response - result = client.stub(:connect, TestRawClient::FakeConnection.new(http_response)) do - client.send(request) - end + result = send_with(client, response) + + _(result).must_be_same_as response + _(captured).must_be_instance_of Auth0::Internal::Http::RateLimit + _(captured.limit).must_equal 100 + _(captured.remaining).must_equal 12 + _(captured.reset).must_equal Time.at(1_724_000_000).utc + end + + it "returns the response unchanged when no handler is configured" do + client = Auth0::Internal::Http::RawClient.new(base_url: "https://tenant.auth0.com", max_retries: 0) + response = TestRawClient.build_response + + _(send_with(client, response)).must_be_same_as response + end + + it "does not let a handler error break the request" do + client = Auth0::Internal::Http::RawClient.new( + base_url: "https://tenant.auth0.com", + max_retries: 0, + rate_limit_handler: ->(_rate_limit) { raise "boom" } + ) + response = TestRawClient.build_response - _(result).must_be_instance_of Auth0::Internal::Http::RawResponse - _(result.code).must_equal "200" - _(result.body).must_equal "{}" - _(result.rate_limit.limit).must_equal 100 - _(result.rate_limit.remaining).must_equal 12 - _(result.rate_limit.reset).must_equal Time.at(1_724_000_000).utc + _(send_with(client, response)).must_be_same_as response end end diff --git a/test/unit/internal/http/test_raw_response.rb b/test/unit/internal/http/test_raw_response.rb deleted file mode 100644 index ffd22c58..00000000 --- a/test/unit/internal/http/test_raw_response.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -describe Auth0::Internal::Http::RawResponse do - module TestRawResponse - # Minimal stand-in for a Net::HTTPResponse. - class FakeHttpResponse - def initialize(code:, body:, headers: {}) - @code = code - @body = body - @headers = headers - end - - attr_reader :code, :body - - def [](name) - @headers[name] - end - - def message - "OK" - end - end - - def self.response(headers: {}) - FakeHttpResponse.new( - code: "200", - body: "{\"ok\":true}", - headers: { - "x-ratelimit-limit" => "100", - "x-ratelimit-remaining" => "7", - "x-ratelimit-reset" => "1724000000" - }.merge(headers) - ) - end - end - - it "delegates #code and #body to the wrapped response" do - wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) - - _(wrapped.code).must_equal "200" - _(wrapped.body).must_equal "{\"ok\":true}" - end - - it "delegates header access via #[]" do - wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) - - _(wrapped["x-ratelimit-remaining"]).must_equal "7" - end - - it "delegates unknown methods to the wrapped response" do - wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) - - _(wrapped.message).must_equal "OK" - _(wrapped.respond_to?(:message)).must_equal true - end - - it "exposes rate limit information parsed from the response headers" do - wrapped = Auth0::Internal::Http::RawResponse.new(TestRawResponse.response) - - _(wrapped.rate_limit).must_be_instance_of Auth0::Internal::Http::RateLimit - _(wrapped.rate_limit.limit).must_equal 100 - _(wrapped.rate_limit.remaining).must_equal 7 - _(wrapped.rate_limit.reset).must_equal Time.at(1_724_000_000).utc - end -end diff --git a/test/unit/test_auth_client_rate_limit.rb b/test/unit/test_auth_client_rate_limit.rb new file mode 100644 index 00000000..e2827a0b --- /dev/null +++ b/test/unit/test_auth_client_rate_limit.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Auth0::Client do + def build_client(**extra) + Auth0::Client.new(domain: "tenant.auth0.com", token: "test-token", **extra) + end + + it "attaches the configured management_rate_limit_handler to the management raw client" do + handler = ->(_rate_limit) {} + client = build_client(management_rate_limit_handler: handler) + + raw_client = client.management.instance_variable_get(:@raw_client) + + _(raw_client.rate_limit_handler).must_be_same_as handler + end + + it "leaves the handler unset when none is configured" do + raw_client = build_client.management.instance_variable_get(:@raw_client) + + _(raw_client.rate_limit_handler).must_be_nil + end +end