elixir-maru / maru

Elixir RESTful Framework
https://maru.readme.io
BSD 3-Clause "New" or "Revised" License
1.32k stars 84 forks source link

Failing to implement POST macro #13

Closed expelledboy closed 8 years ago

expelledboy commented 8 years ago

Hi,

I am failing to implement a 'post' macro. Please can you guide me as to what I am doing wrong? And there is very little documentation around testing a post.

defmodule Skynet.Person.API do
  use Maru.Router
  alias Skynet.Person
  alias Skynet.Repo
  version "v1"

  resource :person do
    # desc "description"
    get ":id" do
      entry = Repo.get!(Person, params[:id])
      present entry, with: Person.Entity
    end

    # desc "description"
    params do
      requires :name, type: String
      requires :phone, type: String, regexp: ~r/^27[0-9]{9}$/
    end
    post do
      %Person{name: params[:name], phone: params[:phone]}
      |> Repo.insert!
      status 200
    end
  end
end
defmodule Skynet.API do
  use Maru.Router

  plug Plug.RequestId
  plug Plug.Logger
  resources do
    mount Skynet.Person.API
  end

  rescue_from Unauthorized, as: e do
    IO.inspect e
    status 401
    "Unauthorized"
  end

  rescue_from :all, as: e do
    IO.inspect e
    status 500
    "Server Error"
  end
end
@ anthony-mac> curl -H "Accept-Version:v1" -H "Content-Type: application/json" --request POST --data '{"name": "anthony", "phone": "27745765000"}' http://127.0.0.1:8000/person
Server Error
2015-10-23 13:32:48.495 [info] POST /person
%Maru.Exceptions.NotFound{method: "POST", path_info: ["person"]}
iex(2)> Skynet.Person.API.__endpoints__
[%Maru.Router.Endpoint{block: {:__block__, [line: 23],
   [{:|>, [line: 37],
     [{:%, [line: 36],
       [{:__aliases__, [counter: 0, line: 36], [:Person]},
        {:%{}, [line: 36],
         [name: {{:., [line: 36], [Access, :get]}, [line: 36],
           [{:params, [line: 36], nil}, :name]},
          phone: {{:., [line: 36], [Access, :get]}, [line: 36],
           [{:params, [line: 36], nil}, :phone]}]}]},
      {{:., [line: 37],
        [{:__aliases__, [counter: 0, line: 37], [:Repo]}, :insert!]},
       [line: 37], []}]}, {:status, [line: 38], [200]}]}, desc: nil,
  helpers: [], method: "POST",
  param_context: [%Maru.Router.Param{attr_name: :name, default: nil, desc: nil,
    group: [], nested: false, parser: Maru.ParamType.String, required: true,
    validators: []},
   %Maru.Router.Param{attr_name: :phone, default: nil, desc: nil, group: [],
    nested: false, parser: Maru.ParamType.String, required: true,
    validators: [regexp: ~r/^27[0-9]{9}$/]}], path: ["person"], version: "v1"},
 %Maru.Router.Endpoint{block: {:__block__, [line: 23],
   [{:=, [line: 26],
     [{:entry, [line: 26], nil},
      {{:., [line: 26],
        [{:__aliases__, [counter: 0, line: 26], [:Repo]}, :get!]}, [line: 26],
       [{:__aliases__, [counter: 0, line: 26], [:Person]},
        {{:., [line: 26], [Access, :get]}, [line: 26],
         [{:params, [line: 26], nil}, :id]}]}]},
    {:present, [line: 27],
     [{:entry, [line: 27], nil},
      [with: {:__aliases__, [line: 27, counter: 189], [:Person, :Entity]}]]}]},
  desc: nil, helpers: [], method: "GET", param_context: [],
  path: ["person", :id], version: "v1"}]

Note: I am using Maru version 0.7.1, because anything above results in the following error during tests:

  1) test v1 post /person (Skynet.Person.Test)
     test/person_test.exs:29
     ** (UndefinedFunctionError) undefined function: :urlencoded.parse/5 (module :urlencoded is not available)
     stacktrace:
       :urlencoded.parse(%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{}, before_send: [], body_params: %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "www.example.com", method: "POST", owner: #PID<0.643.0>, params: %{}, path_info: ["person"], peer: {{127, 0, 0, 1}, 111317}, port: 80, private: %{}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"content-type", "application/json"}, {"accept-version", "v1"}], request_path: "/person", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}, "application", "json", %{}, [parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Poison])
       (plug) lib/plug/parsers.ex:186: Plug.Parsers.reduce/6
       test/person_test.exs:3: Skynet.Person.Test.make_response/2
       test/person_test.exs:37
falood commented 8 years ago

Could you please show me your config.exs file ? Maybe inconsistent versioning strategy was used. And you can wirte UnitTest like this:

defmodule Skynet.Person.APITest do
  use Maru.Test, for: Skynet.Person.API

  test "post /person" do
    assert %Plug.Conn{} = conn(:post, "/person", %{"name" => "anthony", "phone" => "27745765000"}) |> make_response
  end
end

The conn function comes from Plug.Test.

And for the 2nd question: I'll check and fix it as soon as possible, recommend using the latest release.

expelledboy commented 8 years ago

I have found the issue. In the post I had to include a body to the post. So my implementation is now:

    post do
      entry = %Person{name: params[:name], phone: params[:phone]}
              |> Repo.insert!
      status 200
      present entry, with: Skynet.Repo.Entity
    end

The error was very misleading.

I can not use the latest version, as I run into the second issue. If you review plug parser you will see that in the init :urlencoded gets mapped to Plug.Parsers.URLENCODED. See: https://github.com/elixir-lang/plug/blob/master/lib/plug/parsers.ex

expelledboy commented 8 years ago

Perfect this works! Thank you