216 lines
5.2 KiB
Go
216 lines
5.2 KiB
Go
package coverage
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/core"
|
|
)
|
|
|
|
// mockRepo implements Repository for testing.
|
|
type mockRepo struct {
|
|
saved []Decision
|
|
err error
|
|
}
|
|
|
|
func (m *mockRepo) SaveCoverageDecision(_ context.Context, d Decision) error {
|
|
if m.err != nil {
|
|
return m.err
|
|
}
|
|
m.saved = append(m.saved, d)
|
|
return nil
|
|
}
|
|
|
|
func TestNewService_RejectsNilRepo(t *testing.T) {
|
|
_, err := NewService(nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for nil repo, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNewService_SucceedsWithRepo(t *testing.T) {
|
|
s, err := NewService(&mockRepo{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if s == nil {
|
|
t.Fatal("expected non-nil service")
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_Covered(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, err := NewService(repo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-1",
|
|
TenantKey: "t1",
|
|
Channel: "dingtalk",
|
|
Status: "succeeded",
|
|
Answer: "根据知识库,答案是...",
|
|
References: []core.Reference{
|
|
{ID: "r1", Score: 0.9},
|
|
},
|
|
}
|
|
|
|
d, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if d.Verdict != VerdictCovered {
|
|
t.Errorf("verdict = %s, want covered", d.Verdict)
|
|
}
|
|
if len(repo.saved) != 1 {
|
|
t.Fatalf("expected 1 saved decision, got %d", len(repo.saved))
|
|
}
|
|
if repo.saved[0].Verdict != VerdictCovered {
|
|
t.Errorf("saved verdict = %s, want covered", repo.saved[0].Verdict)
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_UncoveredNoReferences(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, _ := NewService(repo)
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-2",
|
|
TenantKey: "t1",
|
|
Channel: "dingtalk",
|
|
Status: "succeeded",
|
|
Answer: "正常回答但没有引用",
|
|
KnowledgeSearched: true,
|
|
}
|
|
|
|
d, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if d.Verdict != VerdictUncovered {
|
|
t.Errorf("verdict = %s, want uncovered", d.Verdict)
|
|
}
|
|
if d.ReasonCode != ReasonUncoveredNoReferences {
|
|
t.Errorf("reason_code = %s, want %s", d.ReasonCode, ReasonUncoveredNoReferences)
|
|
}
|
|
if len(repo.saved) != 1 {
|
|
t.Fatalf("expected 1 saved decision, got %d", len(repo.saved))
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_UncoveredExplicitNoAnswer(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, _ := NewService(repo)
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-3",
|
|
TenantKey: "t1",
|
|
Channel: "dingtalk",
|
|
Status: "succeeded",
|
|
Answer: "未找到相关结果",
|
|
References: []core.Reference{
|
|
{ID: "r1", Score: 0.5},
|
|
},
|
|
}
|
|
|
|
d, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if d.Verdict != VerdictUncovered {
|
|
t.Errorf("verdict = %s, want uncovered", d.Verdict)
|
|
}
|
|
if d.ReasonCode != ReasonUncoveredExplicitNoAnswer {
|
|
t.Errorf("reason_code = %s, want %s", d.ReasonCode, ReasonUncoveredExplicitNoAnswer)
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_RejectsFailedTurn(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, _ := NewService(repo)
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-4",
|
|
Status: "failed",
|
|
}
|
|
|
|
_, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err == nil {
|
|
t.Fatal("expected error for failed turn, got nil")
|
|
}
|
|
if len(repo.saved) != 0 {
|
|
t.Errorf("expected 0 saved decisions for failed turn, got %d", len(repo.saved))
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_PropagatesRepoError(t *testing.T) {
|
|
repo := &mockRepo{err: ErrRepository}
|
|
s, _ := NewService(repo)
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-5",
|
|
Status: "succeeded",
|
|
Answer: "正常回答",
|
|
References: []core.Reference{
|
|
{ID: "r1", Score: 0.5},
|
|
},
|
|
}
|
|
|
|
_, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err == nil {
|
|
t.Fatal("expected error from repo, got nil")
|
|
}
|
|
}
|
|
|
|
func TestService_ObserveSucceededTurnPersistsDecision(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, err := NewService(repo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
turn := core.Turn{
|
|
ID: "turn-observed",
|
|
TenantKey: "tenant",
|
|
Channel: "dingtalk",
|
|
Status: "succeeded",
|
|
Answer: "正常回答",
|
|
References: []core.Reference{
|
|
{ID: "r1", Score: 0.5},
|
|
},
|
|
}
|
|
if err := s.ObserveSucceededTurn(context.Background(), turn); err != nil {
|
|
t.Fatalf("ObserveSucceededTurn: %v", err)
|
|
}
|
|
if len(repo.saved) != 1 || repo.saved[0].TurnID != "turn-observed" {
|
|
t.Fatalf("saved=%+v, want turn-observed", repo.saved)
|
|
}
|
|
}
|
|
|
|
func TestService_EvaluateAndPersist_DecisionHasEvaluatorVersion(t *testing.T) {
|
|
repo := &mockRepo{}
|
|
s, _ := NewService(repo)
|
|
|
|
turn := core.Turn{
|
|
ID: "turn-6",
|
|
TenantKey: "t1",
|
|
Channel: "dingtalk",
|
|
Status: "succeeded",
|
|
Answer: "正常回答",
|
|
References: []core.Reference{
|
|
{ID: "r1", Score: 0.5},
|
|
},
|
|
}
|
|
|
|
d, err := s.EvaluateAndPersist(context.Background(), turn)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if d.EvaluatorVersion != EvaluatorVersion {
|
|
t.Errorf("evaluator_version = %s, want %s", d.EvaluatorVersion, EvaluatorVersion)
|
|
}
|
|
if repo.saved[0].EvaluatorVersion != EvaluatorVersion {
|
|
t.Errorf("saved evaluator_version = %s, want %s", repo.saved[0].EvaluatorVersion, EvaluatorVersion)
|
|
}
|
|
}
|