From 8297382580f0acca4dcb64b63bce989c4902c655 Mon Sep 17 00:00:00 2001 From: folbrich Date: Tue, 4 Aug 2026 09:20:32 +0200 Subject: [PATCH] Add tests locking down ChunkID hex formatting ChunkID.String() and MarshalJSON() were briefly moved to pointer receivers in #273, which meant a ChunkID value was rendered by fmt's byte-array fallback and marshalled as an array of numbers. #379 moved them back to value receivers but added no test, so the same regression could slip in again unnoticed. Cover both the formatting and the JSON encoding of a ChunkID value, including the non-addressable map-value case that skips a pointer-receiver MarshalJSON, plus compile-time assertions that the value type implements fmt.Stringer and json.Marshaler. --- types_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 types_test.go diff --git a/types_test.go b/types_test.go new file mode 100644 index 0000000..b7cbea7 --- /dev/null +++ b/types_test.go @@ -0,0 +1,56 @@ +package desync + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +// String() and MarshalJSON() must stay on value receivers. With pointer +// receivers, a ChunkID value doesn't implement these interfaces and gets +// formatted or marshalled as a raw byte array instead of hex. +var ( + _ fmt.Stringer = ChunkID{} + _ json.Marshaler = ChunkID{} +) + +const testChunkIDHex = "dda036cc9be7d1b1c3f6f4d3d1e2ed0f9e1f74e8b0d1a2b3c4d5e6f708192a3b" + +func TestChunkIDFormatting(t *testing.T) { + id, err := ChunkIDFromString(testChunkIDHex) + require.NoError(t, err) + + // A value, not just a pointer, needs to render as hex + require.Equal(t, testChunkIDHex, fmt.Sprintf("%s", id)) + require.Equal(t, testChunkIDHex, fmt.Sprintf("%v", id)) + require.Equal(t, `"`+testChunkIDHex+`"`, fmt.Sprintf("%q", id)) + require.Equal(t, testChunkIDHex, fmt.Sprintf("%s", &id)) + + // Same for values carried in errors, which are printed by value + require.Equal(t, + fmt.Sprintf("chunk %s missing from store", testChunkIDHex), + ChunkMissing{ID: id}.Error(), + ) +} + +func TestChunkIDMarshalJSON(t *testing.T) { + id, err := ChunkIDFromString(testChunkIDHex) + require.NoError(t, err) + + // Map values aren't addressable, so a pointer-receiver MarshalJSON would + // be skipped here and the ID emitted as an array of numbers + b, err := json.Marshal(map[string]ChunkID{"id": id}) + require.NoError(t, err) + require.JSONEq(t, `{"id":"`+testChunkIDHex+`"}`, string(b)) + + // Round-trip through a struct, as done by inspect-chunks + b, err = json.Marshal([]ChunkAdditionalInfo{{ID: id, UncompressedSize: 1024}}) + require.NoError(t, err) + require.JSONEq(t, `[{"id":"`+testChunkIDHex+`","uncompressed_size":1024}]`, string(b)) + + var got []ChunkAdditionalInfo + require.NoError(t, json.Unmarshal(b, &got)) + require.Equal(t, id, got[0].ID) +}