You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -282,7 +283,7 @@ func (f *fakeStorage) Delete(_ context.Context, _ string) error {
|
||||
// ===== test builder =====
|
||||
|
||||
func buildMessageService(repo *msgFakeRepo, hub *fakeHub, cfg MessageServiceConfig) MessageService {
|
||||
return NewMessageService(repo, newFakeStorage(), hub, noopAuditor{}, discardLogger(), cfg)
|
||||
return NewMessageService(repo, newFakeStorage(), hub, noopAuditor{}, discardLogger(), cfg, nil)
|
||||
}
|
||||
|
||||
func defaultMsgConfig() MessageServiceConfig {
|
||||
@@ -457,7 +458,7 @@ func TestMessageService_Send_CapabilityMismatch(t *testing.T) {
|
||||
a := &Attachment{ID: uuid.New(), UserID: userID, MimeType: "image/png", SizeBytes: 100}
|
||||
specialRepo.addAttachment(a)
|
||||
|
||||
svc := NewMessageService(specialRepo, newFakeStorage(), hub, noopAuditor{}, discardLogger(), defaultMsgConfig())
|
||||
svc := NewMessageService(specialRepo, newFakeStorage(), hub, noopAuditor{}, discardLogger(), defaultMsgConfig(), nil)
|
||||
_, _, err := svc.Send(context.Background(), userID, conv.ID, "hi", []uuid.UUID{a.ID})
|
||||
|
||||
require.Error(t, err)
|
||||
@@ -990,6 +991,130 @@ func TestTruncateByTokens_PreservesNewest(t *testing.T) {
|
||||
require.Equal(t, "hi", result[0].Blocks[0].Text)
|
||||
}
|
||||
|
||||
// ===== composeHistory FK context injection tests =====
|
||||
|
||||
// fakeContextReader 是 ContextReader 的测试替身。
|
||||
type fakeContextReader struct {
|
||||
brief string
|
||||
err error
|
||||
called bool
|
||||
}
|
||||
|
||||
func (f *fakeContextReader) BriefForConversation(_ context.Context, _ *Conversation) (string, error) {
|
||||
f.called = true
|
||||
return f.brief, f.err
|
||||
}
|
||||
|
||||
func newMsgSvcWithReader(repo *msgFakeRepo, reader ContextReader) *messageService {
|
||||
return &messageService{
|
||||
repo: repo,
|
||||
storage: newFakeStorage(),
|
||||
auditor: noopAuditor{},
|
||||
log: discardLogger(),
|
||||
mimeWhite: map[string]bool{},
|
||||
safetyPct: 0.0,
|
||||
briefReader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeHistory_FKBriefMergedIntoSystemPrompt(t *testing.T) {
|
||||
repo := newMsgFakeRepo()
|
||||
reqID := uuid.New()
|
||||
conv := newConv(uuid.New(), uuid.New())
|
||||
conv.SystemPrompt = "STATIC PROMPT"
|
||||
conv.RequirementID = &reqID
|
||||
repo.addConversation(conv)
|
||||
repo.addMessage(&Message{ID: 1, ConversationID: conv.ID, Role: RoleUser, Content: "hi", Status: StatusOK})
|
||||
|
||||
reader := &fakeContextReader{brief: "## 当前需求\n需求 #7:导出报表"}
|
||||
svc := newMsgSvcWithReader(repo, reader)
|
||||
|
||||
model := &LLMModel{ContextWindow: 4096, MaxOutputTokens: 1024}
|
||||
req, err := svc.composeHistory(context.Background(), conv, model)
|
||||
require.NoError(t, err)
|
||||
require.True(t, reader.called)
|
||||
require.Contains(t, req.SystemPrompt, "STATIC PROMPT")
|
||||
require.Contains(t, req.SystemPrompt, "需求 #7:导出报表")
|
||||
}
|
||||
|
||||
func TestComposeHistory_FKBriefCountsAgainstTokenBudget(t *testing.T) {
|
||||
repo := newMsgFakeRepo()
|
||||
reqID := uuid.New()
|
||||
conv := newConv(uuid.New(), uuid.New())
|
||||
conv.RequirementID = &reqID
|
||||
repo.addConversation(conv)
|
||||
// 多条历史消息,预算很小,FK 简报应挤占消息预算导致历史被截断。
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
repo.addMessage(&Message{
|
||||
ID: i, ConversationID: conv.ID, Role: RoleUser,
|
||||
Content: "message content that consumes tokens in the budget", Status: StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
hugeBrief := strings.Repeat("需求上下文 ", 300)
|
||||
reader := &fakeContextReader{brief: hugeBrief}
|
||||
svc := newMsgSvcWithReader(repo, reader)
|
||||
|
||||
// ContextWindow 紧到只够装下简报 + 少量消息:
|
||||
// budget = (500-100)*(1-0) = 400 tokens;简报约 450 tokens(已超 budget),
|
||||
// 剩余预算 < 5 条消息所需 → 消息被截断。
|
||||
model := &LLMModel{ContextWindow: 500, MaxOutputTokens: 100}
|
||||
req, err := svc.composeHistory(context.Background(), conv, model)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, req.SystemPrompt, "需求上下文")
|
||||
// 简报占了预算 → 消息被截断(少于 5 条)。
|
||||
require.Less(t, len(req.Messages), 5)
|
||||
}
|
||||
|
||||
func TestComposeHistory_NilReaderPreservesBehavior(t *testing.T) {
|
||||
repo := newMsgFakeRepo()
|
||||
reqID := uuid.New()
|
||||
conv := newConv(uuid.New(), uuid.New())
|
||||
conv.SystemPrompt = "ONLY STATIC"
|
||||
conv.RequirementID = &reqID
|
||||
repo.addConversation(conv)
|
||||
repo.addMessage(&Message{ID: 1, ConversationID: conv.ID, Role: RoleUser, Content: "hi", Status: StatusOK})
|
||||
|
||||
svc := newMsgSvcWithReader(repo, nil) // 无 reader
|
||||
model := &LLMModel{ContextWindow: 4096, MaxOutputTokens: 1024}
|
||||
req, err := svc.composeHistory(context.Background(), conv, model)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ONLY STATIC", req.SystemPrompt)
|
||||
}
|
||||
|
||||
func TestComposeHistory_NoFKMountSkipsReader(t *testing.T) {
|
||||
repo := newMsgFakeRepo()
|
||||
conv := newConv(uuid.New(), uuid.New()) // 无任何 FK 挂载
|
||||
conv.SystemPrompt = "STATIC"
|
||||
repo.addConversation(conv)
|
||||
repo.addMessage(&Message{ID: 1, ConversationID: conv.ID, Role: RoleUser, Content: "hi", Status: StatusOK})
|
||||
|
||||
reader := &fakeContextReader{brief: "SHOULD NOT APPEAR"}
|
||||
svc := newMsgSvcWithReader(repo, reader)
|
||||
model := &LLMModel{ContextWindow: 4096, MaxOutputTokens: 1024}
|
||||
req, err := svc.composeHistory(context.Background(), conv, model)
|
||||
require.NoError(t, err)
|
||||
require.False(t, reader.called, "reader must not be called without FK mount")
|
||||
require.Equal(t, "STATIC", req.SystemPrompt)
|
||||
}
|
||||
|
||||
func TestComposeHistory_ReaderErrorFallsBackToStatic(t *testing.T) {
|
||||
repo := newMsgFakeRepo()
|
||||
reqID := uuid.New()
|
||||
conv := newConv(uuid.New(), uuid.New())
|
||||
conv.SystemPrompt = "STATIC FALLBACK"
|
||||
conv.RequirementID = &reqID
|
||||
repo.addConversation(conv)
|
||||
repo.addMessage(&Message{ID: 1, ConversationID: conv.ID, Role: RoleUser, Content: "hi", Status: StatusOK})
|
||||
|
||||
reader := &fakeContextReader{err: errs.New(errs.CodeInternal, "boom")}
|
||||
svc := newMsgSvcWithReader(repo, reader)
|
||||
model := &LLMModel{ContextWindow: 4096, MaxOutputTokens: 1024}
|
||||
req, err := svc.composeHistory(context.Background(), conv, model)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "STATIC FALLBACK", req.SystemPrompt)
|
||||
}
|
||||
|
||||
// ===== mimeAllowed / capabilityAllows helpers =====
|
||||
|
||||
func TestMimeAllowed(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user