package artifacts import ( "encoding/json" "reflect" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/source" ) func TestArtifactFromCandidatePreservesCandidateFields(t *testing.T) { candidate := ArtifactCandidate{ Index: 7, ExtractorKey: "generic-extractor", ArtifactType: "generic-artifact", SchemaVersion: "v1", Payload: json.RawMessage(`{"name":"example"}`), SourceRefs: []source.SourceRef{ {SourceID: "source-1", StartUnitID: "u1", EndUnitID: "u2"}, }, Metadata: map[string]any{ "confidence": 0.75, }, } artifact := ArtifactFromCandidate(candidate) if artifact.ExtractorKey != candidate.ExtractorKey { t.Fatalf("ExtractorKey = %q, want %q", artifact.ExtractorKey, candidate.ExtractorKey) } if artifact.ArtifactType != candidate.ArtifactType { t.Fatalf("ArtifactType = %q, want %q", artifact.ArtifactType, candidate.ArtifactType) } if artifact.SchemaVersion != candidate.SchemaVersion { t.Fatalf("SchemaVersion = %q, want %q", artifact.SchemaVersion, candidate.SchemaVersion) } if string(artifact.Payload) != string(candidate.Payload) { t.Fatalf("Payload = %s, want %s", artifact.Payload, candidate.Payload) } if !reflect.DeepEqual(artifact.SourceRefs, candidate.SourceRefs) { t.Fatalf("SourceRefs = %#v, want %#v", artifact.SourceRefs, candidate.SourceRefs) } if !reflect.DeepEqual(artifact.Metadata, candidate.Metadata) { t.Fatalf("Metadata = %#v, want %#v", artifact.Metadata, candidate.Metadata) } candidate.Payload[0] = '[' candidate.SourceRefs[0].StartUnitID = "changed" candidate.Metadata["confidence"] = 0.5 if string(artifact.Payload) != `{"name":"example"}` { t.Fatalf("Payload changed after candidate mutation: %s", artifact.Payload) } if artifact.SourceRefs[0].StartUnitID != "u1" { t.Fatalf("SourceRefs changed after candidate mutation: %#v", artifact.SourceRefs) } if artifact.Metadata["confidence"] != 0.75 { t.Fatalf("Metadata changed after candidate mutation: %#v", artifact.Metadata) } } func TestJSONMarshalUsesExpectedFieldNames(t *testing.T) { candidate := ArtifactCandidate{ Index: 1, ExtractorKey: "generic-extractor", ArtifactType: "generic-artifact", SchemaVersion: "v1", Payload: json.RawMessage(`{"value":true}`), SourceRefs: []source.SourceRef{ {SourceID: "source-1", StartUnitID: "u1", EndUnitID: "u1"}, }, Metadata: map[string]any{ "reviewed": true, }, } rejected := RejectedArtifact{ Candidate: candidate, ValidatorName: "generic-validator", ReasonCode: "invalid", Message: "candidate was not accepted", } gotJSON, err := json.Marshal(rejected) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } var got map[string]any if err := json.Unmarshal(gotJSON, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } assertHasKeys(t, got, "candidate", "validator_name", "reason_code", "message") gotCandidate, ok := got["candidate"].(map[string]any) if !ok { t.Fatalf("candidate = %#v, want object", got["candidate"]) } assertHasKeys(t, gotCandidate, "index", "extractor_key", "artifact_type", "schema_version", "payload", "source_refs", "metadata") gotRefs, ok := gotCandidate["source_refs"].([]any) if !ok { t.Fatalf("source_refs = %#v, want array", gotCandidate["source_refs"]) } if len(gotRefs) != 1 { t.Fatalf("len(source_refs) = %d, want 1", len(gotRefs)) } gotRef, ok := gotRefs[0].(map[string]any) if !ok { t.Fatalf("source_refs[0] = %#v, want object", gotRefs[0]) } assertHasKeys(t, gotRef, "source_id", "start_unit_id", "end_unit_id") } func TestRunManifestOmitsEmptyOptionalFields(t *testing.T) { gotJSON, err := json.Marshal(RunManifest{}) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } if string(gotJSON) != "{}" { t.Fatalf("json.Marshal(RunManifest{}) = %s, want {}", gotJSON) } } func TestRunManifestIncludesPipelineAndArtifactLaneFields(t *testing.T) { manifest := RunManifest{ PipelineID: "pipeline-1", PipelineDigest: "sha256:abc123", LLMProfiles: []LLMProfileManifest{ {ID: "default", Provider: "openai-compatible", Model: "model-a"}, }, ArtifactLanes: []ArtifactLaneManifest{ { ID: "events", Extractor: "event-extractor", Merger: "appendorder", Normalizer: "noop", Validators: []string{"grounded"}, Metadata: map[string]any{ "extractor": map[string]any{"prompt_id": "test.prompt"}, }, }, }, } gotJSON, err := json.Marshal(manifest) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } var got map[string]any if err := json.Unmarshal(gotJSON, &got); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } assertHasKeys(t, got, "pipeline_id", "pipeline_digest", "artifact_lanes", "llm_profiles") profiles, ok := got["llm_profiles"].([]any) if !ok { t.Fatalf("llm_profiles = %#v, want array", got["llm_profiles"]) } if len(profiles) != 1 { t.Fatalf("len(llm_profiles) = %d, want 1", len(profiles)) } profile, ok := profiles[0].(map[string]any) if !ok { t.Fatalf("llm_profiles[0] = %#v, want object", profiles[0]) } assertHasKeys(t, profile, "id", "provider", "model") lanes, ok := got["artifact_lanes"].([]any) if !ok { t.Fatalf("artifact_lanes = %#v, want array", got["artifact_lanes"]) } if len(lanes) != 1 { t.Fatalf("len(artifact_lanes) = %d, want 1", len(lanes)) } lane, ok := lanes[0].(map[string]any) if !ok { t.Fatalf("artifact_lanes[0] = %#v, want object", lanes[0]) } assertHasKeys(t, lane, "id", "extractor", "merger", "normalizer", "validators", "metadata") } func assertHasKeys(t *testing.T, values map[string]any, keys ...string) { t.Helper() for _, key := range keys { if _, ok := values[key]; !ok { t.Fatalf("missing key %q in %#v", key, values) } } }