jsonld-ex/test/unit/encoder_test.exs
2020-05-07 17:21:12 +02:00

459 lines
15 KiB
Elixir

defmodule JSON.LD.EncoderTest do
use ExUnit.Case, async: false
doctest JSON.LD.Encoder
alias RDF.{Dataset, Graph, Description}
alias RDF.NS
import RDF.Sigils
defmodule TestNS do
use RDF.Vocabulary.Namespace
defvocab EX, base_iri: "http://example.com/", terms: [], strict: false
defvocab S, base_iri: "http://schema.org/", terms: [], strict: false
end
alias TestNS.{EX, S}
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.EX}
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.S}
def gets_serialized_to(input, output, opts \\ []) do
data_structs = Keyword.get(opts, :data_structs, [Dataset, Graph])
Enum.each data_structs, fn data_struct ->
assert JSON.LD.Encoder.from_rdf!(data_struct.new(input), opts) == output
end
end
test "pretty printing" do
dataset = Dataset.new {~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>}
assert JSON.LD.Encoder.encode!(dataset) ==
"[{\"@id\":\"http://a/b\",\"http://a/c\":[{\"@id\":\"http://a/d\"}]}]"
assert JSON.LD.Encoder.encode!(dataset, pretty: true) ==
"""
[
{
"@id": "http://a/b",
"http://a/c": [
{
"@id": "http://a/d"
}
]
}
]
""" |> String.trim()
end
test "an empty RDF.Dataset is serialized to an JSON array string" do
assert JSON.LD.Encoder.encode!(Dataset.new) == "[]"
end
describe "simple tests" do
test "One subject IRI object" do
{~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>}
|> gets_serialized_to([%{
"@id" => "http://a/b",
"http://a/c" => [%{"@id" => "http://a/d"}]
}], data_structs: [Dataset, Graph, Description])
end
test "should generate object list" do
[{EX.b, EX.c, EX.d}, {EX.b, EX.c, EX.e}]
|> gets_serialized_to([%{
"@id" => "http://example.com/b",
"http://example.com/c" => [
%{"@id" => "http://example.com/d"},
%{"@id" => "http://example.com/e"}
]
}], data_structs: [Dataset, Graph, Description])
end
test "should generate property list" do
[{EX.b, EX.c, EX.d}, {EX.b, EX.e, EX.f}]
|> gets_serialized_to([%{
"@id" => "http://example.com/b",
"http://example.com/c" => [%{"@id" => "http://example.com/d"}],
"http://example.com/e" => [%{"@id" => "http://example.com/f"}]
}], data_structs: [Dataset, Graph, Description])
end
test "serializes multiple subjects" do
[
{~I<http://test-cases/0001>, NS.RDF.type, ~I<http://www.w3.org/2006/03/test-description#TestCase>},
{~I<http://test-cases/0002>, NS.RDF.type, ~I<http://www.w3.org/2006/03/test-description#TestCase>}
]
|> gets_serialized_to([
%{"@id" => "http://test-cases/0001", "@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]},
%{"@id" => "http://test-cases/0002", "@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]},
])
end
end
describe "literal coercion" do
test "typed literal" do
{EX.a, EX.b, RDF.literal("foo", datatype: EX.d)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "foo", "@type" => "http://example.com/d"}]
}], data_structs: [Dataset, Graph, Description])
end
test "integer" do
{EX.a, EX.b, RDF.literal(1)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => 1}]
}], use_native_types: true)
end
test "integer (non-native)" do
{EX.a, EX.b, RDF.literal(1)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "1","@type" => "http://www.w3.org/2001/XMLSchema#integer"}]
}], use_native_types: false)
end
test "boolean" do
{EX.a, EX.b, RDF.literal(true)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => true}]
}], use_native_types: true)
end
test "boolean (non-native)" do
{EX.a, EX.b, RDF.literal(true)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "true","@type" => "http://www.w3.org/2001/XMLSchema#boolean"}]
}], use_native_types: false)
end
@tag skip: "TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
test "decimal" do
{EX.a, EX.b, RDF.literal(1.0)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "1.0", "@type" => "http://www.w3.org/2001/XMLSchema#decimal"}]
}], use_native_types: true)
end
test "double" do
{EX.a, EX.b, RDF.literal(1.0e0)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => 1.0E0}]
}], use_native_types: true)
end
@tag skip: "TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
test "double (non-native)" do
{EX.a, EX.b, RDF.literal(1.0e0)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "1.0E0", "@type" => "http://www.w3.org/2001/XMLSchema#double"}]
}], use_native_types: false)
end
end
describe "datatyped (non-native) literals" do
%{
integer: 1,
unsignedInt: 1,
nonNegativeInteger: 1,
float: 1.0,
nonPositiveInteger: -1,
negativeInteger: -1,
}
|> Enum.each(fn ({type, _} = data) ->
@tag data: data
test "#{type}", %{data: {type, value}} do
{EX.a, EX.b, RDF.literal(value, datatype: apply(NS.XSD, type, []))}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "#{value}", "@type" => "http://www.w3.org/2001/XMLSchema##{type}"}]
}], use_native_types: false)
end
end)
test "when useNativeTypes" do
{EX.a, EX.b, RDF.literal("foo", datatype: EX.customType)}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "foo", "@type" => to_string(EX.customType)}]
}], use_native_types: true)
end
end
test "encodes language literal" do
{EX.a, EX.b, RDF.literal("foo", language: "en-us")}
|> gets_serialized_to([%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@value" => "foo", "@language" => "en-us"}]
}])
end
describe "blank nodes" do
test "should generate blank nodes" do
{RDF.bnode(:a), EX.a, EX.b}
|> gets_serialized_to([%{
"@id" => "_:a",
"http://example.com/a" => [%{"@id" => "http://example.com/b"}]
}], data_structs: [Dataset, Graph, Description])
end
test "should generate blank nodes as object" do
[
{EX.a, EX.b, RDF.bnode(:a)},
{RDF.bnode(:a), EX.c, EX.d}
]
|> gets_serialized_to([
%{
"@id" => "_:a",
"http://example.com/c" => [%{"@id" => "http://example.com/d"}]
},
%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@id" => "_:a"}]
}
])
end
end
describe "lists" do
%{
"literal list" => {
[
{EX.a, EX.b, RDF.bnode(:e1) },
{RDF.bnode(:e1), NS.RDF.first, ~L"apple"},
{RDF.bnode(:e1), NS.RDF.rest, RDF.bnode(:e2)},
{RDF.bnode(:e2), NS.RDF.first, ~L"banana"},
{RDF.bnode(:e2), NS.RDF.rest, NS.RDF.nil},
],
[%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{
"@list" => [
%{"@value" => "apple"},
%{"@value" => "banana"}
]
}]
}]
},
"iri list" => {
[
{EX.a, EX.b, RDF.bnode(:list)},
{RDF.bnode(:list), NS.RDF.first, EX.c},
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
],
[%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{
"@list" => [
%{"@id" => "http://example.com/c"}
]
}]
}]
},
"empty list" => {
[
{EX.a, EX.b, NS.RDF.nil},
],
[%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@list" => []}]
}]
},
"single element list" => {
[
{EX.a, EX.b, RDF.bnode(:list)},
{RDF.bnode(:list), NS.RDF.first, ~L"apple"},
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
],
[%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@list" => [%{"@value" => "apple"}]}]
}]
},
"single element list without @type" => {
[
{EX.a, EX.b, RDF.bnode(:list)},
{RDF.bnode(:list), NS.RDF.first, RDF.bnode(:a)},
{RDF.bnode(:list), NS.RDF.rest, NS.RDF.nil},
{RDF.bnode(:a), EX.b, ~L"foo"},
],
[
%{
"@id" => "_:a",
"http://example.com/b" => [%{"@value" => "foo"}]
},
%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@list" => [%{"@id" => "_:a"}]}]
},
]
},
"multiple graphs with shared BNode" => {
[
{EX.z, EX.q, RDF.bnode(:z0), EX.G},
{RDF.bnode(:z0), NS.RDF.first, ~L"cell-A", EX.G},
{RDF.bnode(:z0), NS.RDF.rest, RDF.bnode(:z1), EX.G},
{RDF.bnode(:z1), NS.RDF.first, ~L"cell-B", EX.G},
{RDF.bnode(:z1), NS.RDF.rest, NS.RDF.nil, EX.G},
{EX.x, EX.p, RDF.bnode(:z1), EX.G1},
],
[%{
"@id" => "http://www.example.com/G",
"@graph" => [%{
"@id" => "_:z0",
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-A"}],
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@id" => "_:z1"}]
}, %{
"@id" => "_:z1",
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-B"}],
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@list" => []}]
}, %{
"@id" => "http://www.example.com/z",
"http://www.example.com/q" => [%{"@id" => "_:z0"}]
}]
},
%{
"@id" => "http://www.example.com/G1",
"@graph" => [%{
"@id" => "http://www.example.com/x",
"http://www.example.com/p" => [%{"@id" => "_:z1"}]
}]
}]
},
}
|> Enum.each(fn ({title, data}) ->
if title == "multiple graphs with shared BNode" do
@tag skip: "TODO: https://github.com/json-ld/json-ld.org/issues/357"
end
@tag data: data
test title, %{data: {input, output}} do
input |> gets_serialized_to(output)
end
end)
end
describe "quads" do
%{
"simple named graph" => %{
input: {EX.a, EX.b, EX.c, EX.U},
output: [
%{
"@id" => "http://example.com/U",
"@graph" => [%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
}]
},
]
},
"with properties" => %{
input: [
{EX.a, EX.b, EX.c, EX.U},
{EX.U, EX.d, EX.e},
],
output: [
%{
"@id" => "http://example.com/U",
"@graph" => [%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
}],
"http://example.com/d" => [%{"@id" => "http://example.com/e"}]
}
]
},
"with lists" => %{
input: [
{EX.a, EX.b, RDF.bnode(:a), EX.U},
{RDF.bnode(:a), NS.RDF.first, EX.c, EX.U},
{RDF.bnode(:a), NS.RDF.rest, NS.RDF.nil, EX.U},
{EX.U, EX.d, RDF.bnode(:b)},
{RDF.bnode(:b), NS.RDF.first, EX.e},
{RDF.bnode(:b), NS.RDF.rest, NS.RDF.nil},
],
output: [
%{
"@id" => "http://example.com/U",
"@graph" => [%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{"@list" => [%{"@id" => "http://example.com/c"}]}]
}],
"http://example.com/d" => [%{"@list" => [%{"@id" => "http://example.com/e"}]}]
}
]
},
"Two Graphs with same subject and lists" => %{
input: [
{EX.a, EX.b, RDF.bnode(:a), EX.U},
{RDF.bnode(:a), NS.RDF.first, EX.c, EX.U},
{RDF.bnode(:a), NS.RDF.rest, NS.RDF.nil, EX.U},
{EX.a, EX.b, RDF.bnode(:b), EX.V},
{RDF.bnode(:b), NS.RDF.first, EX.e, EX.V},
{RDF.bnode(:b), NS.RDF.rest, NS.RDF.nil, EX.V},
],
output: [
%{
"@id" => "http://example.com/U",
"@graph" => [
%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{
"@list" => [%{"@id" => "http://example.com/c"}]
}]
}
]
},
%{
"@id" => "http://example.com/V",
"@graph" => [
%{
"@id" => "http://example.com/a",
"http://example.com/b" => [%{
"@list" => [%{"@id" => "http://example.com/e"}]
}]
}
]
}
]
},
}
|> Enum.each(fn ({title, data}) ->
@tag data: data
test title, %{data: %{input: input, output: output}} do
input |> gets_serialized_to(output, data_structs: [Dataset])
end
end)
end
describe "problems" do
%{
"xsd:boolean as value" => {
{~I<http://data.wikia.com/terms#playable>, NS.RDFS.range, NS.XSD.boolean},
[%{
"@id" => "http://data.wikia.com/terms#playable",
"http://www.w3.org/2000/01/rdf-schema#range" => [
%{ "@id" => "http://www.w3.org/2001/XMLSchema#boolean" }
]
}]
},
}
|> Enum.each(fn ({title, data}) ->
@tag data: data
test title, %{data: {input, output}} do
input |> gets_serialized_to(output)
end
end)
end
end