253 lines
7.8 KiB
Go
253 lines
7.8 KiB
Go
package transcript
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/generic/merge/appendorder"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/generic/normalize/noop"
|
|
)
|
|
|
|
func TestPipelineConfigLoadsAndResolvesWithSeriatimInput(t *testing.T) {
|
|
cfg := loadPipelineConfig(t)
|
|
|
|
resolved, err := cfg.Resolve(config.ResolveInput{
|
|
PipelineID: "seriatim-fixture",
|
|
Catalog: seriatimTestCatalog(t, ModuleSpec()),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v, want nil", err)
|
|
}
|
|
|
|
if resolved.ResolvedPipeline.Input.Module != Key {
|
|
t.Fatalf("resolved input module = %q, want %q", resolved.ResolvedPipeline.Input.Module, Key)
|
|
}
|
|
if resolved.ResolvedPipeline.Digest == "" {
|
|
t.Fatal("resolved digest is empty")
|
|
}
|
|
|
|
again, err := cfg.Resolve(config.ResolveInput{
|
|
PipelineID: "seriatim-fixture",
|
|
Catalog: seriatimTestCatalog(t, ModuleSpec()),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("second Resolve() error = %v, want nil", err)
|
|
}
|
|
if resolved.ResolvedPipeline.Digest != again.ResolvedPipeline.Digest {
|
|
t.Fatalf("resolved digest = %q, second digest = %q; want stable digest", resolved.ResolvedPipeline.Digest, again.ResolvedPipeline.Digest)
|
|
}
|
|
}
|
|
|
|
func TestPipelineConfigRejectsMissingSeriatimCapability(t *testing.T) {
|
|
spec := ModuleSpec()
|
|
spec.Provides = withoutCapability(spec.Provides, "transcript.timestamps")
|
|
cfg := loadPipelineConfig(t)
|
|
|
|
_, err := cfg.Resolve(config.ResolveInput{
|
|
PipelineID: "seriatim-fixture",
|
|
Catalog: seriatimTestCatalog(t, spec),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Resolve() error = nil, want missing capability error")
|
|
}
|
|
if !strings.Contains(err.Error(), "missing capability") || !strings.Contains(err.Error(), "transcript.timestamps") {
|
|
t.Fatalf("Resolve() error = %q, want missing transcript.timestamps capability", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestPipelineConfigRejectsUnknownLaneSelection(t *testing.T) {
|
|
cfg := loadPipelineConfig(t)
|
|
|
|
_, err := cfg.Resolve(config.ResolveInput{
|
|
PipelineID: "seriatim-fixture",
|
|
Only: []string{"missing"},
|
|
Catalog: seriatimTestCatalog(t, ModuleSpec()),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Resolve() error = nil, want unknown lane error")
|
|
}
|
|
if !strings.Contains(err.Error(), "selected artifact lane") || !strings.Contains(err.Error(), "missing") {
|
|
t.Fatalf("Resolve() error = %q, want unknown lane context", err.Error())
|
|
}
|
|
}
|
|
|
|
func loadPipelineConfig(t *testing.T) config.Config {
|
|
t.Helper()
|
|
|
|
data, err := os.ReadFile("testdata/pipeline.yml")
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(pipeline.yml) error = %v, want nil", err)
|
|
}
|
|
fileCfg, err := config.ParseFileConfigYAML(data)
|
|
if err != nil {
|
|
t.Fatalf("ParseFileConfigYAML() error = %v, want nil", err)
|
|
}
|
|
|
|
cfg := config.Default()
|
|
if err := cfg.ApplyFileConfig(fileCfg); err != nil {
|
|
t.Fatalf("ApplyFileConfig() error = %v, want nil", err)
|
|
}
|
|
|
|
profile, ok := cfg.Pipelines["seriatim-fixture"]
|
|
if !ok {
|
|
t.Fatal("pipeline seriatim-fixture was not loaded")
|
|
}
|
|
if profile.Input.Module != Key {
|
|
t.Fatalf("loaded input module = %q, want %q", profile.Input.Module, Key)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func seriatimTestCatalog(t *testing.T, inputSpec pipeline.ModuleSpec) pipeline.ModuleCatalog {
|
|
t.Helper()
|
|
|
|
inputs := pipeline.NewInputAdapterRegistry()
|
|
chunkers := pipeline.NewChunkerRegistry()
|
|
extractors := pipeline.NewExtractorRegistry()
|
|
mergers := pipeline.NewMergerRegistry()
|
|
normalizers := pipeline.NewNormalizerRegistry()
|
|
outputs := pipeline.NewOutputEncoderRegistry()
|
|
|
|
if reflect.DeepEqual(inputSpec, ModuleSpec()) {
|
|
if err := Register(inputs); err != nil {
|
|
t.Fatalf("register seriatim input: %v", err)
|
|
}
|
|
} else if err := inputs.RegisterWithSpec(inputSpec, func() (contracts.InputAdapter, error) {
|
|
return New(), nil
|
|
}); err != nil {
|
|
t.Fatalf("register seriatim input override: %v", err)
|
|
}
|
|
|
|
mustRegisterChunker(t, chunkers, pipeline.ModuleSpec{
|
|
Key: "fake/chunk",
|
|
Stage: pipeline.StageChunk,
|
|
Requires: []string{"source.transcript"},
|
|
Provides: []string{"chunks"},
|
|
})
|
|
mustRegisterExtractor(t, extractors, pipeline.ModuleSpec{
|
|
Key: "fake/extract",
|
|
Stage: pipeline.StageExtract,
|
|
Requires: []string{"chunks", "transcript.speaker", "transcript.timestamps"},
|
|
Provides: []string{"fake.artifacts"},
|
|
})
|
|
mustRegisterMerger(t, mergers, pipeline.ModuleSpec{
|
|
Key: pipeline.DefaultMergeModule,
|
|
Stage: pipeline.StageMerge,
|
|
Requires: []string{"fake.artifacts"},
|
|
})
|
|
mustRegisterNormalizer(t, normalizers, pipeline.ModuleSpec{
|
|
Key: pipeline.DefaultNormalizeModule,
|
|
Stage: pipeline.StageNormalize,
|
|
})
|
|
mustRegisterOutput(t, outputs, pipeline.ModuleSpec{
|
|
Key: pipeline.DefaultOutputModule,
|
|
Stage: pipeline.StageOutput,
|
|
})
|
|
|
|
return pipeline.ModuleCatalog{
|
|
Inputs: inputs,
|
|
Chunkers: chunkers,
|
|
ArtifactCodecs: pipeline.NewArtifactCodecRegistry(),
|
|
Extractors: extractors,
|
|
Mergers: mergers,
|
|
Normalizers: normalizers,
|
|
ValidatorChains: pipeline.NewValidatorChainRegistry(),
|
|
Outputs: outputs,
|
|
}
|
|
}
|
|
|
|
func mustRegisterChunker(t *testing.T, registry *pipeline.ChunkerRegistry, spec pipeline.ModuleSpec) {
|
|
t.Helper()
|
|
if err := registry.RegisterWithSpec(spec, func() (contracts.Chunker, error) {
|
|
return fakeChunker{}, nil
|
|
}); err != nil {
|
|
t.Fatalf("register chunker: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustRegisterExtractor(t *testing.T, registry *pipeline.ExtractorRegistry, spec pipeline.ModuleSpec) {
|
|
t.Helper()
|
|
if err := registry.RegisterWithSpec(spec, func() (contracts.Extractor, error) {
|
|
return fakeExtractor{}, nil
|
|
}); err != nil {
|
|
t.Fatalf("register extractor: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustRegisterMerger(t *testing.T, registry *pipeline.MergerRegistry, spec pipeline.ModuleSpec) {
|
|
t.Helper()
|
|
if err := registry.RegisterWithSpec(spec, func() (contracts.Merger, error) {
|
|
return appendorder.New(), nil
|
|
}); err != nil {
|
|
t.Fatalf("register merger: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustRegisterNormalizer(t *testing.T, registry *pipeline.NormalizerRegistry, spec pipeline.ModuleSpec) {
|
|
t.Helper()
|
|
if err := registry.RegisterWithSpec(spec, func() (contracts.Normalizer, error) {
|
|
return noop.New(), nil
|
|
}); err != nil {
|
|
t.Fatalf("register normalizer: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustRegisterOutput(t *testing.T, registry *pipeline.OutputEncoderRegistry, spec pipeline.ModuleSpec) {
|
|
t.Helper()
|
|
if err := registry.RegisterWithSpec(spec, func() (contracts.OutputEncoder, error) {
|
|
return fakeOutput{}, nil
|
|
}); err != nil {
|
|
t.Fatalf("register output: %v", err)
|
|
}
|
|
}
|
|
|
|
type fakeChunker struct{}
|
|
|
|
func (fakeChunker) Key() string { return "fake/chunk" }
|
|
|
|
func (fakeChunker) ReferenceSlots() []contracts.ReferenceSlot { return nil }
|
|
|
|
func (fakeChunker) Chunk(ctx context.Context, req contracts.ChunkRequest) (contracts.ChunkResult, error) {
|
|
return contracts.ChunkResult{}, nil
|
|
}
|
|
|
|
type fakeExtractor struct{}
|
|
|
|
func (fakeExtractor) Key() string { return "fake/extract" }
|
|
|
|
func (fakeExtractor) ReferenceSlots() []contracts.ReferenceSlot { return nil }
|
|
|
|
func (fakeExtractor) Extract(ctx context.Context, req contracts.ExtractionRequest) (contracts.ExtractionResult, error) {
|
|
return contracts.ExtractionResult{}, nil
|
|
}
|
|
|
|
type fakeOutput struct{}
|
|
|
|
func (fakeOutput) Key() string { return pipeline.DefaultOutputModule }
|
|
|
|
func (fakeOutput) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
|
|
return contracts.OutputResult{}, nil
|
|
}
|
|
|
|
func withoutCapability(capabilities []string, capability string) []string {
|
|
filtered := make([]string, 0, len(capabilities))
|
|
for _, candidate := range capabilities {
|
|
if candidate != capability {
|
|
filtered = append(filtered, candidate)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
var (
|
|
_ contracts.Chunker = fakeChunker{}
|
|
_ contracts.Extractor = fakeExtractor{}
|
|
_ contracts.OutputEncoder = fakeOutput{}
|
|
)
|