Deep-clone source metadata during chunk materialization
This commit is contained in:
@@ -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} {
|
||||
|
||||
Reference in New Issue
Block a user