Switch CLI configuration to ConfigKit

This commit is contained in:
2026-09-26 18:08:36 +00:00
parent 207c75dda3
commit 36e7e857f5
16 changed files with 295 additions and 959 deletions

View File

@@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net/http"
@@ -18,8 +17,8 @@ import (
"testing"
"time"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/promptkit"
appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config"
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
renderformat "gitea.maximumdirect.net/eric/scriptorium/internal/format"
)
@@ -65,7 +64,7 @@ func TestParseMappingsMalformed(t *testing.T) {
}
func TestParseRunArgsRequiredFlags(t *testing.T) {
configPath := writeAppConfigFile(t, "")
configPath := writeAppConfigFile(t, "{}\n")
_, err := parseRunArgs([]string{"--config", configPath, "--profile-dir", "./profiles", "--prompt", "p", "--input", "a=b"})
if err == nil {
@@ -161,7 +160,7 @@ func TestParseRunArgsRejectsRawLLMAPIKeyFlag(t *testing.T) {
}
func TestParseServeArgsRequiredFlags(t *testing.T) {
configPath := writeAppConfigFile(t, "")
configPath := writeAppConfigFile(t, "{}\n")
_, err := parseServeArgs([]string{"--config", configPath, "--profile-dir", "./profiles"})
if err == nil {
@@ -364,22 +363,9 @@ func TestParseRunArgsInvalidExplicitConfigReturnsError(t *testing.T) {
if err == nil {
t.Fatal("expected invalid explicit config error")
}
if !strings.Contains(err.Error(), "application config error") {
t.Fatalf("expected application config context, got %v", err)
}
}
func TestResolveAppSettingsMissingImplicitConfigDoesNotError(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
settings, err := resolveAppSettings(fs, filepath.Join(t.TempDir(), "missing.yml"), appconfig.CLIOverrides{})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.SchemaDir != defaults.SchemaDirDefault {
t.Fatalf("expected built-in schema dir, got %q", settings.SchemaDir)
}
if settings.ServerAddr != defaults.HTTPAddrDefault {
t.Fatalf("expected built-in server addr, got %q", settings.ServerAddr)
var loadError *configkit.LoadError
if !errors.As(err, &loadError) {
t.Fatalf("expected structured application config error, got %v", err)
}
}
@@ -413,6 +399,85 @@ schema_dir: ./from-config/schemas
}
}
func TestApplicationFlagsPreserveExplicitEmptyValues(t *testing.T) {
configPath := writeAppConfigFile(t, `prompt_dir: ./configured/prompts
profile_dir: ./configured/profiles
schema_dir: ./configured/schemas
server:
addr: ":9090"
artifact_root: ./configured/artifacts
`)
if _, err := parseRunArgs([]string{"--config", configPath, "--prompt", "p", "--prompt-dir="}); err == nil || !strings.Contains(err.Error(), "prompt directory is required") {
t.Fatalf("explicit empty prompt directory restored lower value: %v", err)
}
render, err := parseRenderArgs([]string{"--config", configPath, "--prompt", "p", "--profile-dir=", "--schema-dir="})
if err != nil || render.profileDir != "" || render.schemaDir != "" {
t.Fatalf("render did not preserve explicit empty sources: cfg=%+v err=%v", render, err)
}
serve, err := parseServeArgs([]string{"--config", configPath, "--addr=", "--artifact-root="})
if err != nil || serve.addr != "" || serve.artifactRoot != "" {
t.Fatalf("serve did not preserve explicit empty settings: cfg=%+v err=%v", serve, err)
}
prompt, err := parsePromptInspectionArgs([]string{"--config", configPath, "--prompt", "p", "--prompt-dir="})
if err != nil {
t.Fatal(err)
}
if _, err := resolveAppSettingsForPromptInspection(prompt); err == nil || !strings.Contains(err.Error(), "prompt directory is required") {
t.Fatalf("prompt inspection restored lower directory: %v", err)
}
profile, err := parseProfileInspectionArgs([]string{"--config", configPath, "--profile", "p", "--profile-dir="})
if err != nil {
t.Fatal(err)
}
settings, err := resolveAppSettingsForProfileInspection(profile)
if err != nil || settings.profileDir != "" {
t.Fatalf("profile inspection required a source or restored lower value: settings=%+v err=%v", settings, err)
}
}
func TestApplicationLoadUsesNativeYAMLAndSafeDiagnostics(t *testing.T) {
for _, body := range []string{
"",
"server: null\n",
"server:\n addr: null\n",
"server:\n unknown: 1\n",
"prompt_dir: &alias ./prompts\n",
"prompt_dir: ./prompts\n---\nprofile_dir: ./profiles\n",
} {
path := writeAppConfigFile(t, body)
_, err := parseRunArgs([]string{"--config", path, "--prompt-dir", "./prompts", "--prompt", "p"})
var loadError *configkit.LoadError
if !errors.As(err, &loadError) {
t.Fatalf("native YAML failure %q lost structured diagnostics: %v", body, err)
}
}
const rejected = "sensitive-synthetic-marker"
path := writeAppConfigFile(t, "defaults:\n render_format: "+rejected+"\n")
code, stdout, stderr := runCLICommand(t, renderCommand, []string{"--config", path, "--prompt-dir", "./prompts", "--prompt", "p"})
if code != ExitRuntimeError || stdout != "" || !strings.Contains(stderr, "validate.error") || strings.Contains(stderr, rejected) {
t.Fatalf("unsafe or misclassified load diagnostic: code=%d stdout=%q stderr=%q", code, stdout, stderr)
}
}
type failingDiagnosticWriter struct{}
func (failingDiagnosticWriter) Write([]byte) (int, error) {
return 0, errors.New("diagnostic sink unavailable")
}
func TestApplicationDiagnosticWriterFailure(t *testing.T) {
diagnostics := []configkit.Diagnostic{{Code: "test.error", Message: "safe diagnostic"}}
if err := writeApplicationWarnings(failingDiagnosticWriter{}, diagnostics); err == nil {
t.Fatal("expected diagnostic writer failure")
}
path := writeAppConfigFile(t, "unknown_field: secret-value\n")
if code := runCommand([]string{"--config", path, "--prompt", "sample"}, io.Discard, failingDiagnosticWriter{}); code != ExitRuntimeError {
t.Fatalf("diagnostic writer failure must retain runtime error exit status, got %d", code)
}
}
func TestParseRenderArgsWithExplicitConfigLoadsDirectoriesAndFormat(t *testing.T) {
configPath := writeAppConfigFile(t, `
prompt_dir: ./from-config/prompts
@@ -1533,7 +1598,7 @@ func TestExecutionReportPathValidationAndPreInvocationFailure(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if code != ExitRuntimeError || stdout != "" || !strings.Contains(stderr, "run parse error") || string(data) != "old report" {
if code != ExitRuntimeError || stdout != "" || !strings.Contains(stderr, "source.missing") || string(data) != "old report" {
t.Fatalf("pre-invocation failure changed report: code=%d stdout=%q stderr=%q report=%q", code, stdout, stderr, data)
}
}