337 lines
8 KiB
Go
337 lines
8 KiB
Go
// Copyright (c) 2024-2026 Lethean Contributors
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package levin
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestEncodeStorage_EmptySection(t *testing.T) {
|
|
s := Section{}
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
// 9-byte header + 1-byte varint(0) = 10 bytes.
|
|
assert.Len(t, data, 10)
|
|
|
|
// Verify storage header signatures.
|
|
assert.Equal(t, byte(0x01), data[0])
|
|
assert.Equal(t, byte(0x11), data[1])
|
|
assert.Equal(t, byte(0x01), data[2])
|
|
assert.Equal(t, byte(0x01), data[3])
|
|
assert.Equal(t, byte(0x01), data[4])
|
|
assert.Equal(t, byte(0x01), data[5])
|
|
assert.Equal(t, byte(0x02), data[6])
|
|
assert.Equal(t, byte(0x01), data[7])
|
|
|
|
// Version byte.
|
|
assert.Equal(t, byte(1), data[8])
|
|
|
|
// Entry count varint: 0.
|
|
assert.Equal(t, byte(0x00), data[9])
|
|
}
|
|
|
|
func TestStorage_PrimitivesRoundTrip(t *testing.T) {
|
|
s := Section{
|
|
"u64": Uint64Val(0xDEADBEEFCAFEBABE),
|
|
"u32": Uint32Val(0xCAFEBABE),
|
|
"u16": Uint16Val(0xBEEF),
|
|
"u8": Uint8Val(42),
|
|
"i64": Int64Val(-9223372036854775808),
|
|
"i32": Int32Val(-2147483648),
|
|
"i16": Int16Val(-32768),
|
|
"i8": Int8Val(-128),
|
|
"flag": BoolVal(true),
|
|
"height": StringVal([]byte("hello world")),
|
|
"pi": DoubleVal(3.141592653589793),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
// Unsigned integers.
|
|
u64, err := decoded["u64"].AsUint64()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(0xDEADBEEFCAFEBABE), u64)
|
|
|
|
u32, err := decoded["u32"].AsUint32()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint32(0xCAFEBABE), u32)
|
|
|
|
u16, err := decoded["u16"].AsUint16()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint16(0xBEEF), u16)
|
|
|
|
u8, err := decoded["u8"].AsUint8()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint8(42), u8)
|
|
|
|
// Signed integers.
|
|
i64, err := decoded["i64"].AsInt64()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(-9223372036854775808), i64)
|
|
|
|
i32, err := decoded["i32"].AsInt32()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int32(-2147483648), i32)
|
|
|
|
i16, err := decoded["i16"].AsInt16()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int16(-32768), i16)
|
|
|
|
i8, err := decoded["i8"].AsInt8()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int8(-128), i8)
|
|
|
|
// Bool.
|
|
flag, err := decoded["flag"].AsBool()
|
|
require.NoError(t, err)
|
|
assert.True(t, flag)
|
|
|
|
// String.
|
|
str, err := decoded["height"].AsString()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("hello world"), str)
|
|
|
|
// Double.
|
|
pi, err := decoded["pi"].AsDouble()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3.141592653589793, pi)
|
|
}
|
|
|
|
func TestStorage_NestedObject(t *testing.T) {
|
|
inner := Section{
|
|
"port": Uint16Val(18080),
|
|
"host": StringVal([]byte("127.0.0.1")),
|
|
}
|
|
outer := Section{
|
|
"node_data": ObjectVal(inner),
|
|
"version": Uint32Val(1),
|
|
}
|
|
|
|
data, err := EncodeStorage(outer)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
ver, err := decoded["version"].AsUint32()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint32(1), ver)
|
|
|
|
innerDec, err := decoded["node_data"].AsSection()
|
|
require.NoError(t, err)
|
|
|
|
port, err := innerDec["port"].AsUint16()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint16(18080), port)
|
|
|
|
host, err := innerDec["host"].AsString()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("127.0.0.1"), host)
|
|
}
|
|
|
|
func TestStorage_Uint64Array(t *testing.T) {
|
|
s := Section{
|
|
"heights": Uint64ArrayVal([]uint64{10, 20, 30}),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
arr, err := decoded["heights"].AsUint64Array()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []uint64{10, 20, 30}, arr)
|
|
}
|
|
|
|
func TestStorage_StringArray(t *testing.T) {
|
|
s := Section{
|
|
"peers": StringArrayVal([][]byte{[]byte("foo"), []byte("bar")}),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
arr, err := decoded["peers"].AsStringArray()
|
|
require.NoError(t, err)
|
|
require.Len(t, arr, 2)
|
|
assert.Equal(t, []byte("foo"), arr[0])
|
|
assert.Equal(t, []byte("bar"), arr[1])
|
|
}
|
|
|
|
func TestStorage_ObjectArray(t *testing.T) {
|
|
sections := []Section{
|
|
{"id": Uint32Val(1), "name": StringVal([]byte("alice"))},
|
|
{"id": Uint32Val(2), "name": StringVal([]byte("bob"))},
|
|
}
|
|
s := Section{
|
|
"nodes": ObjectArrayVal(sections),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
arr, err := decoded["nodes"].AsSectionArray()
|
|
require.NoError(t, err)
|
|
require.Len(t, arr, 2)
|
|
|
|
id1, err := arr[0]["id"].AsUint32()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint32(1), id1)
|
|
|
|
name1, err := arr[0]["name"].AsString()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("alice"), name1)
|
|
|
|
id2, err := arr[1]["id"].AsUint32()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint32(2), id2)
|
|
|
|
name2, err := arr[1]["name"].AsString()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("bob"), name2)
|
|
}
|
|
|
|
func TestDecodeStorage_BadSignature(t *testing.T) {
|
|
// Corrupt the first 4 bytes.
|
|
data := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00}
|
|
_, err := DecodeStorage(data)
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrStorageBadSignature)
|
|
}
|
|
|
|
func TestDecodeStorage_TooShort(t *testing.T) {
|
|
_, err := DecodeStorage([]byte{0x01, 0x11})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrStorageTruncated)
|
|
}
|
|
|
|
func TestStorage_ByteIdenticalReencode(t *testing.T) {
|
|
s := Section{
|
|
"alpha": Uint64Val(999),
|
|
"bravo": StringVal([]byte("deterministic")),
|
|
"charlie": BoolVal(false),
|
|
"delta": ObjectVal(Section{
|
|
"x": Int32Val(-42),
|
|
"y": Int32Val(100),
|
|
}),
|
|
"echo": Uint64ArrayVal([]uint64{1, 2, 3}),
|
|
}
|
|
|
|
data1, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data1)
|
|
require.NoError(t, err)
|
|
|
|
data2, err := EncodeStorage(decoded)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, data1, data2, "re-encoded bytes must be identical")
|
|
}
|
|
|
|
func TestStorage_TypeMismatchErrors(t *testing.T) {
|
|
v := Uint64Val(42)
|
|
|
|
_, err := v.AsUint32()
|
|
assert.ErrorIs(t, err, ErrStorageTypeMismatch)
|
|
|
|
_, err = v.AsString()
|
|
assert.ErrorIs(t, err, ErrStorageTypeMismatch)
|
|
|
|
_, err = v.AsBool()
|
|
assert.ErrorIs(t, err, ErrStorageTypeMismatch)
|
|
|
|
_, err = v.AsSection()
|
|
assert.ErrorIs(t, err, ErrStorageTypeMismatch)
|
|
|
|
_, err = v.AsUint64Array()
|
|
assert.ErrorIs(t, err, ErrStorageTypeMismatch)
|
|
}
|
|
|
|
func TestStorage_Uint32Array(t *testing.T) {
|
|
s := Section{
|
|
"ports": Uint32ArrayVal([]uint32{8080, 8443, 9090}),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
arr, err := decoded["ports"].AsUint32Array()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []uint32{8080, 8443, 9090}, arr)
|
|
}
|
|
|
|
func TestDecodeStorage_BadVersion(t *testing.T) {
|
|
// Valid signatures but version 2 instead of 1.
|
|
data := []byte{0x01, 0x11, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x00}
|
|
_, err := DecodeStorage(data)
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrStorageBadVersion)
|
|
}
|
|
|
|
func TestStorage_EmptyArrays(t *testing.T) {
|
|
s := Section{
|
|
"empty_u64": Uint64ArrayVal([]uint64{}),
|
|
"empty_str": StringArrayVal([][]byte{}),
|
|
"empty_obj": ObjectArrayVal([]Section{}),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
u64arr, err := decoded["empty_u64"].AsUint64Array()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, u64arr)
|
|
|
|
strarr, err := decoded["empty_str"].AsStringArray()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, strarr)
|
|
|
|
objarr, err := decoded["empty_obj"].AsSectionArray()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, objarr)
|
|
}
|
|
|
|
func TestStorage_BoolFalseRoundTrip(t *testing.T) {
|
|
s := Section{
|
|
"off": BoolVal(false),
|
|
"on": BoolVal(true),
|
|
}
|
|
|
|
data, err := EncodeStorage(s)
|
|
require.NoError(t, err)
|
|
|
|
decoded, err := DecodeStorage(data)
|
|
require.NoError(t, err)
|
|
|
|
off, err := decoded["off"].AsBool()
|
|
require.NoError(t, err)
|
|
assert.False(t, off)
|
|
|
|
on, err := decoded["on"].AsBool()
|
|
require.NoError(t, err)
|
|
assert.True(t, on)
|
|
}
|