rdf-ex/test/unit/literal_test.exs
Marcel Otto 5819eec0cf Re-integrate XSD.ex
It turned out that the costs of separating the XSD datatypes are too high
and probably not worth the effort, since with its limited scope
probably nobody would want to use XSD.ex outside of the RDF.ex context
anyway.
2020-05-05 23:58:44 +02:00

514 lines
18 KiB
Elixir

defmodule RDF.LiteralTest do
use ExUnit.Case
import RDF.Sigils
import RDF.TestLiterals
alias RDF.{Literal, XSD, LangString}
alias RDF.Literal.{Generic, Datatype}
alias Decimal, as: D
doctest RDF.Literal
alias RDF.NS
@examples %{
XSD.String => ["foo"],
XSD.Integer => [42],
XSD.Double => [3.14],
XSD.Decimal => [Decimal.from_float(3.14)],
XSD.Boolean => [true, false],
}
describe "new/1" do
Enum.each @examples, fn {datatype, example_values} ->
@tag example: %{datatype: datatype, values: example_values}
test "coercion from #{datatype |> Module.split |> List.last |> to_string}", %{example: example} do
Enum.each example.values, fn example_value ->
assert Literal.new(example_value) == example.datatype.new(example_value)
assert Literal.new!(example_value) == example.datatype.new!(example_value)
end
end
end
test "with typed literals" do
Enum.each Datatype.Registry.datatypes() -- [RDF.LangString], fn datatype ->
assert %Literal{literal: typed_literal} = Literal.new(datatype.new("foo"))
assert typed_literal.__struct__ == datatype
end
end
test "when options without datatype given" do
assert Literal.new(true, []) == XSD.Boolean.new(true)
assert Literal.new(42, []) == XSD.Integer.new(42)
assert Literal.new!(true, []) == XSD.Boolean.new!(true)
assert Literal.new!(42, []) == XSD.Integer.new!(42)
end
end
describe "typed construction" do
test "boolean" do
assert Literal.new(true, datatype: NS.XSD.boolean) == XSD.Boolean.new(true)
assert Literal.new(false, datatype: NS.XSD.boolean) == XSD.Boolean.new(false)
assert Literal.new("true", datatype: NS.XSD.boolean) == XSD.Boolean.new("true")
assert Literal.new("false", datatype: NS.XSD.boolean) == XSD.Boolean.new("false")
end
test "integer" do
assert Literal.new(42, datatype: NS.XSD.integer) == XSD.Integer.new(42)
assert Literal.new("42", datatype: NS.XSD.integer) == XSD.Integer.new("42")
end
test "double" do
assert Literal.new(3.14, datatype: NS.XSD.double) == XSD.Double.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.double) == XSD.Double.new("3.14")
end
test "decimal" do
assert Literal.new(3.14, datatype: NS.XSD.decimal) == XSD.Decimal.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.decimal) == XSD.Decimal.new("3.14")
assert Literal.new(Decimal.from_float(3.14), datatype: NS.XSD.decimal) ==
XSD.Decimal.new(Decimal.from_float(3.14))
end
test "unsignedInt" do
assert Literal.new(42, datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new(42)
assert Literal.new("42", datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new("42")
end
test "string" do
assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo")
end
test "unmapped/unknown datatype" do
assert Literal.new("custom typed value", datatype: "http://example/dt") ==
Generic.new("custom typed value", datatype: "http://example/dt")
end
end
describe "language tagged construction" do
test "string literal with a language tag" do
assert Literal.new("Eule", language: "de") == LangString.new("Eule", language: "de")
assert Literal.new!("Eule", language: "de") == LangString.new!("Eule", language: "de")
end
test "non-string literals with a language tag" do
assert Literal.new(1, language: "de") == LangString.new(1, language: "de")
assert Literal.new!(1, language: "de") == LangString.new!(1, language: "de")
end
test "construction of an other than rdf:langString typed and language-tagged literal fails" do
assert Literal.new("Eule", datatype: RDF.langString, language: "de") ==
LangString.new("Eule", language: "de")
assert_raise ArgumentError, fn ->
Literal.new("Eule", datatype: NS.XSD.string, language: "de")
end
end
test "construction of a rdf:langString works, but results in an invalid literal" do
assert Literal.new("Eule", datatype: RDF.langString) == LangString.new("Eule", [])
assert_raise RDF.Literal.InvalidError, fn ->
Literal.new!("Eule", datatype: RDF.langString)
end
end
end
describe "coerce/1" do
test "with boolean" do
assert Literal.coerce(true) == XSD.true()
assert Literal.coerce(false) == XSD.false()
end
test "with string" do
assert Literal.coerce("foo") == XSD.string("foo")
end
test "with integer" do
assert Literal.coerce(42) == XSD.integer(42)
end
test "with float" do
assert Literal.coerce(3.14) == XSD.double(3.14)
end
test "with decimal" do
assert D.from_float(3.14) |> Literal.coerce() == XSD.decimal(3.14)
end
test "with datetime" do
assert DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> Literal.coerce() ==
DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> XSD.datetime()
end
test "with naive datetime" do
assert ~N"2002-04-02T12:00:00" |> Literal.coerce() ==
~N"2002-04-02T12:00:00" |> XSD.datetime()
end
test "with date" do
assert ~D"2002-04-02" |> Literal.coerce() ==
~D"2002-04-02" |> XSD.date()
end
test "with time" do
assert ~T"12:00:00" |> Literal.coerce() ==
~T"12:00:00" |> XSD.time()
end
test "with URI" do
assert URI.parse("http://example.com") |> Literal.coerce() ==
XSD.any_uri("http://example.com")
end
test "with RDF.Literals" do
assert XSD.integer(42) |> Literal.coerce() == XSD.integer(42)
end
test "with RDF datatype Literals" do
assert %XSD.Integer{value: 42} |> Literal.coerce() == XSD.integer(42)
end
test "with inconvertible values" do
assert self() |> Literal.coerce() == nil
end
end
describe "has_datatype?" do
Enum.each literals(~W[all_simple all_plain_lang]a), fn literal ->
@tag literal: literal
test "#{inspect literal} has no datatype", %{literal: literal} do
refute Literal.has_datatype?(literal)
end
end
Enum.each literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} has a datatype", %{literal: literal} do
assert Literal.has_datatype?(literal)
end
end
end
describe "plain?" do
Enum.each literals(:all_plain), fn literal ->
@tag literal: literal
test "#{inspect literal} is plain", %{literal: literal} do
assert Literal.plain?(literal)
end
end
Enum.each literals(:all) -- literals(:all_plain), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} is not plain", %{literal: literal} do
refute Literal.plain?(literal)
end
end
end
describe "simple?" do
Enum.each literals(:all_simple), fn literal ->
@tag literal: literal
test "#{inspect literal} is simple", %{literal: literal} do
assert Literal.simple?(literal)
end
end
Enum.each literals(:all) -- literals(:all_simple), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} is not simple", %{literal: literal} do
refute Literal.simple?(literal)
end
end
end
describe "datatype/1" do
Enum.each literals(:all_simple), fn literal ->
@tag literal: literal
test "simple literal #{inspect literal} has datatype xsd:string", %{literal: literal} do
assert Literal.datatype(literal) == NS.XSD.string
end
end
%{
123 => "integer",
true => "boolean",
false => "boolean",
9223372036854775807 => "integer",
3.1415 => "double",
~D[2017-04-13] => "date",
~N[2017-04-14 15:32:07] => "dateTime",
~T[01:02:03] => "time"
}
|> Enum.each(fn {value, type} ->
@tag data: %{literal: literal = Literal.new(value), type: type}
test "Literal for #{inspect literal} has datatype xsd:#{type}",
%{data: %{literal: literal, type: type}} do
assert Literal.datatype(literal) == apply(NS.XSD, String.to_atom(type), [])
end
end)
end
describe "language" do
Enum.each literals(:all_plain_lang), fn literal ->
@tag literal: literal
test "#{inspect literal} has correct language", %{literal: literal} do
assert Literal.language(literal) == "en"
end
end
Enum.each literals(:all) -- literals(:all_plain_lang), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} has no language", %{literal: literal} do
assert is_nil(Literal.language(literal))
end
end
test "with RDF.LangString literal" do
assert Literal.new("Upper", language: "en") |> Literal.language() == "en"
assert Literal.new("Upper", language: "EN") |> Literal.language() == "en"
assert Literal.new("Upper", language: "") |> Literal.language() == nil
assert Literal.new("Upper", language: nil) |> Literal.language() == nil
end
end
describe "value/1" do
test "with XSD.Datatype literal" do
assert Literal.new("foo") |> Literal.value() == "foo"
assert Literal.new(42) |> Literal.value() == 42
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> Literal.value() == "foo"
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.value() == "foo"
end
end
describe "lexical/1" do
test "with XSD.Datatype literal" do
assert Literal.new("foo") |> Literal.lexical() == "foo"
assert Literal.new(42) |> Literal.lexical() == "42"
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> Literal.lexical() == "foo"
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.lexical() == "foo"
end
end
describe "canonical/1" do
test "with XSD.Datatype literal" do
[
XSD.String.new("foo"),
XSD.Byte.new(42),
]
|> Enum.each(fn
canonical_literal ->
assert Literal.canonical(canonical_literal) == canonical_literal
end)
assert XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42)
assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> XSD.Double.canonical()
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> Literal.canonical() ==
Literal.new("foo", language: "en")
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.canonical() ==
Literal.new("foo", datatype: "http://example.com/dt")
end
end
describe "canonical?/1" do
test "with XSD.Datatype literal" do
assert Literal.new("foo") |> Literal.canonical?() == true
assert Literal.new(42) |> Literal.canonical?() == true
assert Literal.new(3.14) |> Literal.canonical?() == false
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> Literal.canonical?() == true
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.canonical?() == true
end
end
describe "validation" do
test "with XSD.Datatype literal" do
assert Literal.new("foo") |> Literal.valid?() == true
assert Literal.new(42) |> Literal.valid?() == true
assert XSD.Integer.new("foo") |> Literal.valid?() == false
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> Literal.valid?() == true
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.valid?() == true
assert Literal.new("foo", datatype: "") |> Literal.valid?() == false
end
end
describe "equal_value?/2" do
test "with XSD.Datatype literal" do
assert Literal.equal_value?(Literal.new("foo"), Literal.new("foo")) == true
assert Literal.equal_value?(Literal.new(42), XSD.Byte.new(42)) == true
assert Literal.equal_value?(Literal.new("foo"), "foo") == true
assert Literal.equal_value?(Literal.new(42), 42) == true
assert Literal.equal_value?(Literal.new(42), 42.0) == true
assert Literal.equal_value?(Literal.new(false), false) == true
assert Literal.equal_value?(Literal.new(false), true) == false
end
test "with RDF.LangString literal" do
assert Literal.equal_value?(Literal.new("foo", language: "en"),
Literal.new("foo", language: "en")) == true
assert Literal.equal_value?(Literal.new("foo", language: "en"), Literal.new("foo")) == nil
end
test "with generic literal" do
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo", datatype: "http://example.com/dt")) == true
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo")) == nil
end
end
describe "compare/2" do
test "with XSD.Datatype literal" do
assert Literal.compare(Literal.new("foo"), Literal.new("bar")) == :gt
assert Literal.compare(Literal.new(42), XSD.Byte.new(43)) == :lt
end
test "with RDF.LangString literal" do
assert Literal.compare(Literal.new("foo", language: "en"),
Literal.new("bar", language: "en")) == :gt
end
test "with generic literal" do
assert Literal.compare(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("bar", datatype: "http://example.com/dt")) == :gt
end
end
@poem XSD.String.new """
<poem author="Wilhelm Busch">
Kaum hat dies der Hahn gesehen,
Fängt er auch schon an zu krähen:
Kikeriki! Kikikerikih!!
Tak, tak, tak! - da kommen sie.
</poem>
"""
describe "matches?" do
test "without flags" do
[
{~L"abracadabra", ~L"bra", true},
{~L"abracadabra", ~L"^a.*a$", true},
{~L"abracadabra", ~L"^bra", false},
{@poem, ~L"Kaum.*krähen", false},
{@poem, ~L"^Kaum.*gesehen,$", false},
{~L"foobar", ~L"foo$", false},
{~L"noe\u0308l", ~L"noe\\u0308l", true},
{~L"noe\\u0308l", ~L"noe\\\\u0308l", true},
{~L"\u{01D4B8}", ~L"\\U0001D4B8", true},
{~L"\\U0001D4B8", ~L"\\\U0001D4B8", true},
{~L"abracadabra"en, ~L"bra", true},
{"abracadabra", "bra", true},
{XSD.Integer.new("42"), ~L"4", true},
{XSD.Integer.new("42"), ~L"en", false},
]
|> Enum.each(fn {literal, pattern, expected_result} ->
result = Literal.matches?(literal, pattern)
assert result == expected_result,
"expected RDF.Literal.matches?(#{inspect literal}, #{inspect pattern}) to return #{inspect expected_result}, but got #{result}"
end)
end
test "with flags" do
[
{@poem, ~L"Kaum.*krähen", ~L"s", true},
{@poem, ~L"^Kaum.*gesehen,$", ~L"m", true},
{@poem, ~L"kiki", ~L"i", true},
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Literal.matches?(literal, pattern, flags) == result
end)
end
test "with q flag" do
[
{~L"abcd", ~L".*", ~L"q", false},
{~L"Mr. B. Obama", ~L"B. OBAMA", ~L"iq", true},
# If the q flag is used together with the m, s, or x flag, that flag has no effect.
{~L"abcd", ~L".*", ~L"mq", true},
{~L"abcd", ~L".*", ~L"qim", true},
{~L"abcd", ~L".*", ~L"xqm", true},
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Literal.matches?(literal, pattern, flags) == result
end)
end
end
describe "update/2" do
test "it updates value and lexical form" do
assert XSD.string("foo")
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
XSD.string("foobar")
assert XSD.integer(1) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
XSD.integer(2)
assert XSD.byte(42) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
XSD.byte(43)
assert XSD.integer(1)
|> Literal.update(fn i when is_integer(i) -> "0" <> to_string(i) end) ==
XSD.integer("01")
end
test "it does not change the datatype of generic literals" do
assert RDF.literal("foo", datatype: "http://example.com/dt")
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
RDF.literal("foobar", datatype: "http://example.com/dt")
end
test "it does not change the language of language literals" do
assert RDF.langString("foo", language: "en")
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
RDF.langString("foobar", language: "en")
end
test "with as: :lexical opt it passes the lexical form" do
assert XSD.integer(1)
|> Literal.update(fn i when is_binary(i) -> "0" <> i end, as: :lexical) ==
XSD.integer("01")
end
end
describe "String.Chars protocol implementation" do
test "with XSD.Datatype literal" do
assert Literal.new("foo") |> to_string() == "foo"
assert Literal.new(42) |> to_string() == "42"
assert XSD.Integer.new("foo") |> to_string() == "foo"
end
test "with RDF.LangString literal" do
assert Literal.new("foo", language: "en") |> to_string() == "foo"
end
test "with generic literal" do
assert Literal.new("foo", datatype: "http://example.com/dt") |> to_string() == "foo"
assert Literal.new("foo", datatype: "") |> to_string() == "foo"
end
end
end