229 lines
7.7 KiB
Go
229 lines
7.7 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
)
|
|
|
|
func TestResolveRejectsEmptyAndUnknownPipelineID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pipelineID string
|
|
want string
|
|
}{
|
|
{name: "empty", pipelineID: " ", want: "pipeline id"},
|
|
{name: "unknown", pipelineID: "missing", want: "not configured"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := validConfig().Resolve(ResolveInput{PipelineID: tc.pipelineID, Catalog: fakeCatalog(t)})
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.want, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveLaneFilteringSuccessAndFailure(t *testing.T) {
|
|
effective, err := validConfig().Resolve(ResolveInput{
|
|
PipelineID: " example ",
|
|
Only: []string{" notes "},
|
|
Catalog: fakeCatalog(t),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
|
|
if effective.PipelineID != "example" {
|
|
t.Fatalf("unexpected pipeline ID: %q", effective.PipelineID)
|
|
}
|
|
if len(effective.ResolvedPipeline.ArtifactLanes) != 1 || effective.ResolvedPipeline.ArtifactLanes[0].ID != "notes" {
|
|
t.Fatalf("unexpected resolved lanes: %+v", effective.ResolvedPipeline.ArtifactLanes)
|
|
}
|
|
if effective.ResolvedPipeline.Digest == "" {
|
|
t.Fatalf("expected digest")
|
|
}
|
|
|
|
_, err = validConfig().Resolve(ResolveInput{
|
|
PipelineID: "example",
|
|
Only: []string{"missing"},
|
|
Catalog: fakeCatalog(t),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "selected artifact lane") {
|
|
t.Fatalf("expected invalid lane error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveUsesTrimmedPipelineMapKeys(t *testing.T) {
|
|
cfg := validConfig()
|
|
cfg.Pipelines[" example "] = cfg.Pipelines["example"]
|
|
delete(cfg.Pipelines, "example")
|
|
|
|
effective, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: fakeCatalog(t)})
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
if effective.PipelineID != "example" {
|
|
t.Fatalf("unexpected pipeline ID: %q", effective.PipelineID)
|
|
}
|
|
}
|
|
|
|
func TestResolveSurfacesUnknownModuleKeyThroughCatalog(t *testing.T) {
|
|
cfg := validConfig()
|
|
lane := cfg.Pipelines["example"].Artifacts["events"]
|
|
lane.Extract = pipeline.Binding("missing/extract")
|
|
cfg.Pipelines["example"].Artifacts["events"] = lane
|
|
|
|
_, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: fakeCatalog(t)})
|
|
if err == nil || !strings.Contains(err.Error(), "missing/extract") || !strings.Contains(err.Error(), "events") {
|
|
t.Fatalf("expected unknown module error with lane context, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveSurfacesMissingCapabilityThroughCatalog(t *testing.T) {
|
|
_, err := validConfig().Resolve(ResolveInput{
|
|
PipelineID: "example",
|
|
Catalog: fakeCatalog(t, pipeline.ModuleSpec{
|
|
Key: "json",
|
|
Stage: pipeline.StageOutput,
|
|
Requires: []string{"missing-capability"},
|
|
}),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "missing capability") || !strings.Contains(err.Error(), "json") {
|
|
t.Fatalf("expected missing capability error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveCanBindSceneChunkerFromCatalog(t *testing.T) {
|
|
cfg := validConfig()
|
|
profile := cfg.Pipelines["example"]
|
|
profile.Chunk = pipeline.Binding("dnd/scenes")
|
|
lane := profile.Artifacts["events"]
|
|
profile.Artifacts = map[string]pipeline.ArtifactLaneProfile{"events": lane}
|
|
cfg.Pipelines["example"] = profile
|
|
|
|
catalog := fakeCatalog(t,
|
|
pipeline.ModuleSpec{
|
|
Key: "fake/input",
|
|
Stage: pipeline.StageInput,
|
|
Provides: []string{"source.transcript"},
|
|
},
|
|
pipeline.ModuleSpec{
|
|
Key: "fake/extract",
|
|
Stage: pipeline.StageExtract,
|
|
Requires: []string{"chunks", "source.transcript"},
|
|
Provides: []string{"artifact"},
|
|
},
|
|
)
|
|
mustRegisterChunker(t, catalog.Chunkers, pipeline.ModuleSpec{
|
|
Key: "dnd/scenes",
|
|
Stage: pipeline.StageChunk,
|
|
Requires: []string{"source.transcript"},
|
|
Provides: []string{"chunks", "chunks.scenes"},
|
|
})
|
|
|
|
effective, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: catalog})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v, want nil", err)
|
|
}
|
|
if got := effective.ResolvedPipeline.Chunk.Module; got != "dnd/scenes" {
|
|
t.Fatalf("Chunk.Module = %q, want dnd/scenes", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveDigestChangesWhenEffectiveConfigChanges(t *testing.T) {
|
|
cfg := validConfig()
|
|
first, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: fakeCatalog(t)})
|
|
if err != nil {
|
|
t.Fatalf("Resolve first: %v", err)
|
|
}
|
|
|
|
lane := cfg.Pipelines["example"].Artifacts["events"]
|
|
lane.Extract.Options = map[string]any{"temperature": 0.2}
|
|
cfg.Pipelines["example"].Artifacts["events"] = lane
|
|
second, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: fakeCatalog(t)})
|
|
if err != nil {
|
|
t.Fatalf("Resolve second: %v", err)
|
|
}
|
|
|
|
if first.ResolvedPipeline.Digest == second.ResolvedPipeline.Digest {
|
|
t.Fatalf("expected digest to change, got %q", first.ResolvedPipeline.Digest)
|
|
}
|
|
}
|
|
|
|
func TestResolveLLMProfileOverrideAppliesBeforeDigest(t *testing.T) {
|
|
cfg := validConfig()
|
|
profile := cfg.Pipelines["example"]
|
|
profile.Input.LLMProfile = "input-profile"
|
|
profile.Output.LLMProfile = "output-profile"
|
|
lane := profile.Artifacts["events"]
|
|
lane.Merge.LLMProfile = "merge-profile"
|
|
lane.Extract.Validators = pipeline.ValidatorOverride{
|
|
Set: true,
|
|
Validators: []pipeline.ModuleBinding{
|
|
{Module: "fake/llm-validator", LLMProfile: "validator-profile"},
|
|
},
|
|
}
|
|
profile.Artifacts["events"] = lane
|
|
cfg.Pipelines["example"] = profile
|
|
|
|
base, err := cfg.Resolve(ResolveInput{PipelineID: "example", Catalog: fakeCatalog(t)})
|
|
if err != nil {
|
|
t.Fatalf("Resolve base: %v", err)
|
|
}
|
|
effective, err := cfg.Resolve(ResolveInput{
|
|
PipelineID: "example",
|
|
Catalog: fakeCatalog(t),
|
|
LLMProfileOverride: "runtime",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve override: %v", err)
|
|
}
|
|
|
|
if base.ResolvedPipeline.Digest == effective.ResolvedPipeline.Digest {
|
|
t.Fatalf("expected digest to change after LLM profile override")
|
|
}
|
|
for _, binding := range llmCapableBindings(effective.ResolvedPipeline) {
|
|
if binding.LLMProfile != "runtime" {
|
|
t.Fatalf("LLM-capable binding profile = %q, want runtime", binding.LLMProfile)
|
|
}
|
|
}
|
|
if effective.ResolvedPipeline.Input.LLMProfile != "input-profile" {
|
|
t.Fatalf("input profile = %q, want original input-profile", effective.ResolvedPipeline.Input.LLMProfile)
|
|
}
|
|
if effective.ResolvedPipeline.Output.LLMProfile != "output-profile" {
|
|
t.Fatalf("output profile = %q, want original output-profile", effective.ResolvedPipeline.Output.LLMProfile)
|
|
}
|
|
eventLane := effective.ResolvedPipeline.ArtifactLanes[0]
|
|
if eventLane.Merge.LLMProfile != "runtime" {
|
|
t.Fatalf("merge profile = %q, want runtime", eventLane.Merge.LLMProfile)
|
|
}
|
|
validatorChain := findEffectiveValidatorChain(effective.ResolvedPipeline.ValidatorChains, pipeline.StageExtract, "events", "fake/extract")
|
|
if validatorChain == nil || len(validatorChain.Validators) != 1 {
|
|
t.Fatalf("validator chain = %#v, want one extract validator", effective.ResolvedPipeline.ValidatorChains)
|
|
}
|
|
if validatorChain.Validators[0].Binding.LLMProfile != "validator-profile" {
|
|
t.Fatalf("validator profile = %q, want original validator-profile", validatorChain.Validators[0].Binding.LLMProfile)
|
|
}
|
|
}
|
|
|
|
func findEffectiveValidatorChain(chains []pipeline.ResolvedValidatorChain, stage pipeline.ModuleStage, laneID string, module string) *pipeline.ResolvedValidatorChain {
|
|
for i := range chains {
|
|
if chains[i].Stage == stage && chains[i].LaneID == laneID && chains[i].ModuleKey == module {
|
|
return &chains[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func llmCapableBindings(resolved pipeline.ResolvedPipeline) []pipeline.ModuleBinding {
|
|
bindings := []pipeline.ModuleBinding{resolved.Chunk}
|
|
for _, lane := range resolved.ArtifactLanes {
|
|
bindings = append(bindings, lane.Extract, lane.Merge, lane.Normalize)
|
|
}
|
|
return bindings
|
|
}
|