Webmock + HTTParty: stubbed request with real response inside - ruby-on-rails

I have a test, where I test logging.
it "logs out correct messages" do
resp = stub_request(:post, "https://api.sandbox.ebay.com/identity/v1/oauth2/token").and_return(
headers: { "Content-Type" => "application/json" },
body: { access_token: "access_token_value" }.to_json,
)
user = create(:user)
create(:preference, user: user, key: "ebay_refresh_token", string_value: "refresh_token_value")
expect(Rails.logger).to receive(:info).with(/Access token requested for user##{user.id}/)
expect(Rails.logger).to receive(:info).with("Response for user##{user.id} access token request received: #{resp.response.inspect}.")
EbayService.new(user: user).pre_authorize
end
It fails with this error:
Logger received :info with unexpected arguments
expected: ("Response for user#15316 access token request received: #<WebMock::Response:0x00007fe19ec99078)
got: ("Response for user#15316 access token request received: #<HTTParty::Response:0x7fe19dbebac8)
I cannot understand why I keep getting HTTParty::Response instead of WebMock::Response and how can I fix it.
Real http connections are, of course, disabled.
Please, point me in the right direction to think.

Related

Stubbed request not allowing http connections (Real HTTP connections are disabled.)

I am trying to stub a POST request to an external API. The spec test does not replace the ENV variable with my fake one and goes to my local env (localhost:3000) in the stub request returning this error:
Failure/Error: response = http.request(request)
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST http://localhost:3000/Target with body '{"name":"Wayfarer"}' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}
You can stub this request with the following snippet:
stub_request(:post, "http://localhost:3000/Target").
with(
body: "{\"name\":\"Wayfarer\"}",
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: "", headers: {})
My test is:
let!(:user) { create(:user, target: 'Wayfarer') }
before(:each) do
allow(ENV).to receive(:fetch).with('API_METADATA_URL').and_return('http://example.com')
end
describe '#create_target' do
context 'successful API request to create target' do
it 'sends data to API and sets response instance variables' do
target = user.target
stub_request(:post, 'http://example.com/Target').with(
headers: {
},
body: '{
"name": "Wayfarer"
}'
).to_return(
status: 200,
body: '{"id": "uuid",' \
'"name": "Wayfarer",' \
'"created_at": 00000,' \
'"updated_at": 00000}'
)
api_client = ApiClient.new
api_client.create_target(target)
expect(api_client.response_status).to eq(200)
expect(api_client.response_body).to eq(
{
'id' => 'uuid',
'name' => 'Wayfarer',
'created_at' => 00000,
'updated_at' => 00000
}
)
end
end
end
It doesn't even reach the test, instead it seems to run the ApiClient as is including using my local environment variable (as stated above).
I ended up needing a separate .env file for tests (.env.test.local) for the API_METADATA_URL for a url that did not throw the error.

POST request timing out on request to APN server

I'm trying to set up Apple Push Notifications through a Rails back-end. I'm using the NetHttp2 gem to create the connection and jwt to sign my tokens.
Here's a sample of the responses I get when trying various combinations of post requests. I get a quick response if I omit the "authorization" header, and similar if I give it garbage or a fake token. But if I use the actual token that I've encoded, the connection times out. I have no idea what Apple is doing on the back-end - I get no message at all.
[22] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbee8226c90 #body="{\"reason\":\"MissingProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"690A959F-27F0-5B5C-1173-0316D6CE4C91"}>
[23] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { 'authorization' => "bearer garbage", "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbeebbbf060 #body="{\"reason\":\"InvalidProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"8EF335C7-1F05-1D99-5425-CCF200960626"}>
[24] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { 'authorization' => "bearer snwekrwunlsdfu.sdlfknweru.awepi234np2", "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbee7f38f88 #body="{\"reason\":\"InvalidProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"C3B48281-C7F1-22B0-6159-0FD0B79C1D43"}>
[25] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'alert' => 'hello' }", headers: { 'authorization' => "bearer #{token}", "apns-topic" => "something" })
SocketError: Socket was remotely closed
How do I debug this? Is there a way to view what is happening on the Apple server?
If not, has anyone set this up and has some tips? Honestly, I am not at all sure I am properly encoding the JWT token. I am attempting to use my p8 token from Apple and am using the 'ES256' settings but I don't know how to validate it. If I make another request with my token + garbage, it also hangs forever.
Here's the rest of the relevant code:
client = NetHttp2::Client.new("https://api.development.push.apple.com:443",
{ connect_timeout: 100000})
token = JWT.encode({'alg': 'ES256', 'kid': APNS_KEY_ID},
(OpenSSL::PKey::EC.new #p8key), 'ES256',
{'iss': TEAM_ID, 'iat': DateTime.now().to_time.to_i})
After much heartache, the solution
I'm still not totally sure what was going on with Apple, but the answer was with my token encoding. The proper format is:
token = JWT.encode(
{
"iss": TEAM_ID,
"iat": DateTime.now().to_time.to_i
},
private_key,
ALGORITHM,
header_fields= {
"alg": ALGORITHM,
"kid": APNS_KEY_ID
}
)
It seems if you give Apple something with the format authorization => bearer WHATEVER it will hang forever trying to authenticate.
The JWT library is very particular about order and keys (as of 6/29/17).

Issue with JSON request - rails/faraday

I have an issue with rails and faraday when I try to receive access token which should be included in a JSON response from the external Api.
What I want to do is user authentication based on external API.
I assume that the User already has valid credentials (in this case email as username and password).
Now when he connects to my Api, I send JSON request to the external Api to verify whether this user is valid and wait for access token.
Once the access token is sent in a response, user authentication is successful and I have access to the other endpoints
This is my controller
module Api
class AuthenticationController < ApplicationController
def create
client = XXX::AuthClient.new
response = client.authenticate(
email: params[:email],
password: params[:password]
)
api_client = XXX::Client.new(response[:access_token])
if response[:access_token]
api_user = api_client.get_user()
if api_user["id"]
db_user = User.create(xxx_id: api_user["id"], xxx_access_token: response[:access_token])
end
end
render json: { access_token: db_user.access_token }
end
end
end
And this is my AuthClient service
class AuthClient
def initialize
#http_client = Faraday.new('https://auth.xxx.com/')
end
def authenticate(email:, password:)
headers = {
'Content-Type': 'application/json'
}.to_json
body = {
grant_type: "password",
username: email,
password: password,
client_id: "particularclientid",
client_secret: "particularclientsecret"
}.to_json
api_response = http_client.post("/oauth2/token", body)
response = JSON.parse(api_response.body)
if response["access_token"]
{ access_token: access_token }
else
{ error: "autentication error" }
end
end
private
attr_reader :http_client
end
end
What I know is that curl in the following format is correct and I can see User's access token, refresh token etc.
curl -X POST -H "Content-Type: application/json" -d '{
"grant_type": "password",
"username": "test+user#example.com",
"password": "examplepassword",
"client_id": "particularclientid",
"client_secret": "particularclientsecret"
}' "https://auth.xxx.com/oauth2/token"
But when I run my curl
curl -X POST -d 'email=test+user#example.com&password=examplepassword' "http://localhost:3000/api/auth"
I see that my request is not correct. But I have no clue where is the problem because header and body are formatted to JSON (I have entered puts headers, puts body and puts response to verify that).
Started POST "/api/auth" for 127.0.0.1 at 2017-03-31 16:42:26 +0200
Processing by Api::AuthenticationController#create as */*
Parameters: {"email"=>"test user#example.com", "password"=>"[FILTERED]"}
{"Content-Type":"application/json"}
{"grant_type":"password","username":"test user#example.com","password":"examplepassword","client_id":"particularclientid","client_secret":"particularclientsecret"}
{"error"=>"invalid_request", "error_description"=>"The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
Completed 500 Internal Server Error in 610ms (ActiveRecord: 0.0ms)
NoMethodError (undefined method `access_token' for nil:NilClass):
app/controllers/api/authentication_controller.rb:21:in `create'
Is my request incorrect or the problem exists somewhere else?
I am not experienced developer. Just trying to learn enough to start as a Junior RoR. I tried to find a solution on stack and on different sites but I am stucked. Even faraday docs does not help me much
When URIs are escaped, a + is used as a replacement for whitespace. As such, when your controller un-escapes the URI, the + is changed back into a space. If you want to send a space, use %2B instead.
For your first problem, the error message indicates that db_user is nil when you try to do db_user.access_token. So, either response[:access_token] is nil, api_user["id"] is nil, or User.create failed.
You'll need to put in some debugging to find out where your problem is.

How to properly handle token refresh with Spotify SDK and Swift 3. Error Code=3840

tl;dr I'm receiving: JSON text did not start with array or object and option to allow fragments not set. if i'm trying to receive a token and No refresh token available in the session! if I'm trying to renew a token.
I'm trying to setup the token refresh for the Objective-C Spotify iOS SDK beta-25 in Swift 3. I'm using a Heroku Server and the Ruby script provided by Spotify, changed to my credentials.
require 'sinatra'
require 'net/http'
require 'net/https'
require 'base64'
require 'encrypted_strings'
require 'json'
CLIENT_ID = ENV['xxx']
CLIENT_SECRET = ENV['xxx']
ENCRYPTION_SECRET = ENV['xxx']
CLIENT_CALLBACK_URL = ENV['xxx://returnafterlogin']
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")
get '/' do
"Working"
end
post '/swap' do
# This call takes a single POST parameter, "code", which
# it combines with your client ID, secret and callback
# URL to get an OAuth token from the Spotify Auth Service,
# which it will pass back to the caller in a JSON payload.
auth_code = params[:code]
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
request.form_data = {
"grant_type" => "authorization_code",
"redirect_uri" => CLIENT_CALLBACK_URL,
"code" => auth_code
}
response = http.request(request)
# encrypt the refresh token before forwarding to the client
if response.code.to_i == 200
token_data = JSON.parse(response.body)
refresh_token = token_data["refresh_token"]
encrypted_token = refresh_token.encrypt(:symmetric, :password => ENCRYPTION_SECRET)
token_data["refresh_token"] = encrypted_token
response.body = JSON.dump(token_data)
end
status response.code.to_i
return response.body
end
post '/refresh' do
# Request a new access token using the POST:ed refresh token
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
encrypted_token = params[:refresh_token]
refresh_token = encrypted_token.decrypt(:symmetric, :password => ENCRYPTION_SECRET)
request.form_data = {
"grant_type" => "refresh_token",
"refresh_token" => refresh_token
}
response = http.request(request)
status response.code.to_i
return response.body
end
Set by:
SPTAuth.defaultInstance().tokenSwapURL = URL(string: SpotifyCredentials.tokenSwapURLSwap)
SPTAuth.defaultInstance().tokenRefreshURL = URL(string: SpotifyCredentials.tokenSwapURLRefresh)
Now the user is not able to login anymore and I'm receiving the error posted on top. If I'm deleting tokenSwapURL and tokenRefreshURL, everything works again, but the User has to re-auth every 60 minutes.
If I'm trying to refresh the Token with an already logged in user, I receive:
"No refresh token available in the session!"
if SPTAuth.defaultInstance().session != nil {
print("needs login")
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session, callback: { error, session in
if error != nil {
print("\(error?.localizedDescription)") // "No refresh token available in the session!"
return
}
})
}
What am I missing? Help is very appreciated.
I have been able to create a token refresh service for Spotify with the following Git:
https://github.com/adamontherun/SpotifyTokenRefresh
All you need to do is to follow the instructions of the Heroku link within the git project.
I have tried to get in contact with the author of the project, but he wasn't able to tell me, why my approach wasn't working but his is. All I can leave you with is this working Deploy to Heroku link.
Short summary: assuming your JSON parsing works properly, the problem is malformed JSON (server-side).
JSON text did not start with array or object and option to allow fragments not set
can be thrown by 's JSONSerialization.jsonObject(with:options:) which returns
A Foundation object from the JSON data in data, or
nil if an error occurs.
The malformed JSON » nil instead of token » current token gets nilled » result: "the user is not able to login anymore"
Possible explanations for getting malformed JSON include:
It usually is because of some warning message throwing out from your server without putting it in the response array. For example in PHP, some "warning messages" are not caught in your array so that when you finally use "echo json_encode($RESPONSE_ARR)," it is not a JSON format.
— https://stackoverflow.com/a/38680699/3419541
From the same SO page:
You need to debug this in the iOS application. First convert the data to a string and print that and check it. If the string looks alright then print the data itself - sometimes people manage to add 0 bytes or control characters, or two byte order markers or something similar which are invisible in the string but are not legal JSON. — https://stackoverflow.com/a/38681179/3419541

how to access this kind of hash

I am using RestClient to make a post request and i made it so i an error response back so i can print those error messages in console
i tried the following per the restclient gem documentation
begin
response = RestClient.post base_uri, params.to_json, content_type: 'application/json', accept: 'application/json'
rescue RestClient::ExceptionWithResponse => err
error = err.response
p "this is the error response #{error}"
end
when i print err.response i get the following
"this is the error response {\"error\":{\"message\":\"An active access token must be used to query information about the current us
er.\",\"type\":\"OAuthException\",\"code\":2500,\"fbtrace_id\":\"HTzmJ0CcIfd\"}}"
how do i access the message in the above hash to display it in console?
tried
p "this is the error response #{error.message}"
and it gives me "Bad request" - have no idea where it gets that
If you're just looking to output it:
error = JSON.load(err.response)
puts error['error']['message']
You can always format it a bit better:
puts '[Code %d %s] %s' % [
error['error']['code'],
error['error']['type'],
error['error']['message']
]
Note that using puts inside of a Rails process is not going to work very well. You might want to use Rails.logger.debug instead.
The response you received is in JSON. You'll need to decode the JSON first and then interact with the data. Personally, I like MultiJson for this:
begin
response = RestClient.post base_uri, params.to_json, content_type: 'application/json', accept: 'application/json'
rescue RestClient::ExceptionWithResponse => err
error = MultiJson.load(err.response)
p "this is the error response #{error[:message]}"
end

Resources