package contracts import ( "context" "encoding/json" "reflect" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/source" ) var _ InputAdapter = fakeAdapter{} var _ Chunker = fakeChunker{} var _ Extractor[fakeArtifact] = fakeExtractor{} var _ Merger[fakeArtifact] = fakeMerger{} var _ Normalizer[fakeArtifact] = fakeNormalizer{} var _ StructuredLLMClient = fakeLLMClient{} var _ OutputEncoder = fakeOutputEncoder{} 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 TestCloneSerializedArtifactPointerOwnsArtifactData(t *testing.T) { original := &SerializedArtifact{ Kind: "test/artifact", Schema: ArtifactSchema{ID: "test.schema", JSONSchema: []byte(`{"type":"object"}`)}, Content: []byte(`{"items":[]}`), Metadata: map[string]any{"count": 1}, } cloned := CloneSerializedArtifactPointer(original) original.Schema.JSONSchema[0] = '[' original.Content[0] = '[' original.Metadata["count"] = 2 if cloned == nil || string(cloned.Schema.JSONSchema) != `{"type":"object"}` || string(cloned.Content) != `{"items":[]}` || cloned.Metadata["count"] != 1 { t.Fatalf("CloneSerializedArtifactPointer() = %#v, want independent artifact data", cloned) } if CloneSerializedArtifactPointer(nil) != nil { t.Fatal("CloneSerializedArtifactPointer(nil) must return nil") } } 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 TestCloneReferenceSlotsCopiesAcceptedArtifactKinds(t *testing.T) { slots := []ReferenceSlot{{Name: "npcs", AcceptedArtifactKinds: []ArtifactKind{"dnd/npc-registry"}}} clone := CloneReferenceSlots(slots) clone[0].AcceptedArtifactKinds[0] = "changed" if slots[0].AcceptedArtifactKinds[0] != "dnd/npc-registry" { t.Fatalf("source AcceptedArtifactKinds aliased clone: %#v", slots[0].AcceptedArtifactKinds) } } func TestReferenceItemJSONOmitsContent(t *testing.T) { item := ReferenceItem{ SlotName: "roster", MediaType: "text/plain", Content: []byte("reference content"), Digest: "sha256:reference", Origin: ReferenceOrigin{Type: "file", URI: "file:///tmp/roster.txt"}, } encoded, err := json.Marshal(item) if err != nil { t.Fatalf("json.Marshal() error = %v, want nil", err) } var got map[string]any if err := json.Unmarshal(encoded, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v, want nil", err) } if _, ok := got["content"]; ok { t.Fatalf("encoded reference item leaked content: %s", encoded) } if _, ok := got["Content"]; ok { t.Fatalf("encoded reference item leaked Content: %s", encoded) } } func TestLLMInputMaterialCopiesContentAndOmitsContentFromJSON(t *testing.T) { content := []byte("raw source bytes") material := NewLLMInputMaterial("transcript", "application/json", content, "sha256:source", "file:///tmp/source.json") content[0] = 'R' if got := string(material.Content); got != "raw source bytes" { t.Fatalf("material content = %q, want defensive copy", got) } if material.SizeBytes != int64(len("raw source bytes")) { t.Fatalf("SizeBytes = %d, want content length", material.SizeBytes) } clone := material.Clone() clone.Content[0] = 'X' if got := string(material.Content); got != "raw source bytes" { t.Fatalf("cloned material content aliased original: %q", got) } encoded, err := json.Marshal(material) if err != nil { t.Fatalf("json.Marshal() error = %v, want nil", err) } var got map[string]any if err := json.Unmarshal(encoded, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v, want nil", err) } if _, ok := got["content"]; ok { t.Fatalf("encoded material leaked content: %s", encoded) } if _, ok := got["Content"]; ok { t.Fatalf("encoded material leaked Content: %s", encoded) } if got["digest"] != "sha256:source" || got["origin_uri"] != "file:///tmp/source.json" { t.Fatalf("encoded material = %#v, want non-secret provenance", got) } } func TestLLMInputSetCloneCopiesContent(t *testing.T) { set := LLMInputSet{ "transcript": NewLLMInputMaterial("transcript", "application/json", []byte("source"), "sha256:source", "file:///tmp/source.json"), } clone := set.Clone() clone["transcript"].Content[0] = 'S' if got := string(set["transcript"].Content); got != "source" { t.Fatalf("input set clone aliased content: %q", got) } } func TestArtifactSchemaJSONOmitsSchemaContent(t *testing.T) { schema := ArtifactSchema{ ID: "schema-id", Name: "schema-name", Version: "v1", JSONSchema: []byte(`{"type":"object"}`), } encoded, err := json.Marshal(schema) if err != nil { t.Fatalf("json.Marshal() error = %v, want nil", err) } var got map[string]any if err := json.Unmarshal(encoded, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v, want nil", err) } if got["id"] != "schema-id" || got["name"] != "schema-name" || got["version"] != "v1" { t.Fatalf("encoded schema = %#v, want schema provenance", got) } if _, ok := got["json_schema"]; ok { t.Fatalf("encoded schema leaked raw schema content: %s", encoded) } if _, ok := got["JSONSchema"]; ok { t.Fatalf("encoded schema leaked raw schema content: %s", encoded) } } func TestOutputFileJSONShapeOmitsBytes(t *testing.T) { file := OutputFile{ Name: "artifacts/events.json", ContentType: "application/json", Bytes: []byte(`{"ignored":true}`), } encoded, err := json.Marshal(file) if err != nil { t.Fatalf("json.Marshal() error = %v, want nil", err) } var got map[string]any if err := json.Unmarshal(encoded, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v, want nil", err) } if got["name"] != "artifacts/events.json" { t.Fatalf("name = %#v, want logical file name", got["name"]) } if got["content_type"] != "application/json" { t.Fatalf("content_type = %#v, want application/json", got["content_type"]) } if _, ok := got["Bytes"]; ok { t.Fatalf("encoded output file leaked Bytes: %s", encoded) } if _, ok := got["bytes"]; ok { t.Fatalf("encoded output file leaked bytes: %s", encoded) } } type fakeAdapter struct { key string doc *source.SourceDocument } func (adapter fakeAdapter) Key() string { return adapter.key } func (adapter fakeAdapter) Parse(ctx context.Context, req ParseRequest) (*source.SourceDocument, error) { return adapter.doc, nil } type fakeChunker struct { key string } func (chunker fakeChunker) Key() string { return chunker.key } func (chunker fakeChunker) ReferenceSlots() []ReferenceSlot { return nil } func (chunker fakeChunker) Plan(ctx context.Context, req ChunkRequest) (ChunkPlanResult, error) { return ChunkPlanResult{ Plan: source.ChunkPlan{ SourceDigest: req.Source.Digest, Ranges: []source.ChunkRange{{ StartUnitID: req.Source.Units[0].ID, EndUnitID: req.Source.Units[len(req.Source.Units)-1].ID, }}, }, }, nil } type fakeExtractor struct { key string } type fakeArtifact struct{ Value string } func (extractor fakeExtractor) Key() string { return extractor.key } func (extractor fakeExtractor) ReferenceSlots() []ReferenceSlot { return nil } func (extractor fakeExtractor) Extract(ctx context.Context, req TypedExtractionRequest) (TypedExtractionResult[fakeArtifact], error) { value := "example" if req.AmbientContext["mode"] == "chunked" { value = "chunked" } return TypedExtractionResult[fakeArtifact]{Value: fakeArtifact{Value: value}}, nil } type fakeMerger struct { key string } func (merger fakeMerger) Key() string { return merger.key } func (merger fakeMerger) Merge(ctx context.Context, req TypedMergeRequest[fakeArtifact]) (TypedMergeResult[fakeArtifact], error) { return TypedMergeResult[fakeArtifact]{Value: req.ExtractOutputs[0].Value}, nil } type fakeNormalizer struct { key string } func (normalizer fakeNormalizer) Key() string { return normalizer.key } func (normalizer fakeNormalizer) ReferenceSlots() []ReferenceSlot { return nil } func (normalizer fakeNormalizer) Normalize(ctx context.Context, req TypedNormalizeRequest[fakeArtifact]) (TypedNormalizeResult[fakeArtifact], error) { return TypedNormalizeResult[fakeArtifact]{Value: req.MergeOutput.Value}, nil } type fakeLLMClient struct{} func (client fakeLLMClient) CompleteStructured(ctx context.Context, req StructuredCompletionRequest, out any) (StructuredCompletionResponse, error) { return StructuredCompletionResponse{ Content: json.RawMessage(`{"value":"example"}`), }, nil } type fakeOutputEncoder struct { key string } func (encoder fakeOutputEncoder) Key() string { return encoder.key } func (encoder fakeOutputEncoder) Encode(ctx context.Context, req OutputRequest) (OutputResult, error) { return OutputResult{ Files: []OutputFile{ { Name: "artifacts/generic.json", ContentType: "application/json", Bytes: []byte(`{"run_id":"` + req.Manifest.RunID + `","output_count":1}`), }, }, }, nil }