Remove obsolete and misleading tests
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,155 +17,6 @@ var _ Normalizer[fakeArtifact] = fakeNormalizer{}
|
|||||||
var _ StructuredLLMClient = fakeLLMClient{}
|
var _ StructuredLLMClient = fakeLLMClient{}
|
||||||
var _ OutputEncoder = fakeOutputEncoder{}
|
var _ OutputEncoder = fakeOutputEncoder{}
|
||||||
|
|
||||||
func TestFakeExtractorReturnsTypedOutput(t *testing.T) {
|
|
||||||
extractor := fakeExtractor{
|
|
||||||
key: "generic-extractor",
|
|
||||||
}
|
|
||||||
doc := &source.SourceDocument{
|
|
||||||
ID: "source-1",
|
|
||||||
Kind: "document",
|
|
||||||
Format: "text/plain",
|
|
||||||
Digest: "sha256:abc123",
|
|
||||||
Units: []source.SourceUnit{
|
|
||||||
{ID: 1, Kind: "section", Text: "Source text."},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := extractor.Extract(context.Background(), TypedExtractionRequest{Source: doc})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Extract() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if extractor.Key() != "generic-extractor" {
|
|
||||||
t.Fatalf("Key() = %q, want generic-extractor", extractor.Key())
|
|
||||||
}
|
|
||||||
if result.Value.Value != "example" {
|
|
||||||
t.Fatalf("Value = %q, want example", result.Value.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFakeChunkerReturnsSourcePlan(t *testing.T) {
|
|
||||||
doc := &source.SourceDocument{
|
|
||||||
ID: "source-1",
|
|
||||||
Kind: "document",
|
|
||||||
Format: "text/plain",
|
|
||||||
Digest: "sha256:abc123",
|
|
||||||
Units: []source.SourceUnit{
|
|
||||||
{ID: 1, Kind: "section", Text: "Source text."},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
chunker := fakeChunker{key: "generic-chunker"}
|
|
||||||
|
|
||||||
result, err := chunker.Plan(context.Background(), ChunkRequest{Source: doc})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Plan() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if chunker.Key() != "generic-chunker" {
|
|
||||||
t.Fatalf("Key() = %q, want generic-chunker", chunker.Key())
|
|
||||||
}
|
|
||||||
if len(result.Plan.Ranges) != 1 {
|
|
||||||
t.Fatalf("len(Ranges) = %d, want 1", len(result.Plan.Ranges))
|
|
||||||
}
|
|
||||||
if result.Plan.SourceDigest != doc.Digest || result.Plan.Ranges[0].StartUnitID != 1 || result.Plan.Ranges[0].EndUnitID != 1 {
|
|
||||||
t.Fatalf("Plan = %#v, want source digest and unit range", result.Plan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFakeChunkerReceivesPerRunContext(t *testing.T) {
|
|
||||||
doc := &source.SourceDocument{
|
|
||||||
ID: "source-1",
|
|
||||||
Kind: "document",
|
|
||||||
Format: "text/plain",
|
|
||||||
Digest: "sha256:abc123",
|
|
||||||
Units: []source.SourceUnit{
|
|
||||||
{ID: 1, Kind: "section", Text: "Source text."},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
chunker := &recordingChunker{key: "llm-chunker"}
|
|
||||||
|
|
||||||
if _, err := chunker.Plan(context.Background(), ChunkRequest{Source: doc, SessionID: "session", LLMProfile: "profile"}); err != nil {
|
|
||||||
t.Fatalf("Plan() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if chunker.request.SessionID != "session" || chunker.request.LLMProfile != "profile" {
|
|
||||||
t.Fatalf("ChunkRequest = %#v, want per-run session and profile", chunker.request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFakeExtractorReceivesChunkAndAmbientContext(t *testing.T) {
|
|
||||||
extractor := fakeExtractor{key: "generic-extractor"}
|
|
||||||
doc := &source.SourceDocument{
|
|
||||||
ID: "source-1",
|
|
||||||
Kind: "document",
|
|
||||||
Format: "text/plain",
|
|
||||||
Digest: "sha256:abc123",
|
|
||||||
Units: []source.SourceUnit{
|
|
||||||
{ID: 1, Kind: "section", Text: "First source text."},
|
|
||||||
{ID: 2, Kind: "section", Text: "Second source text."},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
chunk := source.Chunk{
|
|
||||||
ID: "source-1:chunk:1",
|
|
||||||
SourceID: doc.ID,
|
|
||||||
Index: 1,
|
|
||||||
Ref: source.SourceRef{SourceID: doc.ID, StartUnitID: 2, EndUnitID: 2},
|
|
||||||
Content: []byte(`{"units":[{"id":2,"kind":"section","text":"Second source text."}]}`),
|
|
||||||
MediaType: "application/json",
|
|
||||||
Units: []source.SourceUnit{doc.Units[1]},
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := extractor.Extract(context.Background(), TypedExtractionRequest{
|
|
||||||
Source: doc,
|
|
||||||
Chunk: &chunk,
|
|
||||||
AmbientContext: map[string]any{"mode": "chunked"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Extract() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if result.Value.Value != "chunked" {
|
|
||||||
t.Fatalf("Value = %q, want chunked", result.Value.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReferenceSetDataTypes(t *testing.T) {
|
|
||||||
references := ReferenceSet{
|
|
||||||
Slots: map[string]ResolvedReferenceSlot{
|
|
||||||
"roster": {
|
|
||||||
Slot: ReferenceSlot{
|
|
||||||
Name: "roster",
|
|
||||||
Description: "Known characters",
|
|
||||||
Required: true,
|
|
||||||
AcceptedMediaTypes: []string{"text/plain"},
|
|
||||||
Multiple: true,
|
|
||||||
MaxBytes: 4096,
|
|
||||||
},
|
|
||||||
Items: []ReferenceItem{
|
|
||||||
{
|
|
||||||
SlotName: "roster",
|
|
||||||
MediaType: "text/plain",
|
|
||||||
Content: []byte("Aria\nBryn\n"),
|
|
||||||
Digest: "sha256:reference",
|
|
||||||
Origin: ReferenceOrigin{
|
|
||||||
Type: "file",
|
|
||||||
URI: "file:///tmp/roster.txt",
|
|
||||||
},
|
|
||||||
SizeBytes: 10,
|
|
||||||
BindingSource: ReferenceBindingSourceConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
item := references.Slots["roster"].Items[0]
|
|
||||||
if item.SlotName != "roster" || item.MediaType != "text/plain" || string(item.Content) != "Aria\nBryn\n" {
|
|
||||||
t.Fatalf("reference item = %#v, want constructed item fields", item)
|
|
||||||
}
|
|
||||||
if item.BindingSource != ReferenceBindingSourceConfig {
|
|
||||||
t.Fatalf("BindingSource = %q, want %q", item.BindingSource, ReferenceBindingSourceConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloneReferenceSlotsEmptyInputReturnsNil(t *testing.T) {
|
func TestCloneReferenceSlotsEmptyInputReturnsNil(t *testing.T) {
|
||||||
if got := CloneReferenceSlots(nil); got != nil {
|
if got := CloneReferenceSlots(nil); got != nil {
|
||||||
t.Fatalf("CloneReferenceSlots(nil) = %#v, want nil", got)
|
t.Fatalf("CloneReferenceSlots(nil) = %#v, want nil", got)
|
||||||
@@ -319,61 +169,6 @@ func TestArtifactSchemaJSONOmitsSchemaContent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFakeMergeNormalizeAndOutputContracts(t *testing.T) {
|
|
||||||
extractOutput := ExtractArtifact[fakeArtifact]{LaneID: "generic-lane", ExtractorKey: "generic-extractor", SourceID: "source-1", ChunkID: "source-1:chunk:0", ChunkIndex: 0, Value: fakeArtifact{Value: "example"}}
|
|
||||||
merger := fakeMerger{key: "generic-merger"}
|
|
||||||
normalizer := fakeNormalizer{key: "generic-normalizer"}
|
|
||||||
encoder := fakeOutputEncoder{key: "generic-output"}
|
|
||||||
|
|
||||||
merged, err := merger.Merge(context.Background(), TypedMergeRequest[fakeArtifact]{
|
|
||||||
LaneID: "generic-lane",
|
|
||||||
ExtractOutputs: []ExtractArtifact[fakeArtifact]{extractOutput},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Merge() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if merger.Key() != "generic-merger" {
|
|
||||||
t.Fatalf("Merger.Key() = %q, want generic-merger", merger.Key())
|
|
||||||
}
|
|
||||||
if merged.Value.Value != "example" {
|
|
||||||
t.Fatalf("merged value = %q, want example", merged.Value.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
normalized, err := normalizer.Normalize(context.Background(), TypedNormalizeRequest[fakeArtifact]{
|
|
||||||
LaneID: "generic-lane",
|
|
||||||
MergeOutput: MergeArtifact[fakeArtifact]{LaneID: "generic-lane", MergerKey: merger.Key(), SourceID: "source-1", Value: merged.Value},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Normalize() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if normalizer.Key() != "generic-normalizer" {
|
|
||||||
t.Fatalf("Normalizer.Key() = %q, want generic-normalizer", normalizer.Key())
|
|
||||||
}
|
|
||||||
if normalized.Value.Value != "example" {
|
|
||||||
t.Fatalf("normalized value = %q, want example", normalized.Value.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded, err := encoder.Encode(context.Background(), OutputRequest{
|
|
||||||
Manifest: artifacts.RunManifest{RunID: "run-1"},
|
|
||||||
NormalizeOutputs: []SerializedOutput{{LaneID: "generic-lane", NormalizerKey: normalizer.Key(), SourceID: "source-1", Artifact: SerializedArtifact{Kind: "test/artifact", Schema: ArtifactSchema{ID: "schema-id", Name: "schema-name", Version: "v1"}, MediaType: "application/json", Content: []byte(`{"value":"example"}`)}}},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Encode() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if encoder.Key() != "generic-output" {
|
|
||||||
t.Fatalf("OutputEncoder.Key() = %q, want generic-output", encoder.Key())
|
|
||||||
}
|
|
||||||
if len(encoded.Files) != 1 {
|
|
||||||
t.Fatalf("len(Files) = %d, want 1", len(encoded.Files))
|
|
||||||
}
|
|
||||||
if encoded.Files[0].ContentType != "application/json" {
|
|
||||||
t.Fatalf("ContentType = %q, want application/json", encoded.Files[0].ContentType)
|
|
||||||
}
|
|
||||||
if string(encoded.Files[0].Bytes) != `{"run_id":"run-1","output_count":1}` {
|
|
||||||
t.Fatalf("Bytes = %s, want encoded output", encoded.Files[0].Bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputFileJSONShapeOmitsBytes(t *testing.T) {
|
func TestOutputFileJSONShapeOmitsBytes(t *testing.T) {
|
||||||
file := OutputFile{
|
file := OutputFile{
|
||||||
Name: "artifacts/events.json",
|
Name: "artifacts/events.json",
|
||||||
@@ -441,24 +236,6 @@ func (chunker fakeChunker) Plan(ctx context.Context, req ChunkRequest) (ChunkPla
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordingChunker struct {
|
|
||||||
key string
|
|
||||||
request ChunkRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chunker *recordingChunker) Key() string {
|
|
||||||
return chunker.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chunker *recordingChunker) ReferenceSlots() []ReferenceSlot {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chunker *recordingChunker) Plan(ctx context.Context, req ChunkRequest) (ChunkPlanResult, error) {
|
|
||||||
chunker.request = req
|
|
||||||
return fakeChunker{key: chunker.key}.Plan(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeExtractor struct {
|
type fakeExtractor struct {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
package llm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestScriptoriumPublicAPIGrounding(t *testing.T) {
|
|
||||||
// Keep this compile-time grounding close to the future Notarius adapter so
|
|
||||||
// dependency upgrades reveal API drift before the runtime cutover.
|
|
||||||
engine, err := scriptorium.NewEngine(
|
|
||||||
scriptorium.Config{
|
|
||||||
PromptDir: "unused-when-prompt-option-is-set",
|
|
||||||
ProfileDir: "",
|
|
||||||
SchemaDir: "",
|
|
||||||
Timeout: time.Second,
|
|
||||||
},
|
|
||||||
scriptorium.WithPromptFS(fstest.MapFS{}, "."),
|
|
||||||
scriptorium.WithProfileFS(fstest.MapFS{}, "."),
|
|
||||||
scriptorium.WithSchemaFS(fstest.MapFS{}, "."),
|
|
||||||
scriptorium.WithProfiles(scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
|
|
||||||
ID: "test-profile",
|
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
|
||||||
Model: "test-model",
|
|
||||||
APIKeyRequired: true,
|
|
||||||
ExtraParams: map[string]any{"mode": "test"},
|
|
||||||
})),
|
|
||||||
scriptorium.WithLLMClient(scriptoriumGroundingLLMClient{}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewEngine() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if engine == nil {
|
|
||||||
t.Fatalf("NewEngine() = nil, want engine")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ func(string) scriptorium.Option = scriptorium.WithPromptFile
|
|
||||||
_ func(string) scriptorium.Option = scriptorium.WithProfileFile
|
|
||||||
_ func(string) scriptorium.Option = scriptorium.WithSchemaFile
|
|
||||||
)
|
|
||||||
|
|
||||||
req := scriptorium.RunRequest{
|
|
||||||
PromptID: "dnd.spells",
|
|
||||||
PromptVersion: "v1",
|
|
||||||
ProfileID: "test-profile",
|
|
||||||
APIKey: "request-scoped-secret",
|
|
||||||
Inputs: map[string]scriptorium.ArtifactRef{
|
|
||||||
"transcript": scriptorium.InlineWithURI("file:///tmp/transcript.json", `{"segments":[]}`),
|
|
||||||
"glossary": scriptorium.Inline(""),
|
|
||||||
"roster": scriptorium.File("/tmp/roster.txt"),
|
|
||||||
},
|
|
||||||
Vars: map[string]string{
|
|
||||||
"session_id": "session-1",
|
|
||||||
},
|
|
||||||
Execution: &scriptorium.ExecutionTargetOverride{
|
|
||||||
Model: "override-model",
|
|
||||||
Temperature: ptr(0.2),
|
|
||||||
MaxTokens: ptr(100),
|
|
||||||
TopP: ptr(0.9),
|
|
||||||
TimeoutSeconds: ptr(30),
|
|
||||||
ServiceTier: "standard",
|
|
||||||
ReasoningEffort: "low",
|
|
||||||
APIKeyEnv: "SCRIPTORIUM_API_KEY",
|
|
||||||
ExtraParams: map[string]any{"provider_option": "value"},
|
|
||||||
},
|
|
||||||
Validation: &scriptorium.OutputContract{
|
|
||||||
Format: scriptorium.FormatJSON,
|
|
||||||
ValidationMode: scriptorium.ValidationJSONSchema,
|
|
||||||
SchemaPath: "schemas/dnd_spells.v1.json",
|
|
||||||
RepairAttempts: 1,
|
|
||||||
},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"artifact_kind": "dnd_spell",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if req.Inputs["transcript"].Type != scriptorium.ArtifactRefInline {
|
|
||||||
t.Fatalf("inline input type = %q, want %q", req.Inputs["transcript"].Type, scriptorium.ArtifactRefInline)
|
|
||||||
}
|
|
||||||
if req.Inputs["roster"].Type != scriptorium.ArtifactRefFile {
|
|
||||||
t.Fatalf("file input type = %q, want %q", req.Inputs["roster"].Type, scriptorium.ArtifactRefFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := scriptorium.RunResult{
|
|
||||||
RunID: "run-1",
|
|
||||||
Artifact: scriptorium.Artifact{
|
|
||||||
Name: "output",
|
|
||||||
ContentType: "application/json",
|
|
||||||
Body: []byte(`{"ok":true}`),
|
|
||||||
URI: "inline://output",
|
|
||||||
Size: int64(len(`{"ok":true}`)),
|
|
||||||
Hash: "sha256:abc",
|
|
||||||
},
|
|
||||||
RawOutput: `{"ok":true}`,
|
|
||||||
PromptID: req.PromptID,
|
|
||||||
PromptVersion: req.PromptVersion,
|
|
||||||
PromptHash: "prompt-hash",
|
|
||||||
RenderedPromptHash: "rendered-prompt-hash",
|
|
||||||
SelectedProfileID: req.ProfileID,
|
|
||||||
ModelName: "test-model",
|
|
||||||
Endpoint: "http://127.0.0.1:1/v1",
|
|
||||||
EffectiveModelParams: scriptorium.ExecutionTarget{
|
|
||||||
Model: "test-model",
|
|
||||||
APIKeyEnv: "SCRIPTORIUM_API_KEY",
|
|
||||||
ExtraParams: map[string]any{"provider_option": "value"},
|
|
||||||
ReasoningEffort: "low",
|
|
||||||
},
|
|
||||||
InputHashes: map[string]string{
|
|
||||||
"transcript": "sha256:def",
|
|
||||||
},
|
|
||||||
Validation: scriptorium.ValidationResult{
|
|
||||||
Status: scriptorium.ValidationPassed,
|
|
||||||
Mode: scriptorium.ValidationJSONSchema,
|
|
||||||
SchemaPath: req.Validation.SchemaPath,
|
|
||||||
RepairAttempts: 1,
|
|
||||||
IsValid: true,
|
|
||||||
},
|
|
||||||
Usage: scriptorium.TokenUsage{
|
|
||||||
PromptTokens: 10,
|
|
||||||
CompletionTokens: 5,
|
|
||||||
TotalTokens: 15,
|
|
||||||
CachedTokens: 3,
|
|
||||||
CacheWriteTokens: 2,
|
|
||||||
},
|
|
||||||
StartTime: time.Unix(1, 0),
|
|
||||||
EndTime: time.Unix(2, 0),
|
|
||||||
Duration: time.Second,
|
|
||||||
}
|
|
||||||
if result.Validation.Status != scriptorium.ValidationPassed {
|
|
||||||
t.Fatalf("validation status = %q, want %q", result.Validation.Status, scriptorium.ValidationPassed)
|
|
||||||
}
|
|
||||||
if result.Usage.TotalTokens != 15 {
|
|
||||||
t.Fatalf("total tokens = %d, want 15", result.Usage.TotalTokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
publicErrors := []error{
|
|
||||||
scriptorium.ErrInvalidConfig,
|
|
||||||
scriptorium.ErrInvalidRequest,
|
|
||||||
scriptorium.ErrPromptNotFound,
|
|
||||||
scriptorium.ErrProfileNotFound,
|
|
||||||
scriptorium.ErrPromptLoad,
|
|
||||||
scriptorium.ErrProfileLoad,
|
|
||||||
scriptorium.ErrArtifactLoad,
|
|
||||||
scriptorium.ErrPromptRender,
|
|
||||||
scriptorium.ErrLLMGenerate,
|
|
||||||
scriptorium.ErrValidation,
|
|
||||||
}
|
|
||||||
for _, publicErr := range publicErrors {
|
|
||||||
if !errors.Is(publicErr, publicErr) {
|
|
||||||
t.Fatalf("sentinel error does not match itself: %v", publicErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type scriptoriumGroundingLLMClient struct{}
|
|
||||||
|
|
||||||
func (scriptoriumGroundingLLMClient) Generate(context.Context, scriptorium.GenerateRequest) (*scriptorium.GenerateResponse, error) {
|
|
||||||
return &scriptorium.GenerateResponse{
|
|
||||||
Content: `{"ok":true}`,
|
|
||||||
Usage: scriptorium.TokenUsage{
|
|
||||||
PromptTokens: 1,
|
|
||||||
CompletionTokens: 1,
|
|
||||||
TotalTokens: 2,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptr[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,6 @@ package validate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApproved(t *testing.T) {
|
func TestApproved(t *testing.T) {
|
||||||
@@ -33,8 +31,3 @@ func TestRejectedTrimsReasonAndMessage(t *testing.T) {
|
|||||||
t.Fatalf("Message = %q, want message", result.Message)
|
t.Fatalf("Message = %q, want message", result.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelpersReturnValidationResults(t *testing.T) {
|
|
||||||
var _ contracts.ValidationResult = Approved()
|
|
||||||
var _ contracts.ValidationResult = Rejected("reason", "message")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package noop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTypedNormalizerPreservesValue(t *testing.T) {
|
|
||||||
normalizer := NewTyped[string]()
|
|
||||||
result, err := normalizer.Normalize(context.Background(), contracts.TypedNormalizeRequest[string]{MergeOutput: contracts.MergeArtifact[string]{Value: "value"}})
|
|
||||||
if err != nil || result.Value != "value" {
|
|
||||||
t.Fatalf("result=%#v err=%v", result, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -164,87 +164,6 @@ func TestRunnerPassesPartyAndGlossaryReferencesToDNDSpellsPrompt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunnerDoesNotExtractSpellMentionedOnlyInPartyReference(t *testing.T) {
|
|
||||||
raw := readDNDSpellsFixture(t)
|
|
||||||
resolved := resolveDNDSpellsPipeline(t)
|
|
||||||
resolved.ResolvedPipeline.ArtifactLanes[0].ExtractReferences.ReferenceSet = dndSpellsReferenceSet(
|
|
||||||
"Mira: wizard who can cast Lightning Bolt",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
llmClient := &fakeSpellsLLMClient{
|
|
||||||
response: extractionResponse{SpellCasts: []spellCastResponse{}},
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := runPreparedPipeline(t, dndSpellsRunnerRegistries(t), resolved.ResolvedPipeline, llmClient, pipeline.RunInput{
|
|
||||||
RawInput: raw,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Run() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(output.NormalizeOutputs) != 1 {
|
|
||||||
t.Fatalf("len(NormalizeOutputs) = %d, want empty spell response output", len(output.NormalizeOutputs))
|
|
||||||
}
|
|
||||||
response := decodeRunnerSpellResponse(t, output.NormalizeOutputs[0].Artifact.Content)
|
|
||||||
if len(response.SpellCasts) != 0 {
|
|
||||||
t.Fatalf("spell_casts = %#v, want no party-reference-only spell casts", response.SpellCasts)
|
|
||||||
}
|
|
||||||
if len(llmClient.requests) != 1 {
|
|
||||||
t.Fatalf("LLM calls = %d, want 1", len(llmClient.requests))
|
|
||||||
}
|
|
||||||
request := llmClient.requests[0]
|
|
||||||
if request.PromptID != spells.PromptID || request.PromptVersion != spells.SchemaVersion {
|
|
||||||
t.Fatalf("prompt = %q/%q, want %q/%q", request.PromptID, request.PromptVersion, spells.PromptID, spells.SchemaVersion)
|
|
||||||
}
|
|
||||||
if got := string(request.Inputs["party"].Content); !strings.Contains(got, "Lightning Bolt") {
|
|
||||||
t.Fatalf("party input = %q, want party-reference-only spell in reference input", got)
|
|
||||||
}
|
|
||||||
if output.Manifest.ValidationStatus != "approved" {
|
|
||||||
t.Fatalf("ValidationStatus = %q, want approved empty extraction", output.Manifest.ValidationStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunnerCarriesDNDSpellCastWithInvalidSourceRefToSerializedOutput(t *testing.T) {
|
|
||||||
raw := readDNDSpellsFixture(t)
|
|
||||||
resolved := resolveDNDSpellsPipeline(t)
|
|
||||||
llmClient := &fakeSpellsLLMClient{
|
|
||||||
response: extractionResponse{
|
|
||||||
SpellCasts: []spellCastResponse{
|
|
||||||
{
|
|
||||||
Caster: "Aria",
|
|
||||||
Spell: "Cure Wounds",
|
|
||||||
Effect: "Heals an injured ally.",
|
|
||||||
NarrativeDescription: "Aria restores the fighter after the fight.",
|
|
||||||
SourceRefs: responseSourceRefs("spell-session", 999, 999),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := runPreparedPipeline(t, dndSpellsRunnerRegistries(t), resolved.ResolvedPipeline, llmClient, pipeline.RunInput{
|
|
||||||
RawInput: raw,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Run() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
if len(output.NormalizeOutputs) != 1 {
|
|
||||||
t.Fatalf("len(NormalizeOutputs) = %d, want 1", len(output.NormalizeOutputs))
|
|
||||||
}
|
|
||||||
response := decodeRunnerSpellResponse(t, output.NormalizeOutputs[0].Artifact.Content)
|
|
||||||
if len(response.SpellCasts) != 1 {
|
|
||||||
t.Fatalf("len(spell_casts) = %d, want 1", len(response.SpellCasts))
|
|
||||||
}
|
|
||||||
if response.SpellCasts[0].SourceRefs[0].SourceID != "spell-session" {
|
|
||||||
t.Fatalf("SourceID = %q, want invalid source ref preserved", response.SpellCasts[0].SourceRefs[0].SourceID)
|
|
||||||
}
|
|
||||||
if len(output.Rejected) != 0 {
|
|
||||||
t.Fatalf("len(Rejected) = %d, want 0", len(output.Rejected))
|
|
||||||
}
|
|
||||||
if output.Manifest.ValidationStatus != "approved" {
|
|
||||||
t.Fatalf("ValidationStatus = %q, want approved", output.Manifest.ValidationStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dndSpellsReferenceSet(party string, glossary string) contracts.ReferenceSet {
|
func dndSpellsReferenceSet(party string, glossary string) contracts.ReferenceSet {
|
||||||
slots := make(map[string]contracts.ResolvedReferenceSlot)
|
slots := make(map[string]contracts.ResolvedReferenceSlot)
|
||||||
if strings.TrimSpace(party) != "" {
|
if strings.TrimSpace(party) != "" {
|
||||||
@@ -282,22 +201,6 @@ func dndSpellsReferenceSet(party string, glossary string) contracts.ReferenceSet
|
|||||||
return contracts.ReferenceSet{Slots: slots}
|
return contracts.ReferenceSet{Slots: slots}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunnerRejectsMalformedDNDSpellsArtifactAtSerializationBoundary(t *testing.T) {
|
|
||||||
raw := readDNDSpellsFixture(t)
|
|
||||||
resolved := resolveDNDSpellsPipeline(t)
|
|
||||||
llmClient := &fakeSpellsLLMClient{response: extractionResponse{}}
|
|
||||||
|
|
||||||
output, err := runPreparedPipeline(t, dndSpellsRunnerRegistries(t), resolved.ResolvedPipeline, llmClient, pipeline.RunInput{
|
|
||||||
RawInput: raw,
|
|
||||||
})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "spell_casts must be present") {
|
|
||||||
t.Fatalf("Run() error = %v, want invalid spell-list serialization error", err)
|
|
||||||
}
|
|
||||||
if len(output.NormalizeOutputs) != 0 {
|
|
||||||
t.Fatalf("len(NormalizeOutputs) = %d, want no serialized malformed artifact", len(output.NormalizeOutputs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveDNDSpellsPipeline(t *testing.T) config.EffectiveConfig {
|
func resolveDNDSpellsPipeline(t *testing.T) config.EffectiveConfig {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user