Created
September 4, 2019 13:41
-
-
Save erikreedstrom/47574baea5f712e24ef1e5ca6a23a5ee to your computer and use it in GitHub Desktop.
Assertions for validating JSON responses in Elixir.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ExUnit.Assertions.JSON do | |
@moduledoc """ | |
Assertions for validating JSON responses. | |
This code was cribbed from [Voorhees](https://github.com/danmcclain/voorhees) | |
and adapted to follow the modern ExUnit `lhs == rhs` assertion syntax, | |
as well as simplify matching logic. | |
""" | |
import ExUnit.Assertions | |
@doc """ | |
Asserts the payload matches the format of the expected keys matched in. | |
""" | |
@spec assert_json_schema(map | list | String.t(), list) :: list | |
def assert_json_schema(payload, expected_keys) when is_binary(payload), | |
do: assert_json_schema(Jason.decode!(payload), expected_keys) | |
def assert_json_schema(payload, expected_keys) do | |
expected_keys = normalize_key_list(expected_keys) | |
content_keys = | |
payload | |
|> Map.keys() | |
|> extract_subkeys(payload) | |
|> normalize_key_list | |
assert content_keys == expected_keys | |
content_keys | |
end | |
@doc """ | |
Asserts the payload matches the values from expected payload. | |
""" | |
@spec assert_json_payload(map | String.t(), list | map) :: map | |
def assert_json_payload(payload, expected_payload) when is_binary(payload), | |
do: assert_json_payload(Jason.decode!(payload), expected_payload) | |
def assert_json_payload(payload, expected_payload) do | |
expected_payload = normalize_map(expected_payload) | |
parsed_payload = payload |> normalize_map() |> filter_out_extra_keys(expected_payload) | |
assert parsed_payload == expected_payload | |
parsed_payload | |
end | |
## PRIVATE FUNCTIONS | |
# REVIEW: Needs clean up | |
defp filter_out_extra_keys(payload, expected_payload) when is_list(payload) do | |
payload | |
|> Enum.with_index() | |
|> Enum.map(fn {value, index} -> | |
filter_out_extra_keys(value, Enum.at(expected_payload, index)) | |
end) | |
end | |
defp filter_out_extra_keys(payload, nil) when is_map(payload), do: payload | |
defp filter_out_extra_keys(payload, expected_payload) when is_map(payload) do | |
payload | |
|> Enum.filter(fn {key, _value} -> expected_payload |> Map.keys() |> Enum.member?(key) end) | |
|> Enum.map(fn | |
{key, value} when is_map(value) or is_list(value) -> | |
{key, filter_out_extra_keys(value, expected_payload[key])} | |
entry -> | |
entry | |
end) | |
|> Enum.into(%{}) | |
end | |
defp filter_out_extra_keys(payload, _expected_payload), do: payload | |
defp normalize_map(map) when is_map(map) do | |
map | |
|> Enum.map(&normalize_map_entry(&1)) | |
|> Enum.into(%{}) | |
end | |
defp normalize_map(list) when is_list(list), do: Enum.map(list, &normalize_map(&1)) | |
defp normalize_map(value), do: value | |
defp normalize_map_entry({_key, %{__struct__: _} = value}), do: flunk("cannot normalize struct: #{inspect(value)}") | |
defp normalize_map_entry({key, value}) when is_map(value) or is_list(value) do | |
{normalize_key(key), normalize_map(value)} | |
end | |
defp normalize_map_entry({key, value}) do | |
{normalize_key(key), value} | |
end | |
defp normalize_key_list([]), do: [] | |
defp normalize_key_list([{key, subkeys} | rest]) when is_list(subkeys) do | |
[{normalize_key(key), Enum.sort(normalize_key_list(subkeys))} | normalize_key_list(rest)] | |
end | |
defp normalize_key_list([key | rest]) do | |
if is_list(key) do | |
# Unwrap lists of keys | |
[normalize_key_list(key) | normalize_key_list(rest)] |> List.first() | |
else | |
[normalize_key(key) | normalize_key_list(rest)] | |
end | |
end | |
defp normalize_key(key) when is_binary(key), do: String.to_atom(key) | |
defp normalize_key(key), do: key | |
defp extract_subkeys([], _map), do: [] | |
defp extract_subkeys([key | rest], map) do | |
case map[key] do | |
value when is_map(value) -> | |
[{key, extract_subkeys(Map.keys(value), value)} | extract_subkeys(rest, map)] | |
value when is_list(value) -> | |
[{key, subkey_arrays(value)} | extract_subkeys(rest, map)] | |
_ -> | |
[key | extract_subkeys(rest, map)] | |
end | |
end | |
defp subkey_arrays(value) do | |
value | |
|> Enum.map(fn | |
element when is_map(element) -> extract_subkeys(Map.keys(element), element) | |
_ -> [] | |
end) | |
|> Enum.uniq() | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment