2026-05-05 10:18:28 -05:00
package promptdef
import (
"context"
"errors"
2026-05-05 10:35:39 -05:00
"io/fs"
2026-05-05 10:18:28 -05:00
"os"
"path/filepath"
2026-05-05 10:35:39 -05:00
"strings"
2026-05-05 10:18:28 -05:00
"testing"
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
)
func TestFilesystemRepository_GetPromptDefinition ( t * testing . T ) {
2026-05-05 10:35:39 -05:00
tmpDir := t . TempDir ( )
if err := copyTree ( "testdata" , tmpDir ) ; err != nil {
t . Fatalf ( "failed to copy testdata: %v" , err )
2026-05-05 10:18:28 -05:00
}
repo := NewFilesystemRepository ( tmpDir )
ctx := context . Background ( )
2026-05-05 10:35:39 -05:00
t . Run ( "valid inline prompt" , func ( t * testing . T ) {
p , err := repo . GetPromptDefinition ( ctx , "valid-inline" , "" )
2026-05-05 10:18:28 -05:00
if err != nil {
t . Fatalf ( "expected no error, got %v" , err )
}
2026-05-05 10:35:39 -05:00
if p . ID != "valid-inline" {
t . Fatalf ( "unexpected id: %q" , p . ID )
2026-05-05 10:18:28 -05:00
}
if p . Version != "1.0.0" {
2026-05-05 10:35:39 -05:00
t . Fatalf ( "unexpected version: %q" , p . Version )
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
if p . OutputFormat != domain . FormatMarkdown {
t . Fatalf ( "unexpected output format: %q" , p . OutputFormat )
}
if p . Validation . ValidationMode != domain . ValidationBasic {
t . Fatalf ( "unexpected validation mode: %q" , p . Validation . ValidationMode )
2026-05-05 10:18:28 -05:00
}
if len ( p . Templates ) != 2 {
2026-05-05 10:35:39 -05:00
t . Fatalf ( "expected 2 messages, got %d" , len ( p . Templates ) )
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
} )
t . Run ( "valid file-backed prompt" , func ( t * testing . T ) {
p , err := repo . GetPromptDefinition ( ctx , "valid-file-backed" , "" )
if err != nil {
t . Fatalf ( "expected no error, got %v" , err )
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
if len ( p . Templates ) != 2 {
t . Fatalf ( "expected 2 messages, got %d" , len ( p . Templates ) )
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
if ! strings . Contains ( p . Templates [ 1 ] . Content , "{{input \"transcript\"}}" ) {
t . Fatalf ( "expected content_file template body to be loaded, got %q" , p . Templates [ 1 ] . Content )
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
if p . Templates [ 1 ] . ContentFile == "" {
t . Fatal ( "expected ContentFile source metadata to be preserved" )
}
if ! filepath . IsAbs ( p . Templates [ 1 ] . ContentFile ) {
t . Fatalf ( "expected resolved content_file path to be absolute, got %q" , p . Templates [ 1 ] . ContentFile )
2026-05-05 10:18:28 -05:00
}
} )
2026-05-05 10:35:39 -05:00
t . Run ( "prompt with default_profile" , func ( t * testing . T ) {
p , err := repo . GetPromptDefinition ( ctx , "with-default-profile" , "" )
if err != nil {
t . Fatalf ( "expected no error, got %v" , err )
}
if p . DefaultProfile != "local-default" {
t . Fatalf ( "unexpected default profile: %q" , p . DefaultProfile )
2026-05-05 10:18:28 -05:00
}
} )
2026-05-05 10:35:39 -05:00
t . Run ( "version lookup" , func ( t * testing . T ) {
_ , err := repo . GetPromptDefinition ( ctx , "valid-inline" , "9.9.9" )
2026-05-05 10:18:28 -05:00
if ! errors . Is ( err , ErrPromptDefinitionNotFound ) {
2026-05-05 10:35:39 -05:00
t . Fatalf ( "expected ErrPromptDefinitionNotFound, got %v" , err )
2026-05-05 10:18:28 -05:00
}
} )
2026-05-05 10:35:39 -05:00
cases := [ ] struct {
name string
id string
targetErr error
errSubstrs [ ] string
} {
{ name : "invalid YAML" , id : "invalid_yaml" , targetErr : ErrInvalidYAML } ,
{ name : "missing id" , id : "missing_id" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "id is required" } } ,
{ name : "no messages" , id : "no_messages" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "at least one message is required" } } ,
{ name : "both content and content_file" , id : "both_content_and_content_file" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "exactly one" } } ,
{ name : "neither content nor content_file" , id : "neither_content_nor_content_file" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "exactly one" } } ,
{ name : "missing content_file" , id : "missing_content_file" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "failed to read content_file" } } ,
{ name : "duplicate input names" , id : "duplicate_input_names" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "duplicate input name" } } ,
{ name : "invalid validation mode" , id : "invalid_validation_mode" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "invalid validation mode" } } ,
{ name : "json_schema without schema_path" , id : "json_schema_without_schema_path" , targetErr : ErrInvalidPromptDefinition , errSubstrs : [ ] string { "schema_path" } } ,
}
for _ , tc := range cases {
t . Run ( tc . name , func ( t * testing . T ) {
_ , err := repo . GetPromptDefinition ( ctx , tc . id , "" )
if ! errors . Is ( err , tc . targetErr ) {
t . Fatalf ( "expected %v, got %v" , tc . targetErr , err )
}
for _ , sub := range tc . errSubstrs {
if ! strings . Contains ( err . Error ( ) , sub ) {
t . Fatalf ( "expected error to contain %q, got %v" , sub , err )
}
}
} )
}
t . Run ( "prompt definition not found" , func ( t * testing . T ) {
_ , err := repo . GetPromptDefinition ( ctx , "does-not-exist" , "" )
if ! errors . Is ( err , ErrPromptDefinitionNotFound ) {
t . Fatalf ( "expected ErrPromptDefinitionNotFound, got %v" , err )
2026-05-05 10:18:28 -05:00
}
} )
2026-05-05 10:35:39 -05:00
}
2026-05-05 10:18:28 -05:00
2026-05-05 10:35:39 -05:00
func copyTree ( src , dst string ) error {
return filepath . WalkDir ( src , func ( path string , d fs . DirEntry , err error ) error {
if err != nil {
return err
}
rel , err := filepath . Rel ( src , path )
if err != nil {
return err
}
if rel == "." {
return nil
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
target := filepath . Join ( dst , rel )
if d . IsDir ( ) {
return os . MkdirAll ( target , 0 o755 )
}
data , err := os . ReadFile ( path )
if err != nil {
return err
2026-05-05 10:18:28 -05:00
}
2026-05-05 10:35:39 -05:00
return os . WriteFile ( target , data , 0 o644 )
2026-05-05 10:18:28 -05:00
} )
}