package onboarding import ( "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/embeddingprovider" "data-terminal-cloud/internal/idempotency" "data-terminal-cloud/internal/orchestration" "data-terminal-cloud/internal/outbox" "data-terminal-cloud/internal/rtc" "github.com/google/uuid" "testing" "time" ) func TestIntegrationFullFirstCallThroughOrchestration(t *testing.T) { f := newFixture(t) f.bound() j := f.registerSDK() // This test exercises the existing SaaS call path for a device whose supplier // credential is already available in the platform credential store. tx, err := f.s.pool.Begin(f.ctx) must(t, err) must(t, f.s.storeRuntimeCredential(f.ctx, tx, f.d, j, []byte("fixture-device-secret-at-least-32-bytes"))) must(t, tx.Commit(f.ctx)) ack := ActivationAckInput{ActivationID: j.ID, ConfigVersion: j.ConfigVersion, LocalGeneration: 1} _, err = f.s.ActivationAck(f.ctx, f.d, uuid.NewString(), requestJSON(ack), ack) must(t, err) attempt := AttemptInput{ActivationID: j.ID, ConfigVersion: j.ConfigVersion, AttemptID: uuid.NewString()} _, err = f.s.Attempt(f.ctx, f.d, uuid.NewString(), requestJSON(attempt), attempt) must(t, err) tokens, err := rtc.NewLocalTokenIssuer([]byte("first-call-fixture-token-key-32-bytes-minimum")) must(t, err) leases, err := orchestration.NewRedisLeaseCache(f.s.redis, "onboarding-test:"+uuid.NewString()) must(t, err) sessions, err := orchestration.NewService(idempotency.NewExecutor(f.s.pool), orchestration.NewRepository(), audit.NewRepository(), outbox.NewStore(f.s.pool), tokens, leases, orchestration.ServiceOptions{RTCAppID: "test-app", VoiceProvider: "mock", EngineMode: "s2s_hybrid", AllocationTTL: time.Minute, TokenTTL: time.Hour, HeartbeatInterval: 10, HardwareBotID: j.Bot, HardwareTransport: j.Transport}) must(t, err) must(t, sessions.ConfigureMemoryEmbedding(f.s.pool, embeddingprovider.Local{})) identity := deviceauth.Identity{ID: f.d.ID, PublicID: f.d.PublicID, TenantID: f.d.TenantID, Status: f.d.Status, ActivationState: f.d.Lifecycle, EnrollmentMode: "physical_confirm", BindingGeneration: f.d.Generation, ActivationTest: true} var revision int64 must(t, f.s.pool.QueryRow(f.ctx, `SELECT desired_revision FROM device_twin WHERE device_id=$1::uuid`, f.d.ID).Scan(&revision)) input := orchestration.HardwareAgentCreateInput{DeviceTime: time.Now(), ConfigRevision: revision, RequestedGoal: "首次通话测试", Capabilities: orchestration.HardwareAgentCapabilities{HardwareAgent: true, SDKVersion: "1.0.0", Transports: []string{"high_quality"}, DynamicSessionConfig: true, ServerEventCallback: true}} _, _, err = sessions.CreateHardwareAgent(f.ctx, identity, uuid.NewString(), uuid.NewString(), requestJSON(input), input) if err != orchestration.ErrOnboardingState { t.Fatalf("normal call before READY: %v", err) } input.Intent = "activation_test" input.ActivationID = j.ID input.AttemptID = attempt.AttemptID input.PermitToken = "" key := uuid.NewString() created, replayed, err := sessions.CreateHardwareAgent(f.ctx, identity, key, uuid.NewString(), requestJSON(input), input) must(t, err) if replayed || created.Session.MaximumDurationSeconds != 60 { t.Fatal("test allocation missing duration") } again, replayed, err := sessions.CreateHardwareAgent(f.ctx, identity, key, uuid.NewString(), requestJSON(input), input) must(t, err) if !replayed || again.ConversationID != created.ConversationID { t.Fatal("test retry allocated another conversation") } _, _, err = sessions.CreateHardwareAgent(f.ctx, identity, uuid.NewString(), uuid.NewString(), requestJSON(input), input) if err == nil { t.Fatal("same permit created two conversations") } startedID, audioID, endedID := uuid.NewString(), uuid.NewString(), uuid.NewString() event := orchestration.HardwareAgentEventInput{EventID: startedID, SequenceNo: 1, EventType: "session_started", OccurredAt: time.Now(), ProviderSessionRef: created.Runtime.ProviderSessionRef, SessionNonce: created.Runtime.SessionNonce, ProviderConnectionRef: "fixture-connection", BotID: created.Runtime.BotID, Transport: created.Runtime.Transport, ConfigDigest: created.Runtime.ConfigDigest, DynamicConfigApplied: true} _, err = sessions.ProcessHardwareAgentEvent(f.ctx, identity, created.ConversationID, requestJSON(event), event) must(t, err) event.EventID = audioID event.SequenceNo = 2 event.EventType = "first_audio_received" event.OccurredAt = time.Now() _, err = sessions.ProcessHardwareAgentEvent(f.ctx, identity, created.ConversationID, requestJSON(event), event) must(t, err) event.EventID = endedID event.SequenceNo = 3 event.EventType = "session_ended" event.OccurredAt = time.Now() _, err = sessions.ProcessHardwareAgentEvent(f.ctx, identity, created.ConversationID, requestJSON(event), event) must(t, err) result := ResultInput{ActivationID: j.ID, AttemptID: attempt.AttemptID, Stage: "CONVERSATION_TEST", Outcome: "SUCCESS_OBSERVED"} _, err = f.s.ActivationResult(f.ctx, f.d, uuid.NewString(), requestJSON(result), result) must(t, err) f.refresh() if f.d.Lifecycle != "ACTIVE" { t.Fatalf("lifecycle=%s", f.d.Lifecycle) } var state string must(t, f.s.pool.QueryRow(f.ctx, `SELECT status FROM device_activation_job WHERE id=$1::uuid`, j.ID).Scan(&state)) if state != "READY" { t.Fatal(state) } // Renewing an otherwise unchanged configuration requires durable ACK, but // reuses this generation's observed first-call evidence. _, err = f.s.pool.Exec(f.ctx, `UPDATE device_activation_job SET config_expires_at=now()-interval '1 second' WHERE id=$1::uuid`, j.ID) must(t, err) tx, err = f.s.pool.Begin(f.ctx) must(t, err) currentJob, err := readJob(f.ctx, tx, f.d.ID) must(t, err) must(t, f.s.ensureConfig(f.ctx, tx, f.d, ¤tJob)) must(t, tx.Commit(f.ctx)) if currentJob.ConfigVersion <= j.ConfigVersion || currentJob.Status != "REGISTERED_OBSERVED" || currentJob.Ack != nil || currentJob.Engine == nil { t.Fatal("invalid renewal state") } ack.ConfigVersion = currentJob.ConfigVersion ack.LocalGeneration++ _, err = f.s.ActivationAck(f.ctx, f.d, uuid.NewString(), requestJSON(ack), ack) must(t, err) must(t, f.s.pool.QueryRow(f.ctx, `SELECT status FROM device_activation_job WHERE id=$1::uuid`, j.ID).Scan(&state)) if state != "READY" { t.Fatal("renewal ACK did not restore READY") } if f.registrar.calls.Load() != 0 { t.Fatal("renewal repeated provider registration") } input.Intent = "" input.ActivationID = "" input.AttemptID = "" input.PermitToken = "" identity.ActivationTest = false identity.ActivationState = "ACTIVE" _, _, err = sessions.CreateHardwareAgent(f.ctx, identity, uuid.NewString(), uuid.NewString(), requestJSON(input), input) must(t, err) }