package artifactpolicy import ( "errors" "strings" "testing" ) func TestClassifySource(t *testing.T) { tests := []struct { name string source string wantKind SourceKind wantKey string wantErrLike string }{ {name: "built in transcript", source: "narratio.transcript.final_trimmed", wantKind: SourceKindBuiltIn}, {name: "built in bounds", source: "narratio.bounds.session", wantKind: SourceKindBuiltIn}, {name: "configured artifact", source: "narratio.artifact.session_recap", wantKind: SourceKindConfiguredArtifact, wantKey: "session_recap"}, {name: "extraction", source: "narratio.extraction.npc_registry", wantKind: SourceKindExtraction, wantKey: "npc_registry"}, {name: "previous session configured", source: "narratio.previous_session.artifact.session_recap", wantKind: SourceKindPreviousArtifact, wantKey: "session_recap"}, {name: "unsupported", source: "narratio.unknown", wantErrLike: "unsupported artifact source"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ClassifySource(tt.source) if tt.wantErrLike != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErrLike) { t.Fatalf("ClassifySource() error = %v, want like %q", err, tt.wantErrLike) } return } if err != nil { t.Fatalf("ClassifySource() error = %v", err) } if got.Kind != tt.wantKind { t.Fatalf("ClassifySource().Kind = %q, want %q", got.Kind, tt.wantKind) } if got.ConfiguredKey != tt.wantKey { t.Fatalf("ClassifySource().ConfiguredKey = %q, want %q", got.ConfiguredKey, tt.wantKey) } }) } } func TestValidatePublishSource(t *testing.T) { configured := map[string]string{"session_recap": "artifacts/session_recap.md"} if _, err := ValidatePublishSource("narratio.artifact.session_recap", configured); err != nil { t.Fatalf("ValidatePublishSource(configured) error = %v", err) } if _, err := ValidatePublishSource("narratio.previous_session.artifact.session_recap", configured); err == nil { t.Fatal("ValidatePublishSource(previous) error = nil, want error") } if _, err := ValidatePublishSource("narratio.artifact.missing", configured); err == nil { t.Fatal("ValidatePublishSource(missing configured) error = nil, want error") } } func TestExtractionSourcePolicy(t *testing.T) { if got := ExtractionSourceID(" npc_registry "); got != "narratio.extraction.npc_registry" { t.Fatalf("ExtractionSourceID() = %q, want narratio.extraction.npc_registry", got) } if key, ok := ParseExtractionSource(" narratio.extraction.npc_registry "); !ok || key != "npc_registry" { t.Fatalf("ParseExtractionSource() = %q, %t; want npc_registry, true", key, ok) } for _, source := range []string{ "narratio.extraction.", "narratio.extraction.NPC", "narratio.extraction.npc-registry", "narratio.extraction.npc_registry.extra", } { if _, ok := ParseExtractionSource(source); ok { t.Fatalf("ParseExtractionSource(%q) unexpectedly matched", source) } } descriptor, err := DescribeScriptoriumInputSource("narratio.extraction.npc_registry") if err != nil { t.Fatalf("DescribeScriptoriumInputSource(extraction) error = %v", err) } if descriptor.Source.Kind != SourceKindExtraction || descriptor.Source.ConfiguredKey != "npc_registry" { t.Fatalf("extraction descriptor = %#v", descriptor) } declared := map[string]struct{}{"npc_registry": {}} if err := ValidateInputReference(descriptor, nil, declared); err != nil { t.Fatalf("ValidateInputReference(declared extraction) error = %v", err) } if err := ValidateInputReference(descriptor, nil, nil); err == nil { t.Fatal("ValidateInputReference(unknown extraction) error = nil, want error") } if _, err := ValidatePublishSourceWithExtractions("narratio.extraction.npc_registry", nil, declared); err != nil { t.Fatalf("ValidatePublishSourceWithExtractions(declared) error = %v", err) } if _, err := ValidatePublishSourceWithExtractions("narratio.extraction.unknown", nil, declared); err == nil { t.Fatal("ValidatePublishSourceWithExtractions(unknown) error = nil, want error") } identities := map[string]struct{}{} for _, sourceID := range []string{ ExtractionSourceID("npc_registry"), ConfiguredSourceID("npc_registry"), PreviousSessionSourceID("npc_registry"), SourceBoundsSession, } { if _, exists := identities[sourceID]; exists { t.Fatalf("source identity collision at %q", sourceID) } identities[sourceID] = struct{}{} } } func TestResolvePublishedDestination(t *testing.T) { configured := map[string]string{"session_recap": "artifacts/session_recap.md"} got, err := ResolvePublishedDestination("narratio.transcript.final_trimmed", "", configured) if err != nil { t.Fatalf("ResolvePublishedDestination(built-in) error = %v", err) } if got != "transcripts/final.trimmed.json" { t.Fatalf("built-in destination = %q, want transcripts/final.trimmed.json", got) } got, err = ResolvePublishedDestination("narratio.transcript.final_markdown", "", configured) if err != nil { t.Fatalf("ResolvePublishedDestination(markdown built-in) error = %v", err) } if got != "transcripts/final.md" { t.Fatalf("markdown built-in destination = %q, want transcripts/final.md", got) } got, err = ResolvePublishedDestination("narratio.artifact.session_recap", "", configured) if err != nil { t.Fatalf("ResolvePublishedDestination(configured) error = %v", err) } if got != "artifacts/session_recap.md" { t.Fatalf("configured destination = %q, want artifacts/session_recap.md", got) } got, err = ResolvePublishedDestination("narratio.transcript.final_trimmed", "published/../published/final.json", configured) if err != nil { t.Fatalf("ResolvePublishedDestination(explicit) error = %v", err) } if got != "published/final.json" { t.Fatalf("explicit destination = %q, want published/final.json", got) } } func TestResolvePublishedDestinationRejectsTraversal(t *testing.T) { configured := map[string]string{"session_recap": "artifacts/session_recap.md"} _, err := ResolvePublishedDestination("narratio.transcript.final_trimmed", "../escape.txt", configured) if err == nil { t.Fatal("ResolvePublishedDestination() error = nil, want traversal rejection") } } func TestDescribeScriptoriumInputSource(t *testing.T) { tests := []struct { name string source string wantKind SourceKind wantKey string wantPrev bool wantErr error wantErrLike string }{ {name: "built in", source: "narratio.transcript.final_trimmed", wantKind: SourceKindBuiltIn}, {name: "built in markdown", source: "narratio.transcript.final_markdown", wantKind: SourceKindBuiltIn}, {name: "prepared players input", source: "narratio.input.players", wantKind: SourceKindStableInput}, {name: "prepared party input", source: "narratio.input.party", wantKind: SourceKindStableInput}, {name: "prepared glossary input", source: "narratio.input.glossary", wantKind: SourceKindStableInput}, {name: "configured", source: "narratio.artifact.session_recap", wantKind: SourceKindConfiguredArtifact, wantKey: "session_recap"}, {name: "previous", source: "narratio.previous_session.artifact.session_recap", wantKind: SourceKindPreviousArtifact, wantKey: "session_recap", wantPrev: true}, {name: "invalid previous", source: "narratio.previous_session.artifact.", wantErr: ErrInvalidPreviousSessionSource}, {name: "unsupported", source: "narratio.unknown", wantErr: ErrUnsupportedScriptoriumInputSource}, {name: "empty", source: " ", wantErr: ErrUnsupportedScriptoriumInputSource}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := DescribeScriptoriumInputSource(tt.source) if tt.wantErr != nil { if !errors.Is(err, tt.wantErr) { t.Fatalf("DescribeScriptoriumInputSource() error = %v, want %v", err, tt.wantErr) } return } if tt.wantErrLike != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErrLike) { t.Fatalf("DescribeScriptoriumInputSource() error = %v, want like %q", err, tt.wantErrLike) } return } if err != nil { t.Fatalf("DescribeScriptoriumInputSource() error = %v", err) } if got.Source.Kind != tt.wantKind { t.Fatalf("DescribeScriptoriumInputSource().Source.Kind = %q, want %q", got.Source.Kind, tt.wantKind) } if got.Source.ConfiguredKey != tt.wantKey { t.Fatalf("DescribeScriptoriumInputSource().Source.ConfiguredKey = %q, want %q", got.Source.ConfiguredKey, tt.wantKey) } if tt.wantPrev && got.PreviousSession == nil { t.Fatal("DescribeScriptoriumInputSource().PreviousSession = nil, want descriptor") } if !tt.wantPrev && got.PreviousSession != nil { t.Fatalf("DescribeScriptoriumInputSource().PreviousSession = %#v, want nil", got.PreviousSession) } }) } } func TestValidateInputConfiguredReference(t *testing.T) { configured := map[string]struct{}{"session_recap": {}} desc, err := DescribeScriptoriumInputSource("narratio.artifact.session_recap") if err != nil { t.Fatalf("DescribeScriptoriumInputSource(configured) error = %v", err) } if err := ValidateInputConfiguredReference(desc, configured); err != nil { t.Fatalf("ValidateInputConfiguredReference(configured) error = %v", err) } prevDesc, err := DescribeScriptoriumInputSource("narratio.previous_session.artifact.session_recap") if err != nil { t.Fatalf("DescribeScriptoriumInputSource(previous) error = %v", err) } if err := ValidateInputConfiguredReference(prevDesc, configured); err != nil { t.Fatalf("ValidateInputConfiguredReference(previous) error = %v", err) } missingDesc, err := DescribeScriptoriumInputSource("narratio.artifact.quest_log") if err != nil { t.Fatalf("DescribeScriptoriumInputSource(missing configured) error = %v", err) } err = ValidateInputConfiguredReference(missingDesc, configured) var unknown *UnknownConfiguredArtifactError if !errors.As(err, &unknown) || unknown.ConfiguredKey != "quest_log" { t.Fatalf("ValidateInputConfiguredReference(missing configured) error = %v, want UnknownConfiguredArtifactError(quest_log)", err) } } func TestPreviousSessionSourceDescriptorForConfiguredKey(t *testing.T) { got, err := PreviousSessionSourceDescriptorForConfiguredKey("session_recap") if err != nil { t.Fatalf("PreviousSessionSourceDescriptorForConfiguredKey() error = %v", err) } if got.SourceID != "narratio.previous_session.artifact.session_recap" { t.Fatalf("SourceID = %q, want narratio.previous_session.artifact.session_recap", got.SourceID) } if got.ConfiguredSourceID != "narratio.artifact.session_recap" { t.Fatalf("ConfiguredSourceID = %q, want narratio.artifact.session_recap", got.ConfiguredSourceID) } if got.ConfiguredKey != "session_recap" { t.Fatalf("ConfiguredKey = %q, want session_recap", got.ConfiguredKey) } }