Files
AskaEth eb1828ee7a
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/orchestration(29 文件,纯替换)
- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留
- 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
2026-10-04 13:30:04 +08:00

243 lines
6.7 KiB
Go

package audit
import (
"context"
"errors"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// texts 用给定的正文文本构建一个仅含文本消息的上下文。
func texts(role model.Role, bodies ...string) []model.Message {
out := make([]model.Message, 0, len(bodies))
for _, b := range bodies {
out = append(out, model.TextMessage(role, b))
}
return out
}
// sameMessage 按角色与拼接后的文本比较两条消息;测试消息都是纯文本,因此这足以判定
// 相等。
func sameMessage(a, b model.Message) bool {
return a.Role == b.Role && messageText(a) == messageText(b)
}
func TestNewContextAuditor_Snapshot(t *testing.T) {
t.Parallel()
msgs := texts(model.RoleUser, "alpha", "bravo", "charlie")
aud := NewContextAuditor(msgs)
// 构造之后再改调用方的切片,不得影响审计结果。
msgs[0].Role = model.RoleSystem
got := aud.Full()
if got[0].Role != model.RoleUser {
t.Fatalf("snapshot leaked caller mutation: role = %q, want %q", got[0].Role, model.RoleUser)
}
}
func TestFull_ReturnsDefensiveCopy(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo"))
got := aud.Full()
got[0].Role = model.RoleSystem
got[1].Role = model.RoleSystem
fresh := aud.Full()
if fresh[0].Role != model.RoleUser || fresh[1].Role != model.RoleUser {
t.Fatalf("Full leaked mutation of its returned copy: %+v", fresh)
}
}
func TestTail(t *testing.T) {
t.Parallel()
full := texts(model.RoleUser, "alpha", "bravo", "charlie", "delta")
aud := NewContextAuditor(full)
tests := []struct {
name string
n int
want []string
}{
{name: "zero returns empty", n: 0, want: nil},
{name: "negative returns empty", n: -1, want: nil},
{name: "single", n: 1, want: []string{"delta"}},
{name: "pair", n: 2, want: []string{"charlie", "delta"}},
{name: "exact length", n: 4, want: []string{"alpha", "bravo", "charlie", "delta"}},
{name: "beyond length clamps to full", n: 9, want: []string{"alpha", "bravo", "charlie", "delta"}},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := aud.Tail(tt.n)
if len(got) != len(tt.want) {
t.Fatalf("Tail(%d) length = %d, want %d", tt.n, len(got), len(tt.want))
}
for i := range got {
if got[i].Role != model.RoleUser || messageText(got[i]) != tt.want[i] {
t.Fatalf("Tail(%d)[%d] = %q/%s, want text %q", tt.n, i, messageText(got[i]), got[i].Role, tt.want[i])
}
}
})
}
}
func TestKeyword_LocateWithSurroundings(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo", "charlie"))
loc, err := aud.Keyword(context.Background(), "rav")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc == nil {
t.Fatal("Keyword returned nil for a present match")
}
if loc.Line != 2 {
t.Fatalf("Line = %d, want 2", loc.Line)
}
if len(loc.Surrounding) != 3 {
t.Fatalf("Surrounding length = %d, want 3", len(loc.Surrounding))
}
if !sameMessage(loc.Surrounding[1], model.TextMessage(model.RoleUser, "bravo")) {
t.Fatalf("matched message missing from window: %+v", loc.Surrounding)
}
if !sameMessage(loc.Surrounding[0], model.TextMessage(model.RoleUser, "alpha")) {
t.Fatalf("above-message missing from window: %+v", loc.Surrounding)
}
if !sameMessage(loc.Surrounding[2], model.TextMessage(model.RoleUser, "charlie")) {
t.Fatalf("below-message missing from window: %+v", loc.Surrounding)
}
}
func TestKeyword_Edges(t *testing.T) {
t.Parallel()
t.Run("first message has no above-neighbor", func(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo"))
loc, err := aud.Keyword(context.Background(), "alp")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc.Line != 1 {
t.Fatalf("Line = %d, want 1", loc.Line)
}
if len(loc.Surrounding) != 2 {
t.Fatalf("Surrounding length = %d, want 2", len(loc.Surrounding))
}
})
t.Run("last message has no below-neighbor", func(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo"))
loc, err := aud.Keyword(context.Background(), "bra")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc.Line != 2 {
t.Fatalf("Line = %d, want 2", loc.Line)
}
if len(loc.Surrounding) != 2 {
t.Fatalf("Surrounding length = %d, want 2", len(loc.Surrounding))
}
})
}
func TestKeyword_CaseInsensitive(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo"))
loc, err := aud.Keyword(context.Background(), "BRAVO")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc == nil || loc.Line != 2 {
t.Fatalf("case-insensitive match failed: line = %d", loc.Line)
}
}
func TestKeyword_NoMatch(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha", "bravo"))
loc, err := aud.Keyword(context.Background(), "zzz")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc != nil {
t.Fatalf("Keyword = %+v, want nil for a missing match", loc)
}
}
func TestKeyword_EmptyAndBlankQuery(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha"))
for _, q := range []string{"", " "} {
_, err := aud.Keyword(context.Background(), q)
if !errors.Is(err, ErrQueryEmpty) {
t.Fatalf("Keyword(%q) error = %v, want ErrQueryEmpty", q, err)
}
}
}
func TestKeyword_CanceledContext(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "alpha"))
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := aud.Keyword(ctx, "alpha")
if !errors.Is(err, context.Canceled) {
t.Fatalf("Keyword error = %v, want context.Canceled", err)
}
}
func TestKeyword_FirstMatchOnly(t *testing.T) {
t.Parallel()
aud := NewContextAuditor(texts(model.RoleUser, "foo", "bar", "foo"))
loc, err := aud.Keyword(context.Background(), "foo")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc.Line != 1 {
t.Fatalf("Line = %d, want 1 (earliest match)", loc.Line)
}
}
func TestKeyword_IgnoresImagePayload(t *testing.T) {
t.Parallel()
msgs := []model.Message{
model.TextMessage(model.RoleUser, "alpha"),
model.ImageMessage(model.RoleUser, "", "http://example.com/bravo.png"),
}
aud := NewContextAuditor(msgs)
loc, err := aud.Keyword(context.Background(), "bravo")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc != nil {
t.Fatalf("Keyword matched an image URL (ignored): %+v", loc)
}
loc, err = aud.Keyword(context.Background(), "alpha")
if err != nil {
t.Fatalf("Keyword unexpected error: %v", err)
}
if loc == nil || loc.Line != 1 {
t.Fatalf("text-part match failed: line = %d", loc.Line)
}
}