Add strict pipeline and session config loading

This commit is contained in:
2026-05-02 10:43:45 -05:00
parent 902e6bc994
commit 4172849f20
9 changed files with 569 additions and 25 deletions

View File

@@ -2,18 +2,22 @@ package app
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
func TestExecuteValidCommands(t *testing.T) {
pipelinePath, sessionPath := writeValidConfigFiles(t)
cases := []struct {
name string
args []string
wantOut string
}{
{name: "run", args: []string{"run"}, wantOut: "narratio run: not yet implemented"},
{name: "plan", args: []string{"plan"}, wantOut: "narratio plan: not yet implemented"},
{name: "run", args: []string{"run", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio run: configuration loaded and valid"},
{name: "plan", args: []string{"plan", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio plan: configuration loaded and valid"},
{name: "status", args: []string{"status"}, wantOut: "narratio status: not yet implemented"},
{name: "resume", args: []string{"resume"}, wantOut: "narratio resume: not yet implemented"},
{name: "run-stage", args: []string{"run-stage", "polish"}, wantOut: "narratio run-stage: not yet implemented"},
@@ -38,6 +42,35 @@ func TestExecuteValidCommands(t *testing.T) {
}
}
func TestExecuteMissingConfigFlags(t *testing.T) {
cases := []struct {
name string
args []string
want string
}{
{name: "run missing flags", args: []string{"run"}, want: "run: --config and --session are required"},
{name: "plan missing flags", args: []string{"plan"}, want: "plan: --config and --session are required"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute(tc.args, &stdout, &stderr)
if code == 0 {
t.Fatalf("exit code = 0, want non-zero")
}
if stdout.Len() != 0 {
t.Fatalf("stdout = %q, want empty", stdout.String())
}
if !strings.Contains(stderr.String(), tc.want) {
t.Fatalf("stderr = %q, want to contain %q", stderr.String(), tc.want)
}
})
}
}
func TestExecuteInvalidCommand(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
@@ -73,3 +106,46 @@ func TestExecuteMissingCommand(t *testing.T) {
t.Fatalf("stderr = %q, want usage message", stderr.String())
}
}
func writeValidConfigFiles(t *testing.T) (string, string) {
t.Helper()
dir := t.TempDir()
pipelinePath := filepath.Join(dir, "pipeline.yml")
sessionPath := filepath.Join(dir, "session.yml")
pipelineYAML := `workspace:
root: /tmp/narratio
storage:
backend: s3
whisperx:
timeout: 15m
seriatim:
timeout: 30s
audita:
timeout: 1h
analyzer:
timeout: 20m
artifacts:
output_dir: artifacts
notification:
timeout: 10s
`
sessionYAML := `session_id: 2026-05-03
inputs:
audio_dir: ./audio
speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`
if err := os.WriteFile(pipelinePath, []byte(pipelineYAML), 0o644); err != nil {
t.Fatalf("write pipeline config: %v", err)
}
if err := os.WriteFile(sessionPath, []byte(sessionYAML), 0o644); err != nil {
t.Fatalf("write session config: %v", err)
}
return pipelinePath, sessionPath
}

View File

@@ -2,10 +2,41 @@ package app
import (
"context"
"flag"
"fmt"
"io"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
// Plan is a placeholder for future stage planning behavior.
func Plan(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "plan")
// Plan validates configuration inputs and reports readiness for future planning.
func Plan(_ context.Context, args []string, out io.Writer) error {
fs := flag.NewFlagSet("plan", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var pipelinePath string
var sessionPath string
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml")
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
if err := fs.Parse(args); err != nil {
return fmt.Errorf("plan: invalid flags: %w", err)
}
if fs.NArg() != 0 {
return fmt.Errorf("plan: unexpected positional arguments")
}
if pipelinePath == "" || sessionPath == "" {
return fmt.Errorf("plan: --config and --session are required")
}
cfg, err := config.Load(pipelinePath, sessionPath)
if err != nil {
return fmt.Errorf("plan: %w", err)
}
if err := config.Validate(cfg); err != nil {
return fmt.Errorf("plan: %w", err)
}
_, err = fmt.Fprintln(out, "narratio plan: configuration loaded and valid")
return err
}

View File

@@ -2,10 +2,41 @@ package app
import (
"context"
"flag"
"fmt"
"io"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
// Run is a placeholder for the future end-to-end pipeline execution command.
func Run(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "run")
// Run validates configuration inputs and reports readiness for future execution.
func Run(_ context.Context, args []string, out io.Writer) error {
fs := flag.NewFlagSet("run", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var pipelinePath string
var sessionPath string
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml")
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
if err := fs.Parse(args); err != nil {
return fmt.Errorf("run: invalid flags: %w", err)
}
if fs.NArg() != 0 {
return fmt.Errorf("run: unexpected positional arguments")
}
if pipelinePath == "" || sessionPath == "" {
return fmt.Errorf("run: --config and --session are required")
}
cfg, err := config.Load(pipelinePath, sessionPath)
if err != nil {
return fmt.Errorf("run: %w", err)
}
if err := config.Validate(cfg); err != nil {
return fmt.Errorf("run: %w", err)
}
_, err = fmt.Fprintln(out, "narratio run: configuration loaded and valid")
return err
}