nov / openid_connect

OpenID Connect Server & Client Library
MIT License
417 stars 122 forks source link

webmock 3.18.1 and openid_connect 2.2.0 are not compatible #80

Closed dmlond closed 1 year ago

dmlond commented 1 year ago

I am using rspec and webmock to unit test a class that uses openid_connect to interact with our institutional openid connect system for oauth authentication. I have a test that is failing, and I cannot figure out how to make it pass.

The class constructor takes an access token, and then uses the following code to authenticate the user, and store the userinfo in an accessor attribute on the instance.

client = OpenIDConnect::Client.new(
      identifier: ENV['OIDC_CLIENT_ID'],
      host: ENV['OIDC_HOST'],
      userinfo_endpoint: '/oidc/userinfo'
)
oidc = OpenIDConnect::AccessToken.new(
  access_token: access_token,
  client: client
)
ui = oidc.userinfo!
@user_info = UserInfo.new ui.raw_attributes

Our unit test uses webmock to mock this call with a predictable response body in json format

subject { described_class.new(access_token: access_token) }
let(:access_token) { SecureRandom.hex }
before(:example) do
  expect(subject.access_token).to eq(access_token)
  stub_request(:get, "https://#{auth_host}/oidc/userinfo")
    .with(headers: { 'Authorization' => "Bearer #{access_token}" })
    .to_return(status: response_status, body: response_body.to_json, headers: {})
end
let(:access_token) { auth_access_token }
let(:response_status) { 200 }
let(:response_body) do
  {
    sub: sub,
    dukeNetID: dukeNetID,
    dukeUniqueID: dukeUniqueID,
    name: Faker::Name.name,
    email: Faker::Internet.email
  }
end
it { expect(call).to eq(true) }

This fails

NoMethodError:
       undefined method `with_indifferent_access' for "{\"sub\":\"lula@bashirian.co\",\"dukeNetID\":\"pattie.jerde093\",\"dukeUniqueID\":\"0226088785\",\"name\":\"Angella Boyle\",\"email\":\"vanetta@pacocha.co\"}":String

               res.body.with_indifferent_access
                       ^^^^^^^^^^^^^^^^^^^^^^^^

I tried changing our response to a ActiveSupport::HashWithIndifferentAccess

before(:example) do
  expect(subject.access_token).to eq(access_token)
  stub_request(:get, "https://#{auth_host}/oidc/userinfo")
    .with(headers: { 'Authorization' => "Bearer #{access_token}" })
    .to_return(status: response_status, body: response_body, headers: {})
end
let(:response_body) do
  ActiveSupport::HashWithIndifferentAccess.new(
    sub: sub,
    dukeNetID: dukeNetID,
    dukeUniqueID: dukeUniqueID,
    name: Faker::Name.name,
    email: Faker::Internet.email
  )
end

This fails too

     Failure/Error:
       stub_request(:get, "https://#{auth_host}/oidc/userinfo")
         .with(headers: { 'Authorization' => "Bearer #{auth_access_token}" })
         .to_return(status: response_status, body: response_body, headers: {})

     WebMock::Response::InvalidBody:
       must be one of: [Proc, IO, Pathname, String, Array]. 'ActiveSupport::HashWithIndifferentAccess' given

access_token.rb line 31 wants calls res.body.with_indifferent_access, but this method is only available on an ActiveSupport::HashWithIndifferentAccess, which is really only available in rails, and webmock does not allow one to be stubbed as a respone.

dmlond commented 1 year ago

I got this to work, so I closed the issue but this is here for posterity in case someone else encounters this. I had to return a json response, and add 'Content-Type' => 'application/json' to my stubbed response headers to make it work correctly

stub_request(:get, "https://#{auth_host}/oidc/userinfo")
  .with(headers: { 'Authorization' => "Bearer #{auth_access_token}" })
  .to_return(status: response_status, body: response_body.to_json, headers: {'Content-Type' => 'application/json'})