Files
gmserialization/test/gmser_schema_export_tests.erl
T
Ulf Wiger b2e9af0acb
Gajumaru Serialization Tests / tests (push) Successful in 48m24s
Add ASN.1 schema export from static serialization templates.
Introduce gmser_schema_export to emit portable ASN.1 type definitions
from tag/version/template triples (RLP remains the wire format). Export
tag/1 and rev_tag/1 for discovery and naming, with docs and eunit coverage.
2026-07-22 11:26:49 +02:00

186 lines
7.0 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @copyright (C) 2026, QPQ AG
%%% @doc
%%% EUnit tests for gmser_schema_export.
%%% @end
%%%-------------------------------------------------------------------
-module(gmser_schema_export_tests).
-include_lib("eunit/include/eunit.hrl").
%% Spend template as in aec_spend_tx (kept local so tests do not
%% depend on aecore).
spend_template() ->
[ {sender_id, id}
, {recipient_id, id}
, {amount, int}
, {gas_price, int}
, {gas, int}
, {ttl, int}
, {nonce, int}
, {payload, binary}
].
signed_tx_template() ->
[ {signatures, [binary]}
, {transaction, binary}
].
name_update_v1_template() ->
[ {account_id, id}
, {nonce, int}
, {name_id, id}
, {name_ttl, int}
, {pointers, [{binary, id}]}
, {client_ttl, int}
, {gas_price, int}
, {gas, int}
, {ttl, int}
].
match(Bin, Re) when is_binary(Bin) ->
re:run(Bin, Re, [{capture, none}]).
%%%===================================================================
%%% Naming
%%%===================================================================
type_name_from_registered_tag_test() ->
Tag = gmser_chain_objects:tag(spend_tx),
?assertEqual(12, Tag),
?assertEqual("SpendTxV1", gmser_schema_export:type_name(Tag, 1)).
type_name_from_atom_test() ->
?assertEqual("SpendTxV1",
gmser_schema_export:type_name(spend_tx, 12, 1)),
?assertEqual("SignedTxV1",
gmser_schema_export:type_name(<<"signed_tx">>, 11, 1)).
type_name_unknown_tag_test() ->
?assertEqual("Object9999V2", gmser_schema_export:type_name(9999, 2)).
%%%===================================================================
%%% Single object export
%%%===================================================================
spend_object_export_test() ->
Tag = gmser_chain_objects:tag(spend_tx),
{ok, "SpendTxV1", Defs} =
gmser_schema_export:object_to_asn1(Tag, 1, spend_template()),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"SpendTxV1 ::= SEQUENCE \\{">>)),
?assertEqual(match, match(Text, <<"tag INTEGER \\(12\\)">>)),
?assertEqual(match, match(Text, <<"vsn INTEGER \\(1\\)">>)),
?assertEqual(match, match(Text, <<"senderId Id">>)),
?assertEqual(match, match(Text, <<"recipientId Id">>)),
?assertEqual(match, match(Text, <<"amount BigInt">>)),
?assertEqual(match, match(Text, <<"gasPrice BigInt">>)),
?assertEqual(match, match(Text, <<"payload OCTET STRING">>)),
%% last field must not have a trailing comma before closing brace
?assertEqual(match, match(Text, <<"payload OCTET STRING\n}">>)).
spend_without_tag_vsn_fields_test() ->
Tag = gmser_chain_objects:tag(spend_tx),
{ok, "SpendTxV1", Defs} =
gmser_schema_export:object_to_asn1(
Tag, 1, spend_template(), #{include_tag_vsn => false}),
Text = iolist_to_binary(Defs),
?assertEqual(nomatch, match(Text, <<"tag INTEGER">>)),
?assertEqual(match, match(Text, <<"senderId Id">>)).
explicit_type_name_test() ->
{ok, "MySpendV1", Defs} =
gmser_schema_export:object_to_asn1(
12, 1, spend_template(), #{type_name => "MySpend"}),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"MySpendV1 ::= SEQUENCE">>)).
%%%===================================================================
%%% Nested types
%%%===================================================================
list_and_tuple_export_test() ->
Tag = gmser_chain_objects:tag(name_update_tx),
{ok, TypeName, Defs} =
gmser_schema_export:object_to_asn1(Tag, 1, name_update_v1_template()),
?assertEqual("NameUpdateTxV1", TypeName),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"pointers SEQUENCE OF SEQUENCE \\{">>)),
?assertEqual(match, match(Text, <<"c1 OCTET STRING">>)),
?assertEqual(match, match(Text, <<"c2 Id">>)).
signed_tx_list_field_test() ->
Tag = gmser_chain_objects:tag(signed_tx),
{ok, "SignedTxV1", Defs} =
gmser_schema_export:object_to_asn1(Tag, 1, signed_tx_template()),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"signatures SEQUENCE OF OCTET STRING">>)),
?assertEqual(match, match(Text, <<"transaction OCTET STRING">>)).
map_items_export_test() ->
Template = [{body, #{items => [{foo, int}, {bar, binary}]}}],
{ok, _, Defs} =
gmser_schema_export:object_to_asn1(
10, 1, Template, #{type_name => account, include_tag_vsn => false}),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"body SEQUENCE \\{">>)),
?assertEqual(match, match(Text, <<"foo BigInt">>)),
?assertEqual(match, match(Text, <<"bar OCTET STRING">>)).
uint_types_export_test() ->
Template = [{a, uint8}, {b, uint16}, {c, uint32},
{d, uint64}, {e, uint128}, {f, bool}],
{ok, _, Defs} =
gmser_schema_export:object_to_asn1(
1, 1, Template,
#{type_name => "Uints", include_tag_vsn => false}),
Text = iolist_to_binary(Defs),
?assertEqual(match, match(Text, <<"a Uint8">>)),
?assertEqual(match, match(Text, <<"b Uint16">>)),
?assertEqual(match, match(Text, <<"c Uint32">>)),
?assertEqual(match, match(Text, <<"d Uint64">>)),
?assertEqual(match, match(Text, <<"e Uint128">>)),
?assertEqual(match, match(Text, <<"f BOOLEAN">>)).
%%%===================================================================
%%% Full module
%%%===================================================================
module_export_test() ->
Objects =
[ {spend_tx,
gmser_chain_objects:tag(spend_tx),
1,
spend_template()}
, {gmser_chain_objects:tag(signed_tx), 1, signed_tx_template()}
],
{ok, Iodata} =
gmser_schema_export:module_to_asn1('GajumaruChainObjects', Objects),
Text = iolist_to_binary(Iodata),
?assertEqual(match, match(Text, <<"GajumaruChainObjects DEFINITIONS">>)),
?assertEqual(match, match(Text, <<"Id ::= SEQUENCE">>)),
?assertEqual(match, match(Text, <<"BigInt ::= INTEGER">>)),
?assertEqual(match, match(Text, <<"SpendTxV1 ::= SEQUENCE">>)),
?assertEqual(match, match(Text, <<"SignedTxV1 ::= SEQUENCE">>)),
?assertEqual(match, match(Text, <<"tag 12 vsn 1 -> SpendTxV1">>)),
?assertEqual(match, match(Text, <<"tag 11 vsn 1 -> SignedTxV1">>)),
?assertEqual(match, re:run(Text, <<"^END">>, [{capture, none}, multiline])).
write_module_test() ->
Tmp = filename:join(
filename:basedir(user_cache, "gmser_schema_export"),
"gmser_schema_export_test.asn"),
ok = filelib:ensure_dir(Tmp),
Objects =
[{gmser_chain_objects:tag(spend_tx), 1, spend_template()}],
?assertEqual(ok, gmser_schema_export:write_module(
Tmp, 'GajumaruChainObjects', Objects)),
{ok, Bin} = file:read_file(Tmp),
?assertEqual(match, match(Bin, <<"SpendTxV1 ::= SEQUENCE">>)),
ok = file:delete(Tmp).
bad_template_test() ->
?assertError({unsupported_template_type, float},
gmser_schema_export:object_to_asn1(
1, 1, [{x, float}])).