package llm import ( "context" "errors" "io/fs" "os" "path" "path/filepath" "testing" "testing/fstest" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" "gitea.maximumdirect.net/eric/audita/v3/internal/prompts" ck "go.maximumdirect.net/configkit" ) func copyEmbeddedPromptFilesystem(t *testing.T) fstest.MapFS { t.Helper() source := prompts.Filesystem() files := make(fstest.MapFS) err := fs.WalkDir(source, prompts.AssetRoot, func(name string, entry fs.DirEntry, err error) error { if err != nil || entry.IsDir() { return err } data, err := fs.ReadFile(source, name) if err != nil { return err } files[name] = &fstest.MapFile{Data: data} return nil }) if err != nil { t.Fatal(err) } return files } func TestRuntimeClassifiesEmbeddedAssetsAndHonorsCancellation(t *testing.T) { loaded, err := config.Load(ck.Input{Source: ck.NoSource()}, "") if err != nil { t.Fatal(err) } _, err = newRuntimeWithPromptFS(context.Background(), loaded, nil, fstest.MapFS{}) if err == nil || !IsAssetError(err) || !errors.Is(err, fs.ErrNotExist) { t.Fatalf("embedded discovery error was not preserved as an asset error: %v", err) } ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = newRuntimeWithPromptFS(ctx, loaded, nil, fstest.MapFS{}) if !errors.Is(err, context.Canceled) || IsAssetError(err) { t.Fatalf("caller cancellation classification: %v", err) } } func TestBrokenEmbeddedPromptFailsEmbeddedOnlyConstruction(t *testing.T) { promptFS := copyEmbeddedPromptFilesystem(t) assetPath := path.Join(prompts.AssetRoot, "modules.grammar.proposal.yaml") promptFS[assetPath] = &fstest.MapFile{Data: []byte("collections:\n audita:\n prompts:\n - id: modules.grammar.proposal\n messages: []\n")} loaded, err := config.Load(ck.Input{Source: ck.NoSource()}, "") if err != nil { t.Fatal(err) } _, err = newRuntimeWithPromptFS(context.Background(), loaded, nil, promptFS) if err == nil || !IsAssetError(err) { t.Fatalf("broken embedded prompt was not classified as an asset error: %v", err) } } func TestOperatorPromptCanRepairInvalidEmbeddedPrompt(t *testing.T) { promptFS := copyEmbeddedPromptFilesystem(t) assetPath := path.Join(prompts.AssetRoot, "modules.grammar.proposal.yaml") promptFS[assetPath] = &fstest.MapFile{Data: []byte("collections:\n audita:\n prompts:\n - id: modules.grammar.proposal\n messages: []\n")} dir := t.TempDir() for name, content := range map[string]string{ "config.yml": "version: 3\n", "prompts.yml": "collections:\n audita:\n prompts:\n - id: modules.grammar.proposal\n messages:\n - role: user\n content: operator message\n output:\n format: text\n validation_mode: none\n", } { if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0600); err != nil { t.Fatal(err) } } loaded, err := config.Load(ck.Input{Source: ck.File(filepath.Join(dir, "config.yml"))}, "") if err != nil { t.Fatal(err) } runtime, err := newRuntimeWithPromptFS(context.Background(), loaded, nil, promptFS) if err != nil { t.Fatalf("valid final configuration rejected: %v", err) } if runtime.Engine == nil { t.Fatal("runtime engine was not retained") } }