textmachine/backend/internal/pipeline/render_memory_test.go

93 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pipeline
import (
"strings"
"testing"
)
func TestMessagesInjectionLayout(t *testing.T) {
tpl := writeTemplate(t, "Стабильный системный префикс.\n---USER---\nПереведи: {{text}}")
v := RenderVars{Book: testBook(), Text: "исходный чанк"}
injection := "ГЛОССАРИЙ:\n阿Q → А-кью"
msgs, err := MessagesWithInjection(tpl, v, injection)
if err != nil {
t.Fatal(err)
}
if len(msgs) != 3 {
t.Fatalf("want 3 messages (system, injection, user), got %d", len(msgs))
}
// [0] stable system prefix carries the cache boundary.
if msgs[0].Role != "system" || !msgs[0].CacheBoundary {
t.Errorf("msg[0] must be the system prefix with CacheBoundary=true: %+v", msgs[0])
}
// [1] injection sits AFTER the boundary (volatile → CacheBoundary=false).
if msgs[1].Role != "system" || msgs[1].CacheBoundary || msgs[1].Content != injection {
t.Errorf("msg[1] must be the injection with CacheBoundary=false: %+v", msgs[1])
}
// [2] the volatile user chunk. It must contain ONLY the rendered chunk, never the
// glossary (ch.Text stays clean — the 3a contract; the coverage gate reads ch.Text).
if msgs[2].Role != "user" {
t.Errorf("msg[2] must be user: %+v", msgs[2])
}
if strings.Contains(msgs[2].Content, "ГЛОССАРИЙ") || strings.Contains(msgs[2].Content, "阿Q") {
t.Errorf("glossary leaked into the user (ch.Text) message: %q", msgs[2].Content)
}
if !strings.Contains(msgs[2].Content, "исходный чанк") {
t.Errorf("user message lost the chunk: %q", msgs[2].Content)
}
}
func TestMessagesEmptyInjectionIdentical(t *testing.T) {
tpl := writeTemplate(t, "Системный префикс.\n---USER---\nТекст: {{text}}")
v := RenderVars{Book: testBook(), Text: "чанк"}
plain, _ := Messages(tpl, v)
withEmpty, _ := MessagesWithInjection(tpl, v, " ") // whitespace-only → no injection
if len(plain) != 2 || len(withEmpty) != 2 {
t.Fatalf("empty injection must yield 2 messages: %d vs %d", len(plain), len(withEmpty))
}
for i := range plain {
if plain[i] != withEmpty[i] {
t.Errorf("empty-injection message %d differs from plain: %+v vs %+v", i, withEmpty[i], plain[i])
}
}
}
// TestInjectionChangesRequestHash: the injection is folded into request_hash via the
// messages, so a different glossary is a different request (a changed approved term
// re-derives the chunk, never serves a stale checkpoint).
func TestInjectionChangesRequestHash(t *testing.T) {
tpl := writeTemplate(t, "Префикс.\n---USER---\n{{text}}")
v := RenderVars{Book: testBook(), Text: "чанк"}
none, _ := Messages(tpl, v)
inj, _ := MessagesWithInjection(tpl, v, "ГЛОССАРИЙ:\n阿Q → А-кью")
hNone := RequestHash("b", 1, 0, 0, "draft", "translator", "m", 0.3, "off", false, 1024, "snap", none)
hInj := RequestHash("b", 1, 0, 0, "draft", "translator", "m", 0.3, "off", false, 1024, "snap", inj)
if hNone == hInj {
t.Error("injection did not change request_hash — a glossary change would silently hit a stale checkpoint")
}
}
func TestRenderGlossaryBlock(t *testing.T) {
confirmed := pickedEntry{entry: &memoryEntry{src: "阿Q", dst: "А-кью", status: "approved"}, disp: memConfirmed}
ambiguous := pickedEntry{entry: &memoryEntry{src: "小D", dst: "Малыш Дэ", status: "auto"}, disp: memAmbiguous}
emptyDst := pickedEntry{entry: &memoryEntry{src: "鈴木", dst: "", status: "auto"}, disp: memAmbiguous}
block := renderGlossaryBlock([]pickedEntry{confirmed, ambiguous, emptyDst})
if !strings.Contains(block, "阿Q → А-кью") {
t.Errorf("confirmed line missing: %q", block)
}
if !strings.Contains(block, "小D → Малыш Дэ ⟨проверить⟩") {
t.Errorf("ambiguous line missing its unverified tag: %q", block)
}
if strings.Contains(block, "鈴木") {
t.Errorf("empty-dst candidate should be skipped (nothing to inject): %q", block)
}
// Nothing to inject → empty block (лучше ничего).
if renderGlossaryBlock(nil) != "" {
t.Error("empty selection must render an empty block")
}
if renderGlossaryBlock([]pickedEntry{emptyDst}) != "" {
t.Error("only-empty-dst selection must render an empty block")
}
}