status
This commit is contained in:
1 parent
c9c25f86eb
commit
40aaf4fea7
56 files changed
+5683
-179
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Voice
|
||||
|
||||
@MainActor
|
||||
struct MediaReconcileTests {
|
||||
private func makeController() -> MediaPlayerController {
|
||||
let config = ServerConfig(
|
||||
baseURL: URL(string: "http://127.0.0.1:9")!,
|
||||
livekitURL: URL(string: "ws://127.0.0.1:9")!
|
||||
)
|
||||
let api = MediaAPI(config: config)
|
||||
return MediaPlayerController(api: api, settings: SettingsStore())
|
||||
}
|
||||
|
||||
private func makeState(
|
||||
desired: String? = "stopped",
|
||||
trackID: String? = nil,
|
||||
audioURL: String? = nil
|
||||
) -> MediaState {
|
||||
let track: MediaTrack? = trackID != nil ? MediaTrack(
|
||||
id: trackID!,
|
||||
kind: nil,
|
||||
videoID: nil,
|
||||
title: nil,
|
||||
channel: nil,
|
||||
duration: nil,
|
||||
status: nil,
|
||||
error: nil,
|
||||
audioURL: audioURL,
|
||||
artURL: nil
|
||||
) : nil
|
||||
return MediaState(
|
||||
room: "default",
|
||||
track: track,
|
||||
desired: desired,
|
||||
reported: nil,
|
||||
seekHint: nil,
|
||||
choices: [],
|
||||
pending: [],
|
||||
version: 1
|
||||
)
|
||||
}
|
||||
|
||||
@Test("reconcile updates published desired to playing")
|
||||
func reconcileDesiredPlaying() {
|
||||
let controller = makeController()
|
||||
#expect(controller.desired == "stopped")
|
||||
|
||||
controller.reconcile(makeState(desired: "playing"))
|
||||
#expect(controller.desired == "playing")
|
||||
}
|
||||
|
||||
@Test("reconcile updates published desired to paused")
|
||||
func reconcileDesiredPaused() {
|
||||
let controller = makeController()
|
||||
controller.reconcile(makeState(desired: "playing"))
|
||||
#expect(controller.desired == "playing")
|
||||
|
||||
controller.reconcile(makeState(desired: "paused"))
|
||||
#expect(controller.desired == "paused")
|
||||
}
|
||||
|
||||
@Test("reconcile updates published desired to stopped")
|
||||
func reconcileDesiredStopped() {
|
||||
let controller = makeController()
|
||||
controller.reconcile(makeState(desired: "playing"))
|
||||
#expect(controller.desired == "playing")
|
||||
|
||||
controller.reconcile(makeState(desired: "stopped"))
|
||||
#expect(controller.desired == "stopped")
|
||||
}
|
||||
|
||||
@Test("reconcile with nil desired defaults to stopped")
|
||||
func reconcileNilDesiredDefaultsToStopped() {
|
||||
let controller = makeController()
|
||||
controller.reconcile(makeState(desired: "playing"))
|
||||
#expect(controller.desired == "playing")
|
||||
|
||||
controller.reconcile(makeState(desired: nil))
|
||||
#expect(controller.desired == "stopped")
|
||||
}
|
||||
|
||||
@Test("late audio: desired=playing before audio_url, then audio_url arrives")
|
||||
func lateAudioReconciliation() {
|
||||
let controller = makeController()
|
||||
|
||||
// Phase 1: server says playing but the track has no audio_url yet
|
||||
// (still downloading). The source is not wired, so play cannot start.
|
||||
let stateNoURL = makeState(desired: "playing", trackID: "track-1", audioURL: nil)
|
||||
controller.reconcile(stateNoURL)
|
||||
#expect(controller.desired == "playing")
|
||||
#expect(controller.track?.id == "track-1")
|
||||
// Player rate should still be zero because no source was wired.
|
||||
#expect(controller.player.rate.isZero)
|
||||
|
||||
// Phase 2: the same track now has an audio_url. The reconcile path
|
||||
// wires the source (wireSource) and then applyDesiredState retries
|
||||
// play because desired is still "playing" and rate is still zero.
|
||||
let stateWithURL = makeState(desired: "playing", trackID: "track-1", audioURL: "/stream/track-1.mp3")
|
||||
controller.reconcile(stateWithURL)
|
||||
#expect(controller.desired == "playing")
|
||||
// The source should now be wired (loadedAudioPath is set internally).
|
||||
// We can't directly observe loadedAudioPath, but we can verify the
|
||||
// player has a current item after wireSource ran.
|
||||
#expect(controller.player.currentItem != nil)
|
||||
}
|
||||
|
||||
@Test("startDefaultPolling restarts after endConversation keeps scope")
|
||||
func pollingRestartsAfterEndConversation() async {
|
||||
let controller = makeController()
|
||||
controller.startDefaultPolling()
|
||||
await controller.endConversation()
|
||||
controller.startDefaultPolling()
|
||||
try? await Task.sleep(for: .milliseconds(50))
|
||||
}
|
||||
|
||||
@Test("togglePlayPause without a loaded track is a no-op")
|
||||
func toggleWithoutTrack() async {
|
||||
let controller = makeController()
|
||||
await controller.togglePlayPause()
|
||||
#expect(controller.track == nil)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user