Files
weatherreporter/internal/promptassets/promptassets_test.go

162 lines
5.8 KiB
Go

package promptassets_test
import (
"context"
"encoding/json"
"io/fs"
"strings"
"testing"
"gitea.maximumdirect.net/eric/promptkit"
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptassets"
"gopkg.in/yaml.v3"
)
type promptDefinition struct {
ID string `yaml:"id"`
Version string `yaml:"version"`
DefaultProfile string `yaml:"default_profile"`
Inputs []struct {
Name string `yaml:"name"`
Required bool `yaml:"required"`
ContentType string `yaml:"content_type"`
} `yaml:"inputs"`
Output struct {
Format string `yaml:"format"`
ValidationMode string `yaml:"validation_mode"`
SchemaPath string `yaml:"schema_path"`
RepairAttempts *int `yaml:"repair_attempts"`
} `yaml:"output"`
}
func TestPromptAssetsDeclareTheFourGeneratedTextPrompts(t *testing.T) {
tests := []struct {
path string
id string
schemaID string
}{
{"daily/daily_generated_text.yml", "weather.daily_generated_text", "daily"},
{"today/today_generated_text.yml", "weather.today_generated_text", "today"},
{"tomorrow/tomorrow_generated_text.yml", "weather.tomorrow_generated_text", "tomorrow"},
{"hourly/hourly_generated_text.yml", "weather.hourly_generated_text", "hourly"},
}
definitions := 0
if err := fs.WalkDir(promptassets.PromptFS(), ".", func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if !entry.IsDir() && strings.HasSuffix(path, ".yml") {
definitions++
}
return nil
}); err != nil {
t.Fatalf("walk embedded prompts: %v", err)
}
if definitions != len(tests) {
t.Fatalf("prompt definitions = %d, want %d", definitions, len(tests))
}
for _, tc := range tests {
t.Run(tc.id, func(t *testing.T) {
data, err := fs.ReadFile(promptassets.PromptFS(), tc.path)
if err != nil {
t.Fatalf("read prompt definition: %v", err)
}
var definition promptDefinition
if err := yaml.Unmarshal(data, &definition); err != nil {
t.Fatalf("decode prompt definition: %v", err)
}
if definition.ID != tc.id || definition.Version != "1.0.0" || definition.DefaultProfile != "gemini-flash-latest" {
t.Fatalf("definition = %#v, want %s version 1.0.0 and gemini-flash-latest", definition, tc.id)
}
if len(definition.Inputs) != 1 || definition.Inputs[0].Name != "data_package" || !definition.Inputs[0].Required || definition.Inputs[0].ContentType != "application/yaml" {
t.Fatalf("inputs = %#v, want one required YAML data_package", definition.Inputs)
}
if definition.Output.Format != "json" || definition.Output.ValidationMode != "json_schema" || definition.Output.SchemaPath != tc.schemaID+".generated_text.schema.json" || definition.Output.RepairAttempts != nil {
t.Fatalf("output = %#v, want JSON schema output without repair attempts", definition.Output)
}
if _, err := promptassets.Schema(tc.schemaID); err != nil {
t.Fatalf("Schema(%q) error = %v", tc.schemaID, err)
}
})
}
}
func TestSchemasAreCanonicalAndIndependent(t *testing.T) {
for _, id := range []string{"daily", "today", "tomorrow", "hourly"} {
t.Run(id, func(t *testing.T) {
data, err := promptassets.Schema(id)
if err != nil {
t.Fatalf("Schema() error = %v", err)
}
var schema struct {
ID string `json:"$id"`
Title string `json:"title"`
Type string `json:"type"`
AdditionalProperties bool `json:"additionalProperties"`
Required []string `json:"required"`
Properties map[string]any `json:"properties"`
}
if err := json.Unmarshal(data, &schema); err != nil {
t.Fatalf("decode schema: %v", err)
}
if schema.Type != "object" || schema.AdditionalProperties || strings.Join(schema.Required, ",") != "summary,forecast_discussion" {
t.Fatalf("schema = %#v, want strict generated-text object", schema)
}
if _, ok := schema.Properties["confidence"]; !ok {
t.Fatalf("schema properties = %#v, want confidence", schema.Properties)
}
if id == "daily" && (schema.ID != "weatherreporter.daily.generated_text.schema.json" || schema.Title != "Daily GeneratedText") {
t.Fatalf("daily schema identity = %q/%q, want corrected Daily identity", schema.ID, schema.Title)
}
data[0] = 'x'
fresh, err := promptassets.Schema(id)
if err != nil || fresh[0] != '{' {
t.Fatalf("Schema() returned shared data or error: %v", err)
}
})
}
}
func TestPromptkitInspectsEmbeddedPromptsOffline(t *testing.T) {
engine, err := promptkit.NewEngine(promptkit.Config{},
promptkit.WithPromptFS(promptassets.PromptFS(), "."),
promptkit.WithSchemaFS(promptassets.SchemaFS(), "."),
)
if err != nil {
t.Fatalf("NewEngine() error = %v", err)
}
for _, id := range []string{"weather.daily_generated_text", "weather.today_generated_text", "weather.tomorrow_generated_text", "weather.hourly_generated_text"} {
t.Run(id, func(t *testing.T) {
inspection, err := engine.InspectPrompt(context.Background(), id, "1.0.0")
if err != nil {
t.Fatalf("InspectPrompt() error = %v", err)
}
if inspection.PromptID != id || inspection.PromptVersion != "1.0.0" || inspection.DefaultProfileID != "gemini-flash-latest" {
t.Fatalf("inspection = %#v", inspection)
}
})
}
}
func TestPromptAssetsExcludeRetiredRuntimeSettings(t *testing.T) {
if err := fs.WalkDir(promptassets.PromptFS(), ".", func(path string, entry fs.DirEntry, err error) error {
if err != nil || entry.IsDir() {
return err
}
data, err := fs.ReadFile(promptassets.PromptFS(), path)
if err != nil {
return err
}
for _, unwanted := range []string{"local-heavy", "pipeline-weather/", "application/json", "repair_attempts:", "weather.daily_report"} {
if strings.Contains(string(data), unwanted) {
t.Fatalf("%s contains retired runtime setting %q", path, unwanted)
}
}
return nil
}); err != nil {
t.Fatalf("walk embedded prompts: %v", err)
}
}