JSON response_body generated by rspec_api_documentation gem is empty (Rails) - ruby-on-rails

I've successfully used rspec_api_documentation gem to generate docs for some other resources in an application.
But when I do the same for a new resource, the JSON file's response_body is empty (though the rest of the JSON exists). And I'm unsure why this is happening.
response_body in generated JSON:
"response_body": "{\n \"data\": {\n \"notes\": [\n\n ]\n },\n \"status\": 200,\n \"message\": \"Ok\"\n}"
Outputed API docs:
{
"data": {
"notes": [
]
},
"status": 200,
"message": "Ok"
}
What I've done:
Tested the endpoint with a curl and can confirm it returns a full response (now I'm just trying to build the documentation)
Created note_factory.rb
Added #notes = create_list(:note, 1,...) in a before(:all) do block at beginning of notes_spec.rb (and printed to console to confirm it creates an object)
Also created below as the test.
get '/api/v1/notes' do
example 'Return all notes' do
explanation 'Return all notes within an account.'
set_jwt_auth_headers(#api_reader)
do_request
expect(status).to eq 200
end
end
I'm confused what I might be missing that causes the JSON response related to RAD to be empty.
Does anyone have any ideas what might cause this?

Related

Stub Httparty call: Wrong number of arguments (given 2, expected 1)

I created a simple ruby file (not Rails) and I am trying to test (using Rspec) a method where I am calling an API. In the test I am trying to mock the call via WebMock but it keeps giving me this error:
Requests::FilesManager#display fetches the files from the API
Failure/Error: Requests::FilesManager.new.display
ArgumentError:
wrong number of arguments (given 2, expected 1)
The files are:
#run.rb
module Requests
require "httparty"
require 'json'
class FilesManager
include HTTParty
def initialize
end
def display
response = HTTParty.get('https://api.publicapis.org/entries', format: :json)
parsed_response = JSON.parse(response.body)
puts "The secret message was: #{parsed_response["message"]}"
end
end
end
and the spec file:
require 'spec_helper'
require_relative '../run'
RSpec.describe Requests::FilesManager do
describe "#display" do
it 'fetches the files from the API' do
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: "", headers: {})
Requests::FilesManager.new.display
end
end
end
EDIT:
So the error seems to come from the line:
JSON.parse(response.body)
If I comment it out it disappears. The problem then is that the output of the call is not a json (even with the format: :json when calling the HTTParty). I tried other solutions but nothing seems to work in making the response json. It is just a string.
Change
response = HTTParty.get('https://api.publicapis.org/entries', format: :json)
to
response = HTTParty.get('https://api.publicapis.org/entries').
I think you don't need the format: :json more so when you explicitly format the response to JSON anyway.
You need to return a json object in the body parameter of the stubbed response:
E.g: For an empty response:
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: "".to_json, headers: {})
OR For a valid response: (Note: You may have to require json to convert a hash to json)
require 'json'
...
stub_request(:get, "https://api.publicapis.org/entries").
to_return(status: 200, body: { entries: { '0': { message: "Hello World" } } }.to_json, headers: {})
Solved!
It seems there was an error because the json gem version that HTTParty uses is too old.
Moved on to RestClient gem for the RESTful API calls. It had another conflict in the mime gem versioning.
Finally moved to Faraday and that solved my problems:
JSON.parse(response.body, :quirks_mode => true)
tl;dr Had the same issue and ended up having to upgrade webmock.
Long form:
Webmock inserts middleware into your calls, so when HTTParty makes the calls they end up going through the Webmock interfaces first.
You can verify this by trying the call standalone (withouth all the rspec config):
bundle console
irb> require "httparty"
=> true
irb> httparty.get("https://google.com")
If that standalone call succeeds, the issue is somewhere within Webmock itself.
For me, somewhere along the line of calls through Webmock was an outdated interface that was incompatible and throwing the Wrong Number of Arguments error. And this was also crashing my debugger (RubyMine).
Upgrading Webmock solved this issue (because they had fixed it in newer versions).

Sendgrid v3 not working for me with rails my_mailer.rb

I want to send a transactional mail via Sendgrid when a user registers (I use devise for authentication). I had this working fine in my_mailer.rb using SMTP as follows:
def confirmation_instructions(record, token, opts={})
# SMTP header for Sendgrid - v2
# headers["X-SMTPAPI"]= {
# "sub": {
# "-CONFIRM_TOKEN-": [
# token
# ]
# },
# "filters": {
# "templates": {
# "settings": {
# "enable": 1,
# "template_id": "1111111-1111-1111-1111-111111111111"
# }
# }
# }
# }.to_json
However, prompted by Sendgrid to use v3 syntax to support newer mail templates, I changed code to the following (from the sendgrid help docs, as opposed to a real understanding):
def confirmation_instructions(record, token, opts={})
require 'sendgrid-ruby'
include SendGrid
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
data = JSON.parse('{
"substitutions": {
"-CONFIRM_TOKEN-": [
token
],
"template_id": "1111111-1111-1111-1111-111111111111"
}')
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.parsed_body
puts response.headers
Now I get the error message:
'NoMethodError (undefined method `include' for #<MyMailer:0x0000000003cfa398>):'
If I comment out the 'include' line I get:
'TypeError (no implicit conversion of nil into String):' on the line: "sg = SendGrid..."
I use the Gem: sendgrid-ruby (5.3.0)
Any ideas would be appreciated - I've been trying to hit on the correct syntax by trial-and-error for a while now and finally admit I am stuck.
UPDATE#1:
The first issue was I was using the wrong API_KEY env. variable (copied from 2 different help docs): "SENDGRID_API_KEY" (in code) vs. SENDGRID_APIKEY_GENERAL (set in Heroku). Fixed.
UPDATE #2:
With the "include" line commented out I now seem to be getting a JSON parse error:
JSON::ParserError (416: unexpected token at 'token
So my 2 current issues are now:
(1) I would like 'token' to be the confirmation token variable but it is not being passed
(2) Sending the below simple (1 line) content of 'data' does not throw up an error, but the appropriate template within Sendgrid is not selected:
data = JSON.parse('{
"template_id": "1111111-1111-1111-1111-111111111111"
}')
UPDATE #3:
Here's an update on the status of my issue and exactly where I am now stuck:
This code works fine (using Sendgrid v2 which I am trying to upgrade from):
def confirmation_instructions(record, token, opts={})
#
# SMTP header for Sendgrid - v2
# This works fine
#
headers["X-SMTPAPI"]= {
"sub": {
"-CONFIRM_TOKEN-": [
token
]
},
"filters": {
"templates": {
"settings": {
"enable": 1,
"template_id": "1111111-1111-1111-1111-111111111111"
}
}
}
}.to_json
This Sendgrid v3 code does not work (the email does get sent via Sendgrid but it does not select the template within Sendgrid - it just uses whatever code is in app/views/my_mailer/confirmation_instructions.html.erb):
#
# Sendgrid API v3
# This sends an email alright but it takes content from app/views/my_mailer/confirmation_instructions.html.erb
# It DOES NOT select the template within Sendgrid
#
data = JSON.parse('{
"template_id": "1111111-1111-1111-1111-111111111111",
"personalizations": [
{
"substitutions": {
"-CONFIRM_TOKEN-": "'+token+'"
}
}
]
}')
sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL2'])
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.parsed_body
puts response.headers
As always, any insight appreciated.
For anyone trying to get SendGrid v3 API working with Ruby/Devise/Heroku and use SendGrid's dynamic transactional emails these tips may help you. I spent a week getting this to work and these steps (& mistakes I made) were not apparent in the various documentation:
Generating the SendGrid API key (on SendGrid website): when generating the key, the API key only appears once allowing you to copy it, from then on it is invisible. As I could not see the key later I mistakenly used the "API Key ID" in my Heroku environment variables, rather than the true API Key.
Ensure the name you give the key in Heroku (for me: "SENDGRID_APIKEY_GENERAL") matches the code you use to reference it i.e. sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL'])
For sending variables to be substituted in the template use "dynamic_template_data" and not "substitutions". This should be within the "personalizations" section (see code example below).
I found it useful to refer to the Sendgrid dynamic template ID by using an environment variable in Heroku (for me: 'SENDGRID_TRANS_EMAIL_CONFIRM_TEMPLATE_ID') as opposed to hard-coding in Ruby (just allowed me to experiment with different templates rather than changing code).
The correct syntax for using a variable in the JSON string in Ruby is e.g. "CONFIRM_TOKEN": "'+token+'" (see code example below)
Do not use other characters in the name: i.e. "CONFIRM_TOKEN" worked but "-CONFIRM_TOKEN-" did not work
In the HTML of the transactional email template on SendGrid use this syntax for the substitution: {{CONFIRM_TOKEN}}
When creating a transactional template on SendGrid you can only have a 'design' view or a 'code' view not both. You must select at the start when creating the template and cannot switch after.
In the devise confirmations_instructions action refer to the user as a record (e.g. email) as record.email
Gemfile: gem 'rails', '5.2.2' ruby '2.6.1' gem 'devise', '4.6.1' gem 'sendgrid-ruby', '6.0.0'
Here is my successful ruby code that I have in my_mailer.rb:
def confirmation_instructions(record, token, opts={})
data = JSON.parse('{
"personalizations": [
{
"to": [
{
"email": "'+record.email+'"
}
],
"subject": "Some nice subject line content",
"dynamic_template_data": {
"CONFIRM_TOKEN": "'+token+'",
"TEST_DATA": "hello"
}
}
],
"from": {
"email": "aaaa#aaaa.com"
},
"content": [
{
"type": "text/plain",
"value": "and easy to do anywhere, even with Ruby"
}
],
"template_id": "'+ENV['SENDGRID_TRANS_EMAIL_CONFIRM_TEMPLATE_ID']+'"
}')
sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL'])
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.headers
You cannot include a module in a method. You have to include it in your class, so outside of the methode, like
class SomethingMailer
require 'sendgrid-ruby'
include SendGrid
def confirmation_instructions(record, token, opts={})
...
end
end
For your third update problem:
You are not sending a JSON but instead you are creating a JSON, then parsing it into a hash, then sending that hash, instead of the JSON.
JSON.parse #parses a JSON into a Hash
You should do the opposite and have a hash that you transform into a JSON
Something like
data = {
template_id: your_template_id # or pass a string
personalizations: [
...
]
}
Then you call
data_json = data.to_json
response = sg.client.mail._("send").post(request_body: data_json)
However this does not explain why your template in app/views/my_mailer/confirmation_instructions.html.erb gets sent. So I think you are either calling a different mailer_method at the same time, or you are not calling your actual confirmation_instructions method at all. Try to confirm that you SendGrid Methods actually is called and see what it returns. It should have returned some kind of error before, when you were sending a hash instead of a string.

expected 422 got 200 Cucumber with rails

Hi i am working on a ROR project with ruby-2.5.1 and rails 5. I am using cucumber in my rails app to test api i am new with cucumber. when i am trying to define feature for invalid data i am getting the error expected 422 got 200.
my feature file:
Feature: Registration Endpoint
Scenario: User registration
Given an application with application_id "1"
When the client make a valid POST /registartions request with application_id: "1"
Then response should have status 200
Scenario: using blank application id
When the client make a POST /registartions request with blank application-id
Then response should have status 422 and JSON:
"""
{ "error": "application_id does not exists" }
"""
my steps file:
Given("an application with application_id {string}") do |string|
string
end
When("the client make a valid POST \/registartions request with application_id: {string}") do |string|
params = {
"data":{
"type":"users",
"attributes":{
"email": "s2#gmail.com",
"password":"password",
"password-confirmation":"password"
}
}
}
header 'application-id', "#{string}"
post '/api/registrations', params
end
Then("response should have status {int}") do |int|
expect(last_response.status).to be(int)
end
When("the client make a POST \/registartions request with blank application-id") do
params = {
"data":{
"type":"users",
"attributes":{
"email": "s2#gmail.com",
"password":"password",
"password-confirmation":"password"
}
}
}
header 'application-id', ''
post '/api/registrations', params
end
Then("response should have status {int} and JSON:") do |int, string|
expect(last_response.status).to be(int)
end
Please help me to fix this issue i am writting this cucumber first time so i don't have the idea how to test with invalid data. Please help me. Thanks in advance.
It looks like you may have found a bug in your application.
If it is meant to respond with a 422 "Unprocessable Entity" response when you don't include the application id, and it's responding with a 200 (OK), then that would seem like the system under test has an issue.

How can I handle generic errors with a JSON response in a hybrid (JSON API + HTML) Rails 5 app?

I spent one day trying several approaches, but still haven't quite got there so decided to ask now...
I have a Rails 5 app which is mainly a JSON API (using the actual JSON API specs), but also a "normal" Rails app with transactional emails and account related pages (reset password, etc).
What I'd like to achieve is that Rails always returns a JSON response with some meaningful error response to all API calls, rather than the default HTML error page or a header only 400 error.
The main cases I'm trying to handle are JSON parsing issues and Ruby exceptions (500 errors).
I tried:
using rescue_from on the ActionController level – seems the framework handles these exceptions before they would reach the controller
Handling them on the Rack level with a middleware – this worked in test but not in dev despite setting consider_all_requests_local to false in both
Registering a new Mime-type and a parser as JSON API Resources gem does it – looked promising, but the parser code is never hit
I'm really at my wit's end, something which sounded so simple ended up being deceptively complicated with me trying to hunt down where are these exceptions get handled in the framework without much success...
Well I managed to work it out in the end, so thought I should share what worked.
What I missed before is that I had to fiddle a bit with MIME types so that Rails would understand and properly use JSON API:
config/initializers/mime_types.rb
JSON_API_MIME_TYPES = %w[
application/vnd.api+json
text/x-json
application/json
].freeze
Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, JSON_API_MIME_TYPES
Mime::Type.register_alias 'application/json', :json, %i[json_api jsonapi]
After this I could finally handle 500 errors in the base controller:
rescue_from StandardError,
with: :render_standard_error
def render_standard_error
render json: {
status: 500,
error: 'Unhandled error',
message: 'An unexpected error occurred.'
}, status: :internal_server_error
end
Then for handling JSON parse errors, this was the solution:
app/middleware/catch_json_parse_errors
class CatchJsonParseErrors
def initialize(app)
#app = app
end
def call(env)
#app.call(env)
rescue ActionDispatch::Http::Parameters::ParseError => error
if JSON_API_MIME_TYPES.include?(env['CONTENT_TYPE']) ||
JSON_API_MIME_TYPES.include?(env['HTTP_ACCEPT'])
return [
400, { 'Content-Type' => 'application/json' },
[{
status: 400,
error: 'JSON parse error',
message: "There was a problem in the JSON you submitted: #{error}"
}.to_json]
]
else
raise error
end
end
end
config/application.rb
require './app/middleware/catch_json_parse_errors'
...
config.middleware.use CatchJsonParseErrors

How to test posts in Rails / Capybara / Cucumber or Rspec

I'm using rspec, cucumber and capybara and I'm looking for a way to test that a malicious user can't hack a form then post to an url he/she doesn't have permission to. I have my permissions set up in cancan such that this "should" work, however, the only way I can test it is by hacking a form myself.
How can I automate this sort of testing? With webrat I could do this in a unit test with rspec with something like
put :update, :user_id => #user.id, :id => #user_achievement.id
response.should contain("Error, you don't have permission to access that!")
In capybara, however, visit only does get's it seems. I can't find a way to do this, I've googled everwhere.
Any help would be much appreciated,
Thanks
I think you can do this with rack-test
https://github.com/brynary/rack-test
in your Gemfile:
gem 'rack-test'
in your env.rb file
module CapybaraApp
def app; Capybara.app; end
end
World(CapybaraApp)
World(Rack::Test::Methods)
step defintions somewhere:
When /^I send a POST request to "([^"]*)"$/ do |path|
post path
end
Most of what I learned came from here: http://www.anthonyeden.com/2010/11/testing-rest-apis-with-cucumber-and-rack-test
UPDATE: I think you can skip the changes to your env.rb file with newer versions of Rails and/or Cucumber (not sure which, I just don't do that part on my newer projects and it works fine)
Same as #Josh Crews I've largely based this off of: http://www.anthonyeden.com/2010/11/testing-rest-apis-with-cucumber-and-rack-test/#comment-159. But there are two notable exceptions: 1) I test the actual response body, 2) I demonstrate how to test a POST request. Here's an example using Rails 3.0.9:
Steps:
# features/step_definitions/api_step.feature
When /^I send a GET request to "([^\"]*)"$/ do |url|
authorize(User.last.email, "cucumber")
header 'Accept', 'application/json'
header 'Content-Type', 'application/json'
get url
end
When /^I send a POST request to "([^\"]*)" with:$/ do |url, body|
authorize(User.last.email, "cucumber")
header 'Accept', 'application/json'
header 'Content-Type', 'application/json'
post url, body
end
Then /^the JSON response should have (\d+) "([^\"]*)" elements$/ do |number_of_children, name|
page = JSON.parse(last_response.body)
page.map { |d| d[name] }.length.should == number_of_children.to_i
end
Then /^I should receive the following JSON response:$/ do |expected_json|
expected_json = JSON.parse(expected_json)
response_json = JSON.parse(last_response.body)
response_json.should == expected_json
end
Then /^I should receive the following JSON object response:$/ do |expected_json|
expected_json = JSON.parse(expected_json)
response_json = JSON.parse(last_response.body)
if expected_json['id'] == 'RESPONSE_ID'
expected_json['id'] = response_json['id']
end
response_json.should == expected_json
end
Feature:
# features/api/some_feature.feature
Feature: Users API
Background:
Given the following users exist:
| id | name |
| 1 | Joe |
| 2 | Sue |
| 3 | Paul |
Scenario: Index action
When I send a GET request to "/users/"
Then the JSON response should have 3 "user" elements
And I should receive the following JSON response:
"""
[
{
"id":1,
"name":"Joe"
},
{
"id":2,
"name":"Sue"
},
{
"id":3,
"name":"Paul"
}
]
"""
Scenario: Create action
When I send a POST request to "/users/" with:
"""
{
"name":"Polly"
}
"""
Then I should receive the following JSON object response:
"""
{
"id":"RESPONSE_ID",
"name":"Polly"
}
"""
And I send a GET request to "/users/"
And the JSON response should have 4 "user" elements

Resources