Files
openclaw/apps/ios/Tests/RealtimeTalkRelaySessionTests.swift
Nimrod Gutman 47dbc675e9 feat(ios): clarify talk realtime fallback (#91201)
Merged via squash.

Prepared head SHA: b6fd32ed6e

Local prep note: pnpm build passed. pnpm check hit the npm shrinkwrap guard because @anthropic-ai/sdk@0.100.1 is no longer resolvable before 2026-05-24T20:18:43Z; the same shrinkwrap guard failure reproduces on current origin/main at 66b91d78fe, and this PR does not touch dependency manifests or lockfiles.

Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-06-07 20:21:34 +03:00

108 lines
3.9 KiB
Swift

import Foundation
import OpenClawKit
import OpenClawProtocol
import Testing
@testable import OpenClaw
@MainActor
private final class UnusedPCMStreamingAudioPlayer: PCMStreamingAudioPlaying {
func play(stream: AsyncThrowingStream<Data, Error>, sampleRate: Double) async -> StreamingPlaybackResult {
fatalError("Playback is not used by this test")
}
func stop() -> Double? {
nil
}
}
@MainActor
@Suite struct RealtimeTalkRelaySessionTests {
@Test func outputPlaybackFinishClearsBargeInStartTime() {
var speakingStates: [Bool] = []
let session = RealtimeTalkRelaySession(
gateway: GatewayNodeSession(),
options: .init(sessionKey: "main", provider: nil, model: nil, voice: nil),
pcmPlayer: UnusedPCMStreamingAudioPlayer(),
onStatus: { _ in },
onSpeakingChanged: { speakingStates.append($0) })
session._test_markOutputAudioStarted(nowMs: 100)
#expect(session._test_isOutputPlaying())
#expect(session._test_outputStartedAtMs() == 100)
session._test_markOutputPlaybackFinished()
#expect(!session._test_isOutputPlaying())
#expect(session._test_outputStartedAtMs() == nil)
#expect(speakingStates == [false])
session._test_markOutputAudioStarted(nowMs: 500)
#expect(session._test_outputStartedAtMs() == 500)
}
@Test func closeAfterClassifiedErrorDoesNotReplaceIssue() async {
var issues: [TalkRuntimeIssue] = []
var statuses: [String] = []
let session = RealtimeTalkRelaySession(
gateway: GatewayNodeSession(),
options: .init(sessionKey: "main", provider: "openai", model: "gpt-realtime-2", voice: nil),
pcmPlayer: UnusedPCMStreamingAudioPlayer(),
onStatus: { statuses.append($0) },
onIssue: { issues.append($0) },
onSpeakingChanged: { _ in })
session._test_setRelaySessionId("relay-1")
await session._test_handleGatewayEvent(EventFrame(
type: "event",
event: "talk.event",
payload: AnyCodable([
"relaySessionId": "relay-1",
"type": "error",
"message": "OpenAI API key rejected with 401",
"code": "realtime_unavailable",
"provider": "openai",
"model": "gpt-realtime-2",
"transport": "gateway-relay",
"phase": "connect",
]),
seq: nil,
stateversion: nil))
await session._test_handleGatewayEvent(EventFrame(
type: "event",
event: "talk.event",
payload: AnyCodable([
"relaySessionId": "relay-1",
"type": "close",
"reason": "error",
]),
seq: nil,
stateversion: nil))
#expect(issues.map(\.code) == [.realtimeUnavailable])
#expect(statuses == ["OpenAI API key rejected with 401"])
}
@Test func closedRelayDoesNotWaitForStartupReady() async {
let session = RealtimeTalkRelaySession(
gateway: GatewayNodeSession(),
options: .init(sessionKey: "main", provider: "openai", model: "gpt-realtime-2", voice: nil),
pcmPlayer: UnusedPCMStreamingAudioPlayer(),
onStatus: { _ in },
onSpeakingChanged: { _ in })
session.stop()
#expect(await session._test_waitForStartupCancelled(timeoutSeconds: 1))
}
@Test func startupReadyWaitCoversGatewayConnectBudget() {
let session = RealtimeTalkRelaySession(
gateway: GatewayNodeSession(),
options: .init(sessionKey: "main", provider: "openai", model: "gpt-realtime-2", voice: nil),
pcmPlayer: UnusedPCMStreamingAudioPlayer(),
onStatus: { _ in },
onSpeakingChanged: { _ in })
#expect(session._test_startupReadyTimeoutSeconds() >= 12)
}
}