264 lines
7.4 KiB
Go
264 lines
7.4 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
func TestDocumentRejectsExplicitInvalidBranches(t *testing.T) {
|
|
t.Parallel()
|
|
for _, slot := range []string{"input", "output"} {
|
|
t.Run(slot+" empty validators", func(t *testing.T) {
|
|
body := "input: {module: input, validators: []}"
|
|
if slot == "output" {
|
|
body = "input: {module: input}\n output: {module: output, validators: []}"
|
|
}
|
|
_, err := loadConfigSource(configkit.Bytes("document.yml", []byte("version: 5\npipelines:\n session:\n "+body+"\n")), LoadOptions{})
|
|
assertDocumentDiagnosticAt(t, err, "/pipelines/session/"+slot+"/validators")
|
|
})
|
|
}
|
|
for _, value := range []string{
|
|
"{path: ''}", "{path: ' '}",
|
|
"{path: '', artifact: {step: previous, lane: registry}}",
|
|
"{path: ' ', artifact: {step: previous, lane: registry}}",
|
|
"{path: null, artifact: {step: previous, lane: registry}}",
|
|
} {
|
|
t.Run(value, func(t *testing.T) {
|
|
content := fmt.Sprintf("version: 5\npipelines:\n session:\n input: {module: input}\n artifacts:\n lane:\n extract:\n module: extract\n references:\n party: %s\n", value)
|
|
_, err := loadConfigSource(configkit.Bytes("document.yml", []byte(content)), LoadOptions{})
|
|
path := "/pipelines/session/artifacts/lane/extract/references/party"
|
|
if value == "{path: ''}" || value == "{path: ' '}" || value == "{path: null, artifact: {step: previous, lane: registry}}" {
|
|
path += "/path"
|
|
}
|
|
assertDocumentDiagnosticAt(t, err, path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDocumentPreservesSupportedEmptyValidatorsAndReferenceForms(t *testing.T) {
|
|
t.Parallel()
|
|
loaded, err := loadConfigSource(configkit.Bytes("document.yml", []byte(`version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: input}
|
|
chunk: {module: chunk, validators: []}
|
|
artifacts:
|
|
lane:
|
|
validators: []
|
|
extract:
|
|
module: extract
|
|
validators: []
|
|
references:
|
|
external: {path: /data/party.txt}
|
|
generated: {artifact: {step: previous, lane: registry}}
|
|
merge: {module: merge, validators: []}
|
|
normalize: {module: normalize, validators: []}
|
|
`)), LoadOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
profile := loaded.Config.Pipelines["session"]
|
|
lane := profile.Artifacts["lane"]
|
|
for _, override := range []bool{profile.Chunk.Validators.Set, lane.Extract.Validators.Set, lane.Merge.Validators.Set, lane.Normalize.Validators.Set} {
|
|
if !override {
|
|
t.Fatal("supported empty validator override lost explicitness")
|
|
}
|
|
}
|
|
if len(lane.Validators) != 0 || lane.Extract.References["external"].Path != "/data/party.txt" ||
|
|
lane.Extract.References["generated"].Artifact.Lane != "registry" {
|
|
t.Fatalf("lane = %#v", lane)
|
|
}
|
|
}
|
|
|
|
func TestDocumentPipelineNormalizesCanonicalIdentifiers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaded, err := loadDocumentSource(configkit.Bytes("document.yml", []byte(`version: 5
|
|
pipelines:
|
|
" session ":
|
|
llm_profile: " local "
|
|
input:
|
|
module: " seriatim "
|
|
artifacts:
|
|
" spells ":
|
|
extract:
|
|
module: " dnd/spells "
|
|
structured_output_repair_attempts: 0
|
|
validators: []
|
|
merge:
|
|
module: dnd/merge
|
|
references:
|
|
previous:
|
|
artifact:
|
|
step: " default "
|
|
lane: " spells "
|
|
`)), LoadOptions{})
|
|
if err != nil {
|
|
var loadError *configkit.LoadError
|
|
if errors.As(err, &loadError) {
|
|
t.Fatalf("loadDocumentSource() diagnostics = %#v", loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("loadDocumentSource() error = %v", err)
|
|
}
|
|
profile, ok := loaded.Config.Pipelines["session"]
|
|
if !ok || profile.Input.Module != "seriatim" || profile.LLMProfile == nil || *profile.LLMProfile != "local" {
|
|
t.Fatalf("normalized pipeline = %#v", loaded.Config.Pipelines)
|
|
}
|
|
lane, ok := profile.Artifacts["spells"]
|
|
if !ok || lane.Extract.StructuredOutputRepairAttempts == nil || *lane.Extract.StructuredOutputRepairAttempts != 0 || lane.Extract.Validators == nil || len(lane.Extract.Validators) != 0 {
|
|
t.Fatalf("normalized lane = %#v", profile.Artifacts)
|
|
}
|
|
selector := lane.Merge.References["previous"].Artifact
|
|
if selector == nil || selector.Step != "default" || selector.Lane != "spells" {
|
|
t.Fatalf("artifact selector = %#v", selector)
|
|
}
|
|
}
|
|
|
|
func TestDocumentPipelineRejectsInvalidShapes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
path string
|
|
}{
|
|
{
|
|
name: "both pipeline forms",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim}
|
|
artifacts: {}
|
|
steps:
|
|
- id: first
|
|
artifacts: {}
|
|
`,
|
|
path: "/pipelines/session/steps",
|
|
},
|
|
{
|
|
name: "empty ordered steps",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim}
|
|
steps: []
|
|
`,
|
|
path: "/pipelines/session/steps",
|
|
},
|
|
{
|
|
name: "normalized pipeline collision",
|
|
source: `version: 5
|
|
pipelines:
|
|
session: {input: {module: seriatim}}
|
|
" session ": {input: {module: seriatim}}
|
|
`,
|
|
path: "/pipelines/session",
|
|
},
|
|
{
|
|
name: "duplicate lane across ordered steps",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim}
|
|
steps:
|
|
- id: first
|
|
artifacts: {summary: {extract: {module: dnd/summary}}}
|
|
- id: second
|
|
artifacts: {summary: {extract: {module: dnd/summary}}}
|
|
`,
|
|
path: "/pipelines/session/steps/1/artifacts/summary",
|
|
},
|
|
{
|
|
name: "invalid reference union",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim}
|
|
references:
|
|
party: {path: ./party.txt, artifact: {step: one, lane: summary}}
|
|
`,
|
|
path: "/pipelines/session/references/party",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
_, err := loadDocumentSource(configkit.Bytes("document.yml", []byte(test.source)), LoadOptions{})
|
|
assertDocumentDiagnosticAt(t, err, test.path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDocumentPipelineRejectsInvalidBindingPlacementAndPresence(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
path string
|
|
}{
|
|
{
|
|
name: "input validators",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input:
|
|
module: seriatim
|
|
validators: [{module: generic/valid_json}]
|
|
`,
|
|
path: "/pipelines/session/input/validators",
|
|
},
|
|
{
|
|
name: "blank selected profile",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
llm_profile: " "
|
|
input: {module: seriatim}
|
|
`,
|
|
path: "/pipelines/session/llm_profile",
|
|
},
|
|
{
|
|
name: "null validators",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim}
|
|
chunk: {module: generic/chunk, validators: null}
|
|
`,
|
|
path: "/pipelines/session/chunk/validators",
|
|
},
|
|
{
|
|
name: "negative retry budget",
|
|
source: `version: 5
|
|
pipelines:
|
|
session:
|
|
input: {module: seriatim, retries: -1}
|
|
`,
|
|
path: "/pipelines/session/input/retries",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
_, err := loadDocumentSource(configkit.Bytes("document.yml", []byte(test.source)), LoadOptions{})
|
|
assertDocumentDiagnosticAt(t, err, test.path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertDocumentDiagnosticAt(t *testing.T, err error, path string) {
|
|
t.Helper()
|
|
var loadError *configkit.LoadError
|
|
if !errors.As(err, &loadError) {
|
|
t.Fatalf("error = %v, want ConfigKit load error", err)
|
|
}
|
|
for _, diagnostic := range loadError.Diagnostics() {
|
|
if diagnostic.Path.String() == path {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("diagnostics = %#v, want a diagnostic at %s", loadError.Diagnostics(), path)
|
|
}
|