Current section

Files

Jump to
exhal test exhal transcoder_test.exs
Raw

test/exhal/transcoder_test.exs

defmodule ExHal.TranscoderTest do
use ExUnit.Case
setup do
hal = """
{
"thing" : 1,
"TheOtherThing": 2,
"yer_mom": true,
"_links": {
"up": { "href": "http://example.com/1" },
"none": [],
"nolink": { "href": null},
"tag": [
{"href": "foo:1"},
{"href": "http://2"},
{"href": "urn:1"}
],
"nested": { "href": "http://example.com/2" },
"fillin": { "href": "http://example.com/3{?data}", "templated": true}
}
}
"""
{:ok, doc: ExHal.Document.parse!(ExHal.client, hal)}
end
test "can we make the most simple transcoder", %{doc: doc} do
defmodule MyTranscoder do
use ExHal.Transcoder
end
assert MyTranscoder.decode!(doc) == %{}
assert %ExHal.Document{} = MyTranscoder.encode!(%{})
end
test "transcode properties", %{doc: doc} do
defmodule NegationConverter do
@behaviour ExHal.Transcoder.ValueConverter
def to_hal(val), do: val * -1
def from_hal(val), do: val * -1
end
defmodule MyOverreachingTranscoder do
use ExHal.Transcoder
defproperty "thing"
defproperty "TheOtherThing", param: :thing2, value_converter: NegationConverter
defproperty "missingThing", param: :thing3
defproperty "yer_mom", param: [:yer, :mom]
end
assert %{thing: 1, thing2: -2, yer: %{mom: true}} == MyOverreachingTranscoder.decode!(doc)
encoded = MyOverreachingTranscoder.encode!(%{thing: 1, thing2: 2, yer: %{mom: true}})
assert 1 == ExHal.get_lazy(encoded, "thing", fn -> :missing end)
assert -2 == ExHal.get_lazy(encoded, "TheOtherThing", fn -> :missing end)
assert true == ExHal.get_lazy(encoded, "yer_mom", fn -> :missing end)
assert :missing == ExHal.get_lazy(encoded, "missingThing", fn -> :missing end)
end
test "re-using transcode params" do
defmodule MySimpleTranscoder do
use ExHal.Transcoder
defproperty "firstUse", param: :thing
defproperty "secondUse", param: :thing
end
encoded = MySimpleTranscoder.encode!(%{thing: "thing_value"})
assert "thing_value" == ExHal.get_lazy(encoded, "firstUse", fn -> :missing end)
assert "thing_value" == ExHal.get_lazy(encoded, "secondUse", fn -> :missing end)
end
test "transcoding with dynamic value converters" do
defmodule DynamicConverter do
@behaviour ExHal.Transcoder.ValueConverterWithOptions
def to_hal(val, factor: multiplier), do: val * multiplier
def from_hal(val, factor: divisor), do: val / divisor
end
defmodule DynamicTranscoder do
use ExHal.Transcoder
defproperty "thing", value_converter: DynamicConverter
end
encoded = DynamicTranscoder.encode!(%{thing: 2}, factor: 2)
assert 4 == ExHal.get_lazy(encoded, "thing", fn -> :missing end)
decoded = DynamicTranscoder.decode!(encoded, factor: 2)
assert 2 == decoded[:thing]
end
test "extract links", %{doc: doc} do
defmodule MyLinkTranscoder do
use ExHal.Transcoder
deflink "up", param: :mylink
deflink "none", param: :none
deflink "nolink", param: :nolink
deflink "nested", param: [:nested, :url]
deflink "fillin", param: :fillin, templated: true
end
assert MyLinkTranscoder.decode!(doc) == %{mylink: "http://example.com/1",
nested: %{url: "http://example.com/2"},
fillin: "http://example.com/3{?data}"
}
encoded = MyLinkTranscoder.encode!(%{mylink: "http://example.com/1",
nested: %{url: "http://example.com/2"},
fillin: "http://example.com/3{?data}"})
assert {:ok, "http://example.com/1"} == ExHal.link_target(encoded, "up")
assert {:ok, "http://example.com/2"} == ExHal.link_target(encoded, "nested")
assert {:ok, "http://example.com/3?data=INFO"} == ExHal.link_target(encoded, "fillin", tmpl_vars: [data: "INFO"])
end
test "don't inject a link that has a null href" do
defmodule MyEmptyLinkTranscoder do
use ExHal.Transcoder
deflink "present"
deflink "present_but_nil"
deflink "absent"
end
encoded = MyEmptyLinkTranscoder.encode!(%{present: "http://example.com/present",
present_but_nil: nil})
assert {:error, %ExHal.Error{reason: "no such link: absent"}} == ExHal.link_target(encoded, "absent")
assert {:error, %ExHal.Error{reason: "no such link: present_but_nil"}} == ExHal.link_target(encoded, "present_but_nil")
end
test "don't try to extract links from document that has no links" do
defmodule MyTinyTranscoder do
use ExHal.Transcoder
deflink "up", param: :mylink
end
hal = """
{
"_links": {}
}
"""
doc = ExHal.Document.parse!(ExHal.client, hal)
assert MyTinyTranscoder.decode!(doc) == %{}
end
test "trying to extract multiple links", %{doc: doc} do
defmodule MyOtherMultiLinkTranscoder do
use ExHal.Transcoder
deflinks "tag", param: :tag
end
%{tag: tags} = MyOtherMultiLinkTranscoder.decode!(doc)
assert Enum.member?(tags, "foo:1")
assert Enum.member?(tags, "http://2")
assert Enum.member?(tags, "urn:1")
encoded = MyOtherMultiLinkTranscoder.encode!(%{tag: ["urn:1", "http://2", "foo:1"]})
assert {:ok, ["urn:1", "http://2", "foo:1"]} == ExHal.link_targets(encoded, "tag")
end
test "trying to extract links with value conversion", %{doc: doc} do
defmodule MyLinkConverter do
@behaviour ExHal.Transcoder.ValueConverter
def to_hal(id) do
"http://example.com/#{id}"
end
def from_hal(up_url) do
{id, _} = up_url
|> String.split("/")
|> List.last
|> Integer.parse
id
end
end
defmodule MyLinkConversionTranscoder do
use ExHal.Transcoder
deflink "up", param: :up_id, value_converter: MyLinkConverter
end
assert MyLinkConversionTranscoder.decode!(doc) == %{up_id: 1}
encoded = MyLinkConversionTranscoder.encode!(%{up_id: 2})
assert {:ok, "http://example.com/2"} == ExHal.link_target(encoded, "up")
end
test "composable transcoders", %{doc: doc} do
defmodule BaseTranscoder do
use ExHal.Transcoder
defproperty "thing"
deflink "up", param: :up_url
end
defmodule ExtTranscoder do
use ExHal.Transcoder
defproperty "TheOtherThing", param: :thing2
deflinks "tag", param: :tag
end
decoded = BaseTranscoder.decode!(doc)
|> ExtTranscoder.decode!(doc)
assert %{thing: 1} = decoded
assert %{thing2: 2} = decoded
assert %{up_url: "http://example.com/1"} = decoded
%{tag: tags} = decoded
assert Enum.member?(tags, "foo:1")
assert Enum.member?(tags, "http://2")
assert Enum.member?(tags, "urn:1")
params = %{thing: 1,
tag: ["urn:1", "http://2", "foo:1"],
thing2: 2,
up_url: "http://example.com/1"}
encoded = BaseTranscoder.encode!(params)
|> ExtTranscoder.encode!(params)
assert 1 == ExHal.get_lazy(encoded, "thing", fn -> :missing end)
assert 2 == ExHal.get_lazy(encoded, "TheOtherThing", fn -> :missing end)
assert {:ok, "http://example.com/1"} == ExHal.link_target(encoded, "up")
assert {:ok, ["urn:1", "http://2", "foo:1"]} == ExHal.link_targets(encoded, "tag")
end
test "patch existing object", %{doc: doc} do
defmodule MartiniTranscoder do
use ExHal.Transcoder
defproperty "baseIngredient", param: [:spirits, :liquor]
end
hal = Poison.encode!(%{"baseIngredient" => "gin"})
doc = ExHal.Document.parse!(ExHal.client, hal)
cocktail = MartiniTranscoder.decode!(doc)
assert %{spirits: %{liquor: "gin"}} == cocktail
json_patches = [
%{"op" => "replace", "path" => "/baseIngredient", "value" => "vodka"},
]
bond_style = MartiniTranscoder.patch!(cocktail, json_patches)
assert %{spirits: %{liquor: "vodka"}} == bond_style
end
test "patch existing object with value converter", %{doc: doc} do
defmodule ShakenIsNotStirredConverter do
@behaviour ExHal.Transcoder.ValueConverter
def from_hal(bool), do: invert_bool(bool)
def to_hal(bool), do: invert_bool(bool)
defp invert_bool(bool), do: !bool
end
defmodule MartiniTranscoder2 do
use ExHal.Transcoder
defproperty "stirred", param: [:shaken], value_converter: ShakenIsNotStirredConverter
end
hal = Poison.encode!(%{stirred: true})
doc = ExHal.Document.parse!(ExHal.client, hal)
cocktail = MartiniTranscoder2.decode!(doc)
assert %{shaken: false} == cocktail
json_patches = [
%{"op" => "replace", "path" => "/stirred", "value" => false},
]
bond_style = MartiniTranscoder2.patch!(cocktail, json_patches)
assert %{shaken: true} == bond_style
end
test "try to patch a non-existing property does nothing" do
defmodule MartiniTranscoder3 do
use ExHal.Transcoder
defproperty "serving_dish"
end
cocktail = %{serving_dish: "mug"}
json_patches = [
%{"op" => "replace", "path" => "/firearm", "value" => "Walther PPK"},
]
assert cocktail == MartiniTranscoder3.patch!(cocktail, json_patches)
end
test "multiple patch operations in one go" do
defmodule MartiniTranscoder4 do
use ExHal.Transcoder
defproperty "garnish_type"
defproperty "garnish_count"
end
cocktail = %{garnish_type: "radish", garnish_count: 1}
json_patches = [
%{"op" => "replace", "path" => "/garnish_type", "value" => "olive"},
%{"op" => "replace", "path" => "/garnish_count", "value" => 2}
]
bond_style = MartiniTranscoder4.patch!(cocktail, json_patches)
assert %{garnish_type: "olive", garnish_count: 2} == bond_style
end
test "protected items can't be changed" do
defmodule CompanyAutoTranscoder do
use ExHal.Transcoder
defproperty "make", protected: true
defproperty "model"
end
bonds_ride = %{make: "Aston Martin", model: "DB5"}
json_patches = [
%{"op" => "replace", "path" => "/make", "value" => "Mopeds-R-Us"},
%{"op" => "replace", "path" => "/model", "value" => "Vanquish"}
]
bonds_new_ride = CompanyAutoTranscoder.patch!(bonds_ride, json_patches)
assert %{make: "Aston Martin", model: "Vanquish"} == bonds_new_ride
end
test "replace (or add) a link" do
defmodule DestinationTranscoder do
use ExHal.Transcoder
deflink "missionLocation", param: :mission_locale
deflink "transportInfo", param: :transport_details
end
links = %{"missionLocation" => %{href: "http://mi5.uk/l/moscow"}}
hal = Poison.encode!(%{_links: links})
doc = ExHal.Document.parse!(ExHal.client, hal)
mission = DestinationTranscoder.decode!(doc)
json_patches = [
%{"op" => "replace", "path" => "/_links/missionLocation", "value" => %{"href" => "http://mi5.uk/l/singapore"}},
%{"op" => "replace", "path" => "/_links/transportInfo", "value" => %{"href" => "http://mi5.uk/travel/123456"}},
%{"op" => "replace", "path" => "/_links/notALinkOrProp", "value" => %{"href" => "http://i_will_be_ignored"}},
]
new_mission = DestinationTranscoder.patch!(mission, json_patches)
# location was changed
assert new_mission.mission_locale == "http://mi5.uk/l/singapore"
# travel info was added (replaced a declared, but not present, link)
assert new_mission.transport_details == "http://mi5.uk/travel/123456"
# an operation for on a non-declared path will be dropped
assert [] == new_mission |> Map.delete(:mission_locale) |> Map.delete(:transport_details) |> Map.keys
end
test "add a link to a deflinks" do
defmodule CoworkersTranscoder do
use ExHal.Transcoder
deflinks "workChums", param: :coworkers
end
hal = Poison.encode!(%{_links: %{"workChums" => [%{href: "http://mi5.uk/q"}, %{href: "http://mi5.uk/m"}]}})
doc = ExHal.Document.parse!(ExHal.client, hal)
workplace = CoworkersTranscoder.decode!(doc)
json_patches = [
%{"op" => "add", "path" => "/_links/workChums/-", "value" => %{"href" => "http://mi5.uk/moneypenny"}}
]
new_workplace = CoworkersTranscoder.patch!(workplace, json_patches)
assert Enum.member?(new_workplace.coworkers, "http://mi5.uk/q")
assert Enum.member?(new_workplace.coworkers, "http://mi5.uk/m")
assert Enum.member?(new_workplace.coworkers, "http://mi5.uk/moneypenny")
end
test "replace a deflinks array" do
defmodule CoworkersTranscoder2 do
use ExHal.Transcoder
deflinks "workChums", param: :coworkers
end
hal = Poison.encode!(%{_links: %{"workChums" => [%{href: "http://mi5.uk/q"}, %{href: "http://mi5.uk/m"}]}})
doc = ExHal.Document.parse!(ExHal.client, hal)
workplace = CoworkersTranscoder2.decode!(doc)
json_patches = [
%{"op" => "replace",
"path" => "/_links/workChums",
"value" => [
%{"href" => "http://spectre.org/u/jaws"},
%{"href" => "http://spectre.org/u/oddjob"}
]
}
]
new_workplace = CoworkersTranscoder2.patch!(workplace, json_patches)
refute Enum.member?(new_workplace.coworkers, "http://mi5.uk/q")
refute Enum.member?(new_workplace.coworkers, "http://mi5.uk/m")
assert Enum.member?(new_workplace.coworkers, "http://spectre.org/u/jaws")
assert Enum.member?(new_workplace.coworkers, "http://spectre.org/u/oddjob")
end
end