Deep-clone source metadata during chunk materialization

This commit is contained in:
2026-07-18 02:10:05 +00:00
parent 205e2a9908
commit 561d65a505
6 changed files with 279 additions and 77 deletions

View File

@@ -3,11 +3,16 @@ package source
import (
"bytes"
"encoding/json"
"math"
"reflect"
"strings"
"testing"
)
type typedMetadataMap map[string]any
type typedMetadataSlice []typedMetadataMap
type typedMetadataArray [2]any
func TestCanonicalizeChunkAnnotations(t *testing.T) {
original := ChunkAnnotations{
"domain/items": json.RawMessage(` { "z": [3, 2, 1], "a": 1.0 } `),
@@ -218,6 +223,87 @@ func TestMaterializeChunkPlanDeepClonesUnitMetadata(t *testing.T) {
}
}
func TestMaterializeChunkPlanClonesConcreteJSONMetadata(t *testing.T) {
doc := planDocument()
doc.Units[0].Metadata = map[string]any{
"typed_map": typedMetadataMap{"bytes": []byte("map")},
"typed_slice": typedMetadataSlice{{"raw": json.RawMessage(`{"slice":true}`)}},
"typed_array": typedMetadataArray{map[string]any{"bytes": []byte("array")}, []any{json.RawMessage(`{"array":true}`)}},
"interface": any(typedMetadataMap{"bytes": []byte("interface")}),
"raw": json.RawMessage(`{"raw":true}`),
"bytes": []byte("bytes"),
}
chunks, err := MaterializeChunkPlan(doc, validChunkPlan(doc))
if err != nil {
t.Fatal(err)
}
again, err := MaterializeChunkPlan(doc, validChunkPlan(doc))
if err != nil {
t.Fatal(err)
}
metadata := chunks[0].Units[0].Metadata
metadata["typed_map"].(typedMetadataMap)["bytes"].([]byte)[0] = 'M'
metadata["typed_slice"].(typedMetadataSlice)[0]["raw"].(json.RawMessage)[0] = '['
metadata["typed_array"].(typedMetadataArray)[0].(map[string]any)["bytes"].([]byte)[0] = 'A'
metadata["typed_array"].(typedMetadataArray)[1].([]any)[0].(json.RawMessage)[0] = '['
metadata["interface"].(typedMetadataMap)["bytes"].([]byte)[0] = 'I'
metadata["raw"].(json.RawMessage)[0] = '['
metadata["bytes"].([]byte)[0] = 'B'
for name, candidate := range map[string]map[string]any{
"source": doc.Units[0].Metadata,
"again": again[0].Units[0].Metadata,
} {
if got := string(candidate["typed_map"].(typedMetadataMap)["bytes"].([]byte)); got != "map" {
t.Fatalf("%s typed map bytes = %q, want map", name, got)
}
if got := string(candidate["typed_slice"].(typedMetadataSlice)[0]["raw"].(json.RawMessage)); got != `{"slice":true}` {
t.Fatalf("%s typed slice raw = %q", name, got)
}
array := candidate["typed_array"].(typedMetadataArray)
if got := string(array[0].(map[string]any)["bytes"].([]byte)); got != "array" || string(array[1].([]any)[0].(json.RawMessage)) != `{"array":true}` {
t.Fatalf("%s typed array = %#v", name, array)
}
if got := string(candidate["interface"].(typedMetadataMap)["bytes"].([]byte)); got != "interface" {
t.Fatalf("%s interface bytes = %q, want interface", name, got)
}
if got := string(candidate["raw"].(json.RawMessage)); got != `{"raw":true}` {
t.Fatalf("%s raw = %q", name, got)
}
if got := string(candidate["bytes"].([]byte)); got != "bytes" {
t.Fatalf("%s bytes = %q, want bytes", name, got)
}
}
}
func TestMaterializeChunkPlanRejectsInvalidMetadata(t *testing.T) {
cyclic := make(map[string]any)
cyclic["self"] = cyclic
for _, tc := range []struct {
name string
value any
want string
}{
{name: "cycle", value: cyclic, want: "metadata.cycle.self contains a cycle"},
{name: "unsupported", value: func() {}, want: "metadata.unsupported has unsupported type func()"},
{name: "nonfinite", value: math.NaN(), want: "metadata.nonfinite has a non-finite number"},
} {
t.Run(tc.name, func(t *testing.T) {
doc := planDocument()
doc.Units[0].Metadata = map[string]any{tc.name: tc.value}
_, first := MaterializeChunkPlan(doc, validChunkPlan(doc))
_, second := MaterializeChunkPlan(doc, validChunkPlan(doc))
if first == nil || !strings.Contains(first.Error(), "clone chunk plan range[0] units: source unit[0] metadata: "+tc.want) {
t.Fatalf("first MaterializeChunkPlan() error = %v, want %q", first, tc.want)
}
if second == nil || second.Error() != first.Error() {
t.Fatalf("MaterializeChunkPlan() errors = %v and %v, want deterministic error", first, second)
}
})
}
}
func planDocument() *SourceDocument {
doc := &SourceDocument{ID: "source-plan", Kind: "test", Format: "application/test", Digest: "sha256:source-plan"}
for _, id := range []int{10, 20, 30, 40, 50} {