hb_message_test_vectors.erl - Message Format Test Vectors
Overview
Purpose: Comprehensive test vectors for message format conversions
Module: hb_message_test_vectors
Framework: EUnit
Coverage: All message codecs and conversion paths
This module provides extensive test vectors to ensure correct functioning of message format conversions in HyperBEAM. It validates conversion between structured messages, TABM format, ANS-104 data items, HTTP signed messages, and flat maps.
Test Coverage
- Format Conversions: Structured ↔ TABM ↔ ANS-104 ↔ HTTP Sig ↔ Flat
- ID Calculation: Signed vs unsigned IDs
- Signature Verification: Valid and invalid signatures
- Round-trip Tests: Format A → Format B → Format A
- Edge Cases: Empty messages, nested structures, special characters
- Commitment Handling: Multiple signers, device-specific commitments
Dependencies
- Testing:
eunit - HyperBEAM:
hb_message,hb_util,hb_opts,hb_cache - Codecs: All
dev_codec_*modules - Arweave:
ar_wallet,ar_bundles - Includes:
include/hb.hrl
Test Categories
1. Format Conversion Tests
Purpose: Validate conversions between all supported formats
Test Pattern:format_conversion_test() ->
% Create message in format A
MsgA = create_test_message(),
% Convert A → B
MsgB = hb_message:convert(MsgA, FormatB, FormatA, #{}),
% Convert B → A
MsgARoundtrip = hb_message:convert(MsgB, FormatA, FormatB, #{}),
% Verify roundtrip preserves data
?assertEqual(MsgA, MsgARoundtrip).2. ID Calculation Tests
Purpose: Verify message ID consistency and determinism
Test Cases:- Unsigned ID calculation
- Signed ID calculation
- ID determinism (same input → same ID)
- ID difference (unsigned ≠ signed)
- Multi-signer IDs
3. Signature Tests
Purpose: Validate signing and verification
Test Cases:- Valid signature verification
- Invalid signature detection
- Tampered message detection
- Multi-device signatures
- Signature preservation across conversions
4. Round-trip Tests
Purpose: Ensure lossless conversion cycles
Test Paths:Structured → TABM → Structured
Structured → ANS-104 → Structured
Structured → HTTPSig → Structured
ANS-104 → TABM → ANS-104
HTTPSig → TABM → HTTPSig5. Edge Case Tests
Purpose: Handle unusual inputs correctly
Test Cases:- Empty messages
- Very large messages
- Deep nesting
- Special characters in keys/values
- Binary data
- Unicode strings
- Null/undefined values
Example Test Vectors
Basic Conversion
basic_conversion_test() ->
Original = #{
<<"type">> => <<"Message">>,
<<"data">> => <<"Hello, World!">>
},
% Structured → TABM
TABM = hb_message:convert(Original, tabm, #{}),
?assert(is_map(TABM)),
?assertEqual(<<"Hello, World!">>, maps:get(<<"data">>, TABM)),
% TABM → Structured
Recovered = hb_message:convert(TABM, <<"structured@1.0">>, tabm, #{}),
?assertEqual(Original, Recovered).Signed Message
signed_message_test() ->
Wallet = ar_wallet:new(),
Original = #{<<"data">> => <<"value">>},
% Sign message
Signed = hb_message:commit(Original, #{priv_wallet => Wallet}),
% Verify signature
?assert(hb_message:verify(Signed, all, #{})),
% Extract unsigned
Unsigned = hb_message:uncommitted(Signed),
?assertEqual(Original, Unsigned),
% IDs differ
SignedID = hb_message:id(Signed, signed, #{}),
UnsignedID = hb_message:id(Signed, unsigned, #{}),
?assertNotEqual(SignedID, UnsignedID).ANS-104 Conversion
ans104_conversion_test() ->
Wallet = ar_wallet:new(),
Original = #{
<<"type">> => <<"Message">>,
<<"data">> => <<"Test data">>,
<<"tags">> => [
{<<"Tag1">>, <<"Value1">>},
{<<"Tag2">>, <<"Value2">>}
]
},
% Convert to ANS-104
ANS104 = hb_message:convert(
Original,
<<"ans104@1.0">>,
<<"structured@1.0">>,
#{}
),
% Should be TX record
?assert(is_record(ANS104, tx)),
% Sign ANS-104
Signed = ar_bundles:sign_item(ANS104, Wallet),
% Verify
?assert(ar_bundles:verify_item(Signed)),
% Convert back
Recovered = hb_message:convert(
Signed,
<<"structured@1.0">>,
<<"ans104@1.0">>,
#{}
),
% Data preserved
?assertEqual(<<"Test data">>, maps:get(<<"data">>, Recovered)).HTTP Signed Message
httpsig_test() ->
Wallet = ar_wallet:new(),
Original = #{
<<"method">> => <<"POST">>,
<<"path">> => <<"/api/endpoint">>,
<<"body">> => <<"Request payload">>
},
% Commit with HTTPSig
Signed = hb_message:commit(
Original,
#{priv_wallet => Wallet},
#{<<"device">> => <<"httpsig@1.0">>}
),
% Has commitments
?assert(maps:is_key(<<"commitments">>, Signed)),
% Verify
?assert(hb_message:verify(Signed, all, #{})),
% Convert to HTTPSig format
HTTPSig = hb_message:convert(Signed, <<"httpsig@1.0">>, #{}),
% Has signature header
?assert(maps:is_key(<<"signature">>, HTTPSig)).Nested Structure
nested_structure_test() ->
Complex = #{
<<"level1">> => #{
<<"level2">> => #{
<<"level3">> => #{
<<"data">> => <<"deep value">>
}
},
<<"array">> => [1, 2, 3]
}
},
% Structured → TABM → Structured
TABM = hb_message:convert(Complex, tabm, #{}),
Recovered = hb_message:convert(TABM, <<"structured@1.0">>, tabm, #{}),
% Deep value preserved
DeepValue = maps:get(
<<"data">>,
maps:get(
<<"level3">>,
maps:get(
<<"level2">>,
maps:get(<<"level1">>, Recovered)
)
)
),
?assertEqual(<<"deep value">>, DeepValue).Unicode and Special Characters
unicode_test() ->
Unicode = #{
<<"emoji">> => <<"🚀🌟💻"/utf8>>,
<<"chinese">> => <<"你好世界"/utf8>>,
<<"arabic">> => <<"مرحبا"/utf8>>,
<<"special">> => <<"!@#$%^&*()"/utf8>>
},
% All formats should preserve Unicode
Formats = [
tabm,
<<"ans104@1.0">>,
<<"httpsig@1.0">>,
<<"structured@1.0">>
],
lists:foreach(
fun(Format) ->
Converted = hb_message:convert(Unicode, Format, #{}),
Back = hb_message:convert(Converted, <<"structured@1.0">>, Format, #{}),
?assertEqual(Unicode, Back)
end,
Formats
).Empty Message
empty_message_test() ->
Empty = #{},
% Should handle empty message
TABM = hb_message:convert(Empty, tabm, #{}),
?assertEqual(#{}, TABM),
% Round-trip
Recovered = hb_message:convert(TABM, <<"structured@1.0">>, tabm, #{}),
?assertEqual(Empty, Recovered).Large Message
large_message_test() ->
% Create large binary
LargeData = binary:copy(<<"test">>, 1000000), % 4MB
Large = #{
<<"data">> => LargeData,
<<"metadata">> => #{
<<"size">> => byte_size(LargeData)
}
},
% Should handle large messages
ANS104 = hb_message:convert(Large, <<"ans104@1.0">>, #{}),
?assert(is_record(ANS104, tx)),
% Data preserved
Recovered = hb_message:convert(ANS104, <<"structured@1.0">>, <<"ans104@1.0">>, #{}),
?assertEqual(LargeData, maps:get(<<"data">>, Recovered)).Test Utilities
Message Generators
% Generate test message with specific properties
generate_test_message(Props) ->
Default = #{
<<"type">> => <<"Test">>,
<<"timestamp">> => os:system_time(millisecond),
<<"data">> => <<"test data">>
},
maps:merge(Default, Props).
% Generate signed test message
generate_signed_message(Props) ->
Wallet = ar_wallet:new(),
Msg = generate_test_message(Props),
hb_message:commit(Msg, #{priv_wallet => Wallet}).
% Generate multi-signer message
generate_multisig_message(NumSigners) ->
Msg = generate_test_message(#{}),
lists:foldl(
fun(_, AccMsg) ->
Wallet = ar_wallet:new(),
hb_message:commit(AccMsg, #{priv_wallet => Wallet})
end,
Msg,
lists:seq(1, NumSigners)
).Verification Helpers
% Verify round-trip conversion
verify_roundtrip(Msg, Format) ->
Converted = hb_message:convert(Msg, Format, #{}),
Recovered = hb_message:convert(Converted, <<"structured@1.0">>, Format, #{}),
Msg =:= Recovered.
% Verify all format round-trips
verify_all_roundtrips(Msg) ->
Formats = [
tabm,
<<"ans104@1.0">>,
<<"httpsig@1.0">>,
<<"flat@1.0">>
],
lists:all(
fun(Format) -> verify_roundtrip(Msg, Format) end,
Formats
).Test Organization
Test Suites
- Conversion Suite: Format conversion tests
- ID Suite: ID calculation and consistency
- Signature Suite: Signing and verification
- Round-trip Suite: Lossless conversion cycles
- Edge Case Suite: Unusual inputs and edge cases
- Performance Suite: Large message handling
Naming Convention
% Pattern: <operation>_<format>_<case>_test
structured_to_tabm_basic_test() -> ...
ans104_roundtrip_signed_test() -> ...
httpsig_verify_tampered_test() -> ...
id_calculation_deterministic_test() -> ...Running Tests
# Run all test vectors
rebar3 eunit --module=hb_message_test_vectors
# Run specific test
rebar3 eunit --module=hb_message_test_vectors --test=basic_conversion_test
# Run with verbose output
rebar3 eunit --module=hb_message_test_vectors --verboseReferences
- Message Module -
hb_message.erl - Codecs -
dev_codec_*.erlmodules - AO Core -
hb_ao.erl - Arweave -
ar_wallet.erl,ar_bundles.erl - EUnit - EUnit Documentation
Notes
- Comprehensive: Tests all format combinations
- Deterministic: Same input always produces same output
- Round-trip: All conversions should be lossless
- Signatures: Multiple signature types tested
- Edge Cases: Handles empty, large, and complex messages
- Unicode: Full Unicode support verified
- ID Consistency: Unsigned vs signed IDs tested
- Multi-signer: Multiple commitment devices tested
- Binary Safety: Binary data preserved correctly
- Nested Structures: Deep nesting handled correctly
- Performance: Large message handling verified
- Error Cases: Invalid signatures detected
- Type Preservation: Data types maintained across conversions
- Metadata: Message metadata preserved
- Future-proof: Easy to add new codec tests