-- Tests for google.protobuf.Struct / Value / ListValue. -- Lua surface: -- * box.NULL ↔ null_value -- * boolean ↔ bool_value -- * number ↔ number_value (double) -- * string ↔ string_value -- * table (hash-like) ↔ Struct -- * table (array-like or pb.wkt.list-tagged) ↔ ListValue local t = require('luatest') local pb = require('pb') local wkt = pb.wkt local function hex(s) local out = {} for i = 1, #s do out[i] = string.format('%02x', s:byte(i)) end return table.concat(out) end -- --------------------------------------------------------------------------- -- Direct Value wire format -- --------------------------------------------------------------------------- local g = t.group('struct_value.wire') g.test_null_round_trip = function() local enc = wkt.Value_encode(pb.NULL) t.assert_equals(hex(enc), '0800', 'NULL encodes as field 1 (null_value) varint 0') t.assert_equals(wkt.Value_decode(enc), pb.NULL) end g.test_bool_branches = function() t.assert_equals(hex(wkt.Value_encode(true)), '2001') t.assert_equals(hex(wkt.Value_encode(false)), '2000') t.assert_equals(wkt.Value_decode(wkt.Value_encode(true)), true) t.assert_equals(wkt.Value_decode(wkt.Value_encode(false)), false) end g.test_number_round_trip = function() local samples = {0, 1, -1, 3.14159, 1e100, -1e-10, math.huge} for _, n in ipairs(samples) do local r = wkt.Value_decode(wkt.Value_encode(n)) if n ~= n then t.assert(r ~= r, 'NaN round trip preserves NaN-ness') else t.assert_equals(r, n) end end end g.test_string_round_trip = function() for _, s in ipairs({'', 'hello', 'unicode: ünîcö∂é', string.rep('x', 1024)}) do t.assert_equals(wkt.Value_decode(wkt.Value_encode(s)), s) end end g.test_empty_buf_decodes_as_null = function() -- Empty Value{} on wire has no kind set → conventional reading is NULL. t.assert_equals(wkt.Value_decode(''), pb.NULL) end g.test_unknown_field_in_value_falls_back_to_null = function() -- A Value with only an unknown field id (e.g. field 99 varint). local buf = '\x98\x06\x2a' -- tag(99, varint), value 42 t.assert_equals(wkt.Value_decode(buf), pb.NULL) end -- --------------------------------------------------------------------------- -- Struct -- --------------------------------------------------------------------------- local gs = t.group('struct_value.struct') gs.test_empty_struct = function() t.assert_equals(wkt.Struct_encode({}), '') local dec = wkt.Struct_decode('') t.assert_equals(next(dec), nil) end gs.test_scalar_struct_round_trip = function() local s = {name = 'Alice', age = 30, active = true, deleted = pb.NULL} local dec = wkt.Struct_decode(wkt.Struct_encode(s)) t.assert_equals(dec.name, 'Alice') t.assert_equals(dec.age, 30) t.assert_equals(dec.active, true) t.assert_equals(dec.deleted, pb.NULL) end gs.test_nested_struct = function() local s = {outer = {inner = {leaf = 42}}} local dec = wkt.Struct_decode(wkt.Struct_encode(s)) t.assert_equals(dec.outer.inner.leaf, 42) end -- --------------------------------------------------------------------------- -- ListValue -- --------------------------------------------------------------------------- local gl = t.group('struct_value.list') gl.test_empty_list = function() t.assert_equals(wkt.ListValue_encode(wkt.list({})), '') local dec = wkt.ListValue_decode('') t.assert_equals(#dec, 0) end gl.test_mixed_element_list_round_trip = function() local list = wkt.list({1, 'two', true, pb.NULL, wkt.struct({k = 'v'})}) local dec = wkt.ListValue_decode(wkt.ListValue_encode(list)) t.assert_equals(#dec, 5) t.assert_equals(dec[1], 1) t.assert_equals(dec[2], 'two') t.assert_equals(dec[3], true) t.assert_equals(dec[4], pb.NULL) t.assert_equals(dec[5].k, 'v') end gl.test_array_like_table_auto_routes_to_list = function() -- A plain {1,2,3} (no tagging) is detected as list because t[1] ~= nil. local v_buf = wkt.Value_encode({10, 20, 30}) local dec = wkt.Value_decode(v_buf) t.assert_equals(type(dec), 'table') t.assert_equals(#dec, 3) t.assert_equals(dec[2], 20) end gl.test_empty_table_routes_to_struct = function() -- Empty {} → Struct (more common dict-like case). local v_buf = wkt.Value_encode({}) -- Tag should be 0x2a (field 5, struct_value). t.assert_equals(v_buf:byte(1), 0x2a) end -- --------------------------------------------------------------------------- -- Round-trip Value through wire then back; verify Struct/List tagging survives. -- --------------------------------------------------------------------------- local gt = t.group('struct_value.tagging') gt.test_decoded_struct_round_trips_byte_equal = function() local original = wkt.struct({a = 1, b = 'hi'}) local enc1 = wkt.Value_encode(original) local roundtrip = wkt.Value_decode(enc1) local enc2 = wkt.Value_encode(roundtrip) t.assert_equals(hex(enc1), hex(enc2)) end gt.test_decoded_empty_list_round_trips_byte_equal = function() -- Without tagging, an empty list would re-encode as struct. Decoded -- ListValue keeps its LIST_MT so the next encode reproduces the wire. local enc1 = wkt.Value_encode(wkt.list({})) local roundtrip = wkt.Value_decode(enc1) local enc2 = wkt.Value_encode(roundtrip) t.assert_equals(hex(enc1), hex(enc2)) end -- --------------------------------------------------------------------------- -- End-to-end: generated Event message exercising payload/attribute/tags. -- --------------------------------------------------------------------------- for _, mode in ipairs({'full', 'runtime'}) do local ge = t.group('struct_value.event.' .. mode) local hello = require(mode .. '.hello.hello_pb') ge.test_event_with_struct_and_list = function() local e = { title = 'launch', payload = wkt.struct({region = 'eu-west-1', priority = 2, urgent = true}), attribute = 'experimental', tags = wkt.list({'alpha', 'beta', 99}), } local dec = hello.Event_decode(hello.Event_encode(e)) t.assert_equals(dec.title, 'launch') t.assert_equals(dec.payload.region, 'eu-west-1') t.assert_equals(dec.payload.priority, 2) t.assert_equals(dec.payload.urgent, true) t.assert_equals(dec.attribute, 'experimental') t.assert_equals(#dec.tags, 3) t.assert_equals(dec.tags[1], 'alpha') t.assert_equals(dec.tags[3], 99) end ge.test_event_attribute_with_null = function() local e = {title = 'x', attribute = pb.NULL} local dec = hello.Event_decode(hello.Event_encode(e)) t.assert_equals(dec.attribute, pb.NULL) end ge.test_event_with_nested_struct = function() local e = { payload = wkt.struct({ stats = wkt.struct({calls = 7, errors = 0}), hosts = wkt.list({'a.example', 'b.example'}), }), } local dec = hello.Event_decode(hello.Event_encode(e)) t.assert_equals(dec.payload.stats.calls, 7) t.assert_equals(dec.payload.stats.errors, 0) t.assert_equals(#dec.payload.hosts, 2) t.assert_equals(dec.payload.hosts[2], 'b.example') end end -- --------------------------------------------------------------------------- -- Parity: full and runtime mode produce identical bytes for Event. -- --------------------------------------------------------------------------- local gp = t.group('struct_value.parity') local hello_full = require('full.hello.hello_pb') local hello_runtime = require('runtime.hello.hello_pb') gp.test_event_bytes_match = function() local e = { title = 'parity', payload = wkt.struct({k = 'v', n = 42, flag = pb.NULL}), attribute = wkt.list({1, 2, 3}), tags = wkt.list({'x', 'y'}), } t.assert_equals(hex(hello_full.Event_encode(e)), hex(hello_runtime.Event_encode(e))) end