How to test controller methods that use HEAD in Phoenix - testing

Currently the documentation is clear and vibrant with the common HTTP verbs, but we started implementing some HEAD routes today and it is not tested the same way as the others.
To test say a GET method:
conn = get conn, controller_path(conn, :controller_method, params)
So I would assume you would just change get to head, but this is not the case.
Here is my route:
template_journeys_count_path HEAD /v1/templates/:template_id/journeys GondorWeb.V1.JourneyController :count
and my controller method:
def count(conn, %{"template_id" => template_id}) do
count = Templates.get_journey_count(template_id)
conn
|> put_resp_header("x-total-count", count)
|> send_resp(204, "")
end
and my test:
conn = head conn, template_journeys_count_path(conn, :count, template.id)
assert response(conn, 204)
But I am getting an error saying no response received and the resp_header that I added what not in conn.resp_headers
Am I missing something? I also tried to set up a connection build using Plug.ConnTest's method build_conn passing the HEAD method into it but still no luck.

OK after more reading and testing using postman. Phoenix will automatically change HEAD requests into GET requests, there for when phoenix was looking for my route in the router, it was hitting the get route that matched the path with was my :index method.
For HEAD routes:
the verb in the router must be a get, ex: get '/items', :index
if you want to share a path, just add the put_resp_header on your returned connection in the controller method, only the header will be sent in the response
It is ok that the response code is not a 204, as per the w3c doc's HEAD requests can have a 200 response
testing a HEAD request, you can just change a the get to a head and test the response_headers and that no body was sent.
To show my changes... here is my router:
get "/journeys", JourneyController, :index
my controller method:
def index(conn, %{"template_id" => template_id}) do
journeys = Templates.list_journeys(template_id)
conn
|> put_resp_header("x-total-count", "#{Enum.count(journeys)}")
|> render("index.json", journeys: journeys)
end
and my test:
test "gets count", %{conn: conn, template: template} do
conn = head conn, template_journey_path(conn, :index, template.id)
assert conn.resp_body == ""
assert Enum.at(get_resp_header(conn, "x-total-count"), 0) == "1"
end

Related

Ajax POST request not hitting Elixir router path

I have this redux-observable epic which does a POST ajax request using RxJS.ajax.post and I don't think it is hitting my Elixir router properly as nothing is happening on my elixir backend. I am doing get requests to get categories correctly and in the same manner so I am hitting other paths in my Elixir router correctly. I am expecting the issue to be with my backend Elixir code not my frontend. I might need to change my path in router.ex.
When I press a button on the frontend, this object is what gets sent to the elixir backend (it dispatches this action with a product as the payload and hits the redux-observable epic below):
onPress = {() => {
props.uploadProduct({
name: props.name,
brand: props.brand,
description: props.description,
image: props.image
})
The epic:
import { ajax } from 'rxjs/observable/dom/ajax'
import { Observable } from 'rxjs'
export const uploadProductEpic = action$ =>
action$.ofType(UPLOAD_PRODUCT)
.mergeMap(action => {
ajax.post('http://localhost:4000/products/', action.payload)
})
.map(response => uploadProductFulfilled(response))
.catch(error => Observable.of(
uploadProductRejected(error))
)
the elixir router:
defmodule Api.Router do
use Plug.Router
if Mix.env == :dev do
use Plug.Debugger
end
plug :match
plug :dispatch
get "/categories/" do
Api.Repo.getCategories(conn)
end
post "/products/:product" do
IO.puts inspect conn
Api.Repo.insertProduct(conn, product)
end
end
IO.puts inspect conn doesn't log anything. So My Elixir router path post "/products/:product" do is not being hit by my POST request. What am I doing wrong?
This is the elixir function in repo.ex that I HOPE will insert the product into my database:
def insertProduct(conn, product) do
product = %Api.Product{name: product.name, brand: product.brand, description: product.description, image: product.image, rating: 0, numberOfVotes: 0}
changeset = Api.Product.changeset(product)
errors = changeset.errors
valid = changeset.valid?
case insert(changeset) do
{:ok, product} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Poison.encode!(%{
successs: "success"
}))
{:error, changeset} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Poison.encode!(%{
failure: "Errors"
}))
end
end
I am a frontend developer just trying to get into Elixir so any guidance and patience is appreciated. Thanks.
Your data is sent in the body of the request, not in the URL, so the route should be:
post "/products"
You'll also need to plug in a JSON parser after plug :match and before plug :dispatch, as described in the Parameter Parsing section in the documentation of Plug.Router:
plug :match
plug Plug.Parsers, parsers: [:json],
pass: ["application/json"],
json_decoder: Poison
plug :dispatch
The JSON data will now be present in conn.body_params, which you can send to your function:
post "/products" do
Api.Repo.insertProduct(conn, conn.body_params)
end
And finally, the keys in the JSON would be strings, so you need to use the bracket syntax to access them instead of dots:
product = %Api.Product{name: product["name"], brand: product["brand"], description: product["description"], image: product["image"], rating: 0, numberOfVotes: 0}
I'm not sure how you've defined Api.Product.changeset, but the default Phoenix convention defines a 2 arity function which calls cast and extracts the data from a map itself. If you're doing the same, you can do this instead:
changeset = Api.Product.changeset(%Api.Product{}, product)

Rails 5 RC1 - POST attributes not available in params

I'm sending a POST request to a Rails 5 RC1 api to create a new vote record. I can see from request.raw_post that the attributes are included in the POST request, but they do not make it into params. I've attempted to permit the attributes with no success.
# article_votes_controller.rb
def article_vote_params
logger.debug "### hello from the params function"
logger.debug request.raw_post
params.permit(:user_id, :user_type, :article_id, :article_type)
logger.debug params.inspect
logger.debug params.to_unsafe_h
end
The above code gives the following output.
### hello from the params function
{"user_id":"1","user_type":"User","article_id":"99","article_type":"Article"}
<ActionController::Parameters {"controller"=>"article_votes", "action"=>"create"} permitted: true>
{"controller"=>"article_votes", "action"=>"create"}
Why wouldn't attributes that are included in the request be included in params?
IIRC the permit method returns a new instance of params, so if you did
p = params.permit(:user_id, :user_type, :article_id, :article_type)
logger.debug p.inspect
You would see the correctly, whitelisted params.

how to use get method in groovyx.net.http.RESTClient properly

I'm trying to get JSON file via get method in RESTClient.
Right now I'm trying
def url = 'http://urlurlurl'
def username = 'username'
def password = 'password'
def restClient = new RESTClient(url)
restClient.auth.basic(username, password)
render restClient
When I see what I get from restClient, is just prints
'groovyx.net.http.RESTClient#65333e2e'
Which is hard to understand.
Given that the url is a endpoint of a API get method, and contains JSON file, how can I retrieve JSON file so I can parse it and use that JSON file?
Also I'm trying this too
def url = 'http://urlurlurl'
def username = 'username'
def password = 'password'
def restClient = new RESTClient(url)
restClient.auth.basic(username, password)
//Adding get method
def jsonData = restClient.get(/* what value should I put in here?? */)
This gives me a forbbiden error that says:
Error 500: Internal Server Error
URI: JsonRender
Class: groovyx.net.http.HttpResponseException
Message: Forbidden
Any suggestions? Examples that uses get method in RESTClient will be nice.
The url should be the base url for your api. For example if we want to search some data from an api which complete url is http://localhost:9200/user/app/_search. So, we have base url as http://localhost:9200/ and the path to api is user/app/_search. Now the request looks like this
def client = new RESTClient( 'http://localhost:9200/' )
def resp = client.get( path : 'user/app/_search')
log.debug (resp.getContentAsString())
Hope this will work out.
Thanks,

Sinatra app as rack middleware TimesOut Rails 3

While in the Rails development environment, I am attempting to add a Sinatra app as a middleware. The Sinatra app uses the geoip gem that processes a user's ip address and returns json with their city.
I can view the returned json by going directly to the example url in the browser or using curl in the command line, http://local.fqdn.org/geoip/locate.json?ip=24.18.211.123. However when I attempt to call the url with wget from within a Rails controller, the Rails app stops responding often crashing my browser and my rails server wont exit using the control+C command.
Any clue to what is happening here? Why would going directly to the url in the browser return the proper response but my call in the controller results in a Time Out?
sinatra-geoip.rb
require 'sinatra'
require 'geoip'
require 'json'
# http://localhost/geoip/locate.json?ip=24.18.211.123
#
# {
# latitude: 47.684700012207
# country_name: "United States"
# area_code: 206
# city: "Seattle"
# region: "WA"
# longitude: -122.384803771973
# postal_code: "98117"
# country_code3: "USA"
# country_code: "US"
# dma_code: 819
# }
class GeoIPServer < Sinatra::Base
get '/geoip/locate.json' do
c = GeoIP.new('/var/www/mywebsite.org/current/GeoLiteCity.dat').city(params[:ip])
body c.to_h.to_json
end
end
routes.rb
mount GeoIPServer => "/geoip"
config/environments/development.rb
Website::Application.configure do
require "sinatra-geoip"
config.middleware.use "GeoIPServer"
...
end
controller
raw_geo_ip = Net::HTTP.get(URI.parse("http://#{geoip_server}/geoip/locate.json?ip=#{request.ip}"))
#geo_ip = JSON.parse(raw_geo_ip)
Our solution was difficult to find. We ended up finding a method in the sinatra source code call forward.
new sinatra-geoip.rb
class GeoIPServer < Sinatra::Base
if defined?(::Rails)
get '/properties.json' do
env["geo_ip.lookup"] = geo_ip_lookup(request.ip)
forward
end
end
def geo_ip_lookup(ip = nil)
ip = ip.nil? ? params[:ip] : ip
result = GeoIP.new('/var/www/mywebsite.org/current/GeoLiteCity.dat').city(ip)
result.to_h.to_json
end
end
Essentially, we removed the /geoip/locate.json route from the file and converted it to a simple method. We needed the geoip lookup to occur when the properties.json was being called, so a new param was added with the geoip information. Then we set the new param equal to #geo_ip variable in the controller.
New properties controller
if Rails.env.development? or Rails.env.test?
# Retrieves param set by sinatra-geoip middleware.
#geo_ip = JSON.parse(env["geo_ip.lookup"] || "{}")
else
# Production and staging code
end
Rather obscure problem and solution. Hopefully it will help someone out there. Cheers.

Posting to Facebook OG with HTTParty only works from the rails console?

I'm having a really weird problem with my rails app and facebook's open graph beta. Whenever I post an action to an object, Facebook returns an error that seems to indicate the URL of the object can't be reached, or the og scraper isn't scraping the URL correctly.
However, when I take the URL the app is generating for the post to Facebook and manually use the HTTParty gem to post it, it works.
Here's my code:
class Post < ActiveRecord::Base
FB_CONFIG = YAML.load_file("#{Rails.root}/config/initializers/facebook.yml")[Rails.env]
def self.to_facebook_og(obj, obj_id, verb, auth, extra)
#requires that a user has granted `publish_actions`
found_obj = obj.classify.constantize.find(obj_id) #find the actual object we're talking about
post_url = self.construct_facebook_action_url(obj, found_obj, verb, auth, extra) #create the URL
begin
ret = HTTParty.post(post_url)
logger.info "Facebook Post Action Response = #{ret}"
rescue HTTParty::ResponseError => e #handle any errors
logger.error {"FACEBOOK Response #{ret.code} / #{e.inspect}"}
flash.alert {"There was a Facebook problem. Please try again."}
return
end
end
def self.construct_facebook_action_url(obj, found_obj, verb, auth, extra)
base = 'https://graph.facebook.com/'
uid = auth.uid
namespace = FB_CONFIG['namespace']
token = "?access_token=#{auth.token}"
og_url = "#{obj}=http://theshortestfiction.com/#{obj.pluralize}/#{found_obj.id}"
fb_url = base + uid + '/' + namespace + ':' + verb + token + '&' + og_url + extra
logger.info fb_url
fb_url
end
def self.lint_og_object(obj_url)
lint_ret = HTTParty.post("https://developers.facebook.com/tools/lint/?url=#{obj_url}&format=json")
logger.info "Facebook Linter Response = #{lint_ret}"
end
end
When an object is read via its controller's show method, the app calls Post.to_facebook. From my logs, I can see that that Post.construct_facebook_action_url is constructing the proper url (because like I said, I can pull the URL from the logs and manually post it from the console). So, I'm assuming there's so problem with how I'm passing the URL to HTTParty? Facebook seems able to tell what object URL it should be looking at. Why does the code I've written not work, but manually in the console, it does?
Even weirder -- once there's been a sucessful post action on the object once, the code seems to work consistently. Facebook insists the problem is that the objects' URLs aren't reachable, but I can't understand how they're not, since I can browse to them.
I think this is actually a timeout issue.
I had the exact same issue as you, using HTTParty and getting the URL can't be reached error.
I moved the code to a background process using Resque and it fixed the problem.