HTTPEx.Backend.Mock.Expectation (HTTPEx v0.2.3)
View SourceDefines a HTTP mock expectation. Consists of a matching part and an expectation part.
Summary
Functions
Finds the expectation that matches the given Request.t()
Gets expects field from given Expectation
Gets matcher field from given Expectation
Looks up which type the given matcher field is
Increases the calls counter in Expectation
Tests if the request mets all defined expectations.
Tests if the request matches all fields.
Builds an Expectation struct from the given Keyword list
Sets expects field for given Expectation
Sets matcher field for given Expectation
Generates a fake response from a map, an error tuple or a http driver struct.
Replaces any vars if a response map has the option replace_body_vars
set to true
.
Validates if the field with the given value is allowed as an expects
Validates if the field with the given value is allowed as a matcher
Types
@type enum_matcher() :: atom()
@type exact_value_matcher() :: {:exact_value, any()}
@type int_matcher() :: integer()
@type map_matcher() :: map()
@type matcher() :: func_matcher() | string_matcher() | string_with_format_matcher() | regex_matcher() | wildcard_matcher() | keyword_list_matcher() | map_matcher() | enum_matcher() | int_matcher() | exact_value_matcher()
@type matcher_field_type() ::
:func
| :string
| :string_with_format
| :regex
| :wildcard
| :keyword_list
| :map
| :enum
| :int
| :exact_value
@type regex_matcher() :: Regex.t()
@type response_error() :: {:error, atom()}
@type response_func() :: (HTTPEx.Request.t() -> response_map() | response_error())
@type response_map() :: %{ :status => Plug.Conn.status(), :body => String.t(), optional(:delay) => number(), optional(:headers) => list(), optional(:replace_body_vars) => boolean() }
@type string_formats() :: :json | :xml | :form
@type string_matcher() :: String.t()
@type string_with_format_matcher() :: {String.t(), string_formats()}
@type t() :: %HTTPEx.Backend.Mock.Expectation{ calls: non_neg_integer(), description: String.t() | nil, expects: %{ body: func_matcher() | string_matcher() | string_with_format_matcher() | regex_matcher() | wildcard_matcher() | exact_value_matcher(), headers: keyword_list_matcher() | wildcard_matcher(), path: string_matcher() | regex_matcher() | wildcard_matcher(), query: map_matcher() | wildcard_matcher() }, global: boolean(), index: non_neg_integer(), matchers: %{ body: func_matcher() | string_matcher() | string_with_format_matcher() | regex_matcher() | wildcard_matcher() | exact_value_matcher(), headers: func_matcher() | keyword_list_matcher() | wildcard_matcher(), host: func_matcher() | string_matcher() | regex_matcher() | wildcard_matcher(), method: enum_matcher() | wildcard_matcher(), path: func_matcher() | string_matcher() | regex_matcher() | wildcard_matcher(), port: int_matcher() | wildcard_matcher(), query: func_matcher() | map_matcher() | wildcard_matcher() }, max_calls: non_neg_integer() | :infinity, min_calls: non_neg_integer(), priority: non_neg_integer(), response: response_func() | response_map() | response_error(), stacktrace: nil | tuple(), type: :assertion | :stub }
@type wildcard_matcher() :: :any
Functions
@spec find_matching_expectation([t()], HTTPEx.Request.t()) :: {:ok, t(), map()} | {:error, atom()} | {:error, atom(), t()}
Finds the expectation that matches the given Request.t()
Example
iex> expectation_1 =
...> %Expectation{index: 0}
...> |> Expectation.set_match!(:host, "www.example.com")
...> |> Expectation.set_match!(:port, 80)
...>
...> expectation_2 =
...> %Expectation{index: 1}
...> |> Expectation.set_match!(:host, "www.example.com")
...> |> Expectation.set_match!(:path, Regex.compile!("api/(?<api_version>[^/]+)/path/*"))
...> |> Expectation.set_match!(:port, 80)
...>
...> expectations = [expectation_1, expectation_2]
...>
...> {:ok, match, vars} =
...> Expectation.find_matching_expectation(
...> expectations,
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1337",
...> body: "Payload OK!",
...> headers: [{"app", "test"}, {"secret", "123"}],
...> method: :post
...> }
...> )
...>
...> match == expectation_1
true
iex> vars
%{}
iex> {:ok, match, vars} =
...> Expectation.find_matching_expectation(
...> expectations,
...> %Request{
...> url: "http://www.example.com/another-path",
...> body: "Payload OK!",
...> headers: [{"app", "test"}, {"secret", "123"}],
...> method: :post
...> }
...> )
...>
...> match == expectation_1
true
iex> vars
%{}
Gets expects field from given Expectation
Examples
iex> expectation = Expectation.set_expect!(%Expectation{}, :body, "OK!")
iex> Expectation.get_expect(expectation, :body)
"OK!"
iex> Expectation.get_expect(expectation, :path)
:any
Gets matcher field from given Expectation
Examples
iex> expectation = Expectation.set_match!(%Expectation{}, :host, "localhost")
iex> Expectation.get_match(expectation, :host)
"localhost"
iex> Expectation.get_match(expectation, :headers)
:any
iex> expectation = Expectation.set_match!(%Expectation{}, :path, fn _request -> true end)
iex> is_function(Expectation.get_match(expectation, :path))
true
@spec get_matcher_type(atom(), matcher()) :: matcher_field_type()
Looks up which type the given matcher field is
Examples
iex> Expectation.get_matcher_type(:host, "localhost")
:string
iex> Expectation.get_matcher_type(:body, {"{}", :json})
:string_with_format
iex> Expectation.get_matcher_type(:body, {"<a>b</a>", :xml})
:string_with_format
iex> Expectation.get_matcher_type(:body, {"foo=bar", :form})
:string_with_format
iex> Expectation.get_matcher_type(:body, {:exact_value, {:form, "data"}})
:exact_value
iex> Expectation.get_matcher_type(:host, fn _request -> true end)
:func
iex> Expectation.get_matcher_type(:query, %{"user_id" => "1234"})
:map
iex> Expectation.get_matcher_type(:headers, [{"Content-Type", "application/json"}])
:keyword_list
iex> Expectation.get_matcher_type(:path, :any)
:wildcard
iex> Expectation.get_matcher_type(:port, 1337)
:int
iex> Expectation.get_matcher_type(:path, ~r/api/)
:regex
iex> Expectation.get_matcher_type(:method, :post)
:enum
iex> Expectation.get_matcher_type(:method, :get)
:enum
Increases the calls counter in Expectation
Example
iex> expectation = %Expectation{}
...> expectation.calls
0
iex> expectation = Expectation.increase_call(expectation)
...> expectation.calls
1
@spec match_expects(HTTPEx.Request.t(), t()) :: expects_result()
Tests if the request mets all defined expectations.
The match_expects
function will always return a tuple with a boolean
and a list of fields that did not match.
Examples
A complex example, mixing different matchers:
iex> expectation =
...> %Expectation{}
...> |> Expectation.set_expect!(:body, fn %Request{} = request -> {String.contains?(request.body, "OK"), %{"var" => 1}} end)
...> |> Expectation.set_expect!(:headers, [{"secret", "123"}])
...> |> Expectation.set_expect!(:path, Regex.compile!("api/(?<api_version>[^/]+)/path"))
...> |> Expectation.set_expect!(:query, %{"user_id" => "1337"})
iex> Expectation.match_expects(
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1337",
...> body: "Payload OK!",
...> headers: [{"app", "test"}, {"secret", "123"}],
...> method: :post
...> },
...> expectation
...> )
{true, [:path, :body, :query, :headers], []}
iex> Expectation.match_expects(
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1339",
...> body: "Some error",
...> headers: [{"app", "test"}, {"geheim", "123"}],
...> method: :post
...> },
...> expectation
...> )
{
false,
[:path],
[
{:body, true, false}, {:query, %{"user_id" => "1337"}, %{"token" => "XYZ", "user_id" => "1339"}}, {:headers, [{"secret", "123"}], [{"app", "test"}, {"geheim", "123"}]}]}
@spec match_request(HTTPEx.Request.t(), t()) :: match_result()
Tests if the request matches all fields.
The match_request
function will always return a tuple with a boolean,
the fields that matched, and a map with collected variables
from regexes or :func matchers that were executed.
These vars can be used in responses to replace placeholders in responses.
Examples
A complex example, mixing different matchers:
iex> expectation =
...> %Expectation{}
...> |> Expectation.set_match!(:host, "www.example.com")
...> |> Expectation.set_match!(:port, 80)
...> |> Expectation.set_match!(:body, fn %Request{} = request -> {String.contains?(request.body, "OK"), %{"var" => 1}} end)
...> |> Expectation.set_match!(:headers, [{"secret", "123"}])
...> |> Expectation.set_match!(:path, Regex.compile!("api/(?<api_version>[^/]+)/path"))
...> |> Expectation.set_match!(:query, %{"user_id" => "1337"})
...> |> Expectation.set_match!(:method, :post)
iex> Expectation.match_request(
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1337",
...> body: "Payload OK!",
...> headers: [{"app", "test"}, {"secret", "123"}],
...> method: :post
...> },
...> expectation
...> )
{true, [:method, :headers, :query, :body, :host, :path, :port], [], %{"api_version" => "v1", "var" => 1}}
iex> Expectation.match_request(
...> %Request{
...> url: "http://www.example.co/api/v2/path/test?token=XYZ",
...> body: "Payload OK!",
...> headers: [{"app", "test"}, {"secret", "123"}],
...> method: :post
...> },
...> expectation
...> )
{false, [:method, :headers, :body, :path, :port], [:host, :query], %{"api_version" => "v2", "var" => 1}}
A couple of examples using the body formatters.
JSON:
iex> payload = JSON.encode!(%{username: "test"})
...> formatted_payload = JSON.encode!(%{username: "test"})
iex> expectation =
...> %Expectation{}
...> |> Expectation.set_match!(:host, "www.example.com")
...> |> Expectation.set_match!(:port, 80)
...> |> Expectation.set_match!(:body, {formatted_payload, :json})
...> |> Expectation.set_match!(:method, :post)
iex> Expectation.match_request(
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1337",
...> body: payload,
...> method: :post
...> },
...> expectation
...> )
{true, [:method, :headers, :query, :body, :host, :path, :port], [], %{}}
XML:
iex> payload = "<test>data</test>"
...> formatted_payload = "<test>\n data\n</test>"
iex> expectation =
...> %Expectation{}
...> |> Expectation.set_match!(:host, "www.example.com")
...> |> Expectation.set_match!(:port, 80)
...> |> Expectation.set_match!(:body, {formatted_payload, :xml})
...> |> Expectation.set_match!(:method, :post)
iex> Expectation.match_request(
...> %Request{
...> url: "http://www.example.com/api/v1/path/test?token=XYZ&user_id=1337",
...> body: payload,
...> method: :post
...> },
...> expectation
...> )
{true, [:method, :headers, :query, :body, :host, :path, :port], [], %{}}
Builds an Expectation struct from the given Keyword list
Options
- description
- method
- endpoint
- body
- headers
- host
- path
- port
- query
- expect_body
- expect_headers
- expect_path
- expect_query
- min_calls
- max_calls
- stacktrace
Examples
iex> Expectation.new!(method: :get, endpoint: "http://www.example.com", response: %{status: 200, body: "OK"}, type: :assert)
%Expectation{matchers: %{host: "www.example.com", method: :get, path: "/", port: 80, body: :any, headers: :any, query: :any}, type: :assertion}
Sets expects field for given Expectation
Examples
iex> Expectation.set_expect!(%Expectation{}, :body, "ok!")
%Expectation{expects: %{body: "ok!", headers: :any, path: :any, query: :any}}
iex> Expectation.set_expect!(%Expectation{}, :body, nil)
** (ArgumentError) Invalid type used for field expectation `body`. Must be one of: `function()`, `String.t()`, `{String.t(), :json | :xml}`, `RegEx.t()`, `:any`, `{:exact_value, any()}`
iex> Expectation.set_expect!(%Expectation{}, :unknown, nil)
** (ArgumentError) Unknown field `unknown` for expectation
Sets matcher field for given Expectation
Examples
iex> Expectation.set_match!(%Expectation{}, :host, "localhost")
%Expectation{matchers: %{body: :any, headers: :any, host: "localhost", method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :body, "OK!")
%Expectation{matchers: %{body: "OK!", headers: :any, host: :any, method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :body, {:exact_value, {:form, "OK!"}})
%Expectation{matchers: %{body: {:exact_value, {:form, "OK!"}}, headers: :any, host: :any, method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :body, {"{}", :json})
%Expectation{matchers: %{body: {"{}", :json}, headers: :any, host: :any, method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :body, {"<test>a</test>", :xml})
%Expectation{matchers: %{body: {"<test>a</test>", :xml}, headers: :any, host: :any, method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :body, {"foo=bar", :form})
%Expectation{matchers: %{body: {"foo=bar", :form}, headers: :any, host: :any, method: :any, path: :any, port: :any, query: :any}}
iex> Expectation.set_match!(%Expectation{}, :host, nil)
** (ArgumentError) Invalid type used for field matcher `host`. Must be one of: `function()`, `String.t()`, `RegEx.t()`, `:any`
iex> Expectation.set_match!(%Expectation{}, :unknown, :any)
** (ArgumentError) Unknown field `unknown` for matcher
Generates a fake response from a map, an error tuple or a http driver struct.
Replaces any vars if a response map has the option replace_body_vars
set to true
.
The response can also be a function. This function receives a Request.t() and must return a valid response().
Validates if the field with the given value is allowed as an expects
Validates if the field with the given value is allowed as a matcher
Examples
An exact value matcher (note: uses ===):
iex> Expectation.validate_matcher_value(:body, {:exact_value, {:form, [a: "data"]}})
:ok
Note, this matcher cannot be used on all fields:
iex> Expectation.validate_matcher_value(:host, {:exact_value, "some-host"})
{:error, :invalid_field_type}
A string matcher:
iex> Expectation.validate_matcher_value(:host, "localhost")
:ok
You can also use a function and either return a true/false:
iex> Expectation.validate_matcher_value(:host, fn _request -> true end)
:ok
Note, that the function has to have an arity of one. Other function arity's will return an error:
iex> Expectation.validate_matcher_value(:host, fn -> true end)
{:error, :invalid_field_type}
You can also supply regexes:
iex> Expectation.validate_matcher_value(:path, Regex.compile!("http://localhost:5000/*"))
:ok
A wildcard:
iex> Expectation.validate_matcher_value(:path, :any)
:ok
Match keyword lists with String.t() values on both sides:
iex> Expectation.validate_matcher_value(:headers, [{"Content-Type", "application/json"}])
:ok
Or with a Regex.t():
iex> Expectation.validate_matcher_value(:headers, [{"Content-Type", ~r/application/}])
:ok
Invalid lists, tuples. types are not valid:
iex> Expectation.validate_matcher_value(:headers, [{"Content-Type", :value}])
{:error, :invalid_field_type}
iex> Expectation.validate_matcher_value(:headers, [])
{:error, :invalid_field_type}
iex> Expectation.validate_matcher_value(:headers, [{"Content-Type", "Value", "Other-Value"}])
{:error, :invalid_field_type}
iex> Expectation.validate_matcher_value(:headers, "Content-Type: application/json")
{:error, :invalid_field_type}
Some fields require an enum, like method
:
iex> Expectation.validate_matcher_value(:method, :post)
:ok
iex> Expectation.validate_matcher_value(:method, :get)
:ok
iex> Expectation.validate_matcher_value(:method, "get")
{:error, :invalid_field_type}
You can also match a map. This is useful if you want to match query params:
iex> Expectation.validate_matcher_value(:query, %{"user_id" => "1234"})
:ok
iex> Expectation.validate_matcher_value(:query, %{user_id: "1234"})
{:error, :invalid_field_type}
iex> Expectation.validate_matcher_value(:query, %{})
{:error, :invalid_field_type}