From 1275f7a52425d3f032af48724bf34914e124f666 Mon Sep 17 00:00:00 2001 From: Steven Klaiber-Noble Date: Thu, 6 Aug 2026 16:57:09 -0700 Subject: [PATCH] Make the repository lint-clean `rake lint` reported eighteen offences, none of them in CI and none of them test failures -- but the task exists now, so leaving it red is a trap for whoever wires it up. Twenty-five were autocorrected, almost all argument alignment and parallel assignment in the pre-existing OAuth conformance tests. Two needed a decision: `create_token` was over both AbcSize and MethodLength. Building the request moves to `token_request`, which is a thing worth naming on its own -- it is where RFC 6749's client_credentials grant and Basic auth actually happen, and the four spec tests around it pass unchanged. `Metrics/AbcSize` is now excluded for `test/**/*`. An assertion-heavy test scores high on ABC by counting assertions, which is not a signal worth acting on; the same reasoning already excludes `Metrics/CyclomaticComplexity` there. At the top of the stack rather than amended into the branches that own these files: the offences span `lib/fragment_client.rb`, `.rubocop.yml` and `test/unit_test.rb`, which three different PRs touch, and threading a style pass through all of them risks conflicts for no gain when RuboCop is not gated. --- .rubocop.yml | 2 ++ Gemfile | 4 ++-- lib/fragment_client.rb | 16 ++++++++++++---- test/conformance_test.rb | 2 +- test/unit_test.rb | 28 +++++++++++++++------------- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 932e7eb..7e5c270 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -26,6 +26,8 @@ Metrics/ParameterLists: Metrics/AbcSize: Max: 30 + Exclude: + - 'test/**/*' # A Sorbet `sig` is a line of its own, so a fully annotated class runs roughly # half again as long as the same code untyped. These limits are set for annotated diff --git a/Gemfile b/Gemfile index be515a2..b40655c 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,7 @@ gem 'minitest', group: :development gem 'pry' gem 'rake', group: :development gem 'reline' +gem 'rexml', '>= 3.4.2' gem 'rubocop', group: :development gem 'rubocop-graphql', group: :development gem 'rubocop-minitest', group: :development @@ -19,6 +20,5 @@ gem 'sorbet', group: :development gem 'sorbet-runtime' gem 'tapioca', group: :development gem 'thor', '>= 1.4.0' -gem 'webmock', group: :development -gem 'rexml', '>= 3.4.2' gem 'uri', '>= 1.0.4' +gem 'webmock', group: :development diff --git a/lib/fragment_client.rb b/lib/fragment_client.rb index aaa1bed..8cce3fd 100644 --- a/lib/fragment_client.rb +++ b/lib/fragment_client.rb @@ -305,10 +305,11 @@ def parse_oauth_url(oauth_url) raise ArgumentError, "oauth_url must be an http or https URL, got #{oauth_url.inspect}" end - sig { returns(Token) } - def create_token - uri = @oauth_url - post = Net::HTTP::Post.new(uri.request_uri) + # RFC 6749 §4.4.2 and §2.3.1: client_credentials over + # application/x-www-form-urlencoded, with the client id and secret as HTTP Basic. + sig { returns(Net::HTTP::Post) } + def token_request + post = Net::HTTP::Post.new(@oauth_url.request_uri) post.basic_auth(@client_id, @client_secret) post.content_type = 'application/x-www-form-urlencoded' post.body = URI.encode_www_form( @@ -316,6 +317,13 @@ def create_token scope: @oauth_scope, client_id: @client_id ) + post + end + + sig { returns(Token) } + def create_token + uri = @oauth_url + post = token_request begin http = Net::HTTP.new(uri.host, uri.port) diff --git a/test/conformance_test.rb b/test/conformance_test.rb index 596e174..ec97f13 100644 --- a/test/conformance_test.rb +++ b/test/conformance_test.rb @@ -144,7 +144,7 @@ def test_key_order_is_canonical end FragmentClient::TypedEntries.reset! - FragmentGraphQl.reset_operations! + FragmentGraphQl.reset_operations! end end diff --git a/test/unit_test.rb b/test/unit_test.rb index 55e73b5..aa61b08 100644 --- a/test/unit_test.rb +++ b/test/unit_test.rb @@ -286,40 +286,41 @@ def test_token_request_conforms_to_oauth2_and_http_specs # of the target URI as the request-target." token_request_path = captured_request_paths.first assert_equal '/oauth2/token', token_request_path, - "Token request must use origin-form request-target (RFC 7230 §5.3.1), got: #{token_request_path}" + "Token request must use origin-form request-target (RFC 7230 §5.3.1), got: #{token_request_path}" # RFC 6749 §4.4.2: "The client makes a request to the token endpoint by # adding the following parameters using the 'application/x-www-form-urlencoded' # format [...] grant_type: REQUIRED. Value MUST be set to 'client_credentials'." body_params = URI.decode_www_form(captured_auth_request.body).to_h assert_equal 'client_credentials', body_params['grant_type'], - 'grant_type must be client_credentials (RFC 6749 §4.4.2)' + 'grant_type must be client_credentials (RFC 6749 §4.4.2)' # RFC 6749 §4.4.2: "scope: OPTIONAL." refute_nil body_params['scope'], - 'scope parameter should be present when configured (RFC 6749 §4.4.2)' + 'scope parameter should be present when configured (RFC 6749 §4.4.2)' # RFC 6749 §4.4.2 requires the token request entity-body to use the # application/x-www-form-urlencoded format per Appendix B, and the # section's example request explicitly sets this Content-Type. content_type = captured_auth_request.headers['Content-Type'] - assert_match(/\Aapplication\/x-www-form-urlencoded\b/, content_type, - 'Token request Content-Type should be application/x-www-form-urlencoded (RFC 6749 §4.4.2)') + assert_match(%r{\Aapplication/x-www-form-urlencoded\b}, content_type, + 'Token request Content-Type should be application/x-www-form-urlencoded (RFC 6749 §4.4.2)') # RFC 6749 §2.3.1: "Clients in possession of a client password MAY use # the HTTP Basic authentication scheme [...] The client identifier is [...] # used as the username; the client password [...] used as the password." auth_header = captured_auth_request.headers['Authorization'] assert_match(/\ABasic /, auth_header, - 'Must use HTTP Basic authentication (RFC 6749 §2.3.1)') + 'Must use HTTP Basic authentication (RFC 6749 §2.3.1)') decoded_credentials = Base64.decode64(auth_header.sub('Basic ', '')) client_id, client_secret = decoded_credentials.split(':', 2) assert_equal 'test_client_id', client_id, - 'Basic auth username must be client_id (RFC 6749 §2.3.1)' + 'Basic auth username must be client_id (RFC 6749 §2.3.1)' assert_equal 'test_client_secret', client_secret, - 'Basic auth password must be client_secret (RFC 6749 §2.3.1)' + 'Basic auth password must be client_secret (RFC 6749 §2.3.1)' ensure - verbose, $VERBOSE = $VERBOSE, nil + verbose = $VERBOSE + $VERBOSE = nil Net::HTTP::Post.remove_method(:initialize) $VERBOSE = verbose end @@ -340,9 +341,10 @@ def test_token_request_uses_origin_form_with_custom_oauth_url oauth_url: 'https://auth.us-east-1.fragment.dev/oauth2/token') assert_equal '/oauth2/token', captured_request_paths.first, - 'Custom oauth_url must also use origin-form request-target (RFC 7230 §5.3.1)' + 'Custom oauth_url must also use origin-form request-target (RFC 7230 §5.3.1)' ensure - verbose, $VERBOSE = $VERBOSE, nil + verbose = $VERBOSE + $VERBOSE = nil Net::HTTP::Post.remove_method(:initialize) $VERBOSE = verbose end @@ -434,8 +436,8 @@ def test_token_request_uses_appendix_b_form_encoding_utf8 body_params = URI.decode_www_form(captured_auth_request.body).to_h assert_equal 'client_credentials', body_params['grant_type'] assert_equal oauth_scope, body_params['scope'], - 'scope should round-trip via x-www-form-urlencoded UTF-8 encoding' + 'scope should round-trip via x-www-form-urlencoded UTF-8 encoding' assert_equal client_id, body_params['client_id'], - 'client_id should round-trip via x-www-form-urlencoded UTF-8 encoding' + 'client_id should round-trip via x-www-form-urlencoded UTF-8 encoding' end end