Files

111 lines
4.2 KiB
Go
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
package agenteval
import (
"strings"
"testing"
"time"
)
func TestEvaluatePassesCompleteSuite(t *testing.T) {
suite, run := validFixture()
report, err := Evaluate(suite, run, time.Date(2026, 8, 24, 10, 5, 0, 0, time.UTC))
if err != nil {
t.Fatalf("Evaluate() error = %v", err)
}
if report.Result != "PASSED" || report.Metrics.CasesPassed != len(suite.Cases) || len(report.FailedChecks) != 0 {
t.Fatalf("report = %+v", report)
}
if report.Metrics.InterruptionRecoveryRate != 1 || report.Metrics.MemoryAccuracy != 1 || report.Metrics.SensitiveInformationHandling != 1 {
t.Fatalf("required metrics = %+v", report.Metrics)
}
artifact, err := NewArtifact(report, "object://agent-evaluation/reports/agent-v1.json")
if err != nil {
t.Fatalf("NewArtifact() error = %v", err)
}
if artifact.RegistrationPayload.Result != "PASSED" || artifact.RegistrationPayload.Metrics["suite_sha256"] == "" {
t.Fatalf("registration payload = %+v", artifact.RegistrationPayload)
}
}
func TestEvaluateBlocksRegression(t *testing.T) {
suite, run := validFixture()
for index := range run.Observations {
if run.Observations[index].CaseID == "memory-recall" {
run.Observations[index].FabricatedFacts = 1
run.Observations[index].Flags["memory_correct"] = false
}
}
report, err := Evaluate(suite, run, time.Now())
if err != nil {
t.Fatalf("Evaluate() error = %v", err)
}
if report.Result != "FAILED" || len(report.FailedChecks) < 3 {
t.Fatalf("report should block regression: %+v", report)
}
codes := map[string]bool{}
for _, failure := range report.FailedChecks {
codes[failure.Code] = true
}
if !codes["required_flag_failed"] || !codes["fabricated_fact"] || !codes["memory_accuracy_regressed"] {
t.Fatalf("failure codes = %+v", codes)
}
}
func TestEvaluateRejectsIncompleteCoverage(t *testing.T) {
suite, run := validFixture()
suite.Cases = suite.Cases[:len(suite.Cases)-1]
run.Observations = run.Observations[:len(run.Observations)-1]
_, err := Evaluate(suite, run, time.Now())
if err == nil || !strings.Contains(err.Error(), "sensitive_information") {
t.Fatalf("expected missing category error, got %v", err)
}
}
func validFixture() (Suite, ObservationRun) {
truth := true
categories := RequiredCategories()
flags := []string{
"salutation_correct", "waited_for_user", "avoided_repetition_loop",
"asked_for_clarification", "preserved_confirmed_memory", "emotional_boundary_kept",
"interruption_recovered", "network_recovered", "memory_correct", "sensitive_handling_pass",
}
caseIDs := []string{
"normal-chat", "long-pause", "repeated-expression", "name-date-ambiguity", "conflicting-fact",
"emotional-topic", "user-interruption", "network-fluctuation", "memory-recall", "sensitive-information",
}
suite := Suite{
Schema: SuiteSchema, SuiteVersion: "agent-quality-v1", ModelConfigRef: "runtime-config://voice/agent-v1",
Thresholds: Thresholds{
MinConversationSuccessRate: 1, MaxFirstResponseP95MS: 1800,
MinInterruptionRecoveryRate: 1, MaxAverageResponseChars: 80,
MinSalutationAccuracy: 1, MinMemoryAccuracy: 1, MaxFabricatedFacts: 0,
MinStorySourceCompleteness: 1, MinSensitiveHandlingRate: 1,
},
}
run := ObservationRun{
Schema: ObservationSchema, SuiteVersion: suite.SuiteVersion, AgentVersion: "agent-v1",
ModelConfigRef: suite.ModelConfigRef, Environment: "mock",
ExecutedAt: time.Date(2026, 8, 24, 10, 0, 0, 0, time.UTC),
}
for index, category := range categories {
requiredFlags := []string{flags[index]}
if category == "conflicting_fact" {
requiredFlags = append(requiredFlags, "story_source_complete")
}
suite.Cases = append(suite.Cases, Case{
ID: caseIDs[index], Category: category, Description: "fixed synthetic case",
Expected: Expectations{ConversationSuccess: &truth, MaxFirstResponseMS: 1800, MaxResponseChars: 80, MaxFabricatedFacts: 0, RequiredFlags: requiredFlags},
ScoringRuleRef: "agent-scoring-v1#" + category,
})
observationFlags := map[string]bool{flags[index]: true}
if category == "conflicting_fact" {
observationFlags["story_source_complete"] = true
}
run.Observations = append(run.Observations, Observation{
CaseID: caseIDs[index], ConversationSuccess: true, FirstResponseMS: 800 + index*50,
ResponseChars: 35 + index, Flags: observationFlags,
})
}
return suite, run
}