package opsconfig import ( "context" "errors" "os" "strings" "testing" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/identity" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) func TestAgentBundleRequiresStandardQualityEvidence(t *testing.T) { databaseURL := os.Getenv("TEST_DATABASE_URL") if databaseURL == "" { t.Skip("TEST_DATABASE_URL is not set") } ctx := context.Background() pool, err := pgxpool.New(ctx, databaseURL) if err != nil { t.Fatal(err) } defer pool.Close() service, err := NewService(pool, audit.NewRepository()) if err != nil { t.Fatal(err) } admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"} approver := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000105"} tenantID := "10000000-0000-0000-0000-000000000010" configKey := "integration.agent.bundle." + uuid.NewString() invalidConfigKey := "integration.agent.bundle.invalid." + uuid.NewString() componentPrefix := "integration.agent.component." + uuid.NewString() + "." keys := []string{"agent-bundle-create-" + uuid.NewString(), "agent-bundle-generic-" + uuid.NewString(), "agent-bundle-standard-" + uuid.NewString(), "agent-bundle-invalid-" + uuid.NewString(), "agent-bundle-mismatched-evidence-" + uuid.NewString()} defer func() { _, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE request_id LIKE 'agent-bundle-%'`) _, _ = pool.Exec(ctx, `DELETE FROM idempotency_record WHERE idempotency_key=ANY($1::text[])`, keys) _, _ = pool.Exec(ctx, `DELETE FROM runtime_config_rollout WHERE config_key=$1`, configKey) _, _ = pool.Exec(ctx, `DELETE FROM ops_config_evaluation WHERE config_version_id IN (SELECT id FROM runtime_config_version WHERE config_key=$1 OR config_key=$2)`, configKey, invalidConfigKey) _, _ = pool.Exec(ctx, `DELETE FROM runtime_config_version WHERE config_key=$1 OR config_key=$2 OR config_key LIKE $3`, configKey, invalidConfigKey, componentPrefix+"%") }() componentIDs := seedPublishedAgentComponents(t, ctx, pool, tenantID, admin.Issuer, admin.Subject, componentPrefix) invalidInput := CreateInput{ TenantID: tenantID, ConfigKey: invalidConfigKey, DisplayName: "Agent组件类型错误验证", ConfigKind: "AGENT_BUNDLE", Payload: map[string]any{ "persona_version_id": componentIDs["PERSONA"], "voice_version_id": componentIDs["PERSONA"], "model_version_id": componentIDs["MODEL"], "routing_version_id": componentIDs["ROUTING"], }, SecretRefs: map[string]string{}, ChangeSummary: "验证错误类型的组件版本不能提交", } invalidVersion, _, err := service.Create(ctx, admin, keys[3], "agent-bundle-invalid-create", invalidInput) if err != nil { t.Fatal(err) } if _, err = service.Submit(ctx, admin, tenantID, invalidVersion.ID, "agent-bundle-invalid-submit", invalidVersion.RowVersion); !errors.Is(err, ErrInvalid) { t.Fatalf("wrong component kind submit error = %v", err) } input := CreateInput{ TenantID: tenantID, ConfigKey: configKey, DisplayName: "Agent质量门禁集成配置", ConfigKind: "AGENT_BUNDLE", Payload: map[string]any{ "persona_version_id": componentIDs["PERSONA"], "voice_version_id": componentIDs["VOICE"], "model_version_id": componentIDs["MODEL"], "routing_version_id": componentIDs["ROUTING"], }, SecretRefs: map[string]string{}, ChangeSummary: "验证标准Agent质量证据门禁", } version, _, err := service.Create(ctx, admin, keys[0], "agent-bundle-create", input) if err != nil { t.Fatal(err) } version, err = service.Submit(ctx, admin, tenantID, version.ID, "agent-bundle-submit", version.RowVersion) if err != nil { t.Fatal(err) } rollout := RolloutInput{ScopeType: "TENANT", RolloutPercent: 100, MaxConcurrentCalls: 10} if _, _, err = service.CreateEvaluation(ctx, admin, tenantID, version.ID, keys[1], "agent-bundle-generic", EvidenceTypeGeneric, "generic-v1", "PASSED", "artifact://generic.json", map[string]any{"passed": true}); err != nil { t.Fatal(err) } if _, err = service.Publish(ctx, approver, tenantID, version.ID, "agent-bundle-publish-generic", PublishInput{Reason: "通用证据不得放行", Rollout: rollout}, version.RowVersion); !errors.Is(err, ErrPublishGate) { t.Fatalf("generic evidence publish error = %v", err) } metrics := map[string]any{ "report_schema": "agent-eval-report-v1", "suite_sha256": "b5cd3b798a01a48ddf0081f2fcc2a917f93681a3ee2fa55bbf4190466de3bef8", "agent_version": "agent-v1", "model_config_ref": "runtime-config-version:" + componentIDs["MODEL"], "config_version_id": version.ID, "environment": "mock", "executed_at": "2026-08-24T10:00:00Z", "cases_total": 10, "cases_passed": 10, "failed_check_count": 0, "conversation_success_rate": 1.0, "first_response_p95_ms": 1200, "interruption_recovery_rate": 1.0, "average_response_chars": 36.5, "salutation_accuracy": 1.0, "memory_accuracy": 1.0, "fabricated_facts": 0, "story_source_completeness": 1.0, "sensitive_information_handling_rate": 1.0, } mismatchedMetrics := make(map[string]any, len(metrics)) for key, value := range metrics { mismatchedMetrics[key] = value } mismatchedMetrics["model_config_ref"] = "runtime-config-version:" + componentIDs["VOICE"] if _, _, err = service.CreateEvaluation(ctx, admin, tenantID, version.ID, keys[4], "agent-bundle-mismatched-evidence", EvidenceTypeAgentQualityV1, "agent-quality-mismatched-v1", "PASSED", "artifact://agent-quality-mismatched.json", mismatchedMetrics); !errors.Is(err, ErrInvalid) { t.Fatalf("mismatched model evidence error = %v", err) } evaluation, _, err := service.CreateEvaluation(ctx, admin, tenantID, version.ID, keys[2], "agent-bundle-standard", EvidenceTypeAgentQualityV1, "agent-quality-v1", "PASSED", "artifact://agent-quality.json", metrics) if err != nil || evaluation.EvidenceType != EvidenceTypeAgentQualityV1 { t.Fatalf("standard evaluation = %+v, error = %v", evaluation, err) } version, err = service.Publish(ctx, approver, tenantID, version.ID, "agent-bundle-publish-standard", PublishInput{Reason: "标准质量证据通过", Rollout: rollout}, version.RowVersion) if err != nil || version.Status != "PUBLISHED" { t.Fatalf("published version = %+v, error = %v", version, err) } } func seedPublishedAgentComponents(t *testing.T, ctx context.Context, pool *pgxpool.Pool, tenantPublicID, oidcIssuer, oidcSubject, keyPrefix string) map[string]string { t.Helper() components := map[string]struct { payload map[string]any refs map[string]string }{ "PERSONA": {payload: map[string]any{"system_prompt": "集成验证人设", "tone": "warm"}, refs: map[string]string{}}, "VOICE": {payload: map[string]any{"provider": "local", "voice_resource_ref": "voice://integration/approved"}, refs: map[string]string{"credential_ref": "kms://integration/voice"}}, "MODEL": {payload: map[string]any{"provider": "local", "model_id": "model://integration/approved"}, refs: map[string]string{"credential_ref": "kms://integration/model"}}, "ROUTING": {payload: map[string]any{"default_engine_mode": "s2s_hybrid", "routes": []any{map[string]any{"goal": "open_conversation", "engine_mode": "s2s_hybrid"}}}, refs: map[string]string{}}, } ids := make(map[string]string, len(components)) for kind, component := range components { publicID := uuid.NewString() tag, err := pool.Exec(ctx, ` INSERT INTO runtime_config_version(public_id,tenant_id,config_key,display_name,config_kind,version_no,status,payload,secret_refs,change_summary,validation_result,created_by,submitted_by,submitted_at,approved_by,approved_at,published_by,published_at) SELECT $1::uuid,t.id,$2,$3,$4,1,'PUBLISHED',$5::jsonb,$6::jsonb,'Agent组合组件引用集成夹具','{"valid":true}'::jsonb,u.id,u.id,now(),u.id,now(),u.id,now() FROM tenant t JOIN user_account u ON u.tenant_id=t.id WHERE t.public_id=$7::uuid AND u.oidc_issuer=$8 AND u.oidc_subject=$9`, publicID, keyPrefix+strings.ToLower(kind), "Agent组件"+kind, kind, mustJSON(component.payload), mustJSON(component.refs), tenantPublicID, oidcIssuer, oidcSubject) if err != nil { t.Fatal(err) } if tag.RowsAffected() != 1 { t.Fatalf("seed %s component affected %d rows", kind, tag.RowsAffected()) } ids[kind] = publicID } return ids }