158 lines
6.3 KiB
Go
158 lines
6.3 KiB
Go
package promptkit_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit"
|
|
"gitea.maximumdirect.net/eric/promptkit"
|
|
)
|
|
|
|
type sourceDocument struct {
|
|
First string `yaml:"first"`
|
|
Second string `yaml:"second"`
|
|
}
|
|
|
|
func TestCollectionInputValidatesWithoutReadingSource(t *testing.T) {
|
|
input, err := promptkit.CollectionInput("docs", configkit.File(filepath.Join(t.TempDir(), "not-read.yaml")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if input.Destination.String() != "/collections/docs" || !input.Imports {
|
|
t.Fatalf("collection input = %+v, want destination /collections/docs with imports enabled", input)
|
|
}
|
|
for _, namespace := range []string{"", " ", "docs/api"} {
|
|
if _, err := promptkit.CollectionInput(namespace, configkit.NoSource()); !errors.Is(err, promptkit.ErrInvalidConfig) {
|
|
t.Errorf("namespace %q error = %v, want ErrInvalidConfig", namespace, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectionInputLoadsNativeImportsAndLaterOverride(t *testing.T) {
|
|
directory := t.TempDir()
|
|
mainPath := filepath.Join(directory, "collection.yaml")
|
|
defaultsPath := filepath.Join(directory, "defaults.yaml")
|
|
if err := os.WriteFile(mainPath, []byte("configkit:\n imports:\n - path: defaults.yaml\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(defaultsPath, []byte("prompts:\n greeting:\n version: imported\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
input, err := promptkit.CollectionInput("docs", configkit.File(mainPath))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}},
|
|
Main: &configkit.Input{
|
|
Source: configkit.Mapping("later collection override", map[string]any{"prompts": map[string]any{"greeting": map[string]any{"version": "overridden"}}}),
|
|
Destination: configkit.Keys("collections", "docs"),
|
|
},
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("construct engine with native collection import: %v", err)
|
|
}
|
|
inspection, err := engine.InspectPrompt(context.Background(), "docs/greeting")
|
|
if err != nil || inspection.PromptVersion == nil || *inspection.PromptVersion != "overridden" {
|
|
t.Fatalf("inspect imported and overlaid prompt = (%+v, %v)", inspection, err)
|
|
}
|
|
}
|
|
|
|
func TestCollectionInputLoadsEmbeddedImportsAndLaterOverride(t *testing.T) {
|
|
files := fstest.MapFS{
|
|
"collection.yaml": {Data: []byte("configkit:\n imports:\n - path: defaults.yaml\n")},
|
|
"defaults.yaml": {Data: []byte("prompts:\n greeting:\n version: imported\n messages:\n - role: user\n content: Hello\n output:\n format: text\n validation_mode: none\n")},
|
|
}
|
|
filesystem := configkit.NewFilesystem("embedded collection", files)
|
|
input, err := promptkit.CollectionInput("docs", filesystem.File("collection.yaml"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
input.Filesystem = &configkit.FilesystemContext{FS: &filesystem, Root: "."}
|
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{input}}},
|
|
Main: &configkit.Input{
|
|
Source: configkit.Mapping("later collection override", map[string]any{"prompts": map[string]any{"greeting": map[string]any{"version": "overridden"}}}),
|
|
Destination: configkit.Keys("collections", "docs"),
|
|
},
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("construct engine with embedded collection import: %v", err)
|
|
}
|
|
inspection, err := engine.InspectPrompt(context.Background(), "docs/greeting")
|
|
if err != nil || inspection.PromptVersion == nil || *inspection.PromptVersion != "overridden" {
|
|
t.Fatalf("inspect imported and overlaid prompt = (%+v, %v)", inspection, err)
|
|
}
|
|
}
|
|
|
|
func TestDirectoryInputsSelectsSortedFilesWithRootContext(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.Mkdir(filepath.Join(root, "nested"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, data := range map[string]string{
|
|
"a.yaml": "first: one\n", "nested/b.yml": "second: two\n", "nested/body.md": "message",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(root, name), []byte(data), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
inputs, err := promptkit.DirectoryInputs(root)
|
|
if err != nil || len(inputs) != 2 || inputs[0].Imports || inputs[1].Imports {
|
|
t.Fatalf("directory inputs: %+v, %v", inputs, err)
|
|
}
|
|
assertSourceInputs(t, inputs, "message")
|
|
if _, err := promptkit.DirectoryInputs(filepath.Join(root, "missing")); err == nil {
|
|
t.Fatal("missing selected directory succeeded")
|
|
}
|
|
if empty, err := promptkit.DirectoryInputs(t.TempDir()); err != nil || len(empty) != 0 {
|
|
t.Fatalf("empty directory: %+v, %v", empty, err)
|
|
}
|
|
}
|
|
|
|
func TestFilesystemInputsSelectsEmbeddedFilesWithRootContext(t *testing.T) {
|
|
files := fstest.MapFS{
|
|
"settings/a.yaml": {Data: []byte("first: one\n")},
|
|
"settings/nested/b.yml": {Data: []byte("second: two\n")},
|
|
"settings/nested/body.md": {Data: []byte("message")},
|
|
}
|
|
inputs, err := promptkit.FilesystemInputs("embedded", files, "settings")
|
|
if err != nil || len(inputs) != 2 || inputs[0].Imports || inputs[1].Imports {
|
|
t.Fatalf("filesystem inputs: %+v, %v", inputs, err)
|
|
}
|
|
assertSourceInputs(t, inputs, "message")
|
|
if _, err := promptkit.FilesystemInputs("embedded", files, "missing"); err == nil {
|
|
t.Fatal("missing selected root succeeded")
|
|
}
|
|
}
|
|
|
|
func assertSourceInputs(t *testing.T, inputs []configkit.Input, expected string) {
|
|
t.Helper()
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[sourceDocument]{
|
|
Underlays: []configkit.Underlay{{Inputs: inputs}},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Config.First != "one" || result.Config.Second != "two" {
|
|
t.Fatalf("configuration files did not compose: %+v", result.Config)
|
|
}
|
|
field, found := result.Metadata().Lookup(configkit.Keys("second"))
|
|
if !found || len(field.Origin.Locations) != 1 {
|
|
t.Fatalf("missing second-field origin: %+v", field)
|
|
}
|
|
context, found := result.SourceContext(field.Origin.Locations[0].SourceID)
|
|
if !found || context.BaseDir != "nested" {
|
|
t.Fatalf("unexpected rooted source context: %+v", context)
|
|
}
|
|
data, err := fs.ReadFile(context.FS, "nested/body.md")
|
|
if err != nil || string(data) != expected {
|
|
t.Fatalf("rooted resource read: %q, %v", data, err)
|
|
}
|
|
}
|