From 223f3751e83a5bad49a052a75c85fdf328a7fbc0 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 6 Jul 2026 17:06:24 +0000 Subject: [PATCH] Add reference slot clone helper --- internal/framework/contracts/contracts.go | 12 +++++ .../framework/contracts/contracts_test.go | 54 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/internal/framework/contracts/contracts.go b/internal/framework/contracts/contracts.go index 04277ee..6c923f5 100644 --- a/internal/framework/contracts/contracts.go +++ b/internal/framework/contracts/contracts.go @@ -132,6 +132,18 @@ type ReferenceSlot struct { MaxBytes int64 `json:"max_bytes,omitempty"` } +func CloneReferenceSlots(slots []ReferenceSlot) []ReferenceSlot { + if len(slots) == 0 { + return nil + } + out := make([]ReferenceSlot, len(slots)) + for i, slot := range slots { + slot.AcceptedMediaTypes = append([]string(nil), slot.AcceptedMediaTypes...) + out[i] = slot + } + return out +} + type ReferenceOrigin struct { Type string `json:"type"` URI string `json:"uri,omitempty"` diff --git a/internal/framework/contracts/contracts_test.go b/internal/framework/contracts/contracts_test.go index 21dfe6e..350745e 100644 --- a/internal/framework/contracts/contracts_test.go +++ b/internal/framework/contracts/contracts_test.go @@ -3,6 +3,7 @@ package contracts import ( "context" "encoding/json" + "reflect" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/artifacts" @@ -225,6 +226,59 @@ func TestReferenceSetDataTypes(t *testing.T) { } } +func TestCloneReferenceSlotsEmptyInputReturnsNil(t *testing.T) { + if got := CloneReferenceSlots(nil); got != nil { + t.Fatalf("CloneReferenceSlots(nil) = %#v, want nil", got) + } + if got := CloneReferenceSlots([]ReferenceSlot{}); got != nil { + t.Fatalf("CloneReferenceSlots(empty) = %#v, want nil", got) + } +} + +func TestCloneReferenceSlotsPreservesFields(t *testing.T) { + slots := []ReferenceSlot{ + { + Name: "roster", + Description: "Known characters", + Required: true, + AcceptedMediaTypes: []string{"text/plain", "text/markdown"}, + Multiple: true, + MaxBytes: 4096, + }, + { + Name: "glossary", + Description: "Campaign terms", + MaxBytes: 2048, + }, + } + + got := CloneReferenceSlots(slots) + + if !reflect.DeepEqual(got, slots) { + t.Fatalf("CloneReferenceSlots() = %#v, want %#v", got, slots) + } +} + +func TestCloneReferenceSlotsCopiesAcceptedMediaTypes(t *testing.T) { + slots := []ReferenceSlot{ + { + Name: "party", + AcceptedMediaTypes: []string{"application/json", "text/plain"}, + }, + } + + got := CloneReferenceSlots(slots) + got[0].Name = "changed" + got[0].AcceptedMediaTypes[0] = "text/markdown" + + if slots[0].Name != "party" { + t.Fatalf("source slot name = %q, want unchanged", slots[0].Name) + } + if slots[0].AcceptedMediaTypes[0] != "application/json" { + t.Fatalf("source AcceptedMediaTypes aliased clone: %#v", slots[0].AcceptedMediaTypes) + } +} + func TestReferenceItemJSONOmitsContent(t *testing.T) { item := ReferenceItem{ SlotName: "roster",