package promptfs import ( "io/fs" "strings" "testing" "testing/fstest" ) func TestModulePromptFSCombinesModuleAndSharedPrompts(t *testing.T) { sharedFS := fstest.MapFS{ "shared/system.md": {Data: []byte("system")}, "shared/reference.md": {Data: []byte("reference")}, "nested/transcript.md": {Data: []byte("transcript")}, } fsys, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/module.test.yaml": {Data: []byte("id: module.test")}, "assets/prompts/task.md": {Data: []byte("task")}, }, []ModulePromptFile{ {Name: "module.test.yaml", Path: "assets/prompts/module.test.yaml"}, {Name: "task.md", Path: "assets/prompts/task.md"}, }, SharedPromptFile{Name: "system.md", FS: sharedFS, Path: "shared/system.md"}, SharedPromptFile{Name: "reference.md", FS: sharedFS, Path: "shared/reference.md"}, SharedPromptFile{Name: "transcript.md", FS: sharedFS, Path: "nested/transcript.md"}, ) if err != nil { t.Fatalf("ModulePromptFS() error = %v, want nil", err) } tests := map[string]string{ "assets/prompts/module.test/module.test.yaml": "id: module.test", "assets/prompts/module.test/task.md": "task", "assets/prompts/module.test/sharedassets/system.md": "system", "assets/prompts/module.test/sharedassets/reference.md": "reference", "assets/prompts/module.test/sharedassets/transcript.md": "transcript", } for path, want := range tests { data, err := fs.ReadFile(fsys, path) if err != nil { t.Fatalf("ReadFile(%q) error = %v, want nil", path, err) } if string(data) != want { t.Fatalf("ReadFile(%q) = %q, want %q", path, data, want) } } entries, err := fs.ReadDir(fsys, ".") if err != nil { t.Fatalf("ReadDir(.) error = %v, want nil", err) } if len(entries) != 1 || entries[0].Name() != "assets" || !entries[0].IsDir() { t.Fatalf("ReadDir(.) = %#v, want assets directory", entries) } entries, err = fs.ReadDir(fsys, "assets/prompts/module.test/sharedassets") if err != nil { t.Fatalf("ReadDir(sharedassets) error = %v, want nil", err) } if len(entries) != 3 || entries[0].Name() != "reference.md" || entries[1].Name() != "system.md" || entries[2].Name() != "transcript.md" { t.Fatalf("ReadDir(sharedassets) = %#v, want sorted shared prompt entries", entries) } } func TestModulePromptFSRejectsMissingModuleFile(t *testing.T) { _, err := ModulePromptFS("module.test", fstest.MapFS{}, []ModulePromptFile{ {Name: "task.md", Path: "assets/prompts/task.md"}, }) if err == nil || !strings.Contains(err.Error(), "read module prompt asset assets/prompts/task.md") { t.Fatalf("ModulePromptFS() error = %v, want missing module asset context", err) } } func TestModulePromptFSRejectsMissingSharedFile(t *testing.T) { _, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/task.md": {Data: []byte("task")}, }, []ModulePromptFile{ {Name: "task.md", Path: "assets/prompts/task.md"}, }, SharedPromptFile{Name: "system.md", FS: fstest.MapFS{}, Path: "shared/system.md"}) if err == nil || !strings.Contains(err.Error(), "read shared prompt asset shared/system.md") { t.Fatalf("ModulePromptFS() error = %v, want missing shared asset context", err) } } func TestModulePromptFSRejectsNestedVirtualFileName(t *testing.T) { _, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/task.md": {Data: []byte("task")}, }, []ModulePromptFile{ {Name: "nested/task.md", Path: "assets/prompts/task.md"}, }) if err == nil || !strings.Contains(err.Error(), "must not contain path separators") { t.Fatalf("ModulePromptFS() error = %v, want nested file name error", err) } } func TestModulePromptFSRejectsNestedSharedFileName(t *testing.T) { _, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/task.md": {Data: []byte("task")}, }, []ModulePromptFile{ {Name: "task.md", Path: "assets/prompts/task.md"}, }, SharedPromptFile{Name: "nested/system.md", FS: fstest.MapFS{}, Path: "shared/system.md"}) if err == nil || !strings.Contains(err.Error(), "shared prompt file name") || !strings.Contains(err.Error(), "must not contain path separators") { t.Fatalf("ModulePromptFS() error = %v, want nested shared file name error", err) } } func TestModulePromptFSRejectsDuplicateNormalizedDestinations(t *testing.T) { for _, test := range []struct { name string call func() error want string }{ { name: "module", call: func() error { _, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/first.md": {Data: []byte("first")}, "assets/prompts/second.md": {Data: []byte("second")}, }, []ModulePromptFile{ {Name: "task.md", Path: "assets/prompts/first.md"}, {Name: " task.md ", Path: "assets/prompts/second.md"}, }) return err }, want: `duplicate module prompt destination "assets/prompts/module.test/task.md"`, }, { name: "sharedassets", call: func() error { shared := fstest.MapFS{ "shared/first.md": {Data: []byte("first")}, "shared/second.md": {Data: []byte("second")}, } _, err := ModulePromptFS("module.test", fstest.MapFS{}, nil, SharedPromptFile{Name: "task.md", FS: shared, Path: "shared/first.md"}, SharedPromptFile{Name: " task.md ", FS: shared, Path: "shared/second.md"}, ) return err }, want: `duplicate sharedassets prompt destination "assets/prompts/module.test/sharedassets/task.md"`, }, } { t.Run(test.name, func(t *testing.T) { if err := test.call(); err == nil || err.Error() != test.want { t.Fatalf("ModulePromptFS() error = %v, want %q", err, test.want) } }) } } func TestModulePromptFSAllowsMatchingModuleAndSharedBasenames(t *testing.T) { fsys, err := ModulePromptFS("module.test", fstest.MapFS{ "assets/prompts/system.md": {Data: []byte("module")}, }, []ModulePromptFile{{Name: "system.md", Path: "assets/prompts/system.md"}}, SharedPromptFile{Name: "system.md", FS: fstest.MapFS{"shared/system.md": {Data: []byte("shared")}}, Path: "shared/system.md"}, ) if err != nil { t.Fatal(err) } for name, want := range map[string]string{ "assets/prompts/module.test/system.md": "module", "assets/prompts/module.test/sharedassets/system.md": "shared", } { data, err := fs.ReadFile(fsys, name) if err != nil || string(data) != want { t.Fatalf("ReadFile(%q) = %q, %v; want %q", name, data, err, want) } } } func TestModulePromptFSRejectsInvalidModuleDir(t *testing.T) { _, err := ModulePromptFS(".", fstest.MapFS{}, nil) if err == nil || !strings.Contains(err.Error(), "module prompt directory") { t.Fatalf("ModulePromptFS() error = %v, want module directory context", err) } } func TestModulePromptFSRejectsNilFilesystems(t *testing.T) { _, err := ModulePromptFS("module.test", nil, nil) if err == nil || !strings.Contains(err.Error(), "module prompt filesystem must not be nil") { t.Fatalf("ModulePromptFS() error = %v, want nil module filesystem error", err) } _, err = ModulePromptFS("module.test", fstest.MapFS{}, nil, SharedPromptFile{Name: "system.md", Path: "shared/system.md"}) if err == nil || !strings.Contains(err.Error(), "shared prompt file \"system.md\" filesystem must not be nil") { t.Fatalf("ModulePromptFS() error = %v, want nil shared filesystem error", err) } }