package wal import ( "bytes" "encoding/binary" "testing" ) func TestEntryRoundtrip(t *testing.T) { maxKey := bytes.Repeat([]byte("k"), int(MaxWalKeyBytes)) cases := []struct { name string e *WalEntry }{ {"put_inline", &WalEntry{OpPut, VKInline, []byte("key1"), []byte("val1")}}, {"put_inline_empty_value", &WalEntry{OpPut, VKInline, []byte("key2"), []byte{}}}, {"delete", &WalEntry{OpDelete, VKNone, []byte("key3"), nil}}, {"put_max_key", &WalEntry{OpPut, VKInline, maxKey, []byte("v")}}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { encoded, err := EncodeEntry(tc.e) if err != nil { t.Fatalf("encode: %v", err) } got, consumed, err := DecodeEntry(encoded) if err != nil { t.Fatalf("decode: %v", err) } if consumed != len(encoded) { t.Fatalf("consumed %d != encoded len %d", consumed, len(encoded)) } if got.OpType != tc.e.OpType { t.Errorf("OpType: got %d, want %d", got.OpType, tc.e.OpType) } if got.ValueKind != tc.e.ValueKind { t.Errorf("ValueKind: got %d, want %d", got.ValueKind, tc.e.ValueKind) } if !bytes.Equal(got.Key, tc.e.Key) { t.Errorf("Key: got %q, want %q", got.Key, tc.e.Key) } if !bytes.Equal(got.Value, tc.e.Value) { t.Errorf("Value: got %q, want %q", got.Value, tc.e.Value) } }) } } func TestEntryValidation(t *testing.T) { bigKey := bytes.Repeat([]byte("k"), int(MaxWalKeyBytes)+1) bigVal := bytes.Repeat([]byte("v"), int(MaxWalInlineValueBytes)+1) cases := []struct { name string e *WalEntry wantErr bool }{ {"op_invalid", &WalEntry{OpInvalid, VKNone, []byte("k"), nil}, true}, {"put_vk_none", &WalEntry{OpPut, VKNone, []byte("k"), nil}, true}, {"delete_vk_inline", &WalEntry{OpDelete, VKInline, []byte("k"), nil}, true}, {"key_empty", &WalEntry{OpPut, VKInline, []byte{}, []byte("v")}, true}, {"key_too_big", &WalEntry{OpPut, VKInline, bigKey, []byte("v")}, true}, {"put_inline_val_too_big", &WalEntry{OpPut, VKInline, []byte("k"), bigVal}, true}, {"put_vlptr_val_empty", &WalEntry{OpPut, VKValueLogPointer, []byte("k"), []byte{}}, true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := EncodeEntry(tc.e) if (err != nil) != tc.wantErr { t.Errorf("EncodeEntry() error = %v, wantErr %v", err, tc.wantErr) } }) } } func TestEntryDecodeTruncated(t *testing.T) { // Build a valid encoded entry, then truncate mid-varint. e := &WalEntry{OpPut, VKInline, []byte("key1"), []byte("value1")} full, err := EncodeEntry(e) if err != nil { t.Fatal(err) } // Truncate to just 1 byte — not enough for header. _, _, err = DecodeEntry(full[:1]) if err == nil { t.Error("expected error for 1-byte data") } // Build data with an incomplete varint: opType + valueKind + start of varint (0xFF means more bytes follow). trunc := []byte{OpPut, VKInline, 0xFF} _, _, err = DecodeEntry(trunc) if err == nil { t.Error("expected error for truncated varint") } // Also test: varint specifies more bytes than available. // Encode a large key length varint but don't provide the key bytes. varintBuf := make([]byte, binary.MaxVarintLen64) n := binary.PutUvarint(varintBuf, 1000) // keyLen = 1000 data := []byte{OpPut, VKInline} data = append(data, varintBuf[:n]...) data = append(data, varintBuf[:n]...) // valLen varint (also 1000) // Don't append any key/value bytes. _, _, err = DecodeEntry(data) if err == nil { t.Error("expected error for missing key/value bytes") } }