Files
notarius/internal/modules/dnd/normalize/npcs/context_material_test.go

133 lines
5.8 KiB
Go

package npcs
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
func TestBuildNormalizeContextMaterialsUsesDocumentOrderAndOwnedInputs(t *testing.T) {
doc := &source.SourceDocument{ID: "session", Units: []source.SourceUnit{
{ID: 40, Kind: "narration", Text: "zero"},
{ID: 10, Kind: "speech", Text: "one", Metadata: map[string]any{"speaker": map[string]any{"name": "Mira"}}},
{ID: 70, Kind: "speech", Text: "two"},
{ID: 20, Kind: "narration", Text: "three"},
{ID: 90, Kind: "speech", Text: "four"},
{ID: 30, Kind: "narration", Text: "five"},
}}
records := []dnd.NPC{
{Name: "Mira Thorn", ID: "npc:sha256:internal", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 20}}},
{Name: "Captain Vale", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 90, EndUnitID: 90}}},
{Name: "Broken", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 10}}},
}
before := append([]dnd.NPC(nil), records...)
materials, ready, err := buildNormalizeContextMaterials(doc, records, 1)
if err != nil || !ready {
t.Fatalf("buildNormalizeContextMaterials() = %#v, %t, %v; want ready materials", materials, ready, err)
}
if !reflect.DeepEqual(records, before) {
t.Fatalf("records mutated to %#v", records)
}
for _, material := range []struct {
name string
data []byte
}{
{name: "candidates", data: materials.Candidates.Content},
{name: "transcript", data: materials.Transcript.Content},
} {
if !json.Valid(material.data) || string(material.data) == "" {
t.Fatalf("%s content = %q, want JSON", material.name, material.data)
}
digest := sha256.Sum256(material.data)
wantDigest := "sha256:" + hex.EncodeToString(digest[:])
got := materials.Candidates
if material.name == "transcript" {
got = materials.Transcript
}
if got.Name != material.name || got.MediaType != "application/json" || got.OriginURI != "" || got.Digest != wantDigest {
t.Fatalf("%s material = %#v, want owned JSON material", material.name, got)
}
}
encoded := string(materials.Candidates.Content) + string(materials.Transcript.Content)
if strings.Contains(encoded, "npc:sha256:internal") || strings.Contains(encoded, doc.ID) {
t.Fatalf("model material leaked private identifier or source id: %s", encoded)
}
var candidates normalizeCandidateInput
if err := json.Unmarshal(materials.Candidates.Content, &candidates); err != nil {
t.Fatal(err)
}
if len(candidates.NPCs) != 2 || candidates.NPCs[0].Name != "Mira Thorn" || candidates.NPCs[0].SourceRefs[0] != (normalizeCandidateSourceRef{StartUnitID: 10, EndUnitID: 20}) {
t.Fatalf("candidates = %#v, want two valid current-order candidates", candidates)
}
var transcript normalizeTranscriptInput
if err := json.Unmarshal(materials.Transcript.Content, &transcript); err != nil {
t.Fatal(err)
}
if len(transcript.Windows) != 1 || len(transcript.Windows[0].Units) != 6 {
t.Fatalf("transcript = %#v, want one coalesced window", transcript)
}
units := transcript.Windows[0].Units
for index, wantID := range []int{40, 10, 70, 20, 90, 30} {
if units[index].ID != wantID {
t.Fatalf("unit %d id = %d, want source-order id %d", index, units[index].ID, wantID)
}
}
if units[0].Cited || !units[1].Cited || !units[2].Cited || !units[3].Cited || !units[4].Cited || units[5].Cited {
t.Fatalf("citation markers = %#v, want original ranges only", units)
}
if units[1].Metadata["speaker"].(map[string]any)["name"] != "Mira" {
t.Fatalf("metadata = %#v, want copied generic metadata", units[1].Metadata)
}
windows, err := normalizeContextWindows(doc.Units, []sourceInterval{{start: 1, end: 1}}, make([]bool, len(doc.Units)))
if err != nil {
t.Fatal(err)
}
windows[0].Units[0].Metadata["speaker"].(map[string]any)["name"] = "changed"
if doc.Units[1].Metadata["speaker"].(map[string]any)["name"] != "Mira" {
t.Fatal("copied metadata aliases source document")
}
}
func TestBuildNormalizeContextMaterialsExcludesInvalidReferencesAndCoalescesAdjacentWindows(t *testing.T) {
doc := &source.SourceDocument{ID: "session", Units: []source.SourceUnit{
{ID: 9}, {ID: 3}, {ID: 8}, {ID: 1}, {ID: 7}, {ID: 2}, {ID: 6},
}}
records := []dnd.NPC{
{Name: "One", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 3, EndUnitID: 3}}},
{Name: "Two", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 8, EndUnitID: 8}}},
{Name: "Blank"},
{Name: "Missing", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 99, EndUnitID: 99}}},
{Name: "Foreign", SourceRefs: []source.SourceRef{{SourceID: "other", StartUnitID: 1, EndUnitID: 1}}},
{Name: "Reversed", SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 6, EndUnitID: 3}}},
}
materials, ready, err := buildNormalizeContextMaterials(doc, records, 0)
if err != nil || !ready {
t.Fatalf("buildNormalizeContextMaterials() error = %v, ready = %t", err, ready)
}
var candidates normalizeCandidateInput
if err := json.Unmarshal(materials.Candidates.Content, &candidates); err != nil {
t.Fatal(err)
}
if got := []string{candidates.NPCs[0].Name, candidates.NPCs[1].Name}; !reflect.DeepEqual(got, []string{"One", "Two"}) {
t.Fatalf("candidate names = %#v, want only valid records", got)
}
var transcript normalizeTranscriptInput
if err := json.Unmarshal(materials.Transcript.Content, &transcript); err != nil {
t.Fatal(err)
}
if len(transcript.Windows) != 1 || len(transcript.Windows[0].Units) != 2 {
t.Fatalf("windows = %#v, want adjacent cited units coalesced", transcript.Windows)
}
if transcript.Windows[0].Units[0].ID != 3 || transcript.Windows[0].Units[1].ID != 8 {
t.Fatalf("window units = %#v, want document-order adjacent units", transcript.Windows[0].Units)
}
}