Current section
Files
Jump to
Current section
Files
test/elixir_proto_test.exs
defmodule ElixirProtoTest do
use ExUnit.Case, async: false
setup do
# Reset registry for clean tests but re-register test modules
ElixirProto.SchemaRegistry.reset!()
# Manually register test schemas since @after_compile already ran
ElixirProto.SchemaRegistry.force_register_index("myapp.ctx.user", 1)
ElixirProto.SchemaRegistry.force_register_index("myapp.ctx.post", 2)
# Re-register in the main schema registry too
registry = %{
"myapp.ctx.user" => %{
module: ElixirProtoTest.User,
fields: [:id, :name, :email, :age, :active],
field_indices: %{id: 1, name: 2, email: 3, age: 4, active: 5},
index_fields: %{1 => :id, 2 => :name, 3 => :email, 4 => :age, 5 => :active}
},
"myapp.ctx.post" => %{
module: ElixirProtoTest.Post,
fields: [:id, :title, :content, :author_id, :created_at],
field_indices: %{id: 1, title: 2, content: 3, author_id: 4, created_at: 5},
index_fields: %{1 => :id, 2 => :title, 3 => :content, 4 => :author_id, 5 => :created_at}
}
}
:persistent_term.put({ElixirProto.Schema.Registry, :schemas}, registry)
:ok
end
# Test schemas
defmodule User do
use ElixirProto.Schema, name: "myapp.ctx.user", index: 1
defschema [:id, :name, :email, :age, :active]
end
defmodule Post do
use ElixirProto.Schema, name: "myapp.ctx.post", index: 2
defschema [:id, :title, :content, :author_id, :created_at]
end
# Regular struct without ElixirProto schema (for testing error cases)
defmodule UnknownStruct do
defstruct [:field]
end
describe "encode/1" do
test "encodes struct to binary" do
user = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
encoded = ElixirProto.encode(user)
assert is_binary(encoded)
assert byte_size(encoded) > 0
end
test "skips nil fields for space efficiency" do
user_full = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
user_partial = %User{id: 1, name: "Alice"}
encoded_full = ElixirProto.encode(user_full)
encoded_partial = ElixirProto.encode(user_partial)
# Partial should be smaller since it skips nil fields
assert byte_size(encoded_partial) < byte_size(encoded_full)
end
test "raises error for unknown schema" do
unknown = %UnknownStruct{field: "value"}
assert_raise ArgumentError, ~r/Schema not found for module/, fn ->
ElixirProto.encode(unknown)
end
end
test "handles various data types" do
post = %Post{
id: 42,
title: "Test Post",
content: "This is a test post with various data types",
author_id: 1,
created_at: ~D[2023-01-01]
}
encoded = ElixirProto.encode(post)
assert is_binary(encoded)
end
end
describe "decode/1" do
test "decodes binary back to original struct" do
original = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
encoded = ElixirProto.encode(original)
decoded = ElixirProto.decode(encoded)
assert decoded == original
assert decoded.id == 1
assert decoded.name == "Alice"
assert decoded.email == "alice@example.com"
assert decoded.age == 30
assert decoded.active == true
end
test "handles partial structs with nil fields" do
original = %User{id: 1, name: "Alice"}
encoded = ElixirProto.encode(original)
decoded = ElixirProto.decode(encoded)
assert decoded.id == 1
assert decoded.name == "Alice"
assert decoded.email == nil
assert decoded.age == nil
assert decoded.active == nil
end
test "raises error for unknown schema in encoded data" do
# Manually create encoded data with unknown schema index
fake_data = {999, {"test"}} # Use invalid schema index
encoded = fake_data |> :erlang.term_to_binary() |> :zlib.compress()
assert_raise ArgumentError, ~r/Schema index 999 not found/, fn ->
ElixirProto.decode(encoded)
end
end
test "handles various data types correctly" do
original = %Post{
id: 42,
title: "Test Post",
content: "This is a test post",
author_id: 1,
created_at: ~D[2023-01-01]
}
encoded = ElixirProto.encode(original)
decoded = ElixirProto.decode(encoded)
assert decoded == original
assert decoded.id == 42
assert decoded.title == "Test Post"
assert decoded.content == "This is a test post"
assert decoded.author_id == 1
assert decoded.created_at == ~D[2023-01-01]
end
end
describe "round-trip encoding" do
test "multiple encode/decode cycles preserve data" do
original = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
# Multiple round trips
result1 = original |> ElixirProto.encode() |> ElixirProto.decode()
result2 = result1 |> ElixirProto.encode() |> ElixirProto.decode()
result3 = result2 |> ElixirProto.encode() |> ElixirProto.decode()
assert result1 == original
assert result2 == original
assert result3 == original
end
test "works with complex nested data" do
post = %Post{
id: 1,
title: "Complex Post",
content: %{
"text" => "Hello world",
"metadata" => %{"tags" => ["elixir", "proto"], "count" => 42}
},
author_id: 123,
created_at: DateTime.utc_now()
}
encoded = ElixirProto.encode(post)
decoded = ElixirProto.decode(encoded)
assert decoded == post
assert decoded.content["text"] == "Hello world"
assert decoded.content["metadata"]["tags"] == ["elixir", "proto"]
end
end
describe "space efficiency" do
test "encoded size is reasonable" do
user = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
# Compare with raw term_to_binary
proto_encoded = ElixirProto.encode(user)
raw_encoded = :erlang.term_to_binary(user)
# ElixirProto should be reasonably sized (compression helps)
assert byte_size(proto_encoded) > 0
assert byte_size(raw_encoded) > 0 # Just ensure both work
end
test "nil field omission saves space" do
user_with_nils = %User{id: 1, name: "Alice"} # email, age, active are nil
user_without_nils = %User{id: 1, name: "Alice", email: "", age: 0, active: false}
encoded_with_nils = ElixirProto.encode(user_with_nils)
encoded_without_nils = ElixirProto.encode(user_without_nils)
# Version with nils should be smaller
assert byte_size(encoded_with_nils) < byte_size(encoded_without_nils)
end
end
describe "demonstration" do
test "README example usage works correctly" do
# Create test data as shown in README
user = %User{id: 1, name: "Alice", email: "alice@example.com", age: 30, active: true}
# Serialize
encoded = ElixirProto.encode(user)
assert is_binary(encoded)
assert byte_size(encoded) > 0
# Deserialize
decoded = ElixirProto.decode(encoded)
assert decoded == user
# Test the specific example from README
assert decoded.id == 1
assert decoded.name == "Alice"
assert decoded.email == "alice@example.com"
assert decoded.age == 30
assert decoded.active == true
# Demonstrate space efficiency with nil omission
user_partial = %User{id: 1, name: "Alice"} # Only id and name set
encoded_partial = ElixirProto.encode(user_partial)
decoded_partial = ElixirProto.decode(encoded_partial)
# Partial user should decode correctly with nil for missing fields
assert decoded_partial.id == 1
assert decoded_partial.name == "Alice"
assert decoded_partial.email == nil
assert decoded_partial.age == nil
assert decoded_partial.active == nil
# Partial encoding should be smaller
assert byte_size(encoded_partial) < byte_size(encoded)
end
end
end