Closed JWesorick closed 10 years ago
I'm guessing ActionController::TestRequest
must be changing something along the way. I honestly don't know off hand where you'd have to call ApiAuth.sign!
so that ActionController::TestRequest
works with it. Let me know if you end up digging into it and figuring it out.
Depending on what you're trying to test though, it might make sense to stub out the authentication check instead of trying to sign the request headers. For example you could do something like controller.stub(api_authenticate: true)
or ApiAuth.stub('authentic?' => true)
if you're just to pass the authentication to test other things.
So for now I'm using
controller.class.skip_before_filter :api_authenticate
and I'll continue to work on figuring out how to test that the api_authenticate
method is working.
Great. Ideally the api_auth specs should verify that authentication is working correctly. An individual app would only need to test that the app is calling to ApiAuth correctly / at the right time.
We worked around this by mocking the 'fullpath' attribute of the TestRequest object. The problem was the request_uri was not present when #sign! was called, but it was present when #authentic? was called. So the hash being built was different on both sides. The content_type also needs to be there before calling #sign! in the test. Hope this helps.
# controller_path is whatever :index routes to. e.g. '/home'
allow_any_instance_of(ActionDispatch::TestRequest).to receive(:fullpath).and_return(controller_path)
request.env["HTTP_ACCEPT"] = "application/json"
ApiAuth.sign!(request, 'my_access_id', ApiConfig.secret_key)
get :index
Awesome, that works. Thanks!
How can I use it in specs with type request, where object request is missing?
@kjg do you know the answer to above? I too am running Rails 5.1 and trying to write rspec tests for my API which I just secured with ApiAuth, but the request tests do no have direct access to the request object. It is possible we could sign manually, but I am not loving that idea...
If anyone runs into this, I solved this by creating a custom API helper (don't forget to include in your spec_helper.rb file).
Here is my custom /spec/support/request_helper.rb
file
module Requests
module ApiHelpers
include Rack::Test::Methods
def json
JSON.parse(last_response.body).deep_symbolize_keys
end
def result
json[:result]
end
def api_request(api_account, path, type = :get, data = nil)
sign_request_raw(api_account.id, api_account.secret_key, path, type, data)
case type
when :get
get path
when :put
put path, data
when :post
post path, data
when :delete
delete path
else
raise 'Unknown method'
end
expect(json.has_key?(:messages)).to be_truthy
expect(json[:messages].keys).to match_array [:info, :warn, :error]
if last_response.status >= 200 && last_response.status < 300
expect(json.has_key?(:result)).to be_truthy
end
last_response
end
private
def sign_request_raw(id, secret_key, path, type, data)
allow_any_instance_of(ActionDispatch::TestRequest).to receive(:fullpath).and_return(path)
timestamp = Time.now.utc.httpdate
content_type = "application/json"
content_md5 = ''
header "ACCEPT", content_type
header "CONTENT_TYPE", content_type
header "DATE", timestamp
unless data.nil?
content_md5 = Digest::MD5.hexdigest(data)
header "CONTENT_MD5", content_md5
end
c_string = canonical_string(type, content_type, content_md5, path, timestamp)
digest = OpenSSL::Digest.new('sha1')
sig = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret_key, c_string))
header "AUTHORIZATION", "APIAuth #{id}:#{sig}"
end
def canonical_string(request_method, content_type, content_md5, path, timestamp)
[request_method.upcase,
content_type,
content_md5,
path,
timestamp].join(',')
end
end
end
At the end of your spec_helpers.rb file add this: config.include Requests::ApiHelpers, type: :request
I'm using RSpec in a Rails app. Right now I'm signing the headers just before the reqeust (get :index) but it appears the headers are getting changed somewhere. Where can I put the call to ApiAuth.sign! to get it to work?
Also here is what I have in my before(:each).