2026-08-26 06:41:07 +08:00
package orchestration
import (
"context"
"encoding/json"
"errors"
"os"
"strings"
"sync/atomic"
"testing"
"time"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/database"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/embeddingprovider"
"data-terminal-cloud/internal/idempotency"
platformidentity "data-terminal-cloud/internal/identity"
"data-terminal-cloud/internal/opsconfig"
"data-terminal-cloud/internal/outbox"
"data-terminal-cloud/internal/ratelimit"
"data-terminal-cloud/internal/rtc"
"data-terminal-cloud/internal/voiceprovider"
"github.com/jackc/pgx/v5/pgxpool"
)
const (
testDeviceID = "90000000-0000-0000-0000-000000000501"
testDevicePublicID = "90000000-0000-0000-0000-000000000511"
testSubjectID = "90000000-0000-0000-0000-000000000401"
testSubjectPublicID = "90000000-0000-0000-0000-000000000411"
testPriceBookID = "90000000-0000-0000-0000-000000000601"
testPersonaConfigID = "90000000-0000-0000-0000-000000000701"
testPersonaConfigPublicID = "90000000-0000-0000-0000-000000000711"
testVoiceConfigID = "90000000-0000-0000-0000-000000000702"
testVoiceConfigPublicID = "90000000-0000-0000-0000-000000000712"
testModelConfigID = "90000000-0000-0000-0000-000000000703"
testModelConfigPublicID = "90000000-0000-0000-0000-000000000713"
testRoutingConfigID = "90000000-0000-0000-0000-000000000704"
testRoutingConfigPublicID = "90000000-0000-0000-0000-000000000714"
2026-08-29 21:56:51 +08:00
testConsentSnapshotID = "90000000-0000-0000-0000-000000000801"
testPriorContextID = "90000000-0000-0000-0000-000000000802"
testPriorConversationID = "90000000-0000-0000-0000-000000000803"
2026-08-26 06:41:07 +08:00
testBundleConfigKey = "orchestration-test-agent-bundle"
testBundleCreateKey = "orchestration-agent-bundle-create"
testBundleEvaluationKey = "orchestration-agent-bundle-evaluation"
)
type testLeaseCache struct {}
func ( testLeaseCache ) Refresh ( context . Context , LeaseRecord ) error { return nil }
func ( testLeaseCache ) End ( context . Context , string , string , time . Time ) error { return nil }
type failingTokenIssuer struct {}
func ( failingTokenIssuer ) Issue ( string , string , time . Time ) ( string , error ) {
return "" , errors . New ( "simulated token signing failure" )
}
type countingEmbedder struct { calls atomic . Int32 }
func ( e * countingEmbedder ) Embed ( ctx context . Context , content string ) ( embeddingprovider . Result , error ) {
e . calls . Add ( 1 )
return ( embeddingprovider . Local {}). Embed ( ctx , content )
}
type testVoiceProvider struct {
startRequest voiceprovider . StartRequest
stopRequest voiceprovider . StopRequest
startStatus string
stopStatus string
queryStatus string
startErr error
stopErr error
queryErr error
}
func ( p * testVoiceProvider ) Start ( _ context . Context , request voiceprovider . StartRequest ) ( voiceprovider . StartResponse , error ) {
p . startRequest = request
if p . startErr != nil {
return voiceprovider . StartResponse {}, p . startErr
}
status := p . startStatus
if status == "" {
status = "STARTING"
}
return voiceprovider . StartResponse { TaskRef : request . TaskRef , Status : status }, nil
}
func ( p * testVoiceProvider ) Query ( _ context . Context , request voiceprovider . QueryRequest ) ( voiceprovider . QueryResponse , error ) {
if p . queryErr != nil {
return voiceprovider . QueryResponse {}, p . queryErr
}
status := p . queryStatus
if status == "" {
status = "STARTING"
}
return voiceprovider . QueryResponse { TaskRef : request . TaskRef , Status : status }, nil
}
func ( p * testVoiceProvider ) Stop ( _ context . Context , request voiceprovider . StopRequest ) ( voiceprovider . StopResponse , error ) {
p . stopRequest = request
if p . stopErr != nil {
return voiceprovider . StopResponse {}, p . stopErr
}
status := p . stopStatus
if status == "" {
status = "STOPPING"
}
return voiceprovider . StopResponse { TaskRef : request . TaskRef , Status : status }, nil
}
type allowProviderStart struct {}
func ( allowProviderStart ) Allow ( context . Context , string , string ) ( ratelimit . Result , error ) {
return ratelimit . Result { Allowed : true }, nil
}
func TestServiceIdempotentlyCreatesConversationWithoutPersistingToken ( t * testing . T ) {
databaseURL := os . Getenv ( "TEST_DATABASE_URL" )
if databaseURL == "" {
t . Skip ( "TEST_DATABASE_URL is not set" )
}
ctx := context . Background ()
pool , err := database . Open ( ctx , databaseURL , "orchestration-service-test" )
if err != nil {
t . Fatal ( err )
}
defer pool . Close ()
cleanupOrchestrationFixture ( t , ctx , pool )
defer cleanupOrchestrationFixture ( t , context . Background (), pool )
fixtureStatements := [] struct {
query string
args [] any
}{
{ `INSERT INTO subject_profile (id, public_id, tenant_id, household_id, preferred_name, profile_data, conversation_preferences, status)
VALUES ($1::uuid, '90000000-0000-0000-0000-000000000411', '10000000-0000-0000-0000-000000000001', '10000000-0000-0000-0000-000000000201', '服务测试长者',
'{"background":"年轻时从事铁路工作","interests":["老照片","家乡变化"],"internal_note":"不得进入语音上下文"}',
2026-08-29 21:56:51 +08:00
'{"conversation_goal":"聊聊第一次参加工作的经历","pace":"slow","preferred_topics":["工作经历"],"avoided_topics":["收入"]}', 'active')` , [] any { testSubjectID }},
2026-08-26 06:41:07 +08:00
{ `INSERT INTO device (id, public_id, tenant_id, serial_digest, model, status, credential_ref)
VALUES ($1::uuid, $2::uuid, '10000000-0000-0000-0000-000000000001', repeat('9',64), 'service-test', 'registered', 'test://service')` , [] any { testDeviceID , testDevicePublicID }},
{ `INSERT INTO device_binding (tenant_id, device_id, household_id, subject_id, reason)
VALUES ('10000000-0000-0000-0000-000000000001', $1::uuid, '10000000-0000-0000-0000-000000000201', $2::uuid, 'integration test')` , [] any { testDeviceID , testSubjectID }},
{ `INSERT INTO device_twin (device_id, tenant_id) VALUES ($1::uuid, '10000000-0000-0000-0000-000000000001')` , [] any { testDeviceID }},
2026-08-29 21:56:51 +08:00
{ `INSERT INTO consent_snapshot (id,tenant_id,subject_id,snapshot_version,effective_consents)
VALUES ($1::uuid,'10000000-0000-0000-0000-000000000001',$2::uuid,1,'{"voice_chat":true,"transcript":true,"audio_recording":false,"story_extraction":true,"personalized_memory":true}'::jsonb)` , [] any { testConsentSnapshotID , testSubjectID }},
{ `INSERT INTO context_snapshot (id,tenant_id,subject_id,profile_version,consent_snapshot_id,memory_versions,prompt_bundle_version,engine_mode,content_digest,sanitized_summary)
VALUES ($1::uuid,'10000000-0000-0000-0000-000000000001',$2::uuid,1,$3::uuid,'[]'::jsonb,'prior-test','cascade',repeat('b',64),'此前通话上下文')` , [] any { testPriorContextID , testSubjectID , testConsentSnapshotID }},
{ `INSERT INTO conversation (id,tenant_id,household_id,subject_id,device_id,consent_snapshot_id,context_snapshot_id,provider_task_ref,engine_mode,retention_mode,status,allocation_expires_at,started_at,ended_at,created_at,updated_at)
VALUES ($1::uuid,'10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000201',$2::uuid,$3::uuid,$4::uuid,$5::uuid,'prior-context-task','cascade','transcript_only','COMPLETED','2026-08-23T10:30:00Z','2026-08-23T10:00:00Z','2026-08-23T10:20:00Z','2026-08-23T10:00:00Z','2026-08-23T10:20:00Z')` , [] any { testPriorConversationID , testSubjectID , testDeviceID , testConsentSnapshotID , testPriorContextID }},
{ `INSERT INTO conversation_turn (tenant_id,subject_id,conversation_id,turn_key,sequence_no,speaker,start_at,end_at,final_text,source_type)
VALUES ('10000000-0000-0000-0000-000000000001',$1::uuid,$2::uuid,'prior-subject-1',1,'subject','2026-08-23T10:01:00Z','2026-08-23T10:01:05Z','我注册了一个个体工商户。','provider')` , [] any { testSubjectID , testPriorConversationID }},
{ `INSERT INTO conversation_summary (tenant_id,subject_id,conversation_id,summary_type,content,source_turn_ids,model_ref,prompt_version,generation_version,status,created_at)
VALUES ('10000000-0000-0000-0000-000000000001',$1::uuid,$2::uuid,'conversation_summary','上次聊到刚注册了一个个体工商户,准备开始新的工作。','["prior-subject-1"]'::jsonb,'mock-content-v1','prompt-v1',1,'READY','2026-08-23T10:21:00Z')` , [] any { testSubjectID , testPriorConversationID }},
2026-08-26 06:41:07 +08:00
{ `INSERT INTO consent_record (tenant_id,subject_id,consent_type,policy_version,notice_digest,actor_user_id)
VALUES ('10000000-0000-0000-0000-000000000001',$1::uuid,'personalized_memory','orchestration-service-test',repeat('a',64),'10000000-0000-0000-0000-000000000101')` , [] any { testSubjectID }},
{ `INSERT INTO runtime_config_version (
id,public_id,tenant_id,config_key,display_name,config_kind,version_no,status,payload,change_summary,
created_by,submitted_by,submitted_at,approved_by,approved_at,published_by,published_at
) VALUES (
$1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','orchestration-test-persona','编排测试人设','PERSONA',1,'PUBLISHED',
'{"system_prompt":"你是已审核发布的陪伴助手。","tone":"warm"}'::jsonb,'编排集成测试',
'10000000-0000-0000-0000-000000000102','10000000-0000-0000-0000-000000000102','2026-08-23T11:00:00Z',
'10000000-0000-0000-0000-000000000102','2026-08-23T11:01:00Z','10000000-0000-0000-0000-000000000102','2026-08-23T11:02:00Z'
)` , [] any { testPersonaConfigID , testPersonaConfigPublicID }},
{ `INSERT INTO runtime_config_version (
id,public_id,tenant_id,config_key,display_name,config_kind,version_no,status,payload,change_summary,
created_by,submitted_by,submitted_at,approved_by,approved_at,published_by,published_at
) VALUES (
$1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','orchestration-test-voice','编排测试音色','VOICE',1,'PUBLISHED',
'{"provider":"mock","voice_resource_ref":"voice://orchestration/approved"}'::jsonb,'编排集成测试',
'10000000-0000-0000-0000-000000000102','10000000-0000-0000-0000-000000000102','2026-08-23T11:00:00Z',
'10000000-0000-0000-0000-000000000102','2026-08-23T11:01:00Z','10000000-0000-0000-0000-000000000102','2026-08-23T11:02:00Z'
)` , [] any { testVoiceConfigID , testVoiceConfigPublicID }},
{ `INSERT INTO runtime_config_version (
id,public_id,tenant_id,config_key,display_name,config_kind,version_no,status,payload,change_summary,
created_by,submitted_by,submitted_at,approved_by,approved_at,published_by,published_at
) VALUES (
$1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','orchestration-test-model','编排测试模型','MODEL',1,'PUBLISHED',
'{"provider":"mock","model_id":"model://orchestration/approved"}'::jsonb,'编排集成测试',
'10000000-0000-0000-0000-000000000102','10000000-0000-0000-0000-000000000102','2026-08-23T11:00:00Z',
'10000000-0000-0000-0000-000000000102','2026-08-23T11:01:00Z','10000000-0000-0000-0000-000000000102','2026-08-23T11:02:00Z'
)` , [] any { testModelConfigID , testModelConfigPublicID }},
{ `INSERT INTO runtime_config_version (
id,public_id,tenant_id,config_key,display_name,config_kind,version_no,status,payload,change_summary,
created_by,submitted_by,submitted_at,approved_by,approved_at,published_by,published_at
) VALUES (
$1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','orchestration-test-routing','编排测试路由','ROUTING',1,'PUBLISHED',
'{"default_engine_mode":"cascade","routes":[{"priority":1,"provider":"mock"}]}'::jsonb,'编排集成测试',
'10000000-0000-0000-0000-000000000102','10000000-0000-0000-0000-000000000102','2026-08-23T11:00:00Z',
'10000000-0000-0000-0000-000000000102','2026-08-23T11:01:00Z','10000000-0000-0000-0000-000000000102','2026-08-23T11:02:00Z'
)` , [] any { testRoutingConfigID , testRoutingConfigPublicID }},
{ `INSERT INTO billing_price_book (
id,public_id,tenant_id,provider,currency,version_no,status,source_ref,change_summary,
effective_from,created_by,published_by,published_at
) VALUES (
$1::uuid,'90000000-0000-0000-0000-000000000611','10000000-0000-0000-0000-000000000001',
'mock','CNY',9001,'PUBLISHED','integration-test','provider usage integration test',
'2026-01-01T00:00:00Z','10000000-0000-0000-0000-000000000102','10000000-0000-0000-0000-000000000102','2026-01-01T00:00:00Z'
)` , [] any { testPriceBookID }},
{ `INSERT INTO billing_price_rate (price_book_id,usage_category,metric_code,unit,unit_price_micros)
VALUES ($1::uuid,'LLM','input_tokens','token',2)` , [] any { testPriceBookID }},
}
for _ , statement := range fixtureStatements {
if _ , err := pool . Exec ( ctx , statement . query , statement . args ... ); err != nil {
t . Fatal ( err )
}
}
configService , err := opsconfig . NewService ( pool , audit . NewRepository ())
if err != nil {
t . Fatal ( err )
}
2026-08-29 21:56:51 +08:00
configAdmin := platformidentity . Principal { Issuer : "http://localhost:58180/realms/voice-local" , Subject : "00000000-0000-0000-0000-000000000104" }
configApprover := platformidentity . Principal { Issuer : configAdmin . Issuer , Subject : "00000000-0000-0000-0000-000000000105" }
2026-08-26 06:41:07 +08:00
bundle , created , err := configService . Create ( ctx , configAdmin , testBundleCreateKey , "orchestration-agent-bundle-create" , opsconfig . CreateInput {
TenantID : "10000000-0000-0000-0000-000000000010" , ConfigKey : testBundleConfigKey ,
DisplayName : "编排测试智能体包" , ConfigKind : "AGENT_BUNDLE" , ChangeSummary : "验证运营发布的自定义版本族进入通话运行时" ,
Payload : map [ string ] any {
"persona_version_id" : testPersonaConfigPublicID , "voice_version_id" : testVoiceConfigPublicID ,
"model_version_id" : testModelConfigPublicID , "routing_version_id" : testRoutingConfigPublicID ,
}, SecretRefs : map [ string ] string {},
})
if err != nil || ! created {
t . Fatalf ( "create Agent bundle=%+v created=%v err=%v" , bundle , created , err )
}
bundle , err = configService . Submit ( ctx , configAdmin , "10000000-0000-0000-0000-000000000010" , bundle . ID , "orchestration-agent-bundle-submit" , bundle . RowVersion )
if err != nil {
t . Fatal ( err )
}
qualityMetrics := map [ string ] any {
"report_schema" : "agent-eval-report-v1" , "suite_sha256" : "b5cd3b798a01a48ddf0081f2fcc2a917f93681a3ee2fa55bbf4190466de3bef8" ,
"agent_version" : "orchestration-integration-v1" , "model_config_ref" : "runtime-config-version:" + testModelConfigPublicID ,
"config_version_id" : bundle . 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 ,
}
if _ , created , err = configService . CreateEvaluation ( ctx , configAdmin , "10000000-0000-0000-0000-000000000010" , bundle . ID ,
testBundleEvaluationKey , "orchestration-agent-bundle-evaluation" , opsconfig . EvidenceTypeAgentQualityV1 ,
"orchestration-agent-quality-v1" , "PASSED" , "artifact://orchestration-agent-quality.json" , qualityMetrics ); err != nil || ! created {
t . Fatalf ( "register Agent quality evidence created=%v err=%v" , created , err )
}
bundle , err = configService . Publish ( ctx , configApprover , "10000000-0000-0000-0000-000000000010" , bundle . ID ,
"orchestration-agent-bundle-publish" , opsconfig . PublishInput { Reason : "标准质量证据通过后灰度到测试长者" , Rollout : opsconfig . RolloutInput {
ScopeType : "SUBJECT" , ScopeID : testSubjectPublicID , RolloutPercent : 100 , MaxConcurrentCalls : 100 ,
}}, bundle . RowVersion )
if err != nil || bundle . Status != "PUBLISHED" {
t . Fatalf ( "publish Agent bundle=%+v err=%v" , bundle , err )
}
issuer , _ := rtc . NewLocalTokenIssuer ([] byte ( "01234567890123456789012345678901" ))
service , err := NewService (
idempotency . NewExecutor ( pool ), NewRepository (), audit . NewRepository (), outbox . NewStore ( pool ), issuer ,
testLeaseCache {},
ServiceOptions { RTCAppID : "test-app" , EngineMode : "s2s_hybrid" , AllocationTTL : 30 * time . Second , TokenTTL : 5 * time . Minute , HeartbeatInterval : 15 },
)
if err != nil {
t . Fatal ( err )
}
embedder := & countingEmbedder {}
if err = service . ConfigureMemoryEmbedding ( pool , embedder ); err != nil {
t . Fatal ( err )
}
service . now = func () time . Time { return time . Date ( 2026 , 8 , 23 , 12 , 0 , 0 , 0 , time . UTC ) }
identity := deviceauth . Identity { ID : testDeviceID , PublicID : testDevicePublicID , TenantID : "10000000-0000-0000-0000-000000000001" , Status : "registered" }
resume := true
rtcCredentialRenewal := true
input := CreateInput { DeviceTime : service . now (), Capabilities : Capabilities { RTC : true , AudioTracks : [] string { "near" }, UploadResume : & resume , RTCCredentialRenewal : & rtcCredentialRenewal }, Network : Network { Type : "test" }, RequestedGoal : "open_conversation" }
body := [] byte ( `{"device_time":"2026-08-23T12:00:00Z","capabilities":{"rtc":true,"audio_tracks":["near"],"upload_resume":true,"rtc_credential_renewal":true},"network":{"type":"test"},"requested_goal":"open_conversation"}` )
first , replayed , err := service . Create ( ctx , identity , "idempotency-test-0001" , "request-1" , body , input )
if err != nil || replayed || first . RTC . Token == "" {
t . Fatalf ( "first response=%#v replayed=%v err=%v" , first , replayed , err )
}
var frozenContext , frozenBundleVersion string
if err := pool . QueryRow ( ctx , `SELECT cs.sanitized_summary,cs.prompt_bundle_version FROM context_snapshot cs JOIN conversation c ON c.context_snapshot_id=cs.id WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & frozenContext , & frozenBundleVersion ); err != nil {
t . Fatal ( err )
}
if frozenBundleVersion != "cfg:" + bundle . ID || first . Session . EngineMode != "cascade" {
t . Fatalf ( "frozen bundle=%q engine=%q" , frozenBundleVersion , first . Session . EngineMode )
}
2026-08-29 21:56:51 +08:00
for _ , expected := range [] string { "服务测试长者" , "年轻时从事铁路工作" , "老照片" , "聊聊第一次参加工作的经历" , "放慢语速" , "工作经历" , "交流边界" , "收入" , "最近第1次通话摘要" , "个体工商户" } {
2026-08-26 06:41:07 +08:00
if ! strings . Contains ( frozenContext , expected ) {
t . Fatalf ( "frozen context %q does not contain %q" , frozenContext , expected )
}
}
if strings . Contains ( frozenContext , "不得进入语音上下文" ) || strings . Contains ( frozenContext , "open_conversation" ) {
t . Fatalf ( "frozen context exposed non-whitelisted or technical fields: %q" , frozenContext )
}
second , replayed , err := service . Create ( ctx , identity , "idempotency-test-0001" , "request-2" , body , input )
if err != nil || ! replayed || second . ConversationID != first . ConversationID || second . RTC . Token != first . RTC . Token {
t . Fatalf ( "second response=%#v replayed=%v err=%v" , second , replayed , err )
}
if _ , _ , err := service . Create ( ctx , identity , "idempotency-test-0001" , "request-3" , append ( body , ' ' ), input ); ! errors . Is ( err , idempotency . ErrKeyConflict ) {
t . Fatalf ( "conflicting body error = %v" , err )
}
if embedder . calls . Load () != 1 {
t . Fatalf ( "embedding calls=%d want=1; sequential replay and body conflict must not repeat provider billing" , embedder . calls . Load ())
}
var conversations , histories , audits , events int
var tokenPersisted bool
if err := pool . QueryRow ( ctx , "SELECT count(*) FROM conversation WHERE device_id=$1::uuid" , testDeviceID ). Scan ( & conversations ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM conversation_state_history h JOIN conversation c ON c.id=h.conversation_id WHERE c.device_id=$1::uuid` , testDeviceID ). Scan ( & histories ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , "SELECT count(*) FROM audit_event WHERE actor_public_id=$1::uuid AND action='conversation.create'" , testDevicePublicID ). Scan ( & audits ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM outbox_event o JOIN conversation c ON c.id=o.aggregate_id WHERE c.device_id=$1::uuid AND o.event_type='conversation.room_issued'` , testDeviceID ). Scan ( & events ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT EXISTS (SELECT 1 FROM idempotency_record WHERE actor_key=$1 AND response_body::text LIKE '%dev1.%')` , "device:" + testDevicePublicID ). Scan ( & tokenPersisted ); err != nil {
t . Fatal ( err )
}
2026-08-29 21:56:51 +08:00
if conversations != 2 || histories != 2 || audits != 1 || events != 1 || tokenPersisted {
2026-08-26 06:41:07 +08:00
t . Fatalf ( "conversation=%d history=%d audit=%d event=%d tokenPersisted=%v" , conversations , histories , audits , events , tokenPersisted )
}
activateInput := ActivateInput {
JoinedAt : service . now (). Add ( time . Second ), RoomID : first . RTC . RoomID , RTCUserID : first . RTC . UserID ,
RTCJoinResult : "success" , ConfigRevision : 1 ,
}
activateBody , _ := json . Marshal ( activateInput )
activated , replayed , err := service . Activate ( ctx , identity , first . ConversationID , "activate-idempotency-0001" , "activation-request" , activateBody , activateInput )
if err != nil || replayed || activated . Status != "STARTING_AGENT" {
t . Fatalf ( "activation=%#v replayed=%v err=%v" , activated , replayed , err )
}
replayedActivation , replayed , err := service . Activate ( ctx , identity , first . ConversationID , "activate-idempotency-0001" , "activation-request-replay" , activateBody , activateInput )
if err != nil || ! replayed || replayedActivation != activated {
t . Fatalf ( "replayed activation=%#v replayed=%v err=%v" , replayedActivation , replayed , err )
}
var conversationInternalID , providerTaskRef string
if err := pool . QueryRow ( ctx , `SELECT id::text, provider_task_ref FROM conversation WHERE public_id=$1::uuid` , first . ConversationID ). Scan ( & conversationInternalID , & providerTaskRef ); err != nil {
t . Fatal ( err )
}
providerClient := & testVoiceProvider {}
providerHealth , err := NewProviderHealth ( pool , 3 , time . Minute )
if err != nil {
t . Fatal ( err )
}
startProcessor , err := NewStartProcessor ( pool , providerClient , providerHealth , 75 * time . Second )
if err != nil {
t . Fatal ( err )
}
startProcessor . now = func () time . Time { return service . now (). Add ( 2 * time . Second ) }
if err := startProcessor . Handle ( ctx , outbox . Event { AggregateID : conversationInternalID }); err != nil {
t . Fatal ( err )
}
if providerClient . startRequest . TaskRef != providerTaskRef || providerClient . startRequest . ConversationID != first . ConversationID || providerClient . startRequest . RoomID != first . RTC . RoomID || providerClient . startRequest . EngineMode != first . Session . EngineMode {
t . Fatalf ( "provider start request=%#v" , providerClient . startRequest )
}
if providerClient . startRequest . ContextSummary != frozenContext {
t . Fatalf ( "provider context=%q want frozen context=%q" , providerClient . startRequest . ContextSummary , frozenContext )
}
if providerClient . startRequest . PromptBundleVersion != frozenBundleVersion ||
providerClient . startRequest . AgentBundleVersion != bundle . ID ||
providerClient . startRequest . PersonaVersion != testPersonaConfigPublicID ||
providerClient . startRequest . VoiceVersion != testVoiceConfigPublicID ||
providerClient . startRequest . ModelVersion != testModelConfigPublicID ||
providerClient . startRequest . RoutingVersion != testRoutingConfigPublicID ||
providerClient . startRequest . SystemPrompt != "你是已审核发布的陪伴助手。" ||
providerClient . startRequest . PersonaTone != "warm" ||
providerClient . startRequest . VoiceResourceRef != "voice://orchestration/approved" ||
providerClient . startRequest . ModelResourceRef != "model://orchestration/approved" {
t . Fatalf ( "provider bundle request=%#v" , providerClient . startRequest )
}
processor , _ := NewProviderEventProcessor ( pool , 75 * time . Second )
processor . now = func () time . Time { return service . now (). Add ( 3 * time . Second ) }
callback := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-started-1" , TaskRef : providerTaskRef ,
ConversationID : first . ConversationID , EventType : "voice_chat.started" , Status : "ACTIVE" ,
OccurredAt : service . now (). Add ( 2500 * time . Millisecond ),
}
rawCallback , _ := json . Marshal ( callback )
processed , err := processor . Process ( ctx , callback , rawCallback )
if err != nil || processed . Status != "ACTIVE" || processed . Duplicate || processed . Ignored {
t . Fatalf ( "processed=%#v err=%v" , processed , err )
}
duplicateCallback , err := processor . Process ( ctx , callback , rawCallback )
if err != nil || ! duplicateCallback . Duplicate || duplicateCallback . Status != "ACTIVE" {
t . Fatalf ( "duplicate=%#v err=%v" , duplicateCallback , err )
}
conflictingCallback := callback
conflictingCallback . Status = "FAILED"
conflictingBody , _ := json . Marshal ( conflictingCallback )
if _ , err := processor . Process ( ctx , conflictingCallback , conflictingBody ); ! errors . Is ( err , ErrProviderEventConflict ) {
t . Fatalf ( "conflicting callback error=%v" , err )
}
callback . EventID = "provider-event-started-late"
lateBody , _ := json . Marshal ( callback )
lateCallback , err := processor . Process ( ctx , callback , lateBody )
if err != nil || ! lateCallback . Ignored || lateCallback . Status != "ACTIVE" {
t . Fatalf ( "late=%#v err=%v" , lateCallback , err )
}
var finalStatus , finalTaskStatus string
var providerEvents int
if err := pool . QueryRow ( ctx , `SELECT c.status, r.status FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & finalStatus , & finalTaskStatus ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM provider_event e JOIN conversation c ON c.id=e.conversation_id WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & providerEvents ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM conversation_state_history h JOIN conversation c ON c.id=h.conversation_id WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & histories ); err != nil {
t . Fatal ( err )
}
if finalStatus != "ACTIVE" || finalTaskStatus != "ACTIVE" || providerEvents != 2 || histories != 4 {
t . Fatalf ( "conversation=%s task=%s events=%d history=%d" , finalStatus , finalTaskStatus , providerEvents , histories )
}
heartbeatOptions := HeartbeatServiceOptions {
RTCAppID : "test-app" , LeaseTTL : time . Minute , TokenTTL : 5 * time . Minute , CredentialRenewalWindow : 30 * time . Second ,
}
heartbeatService , err := NewHeartbeatService ( pool , NewRepository (), testLeaseCache {}, issuer , heartbeatOptions )
if err != nil {
t . Fatal ( err )
}
heartbeatService . now = func () time . Time { return time . Date ( 2026 , 8 , 23 , 12 , 0 , 3 , 500 * 1e6 , time . UTC ) }
oldCredentialExpiry := heartbeatService . now (). Add ( 20 * time . Second )
if _ , err := pool . Exec ( ctx , `UPDATE rtc_task SET credential_issued_at=$2,credential_expires_at=$3 WHERE conversation_id=$1::uuid` , conversationInternalID , service . now (), oldCredentialExpiry ); err != nil {
t . Fatal ( err )
}
monotonicMS := int64 ( 12345 )
heartbeatInput := HeartbeatInput { SequenceNo : 1 , DeviceTime : heartbeatService . now (), MonotonicMS : & monotonicMS , ConversationStatus : "ACTIVE" , RTC : RTCHeartbeat { Joined : true , Uplink : true , Downlink : true }, Network : Network { Type : "wifi" }, FirmwareVersion : "1.0.0" , ReportedConfigRevision : 1 , RecordingQueue : RecordingQueue {}, RTCCredentialExpiresAt : & oldCredentialExpiry , CommandResults : [] json . RawMessage {}}
failingHeartbeatService , err := NewHeartbeatService ( pool , NewRepository (), testLeaseCache {}, failingTokenIssuer {}, heartbeatOptions )
if err != nil {
t . Fatal ( err )
}
failingHeartbeatService . now = heartbeatService . now
if _ , err = pool . Exec ( ctx , `UPDATE conversation SET rtc_credential_renewal_supported=false WHERE id=$1::uuid` , conversationInternalID ); err != nil {
t . Fatal ( err )
}
legacyHeartbeat , err := failingHeartbeatService . Heartbeat ( ctx , identity , first . ConversationID , heartbeatInput )
if err != nil || len ( legacyHeartbeat . Actions ) != 1 || legacyHeartbeat . Actions [ 0 ]. Type != "continue" {
t . Fatalf ( "legacy heartbeat=%#v err=%v" , legacyHeartbeat , err )
}
var legacyExpiry time . Time
if err = pool . QueryRow ( ctx , `SELECT credential_expires_at FROM rtc_task WHERE conversation_id=$1::uuid` , conversationInternalID ). Scan ( & legacyExpiry ); err != nil {
t . Fatal ( err )
}
if ! legacyExpiry . Equal ( oldCredentialExpiry ) {
t . Fatalf ( "legacy client rotated credential metadata: expiry=%s" , legacyExpiry )
}
if _ , err = pool . Exec ( ctx , `UPDATE conversation SET rtc_credential_renewal_supported=true,last_heartbeat_seq=0 WHERE id=$1::uuid` , conversationInternalID ); err != nil {
t . Fatal ( err )
}
if _ , err = failingHeartbeatService . Heartbeat ( ctx , identity , first . ConversationID , heartbeatInput ); err == nil {
t . Fatal ( "credential signing failure did not abort heartbeat" )
}
var expiryAfterFailure time . Time
var sequenceAfterFailure int64
if err = pool . QueryRow ( ctx , `SELECT r.credential_expires_at,c.last_heartbeat_seq FROM rtc_task r JOIN conversation c ON c.id=r.conversation_id WHERE c.id=$1::uuid` , conversationInternalID ). Scan ( & expiryAfterFailure , & sequenceAfterFailure ); err != nil {
t . Fatal ( err )
}
if ! expiryAfterFailure . Equal ( oldCredentialExpiry ) || sequenceAfterFailure != 0 {
t . Fatalf ( "failed renewal committed metadata expiry=%s sequence=%d" , expiryAfterFailure , sequenceAfterFailure )
}
heartbeat , err := heartbeatService . Heartbeat ( ctx , identity , first . ConversationID , heartbeatInput )
if err != nil || heartbeat . LeaseExpiresAt == nil || len ( heartbeat . Actions ) != 2 || heartbeat . Actions [ 0 ]. Type != "continue" || heartbeat . Actions [ 1 ]. Type != "renew_rtc_credential" || heartbeat . Actions [ 1 ]. RTC == nil || heartbeat . Actions [ 1 ]. RTC . Token == "" || ! heartbeat . Actions [ 1 ]. RTC . ExpiresAt . Equal ( heartbeatService . now (). Add ( 5 * time . Minute )) {
t . Fatalf ( "heartbeat=%#v err=%v" , heartbeat , err )
}
staleHeartbeat , err := heartbeatService . Heartbeat ( ctx , identity , first . ConversationID , heartbeatInput )
if err != nil || staleHeartbeat . LeaseExpiresAt == nil || ! staleHeartbeat . LeaseExpiresAt . Equal ( * heartbeat . LeaseExpiresAt ) || len ( staleHeartbeat . Actions ) != 2 || staleHeartbeat . Actions [ 1 ]. RTC == nil || staleHeartbeat . Actions [ 1 ]. RTC . Token != heartbeat . Actions [ 1 ]. RTC . Token {
t . Fatalf ( "stale heartbeat=%#v err=%v" , staleHeartbeat , err )
}
heartbeatInput . SequenceNo = 2
heartbeatInput . RTCCredentialExpiresAt = & heartbeat . Actions [ 1 ]. RTC . ExpiresAt
heartbeatInput . ReportedConfigRevision = 2
if _ , err := heartbeatService . Heartbeat ( ctx , identity , first . ConversationID , heartbeatInput ); ! errors . Is ( err , ErrConfigVersionConflict ) {
t . Fatalf ( "future heartbeat configuration error=%v" , err )
}
service . now = func () time . Time { return time . Date ( 2026 , 8 , 23 , 12 , 0 , 4 , 0 , time . UTC ) }
stopInput := StopInput {
Reason : "handset_on_hook" , DeviceTime : service . now (), LastHeartbeatSequence : 2 ,
RTCLeft : true , Recording : StopRecording { NearSegments : 1 , FarSegments : 1 },
}
stopBody := [] byte ( `{"reason":"handset_on_hook","device_time":"2026-08-23T12:00:04Z","last_heartbeat_sequence":2,"rtc_left":true,"recording":{"near_segments":1,"far_segments":1}}` )
stopped , replayed , err := service . Stop ( ctx , identity , first . ConversationID , "stop-idempotency-0001" , "stop-request-1" , stopBody , stopInput )
if err != nil || replayed || stopped . Status != "STOPPING" {
t . Fatalf ( "stop=%#v replayed=%v err=%v" , stopped , replayed , err )
}
replayedStop , replayed , err := service . Stop ( ctx , identity , first . ConversationID , "stop-idempotency-0001" , "stop-request-2" , stopBody , stopInput )
if err != nil || ! replayed || replayedStop . Status != "STOPPING" {
t . Fatalf ( "replayed stop=%#v replayed=%v err=%v" , replayedStop , replayed , err )
}
var stopEvents , stopAudits int
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM outbox_event o JOIN conversation c ON c.id=o.aggregate_id
WHERE c.public_id=$1::uuid AND o.event_type='conversation.agent_stop_requested'` , first . ConversationID ). Scan ( & stopEvents ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM audit_event a JOIN conversation c ON c.id=a.resource_id
WHERE c.public_id=$1::uuid AND a.action='conversation.stop'` , first . ConversationID ). Scan ( & stopAudits ); err != nil {
t . Fatal ( err )
}
if stopEvents != 1 || stopAudits != 1 {
t . Fatalf ( "stop events=%d audits=%d" , stopEvents , stopAudits )
}
stopProcessor , err := NewStopProcessor ( pool , providerClient , providerHealth )
if err != nil {
t . Fatal ( err )
}
stopProcessor . now = func () time . Time { return service . now (). Add ( 500 * time . Millisecond ) }
if err := stopProcessor . Handle ( ctx , outbox . Event { AggregateID : conversationInternalID }); err != nil {
t . Fatal ( err )
}
if providerClient . stopRequest . TaskRef != providerTaskRef || providerClient . stopRequest . ConversationID != first . ConversationID || providerClient . stopRequest . Reason != stopInput . Reason {
t . Fatalf ( "provider stop request=%#v" , providerClient . stopRequest )
}
processor . now = func () time . Time { return time . Date ( 2026 , 8 , 23 , 12 , 0 , 5 , 0 , time . UTC ) }
stopCallback := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-stopped-1" , TaskRef : providerTaskRef ,
ConversationID : first . ConversationID , EventType : "voice_chat.stopped" , Status : "COMPLETED" ,
OccurredAt : service . now (). Add ( time . Second ),
}
rawStopCallback , _ := json . Marshal ( stopCallback )
completed , err := processor . Process ( ctx , stopCallback , rawStopCallback )
if err != nil || completed . Status != "COMPLETED" || completed . Ignored {
t . Fatalf ( "completed=%#v err=%v" , completed , err )
}
duplicateStop , err := processor . Process ( ctx , stopCallback , rawStopCallback )
if err != nil || ! duplicateStop . Duplicate || duplicateStop . Status != "COMPLETED" {
t . Fatalf ( "duplicate stop=%#v err=%v" , duplicateStop , err )
}
if err := pool . QueryRow ( ctx , `SELECT c.status, r.status FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id
WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & finalStatus , & finalTaskStatus ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM conversation_state_history h JOIN conversation c ON c.id=h.conversation_id
WHERE c.public_id=$1::uuid` , first . ConversationID ). Scan ( & histories ); err != nil {
t . Fatal ( err )
}
if finalStatus != "COMPLETED" || finalTaskStatus != "COMPLETED" || histories != 6 {
t . Fatalf ( "final conversation=%s task=%s history=%d" , finalStatus , finalTaskStatus , histories )
}
// Exercise the synchronous ACTIVE/COMPLETED provider responses in addition
// to the asynchronous callback path above. This is the other supported
// provider contract and must preserve the same durable state transitions.
secondConversation , replayed , err := service . Create ( ctx , identity , "idempotency-test-0002" , "request-4" , body , input )
if err != nil || replayed {
t . Fatalf ( "second conversation=%#v replayed=%v err=%v" , secondConversation , replayed , err )
}
secondActivateInput := activateInput
secondActivateInput . RoomID = secondConversation . RTC . RoomID
secondActivateInput . RTCUserID = secondConversation . RTC . UserID
secondActivateBody , _ := json . Marshal ( secondActivateInput )
if _ , replayed , err := service . Activate ( ctx , identity , secondConversation . ConversationID , "activate-idempotency-0002" , "activation-request-2" , secondActivateBody , secondActivateInput ); err != nil || replayed {
t . Fatalf ( "second activation replayed=%v err=%v" , replayed , err )
}
var secondInternalID string
if err := pool . QueryRow ( ctx , `SELECT id::text FROM conversation WHERE public_id=$1::uuid` , secondConversation . ConversationID ). Scan ( & secondInternalID ); err != nil {
t . Fatal ( err )
}
providerClient . startStatus = "ACTIVE"
secondStartProcessor , err := NewStartProcessor ( pool , providerClient , providerHealth , 75 * time . Second )
if err != nil {
t . Fatal ( err )
}
if err := secondStartProcessor . ConfigureStartProtection ( allowProviderStart {}, 100 ); err != nil {
t . Fatal ( err )
}
secondStartProcessor . now = func () time . Time { return service . now (). Add ( 2 * time . Minute ) }
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : secondInternalID }); err != nil {
t . Fatal ( err )
}
secondStopInput := stopInput
secondStopInput . DeviceTime = service . now (). Add ( 2 * time . Minute + time . Second )
secondStopBody , _ := json . Marshal ( secondStopInput )
if _ , replayed , err := service . Stop ( ctx , identity , secondConversation . ConversationID , "stop-idempotency-0002" , "stop-request-3" , secondStopBody , secondStopInput ); err != nil || replayed {
t . Fatalf ( "second stop replayed=%v err=%v" , replayed , err )
}
providerClient . stopStatus = "COMPLETED"
secondStopProcessor , err := NewStopProcessor ( pool , providerClient , providerHealth )
if err != nil {
t . Fatal ( err )
}
secondStopProcessor . now = func () time . Time { return service . now (). Add ( 2 * time . Minute + 2 * time . Second ) }
if err := secondStopProcessor . Handle ( ctx , outbox . Event { AggregateID : secondInternalID }); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT c.status, r.status FROM conversation c JOIN rtc_task r ON r.conversation_id=c.id WHERE c.id=$1::uuid` , secondInternalID ). Scan ( & finalStatus , & finalTaskStatus ); err != nil {
t . Fatal ( err )
}
if finalStatus != "COMPLETED" || finalTaskStatus != "COMPLETED" {
t . Fatalf ( "second final conversation=%s task=%s" , finalStatus , finalTaskStatus )
}
usageCallback := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-usage-1" , TaskRef : providerTaskRef ,
ConversationID : first . ConversationID , EventType : "voice_chat.usage" , Status : "FINAL" ,
OccurredAt : service . now (). Add ( 3 * time . Minute ), Usage : [] voiceprovider . UsageMetric {{
UsageCategory : "LLM" , MetricCode : "input_tokens" , Quantity : "12" , Unit : "token" ,
}},
}
usageBody , _ := json . Marshal ( usageCallback )
usageResult , err := processor . Process ( ctx , usageCallback , usageBody )
if err != nil || usageResult . UsageAccepted != 1 {
t . Fatalf ( "provider usage result=%#v err=%v" , usageResult , err )
}
// A provider can end an active call before a device stop request arrives.
// Verify that callback-only termination creates both durable transitions and
// schedules content extraction.
providerEndedConversation , replayed , err := service . Create ( ctx , identity , "idempotency-test-0003" , "request-5" , body , input )
if err != nil || replayed {
t . Fatalf ( "provider-ended conversation=%#v replayed=%v err=%v" , providerEndedConversation , replayed , err )
}
providerEndedActivate := activateInput
providerEndedActivate . RoomID = providerEndedConversation . RTC . RoomID
providerEndedActivate . RTCUserID = providerEndedConversation . RTC . UserID
providerEndedActivateBody , _ := json . Marshal ( providerEndedActivate )
if _ , replayed , err := service . Activate ( ctx , identity , providerEndedConversation . ConversationID , "activate-idempotency-0003" , "activation-request-3" , providerEndedActivateBody , providerEndedActivate ); err != nil || replayed {
t . Fatalf ( "provider-ended activation replayed=%v err=%v" , replayed , err )
}
var providerEndedInternalID , providerEndedTaskRef string
if err := pool . QueryRow ( ctx , `SELECT id::text,provider_task_ref FROM conversation WHERE public_id=$1::uuid` , providerEndedConversation . ConversationID ). Scan ( & providerEndedInternalID , & providerEndedTaskRef ); err != nil {
t . Fatal ( err )
}
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : providerEndedInternalID }); err != nil {
t . Fatal ( err )
}
providerEndedCallback := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-ended-active" , TaskRef : providerEndedTaskRef ,
ConversationID : providerEndedConversation . ConversationID , EventType : "voice_chat.stopped" , Status : "COMPLETED" ,
OccurredAt : service . now (). Add ( 4 * time . Minute ),
}
providerEndedBody , _ := json . Marshal ( providerEndedCallback )
providerEndedResult , err := processor . Process ( ctx , providerEndedCallback , providerEndedBody )
if err != nil || providerEndedResult . Status != "COMPLETED" || providerEndedResult . Ignored {
t . Fatalf ( "provider-ended result=%#v err=%v" , providerEndedResult , err )
}
// A runtime failure moves an active conversation to STOPPING and repeated
// failure delivery remains safe while retaining the first failure cause.
failedConversation , replayed , err := service . Create ( ctx , identity , "idempotency-test-0004" , "request-6" , body , input )
if err != nil || replayed {
t . Fatalf ( "failed conversation=%#v replayed=%v err=%v" , failedConversation , replayed , err )
}
failedActivate := activateInput
failedActivate . RoomID = failedConversation . RTC . RoomID
failedActivate . RTCUserID = failedConversation . RTC . UserID
failedActivateBody , _ := json . Marshal ( failedActivate )
if _ , replayed , err := service . Activate ( ctx , identity , failedConversation . ConversationID , "activate-idempotency-0004" , "activation-request-4" , failedActivateBody , failedActivate ); err != nil || replayed {
t . Fatalf ( "failed activation replayed=%v err=%v" , replayed , err )
}
var failedInternalID , failedTaskRef string
if err := pool . QueryRow ( ctx , `SELECT id::text,provider_task_ref FROM conversation WHERE public_id=$1::uuid` , failedConversation . ConversationID ). Scan ( & failedInternalID , & failedTaskRef ); err != nil {
t . Fatal ( err )
}
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : failedInternalID }); err != nil {
t . Fatal ( err )
}
failedCallback := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-runtime-failed-1" , TaskRef : failedTaskRef ,
ConversationID : failedConversation . ConversationID , EventType : "voice_chat.failed" , Status : "FAILED" ,
OccurredAt : service . now (). Add ( 5 * time . Minute ),
}
failedBody , _ := json . Marshal ( failedCallback )
failedResult , err := processor . Process ( ctx , failedCallback , failedBody )
if err != nil || failedResult . Status != "STOPPING" || failedResult . Ignored {
t . Fatalf ( "runtime failure result=%#v err=%v" , failedResult , err )
}
failedCallback . EventID = "provider-event-runtime-failed-2"
repeatedFailedBody , _ := json . Marshal ( failedCallback )
repeatedFailedResult , err := processor . Process ( ctx , failedCallback , repeatedFailedBody )
if err != nil || repeatedFailedResult . Status != "STOPPING" || repeatedFailedResult . Ignored {
t . Fatalf ( "repeated runtime failure result=%#v err=%v" , repeatedFailedResult , err )
}
failedStopProcessor , err := NewStopProcessor ( pool , providerClient , providerHealth )
if err != nil {
t . Fatal ( err )
}
providerClient . stopErr = & voiceprovider . Error { Operation : "stop" , Temporary : true , Cause : errors . New ( "temporary test failure" )}
if err := failedStopProcessor . Handle ( ctx , outbox . Event { AggregateID : failedInternalID }); err == nil {
t . Fatal ( "temporary provider stop failure was not retried" )
}
providerClient . stopErr = nil
providerClient . stopStatus = "COMPLETED"
if err := failedStopProcessor . Handle ( ctx , outbox . Event { AggregateID : failedInternalID }); err != nil {
t . Fatal ( err )
}
// Reconciliation after an ambiguous start must query first. A still-starting
// response is retried, a confirmed 404 permits the same stable task to be
// started again, and a later ACTIVE query closes the ambiguity safely.
reconciledConversation , replayed , err := service . Create ( ctx , identity , "idempotency-test-0005" , "request-7" , body , input )
if err != nil || replayed {
t . Fatalf ( "reconciled conversation=%#v replayed=%v err=%v" , reconciledConversation , replayed , err )
}
reconciledActivate := activateInput
reconciledActivate . RoomID = reconciledConversation . RTC . RoomID
reconciledActivate . RTCUserID = reconciledConversation . RTC . UserID
reconciledActivateBody , _ := json . Marshal ( reconciledActivate )
if _ , replayed , err := service . Activate ( ctx , identity , reconciledConversation . ConversationID , "activate-idempotency-0005" , "activation-request-5" , reconciledActivateBody , reconciledActivate ); err != nil || replayed {
t . Fatalf ( "reconciled activation replayed=%v err=%v" , replayed , err )
}
var reconciledInternalID string
if err := pool . QueryRow ( ctx , `SELECT id::text FROM conversation WHERE public_id=$1::uuid` , reconciledConversation . ConversationID ). Scan ( & reconciledInternalID ); err != nil {
t . Fatal ( err )
}
if _ , err := pool . Exec ( ctx , `UPDATE rtc_task SET status='UNKNOWN' WHERE conversation_id=$1::uuid` , reconciledInternalID ); err != nil {
t . Fatal ( err )
}
providerClient . queryStatus = "STARTING"
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : reconciledInternalID }); err == nil {
t . Fatal ( "still-starting reconciliation was not retried" )
}
if _ , err := pool . Exec ( ctx , `UPDATE rtc_task SET status='UNKNOWN' WHERE conversation_id=$1::uuid` , reconciledInternalID ); err != nil {
t . Fatal ( err )
}
providerClient . queryErr = & voiceprovider . Error { Operation : "query" , StatusCode : 404 }
providerClient . startErr = & voiceprovider . Error { Operation : "start" , Temporary : true , Cause : errors . New ( "ambiguous start" )}
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : reconciledInternalID }); err == nil {
t . Fatal ( "temporary provider start failure was not retried" )
}
providerClient . queryErr = nil
providerClient . startErr = nil
providerClient . queryStatus = "ACTIVE"
if err := secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : reconciledInternalID }); err != nil {
t . Fatal ( err )
}
reconciledStopInput := stopInput
reconciledStopInput . DeviceTime = service . now (). Add ( 6 * time . Minute + time . Second )
reconciledStopBody , _ := json . Marshal ( reconciledStopInput )
if _ , replayed , err := service . Stop ( ctx , identity , reconciledConversation . ConversationID , "stop-idempotency-0005" , "stop-request-5" , reconciledStopBody , reconciledStopInput ); err != nil || replayed {
t . Fatalf ( "reconciled stop replayed=%v err=%v" , replayed , err )
}
if err := failedStopProcessor . Handle ( ctx , outbox . Event { AggregateID : reconciledInternalID }); err != nil {
t . Fatal ( err )
}
// A permanent provider start rejection is not retryable. It must close the
// authoritative state immediately so the device can leave the RTC room with
// a stable, non-sensitive failure code instead of timing out in STARTING_AGENT.
rejectedConversation , replayed , err := service . Create ( ctx , identity , "idempotency-test-0006" , "request-8" , body , input )
if err != nil || replayed {
t . Fatalf ( "rejected conversation=%#v replayed=%v err=%v" , rejectedConversation , replayed , err )
}
rejectedActivate := activateInput
rejectedActivate . RoomID = rejectedConversation . RTC . RoomID
rejectedActivate . RTCUserID = rejectedConversation . RTC . UserID
rejectedActivateBody , _ := json . Marshal ( rejectedActivate )
if _ , replayed , err = service . Activate ( ctx , identity , rejectedConversation . ConversationID , "activate-idempotency-0006" , "activation-request-6" , rejectedActivateBody , rejectedActivate ); err != nil || replayed {
t . Fatalf ( "rejected activation replayed=%v err=%v" , replayed , err )
}
var rejectedInternalID string
if err = pool . QueryRow ( ctx , `SELECT id::text FROM conversation WHERE public_id=$1::uuid` , rejectedConversation . ConversationID ). Scan ( & rejectedInternalID ); err != nil {
t . Fatal ( err )
}
providerClient . startErr = & voiceprovider . Error { Operation : "start" , StatusCode : 400 , Cause : errors . New ( "invalid provider request" )}
if err = secondStartProcessor . Handle ( ctx , outbox . Event { AggregateID : rejectedInternalID }); err != nil {
t . Fatalf ( "permanent provider rejection was not durably consumed: %v" , err )
}
providerClient . startErr = nil
rejectedState , err := service . GetDeviceState ( ctx , identity , rejectedConversation . ConversationID )
if err != nil || rejectedState . Status != "FAILED" || rejectedState . Action != "leave_room" || rejectedState . FailureCode != "provider_start_rejected" {
t . Fatalf ( "rejected device state=%#v err=%v" , rejectedState , err )
}
var rejectedTaskStatus string
var rejectedHistory , rejectedAudits int
if err = pool . QueryRow ( ctx , `SELECT status FROM rtc_task WHERE conversation_id=$1::uuid` , rejectedInternalID ). Scan ( & rejectedTaskStatus ); err != nil {
t . Fatal ( err )
}
if err = pool . QueryRow ( ctx , `SELECT count(*) FROM conversation_state_history WHERE conversation_id=$1::uuid AND from_status='STARTING_AGENT' AND to_status='FAILED'` , rejectedInternalID ). Scan ( & rejectedHistory ); err != nil {
t . Fatal ( err )
}
if err = pool . QueryRow ( ctx , `SELECT count(*) FROM audit_event WHERE resource_id=$1::uuid AND action='conversation.agent_start_failed'` , rejectedInternalID ). Scan ( & rejectedAudits ); err != nil {
t . Fatal ( err )
}
if rejectedTaskStatus != "FAILED" || rejectedHistory != 1 || rejectedAudits != 1 {
t . Fatalf ( "rejected task=%s history=%d audits=%d" , rejectedTaskStatus , rejectedHistory , rejectedAudits )
}
transcriptService , err := NewTranscriptBatchService ( pool , idempotency . NewExecutor ( pool ), audit . NewRepository (), outbox . NewStore ( pool ))
if err != nil {
t . Fatal ( err )
}
providerTurn := TranscriptTurnInput {
TurnKey : "provider-final-1" , SequenceNo : 1 , Speaker : "subject" ,
StartAt : time . Date ( 2026 , 8 , 23 , 12 , 0 , 1 , 0 , time . UTC ), EndAt : time . Date ( 2026 , 8 , 23 , 12 , 0 , 2 , 0 , time . UTC ),
FinalText : "供应商已经返回的终稿" , Interrupted : false ,
}
lateTranscript := voiceprovider . CallbackEvent {
Provider : "mock" , EventID : "provider-event-transcript-late-1" , TaskRef : providerTaskRef ,
ConversationID : first . ConversationID , EventType : "voice_chat.transcript.final" , Status : "FINAL" ,
OccurredAt : service . now (). Add ( 2 * time . Second ), Turns : [] voiceprovider . FinalTurn {{
TurnKey : providerTurn . TurnKey , SequenceNo : providerTurn . SequenceNo , Speaker : providerTurn . Speaker ,
StartAt : providerTurn . StartAt , EndAt : providerTurn . EndAt , FinalText : providerTurn . FinalText ,
Interrupted : providerTurn . Interrupted , ProviderRef : "provider-turn-1" ,
}},
}
lateTranscriptBody , _ := json . Marshal ( lateTranscript )
lateTranscriptResult , err := processor . Process ( ctx , lateTranscript , lateTranscriptBody )
if err != nil || lateTranscriptResult . TurnsAccepted != 1 || lateTranscriptResult . Status != "COMPLETED" {
t . Fatalf ( "late transcript=%#v err=%v" , lateTranscriptResult , err )
}
deviceTurn := TranscriptTurnInput {
TurnKey : "device-final-2" , SequenceNo : 2 , Speaker : "agent" ,
StartAt : time . Date ( 2026 , 8 , 23 , 12 , 0 , 2 , 0 , time . UTC ), EndAt : time . Date ( 2026 , 8 , 23 , 12 , 0 , 3 , 0 , time . UTC ),
FinalText : "终端补传的缺失终稿" , Interrupted : true ,
}
transcriptInput := TranscriptBatchInput { Turns : [] TranscriptTurnInput { providerTurn , deviceTurn }}
transcriptBody , _ := json . Marshal ( transcriptInput )
transcriptResponse , replayed , err := transcriptService . Submit ( ctx , identity , first . ConversationID , "transcript-idempotency-0001" , "transcript-request-1" , transcriptBody , transcriptInput )
if err != nil || replayed || transcriptResponse . AcceptedCount != 1 || transcriptResponse . DuplicateCount != 1 {
t . Fatalf ( "transcript response=%#v replayed=%v err=%v" , transcriptResponse , replayed , err )
}
replayedTranscript , replayed , err := transcriptService . Submit ( ctx , identity , first . ConversationID , "transcript-idempotency-0001" , "transcript-request-2" , transcriptBody , transcriptInput )
if err != nil || ! replayed || replayedTranscript != transcriptResponse {
t . Fatalf ( "replayed transcript=%#v replayed=%v err=%v" , replayedTranscript , replayed , err )
}
duplicateTranscript , replayed , err := transcriptService . Submit ( ctx , identity , first . ConversationID , "transcript-idempotency-0002" , "transcript-request-3" , transcriptBody , transcriptInput )
if err != nil || replayed || duplicateTranscript . AcceptedCount != 0 || duplicateTranscript . DuplicateCount != 2 {
t . Fatalf ( "duplicate transcript=%#v replayed=%v err=%v" , duplicateTranscript , replayed , err )
}
conflictingInput := TranscriptBatchInput { Turns : [] TranscriptTurnInput { deviceTurn }}
conflictingInput . Turns [ 0 ]. FinalText = "不一致的终稿"
conflictingBody , _ = json . Marshal ( conflictingInput )
if _ , _ , err := transcriptService . Submit ( ctx , identity , first . ConversationID , "transcript-idempotency-0003" , "transcript-request-4" , conflictingBody , conflictingInput ); err == nil {
t . Fatal ( "conflicting transcript was accepted" )
} else {
var conflict * TranscriptConflictError
if ! errors . As ( err , & conflict ) || len ( conflict . Conflicts ) != 1 {
t . Fatalf ( "conflicting transcript error=%v" , err )
}
}
wrongDevice := identity
wrongDevice . ID = "90000000-0000-0000-0000-000000000599"
if _ , _ , err := transcriptService . Submit ( ctx , wrongDevice , first . ConversationID , "transcript-idempotency-0004" , "transcript-request-5" , transcriptBody , transcriptInput ); ! errors . Is ( err , ErrConversationNotFound ) {
t . Fatalf ( "wrong device transcript error=%v" , err )
}
if _ , err := pool . Exec ( ctx , `UPDATE conversation SET retention_mode='none' WHERE public_id=$1::uuid` , first . ConversationID ); err != nil {
t . Fatal ( err )
}
if _ , _ , err := transcriptService . Submit ( ctx , identity , first . ConversationID , "transcript-idempotency-0005" , "transcript-request-6" , transcriptBody , transcriptInput ); ! errors . Is ( err , ErrTranscriptStorageNotAllowed ) {
t . Fatalf ( "retention-disabled transcript error=%v" , err )
}
var turnCount , deviceTurnCount , transcriptAudits , extractionEvents , providerExtractionEvents int
var extractionGeneration int64
if err := pool . QueryRow ( ctx , `SELECT count(*),count(*) FILTER (WHERE source_type='device') FROM conversation_turn
WHERE conversation_id=(SELECT id FROM conversation WHERE public_id=$1::uuid)` , first . ConversationID ). Scan ( & turnCount , & deviceTurnCount ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM audit_event WHERE actor_public_id=$1::uuid AND action='conversation.transcript_batch.submit'` , testDevicePublicID ). Scan ( & transcriptAudits ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*),max((payload->>'generation_version')::bigint) FROM outbox_event WHERE aggregate_id=(SELECT id FROM conversation WHERE public_id=$1::uuid)
AND event_type='conversation.content_extraction_requested'
AND deduplication_key LIKE 'content-extraction-v1:%:device:%'` , first . ConversationID ). Scan ( & extractionEvents , & extractionGeneration ); err != nil {
t . Fatal ( err )
}
if err := pool . QueryRow ( ctx , `SELECT count(*) FROM outbox_event WHERE aggregate_id=(SELECT id FROM conversation WHERE public_id=$1::uuid)
AND event_type='conversation.content_extraction_requested'
AND deduplication_key LIKE 'content-extraction-v1:%:provider:%'` , first . ConversationID ). Scan ( & providerExtractionEvents ); err != nil {
t . Fatal ( err )
}
if turnCount != 2 || deviceTurnCount != 1 || transcriptAudits != 2 || extractionEvents != 1 || providerExtractionEvents != 1 || extractionGeneration != 2 {
t . Fatalf ( "turns=%d device=%d audits=%d device_extraction=%d provider_extraction=%d generation=%d" , turnCount , deviceTurnCount , transcriptAudits , extractionEvents , providerExtractionEvents , extractionGeneration )
}
}
func cleanupOrchestrationFixture ( t * testing . T , ctx context . Context , pool * pgxpool . Pool ) {
t . Helper ()
statements := [] struct {
query string
arg string
}{
{ `DELETE FROM runtime_config_rollout WHERE config_key=$1` , testBundleConfigKey },
{ `DELETE FROM ops_config_evaluation WHERE config_version_id IN (SELECT id FROM runtime_config_version WHERE config_key=$1)` , testBundleConfigKey },
{ `DELETE FROM runtime_config_version WHERE config_key=$1` , testBundleConfigKey },
{ `DELETE FROM audit_event WHERE request_id LIKE $1` , "orchestration-agent-bundle-%" },
{ `DELETE FROM idempotency_record WHERE idempotency_key=$1` , testBundleCreateKey },
{ `DELETE FROM idempotency_record WHERE idempotency_key=$1` , testBundleEvaluationKey },
{ `DELETE FROM runtime_config_version WHERE id=$1::uuid` , testPersonaConfigID },
{ `DELETE FROM runtime_config_version WHERE id=$1::uuid` , testVoiceConfigID },
{ `DELETE FROM runtime_config_version WHERE id=$1::uuid` , testModelConfigID },
{ `DELETE FROM runtime_config_version WHERE id=$1::uuid` , testRoutingConfigID },
2026-08-29 21:56:51 +08:00
{ `DELETE FROM conversation_summary WHERE conversation_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
2026-08-26 06:41:07 +08:00
{ `DELETE FROM conversation_turn WHERE conversation_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM provider_event WHERE conversation_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM conversation_state_history WHERE conversation_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM rtc_task WHERE conversation_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM outbox_event WHERE aggregate_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM audit_event WHERE resource_id IN (SELECT id FROM conversation WHERE device_id=$1::uuid)` , testDeviceID },
{ `DELETE FROM audit_event WHERE actor_public_id=$1::uuid` , testDevicePublicID },
{ `DELETE FROM idempotency_record WHERE actor_key=$1` , "device:" + testDevicePublicID },
{ `DELETE FROM conversation WHERE device_id=$1::uuid` , testDeviceID },
{ `DELETE FROM context_snapshot WHERE subject_id=$1::uuid` , testSubjectID },
{ `DELETE FROM consent_snapshot WHERE subject_id=$1::uuid` , testSubjectID },
{ `DELETE FROM consent_record WHERE subject_id=$1::uuid` , testSubjectID },
{ `DELETE FROM device_binding WHERE device_id=$1::uuid` , testDeviceID },
{ `DELETE FROM device_presence_state WHERE device_id=$1::uuid` , testDeviceID },
{ `DELETE FROM device_twin WHERE device_id=$1::uuid` , testDeviceID },
{ `DELETE FROM device WHERE id=$1::uuid` , testDeviceID },
{ `DELETE FROM subject_profile WHERE id=$1::uuid` , testSubjectID },
{ `DELETE FROM billing_price_book WHERE id=$1::uuid` , testPriceBookID },
}
for _ , statement := range statements {
if _ , err := pool . Exec ( ctx , statement . query , statement . arg ); err != nil {
t . Fatalf ( "cleanup fixture: %v" , err )
}
}
}