henriquesati

henriquesati

Plug test messing up body request

I’m trying to test my API endpoints that communicate with a docker container SQL. When running insomnia calls, the api behavior is fine, but now trying to automate the tests i’m experiencing the following issues
the endpoint is ignoring some fields, although i’m sending it corretly, (same request are being mane on imnsonia and it is working)
i’m not being able to set my logs on the console even using Logger
I tried to to parse my body request and log to see what is going on, but was also unable to log the request_body parsed, also is like the console just ignores it
test.file

defmodule TestesPay.MyRouterTest do
  use ExUnit.Case, async: false
  use Plug.Test

  alias TestesPay.MyRouter

  setup_all do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
    Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
    :ok
  end

  test "POST /api/create_contract com dados válidos" do
   body = %{
      "chain" => "1",
      "coin" => "ETH",
      "transaction_value" => 2,
      "transaction_charged" => 6.78,
      "ref" => "REF123",
      "ref_fee" => "2.0",
      "payload" => "data_payload"
    }

    conn = conn(:post, "/api/create_contract", body)
    |> put_req_header("content-type", "application/json")
    |> MyRouter.call(%{})

    IO.inspect(conn, label: "Connection after MyRouter.call")
    IO.inspect(conn.resp_body, label: "body response")

    assert conn.status == 200
  end

  test "POST /api/create_contract com dados inválidos" do
    body = %{
      "chain" => "example_chain"
    }
    |> Jason.encode!()

    conn = conn(:post, "/api/create_contract", body)
    |> put_req_header("content-type", "application/json")
    |> MyRouter.call(%{})

    IO.inspect(conn, label: "Connection after MyRouter.call")
    IO.inspect(conn.resp_body, label: "response body")

    assert conn.status == 400
    assert Jason.decode!(conn.resp_body)["msg"] == "expected_error_message"
  end
end

my plug:

defmodule MyApp.PlugServer.Plugs.CheckRequestFields do
  def init(options), do: options
  alias Plug.Conn

  def call(%Plug.Conn{request_path: path} = conn, opts) do
    {:ok, body_as_json, conn} = Plug.Conn.read_body(conn, opts)
    if path in opts[:paths] do
      verify_tuple = verify_request_fields!(body_as_json, opts[:fields], MyApp.Validate.CustomRules.get_rules(conn.request_path)) ##cuidado com essa linha ao começar muitos roteamentos
  
      conn = case verify_tuple do
        {:ok, _} -> 
          conn
          |> Conn.assign(:resp, verify_tuple)
          |> Conn.assign(:status, 200)
        {:error, _} -> 
          conn
          |> Conn.assign(:resp, verify_tuple)
          |> Conn.assign(:status, 400)
      end
      conn
    else
      conn = Conn.assign(conn, :resp, {:error, %{"msg" => "algo deu errado em call check request fields"}})
      conn
    end
  end


    defp verify_request_fields!(body, fields, rules) do
      {:ok, data} = Jason.decode(body)
        with {:ok, response} <- Validate.validate(data, rules) do
          IO.inspect(response)
            {:ok, response}
        else
          {:error, array_of_errors} ->
            error_map = %{
              "field" => List.first((List.first(array_of_errors).path)),
              "msg" => List.first(array_of_errors).message
            }
                {:error, error_map}
        end
  end
end

and my router:

defmodule TestesPay.MyRouter do
  use Plug.Router
  alias MyApp.PlugServer.Plugs.CheckRequestFields
  
  plug CheckRequestFields, fields: ["chain", "coin", "coin", "transaction_value", "transaction_charged", "ref", "ref_fee", "payload"], paths: ["/api/create_contract"]
  plug :match
  plug :dispatch


  post "/api/create_contract" do
    with {:ok, response} <- conn.assigns.resp,
    {:ok, json_response} = Jason.encode(response),
    {:ok, changeset} = MyApp.ControllerCreateContract.create(response)
     do
      conn
      |> put_resp_content_type("application/json")
      |> resp(conn.assigns.status, json_response)
      |>send_resp()
    else
      {:error, response} ->    
        {:ok, json_response} = Jason.encode(response)
        conn
      |> put_resp_content_type("application/json")
      |> resp(conn.assigns.status, json_response)
      |>send_resp()
    end
  end

  match _ do
    send_resp(conn, 404, "oops")
  end
end

config.file

use Mix.Config


config :testes_pay, MyApp.Repo,
  pool: Ecto.Adapters.SQL.Sandbox,
  database: "teste2",
  username: "pedri",
  password: "1234",
  hostname: "localhost",
  port: 8080

config :testes_pay, ecto_repos: [MyApp.Repo]

config :testes_pay, TestesPay.MyRouter,
  http: [port: 4002],
  server: true

# config :logger,
# backends: [:console],
# compile_time_purge_matching: :debug

output: ` └─ lib/plug_server/router.ex:13:11: TestesPay.MyRouter.do_match/4

Connection after MyRouter.call: %Plug.Conn{
adapter: {Plug.Adapters.Test.Conn, :…},
assigns: %{
status: 400,
resp: {:error, %{“field” => “coin”, “msg” => “is required”}}
},
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.478.0>,
params: %{},
path_info: [“api”, “create_contract”],
path_params: %{},
port: 80,
private: %{
plug_route: {“/api/create_contract”,
#Function<1.74211796/2 in TestesPay.MyRouter.do_match/4>}
},
query_params: %Plug.Conn.Unfetched{aspect: :query_params},
query_string: “”,
remote_ip: {127, 0, 0, 1},
req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},
req_headers: [{“content-type”, “application/json”}],
request_path: “/api/create_contract”,
resp_body: “{"field":"coin","msg":"is required"}”,
resp_cookies: %{},
resp_headers: [
{“cache-control”, “max-age=0, private, must-revalidate”},
{“content-type”, “application/json; charset=utf-8”}
],
scheme: :http,
script_name: ,
secret_key_base: nil,
state: :sent,
status: 400
}
response body: “{"field":"coin","msg":"is required"}”

  1. test POST /api/create_contract com dados inválidos (TestesPay.MyRouterTest)
    test/integration_test.exs:34
    Assertion with == failed
    code: assert Jason.decode!(conn.resp_body)[“msg”] == “expected_error_message”
    left: “is required”
    right: “expected_error_message”
    test/integration_test.exs:48: (test)

  2. test POST /api/create_contract com dados válidos (TestesPay.MyRouterTest)
    test/integration_test.exs:13
    ** (MatchError) no match of right hand side value: {:error, %Jason.DecodeError{position: 1, token: nil, data: “–plug_conn_test–”}}
    code: |> MyRouter.call(%{})
    stacktrace:
    (testes_pay 0.1.0) lib/plug_server/plugs/check_request_fields.ex:29: MyApp.PlugServer.Plugs.CheckRequestFields.verify_request_fields!/3
    (testes_pay 0.1.0) lib/plug_server/plugs/check_request_fields.ex:8: MyApp.PlugServer.Plugs.CheckRequestFields.call/2
    (testes_pay 0.1.0) lib/plug_server/router.ex:1: TestesPay.MyRouter.plug_builder_call/2
    test/integration_test.exs:26: (test)

Finished in 0.2 seconds (0.00s async, 0.2s sync)
2 tests, 2 failures`

just to be clear about what I mentioned, I’m not being able to log. When using logger, the console just ignore

    conn = conn(:post, "/api/create_contract", body) |> put_req_header("content-type", "application/json")
           |> MyRouter.call(%{})

    {:ok, body_as_json, conn} = Plug.Conn.read_body(conn)
    {:ok, data} = Jason.decode(body_as_json)

    Logger.info("params@! ---------------------------------------------->>>>>")  # Log params
    Logger.info("Conexão após chamada para MyRouter.call:", conn)  # Log connection details
    Logger.info("Corpo da resposta:", conn.resp_body) 

Marked As Solved

LostKobrakai

LostKobrakai

Not every test for a plug runs a parser for the body. I’d argue a large majority don’t – the ones, which test individual plugs, controllers, …. Therefore people often just care about the resulting body_params, which you can directly set as a map. conn.body is never read on such tests.

E.g. the bulk of phoenix tests never touch the endpoint.ex, which would run Plug.Parsers / does the read_body – the expectation here being that parsers work (and are tested elsewhere) and don’t need to be tested in your codebase. Also encoding and then parsing params would for the most part be wasted cpu cycles if you don’t need to test the parsing, but just need to set params.

Also Liked

LostKobrakai

LostKobrakai

You can only pass a map if all you care for is setting conn.body_params directly. The test adapter cannot automatically encode that map for the body content, hence it being set to "--plug_conn_test--".

So if you run such a conn through a piece of code, which tries to parse the body it’s going to fail. In that case you need to provide a binary to set the body.

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement