//
//  AudioVideoConfigurationTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class AudioVideoConfigurationTests: XCTestCase {
    func testDefaultConfigurations() {
        let audioVideoConfig = AudioVideoConfiguration()
        XCTAssertEqual(audioVideoConfig.audioMode, .stereo48K)
        XCTAssertEqual(audioVideoConfig.callKitEnabled, false)

        let audioVideoConfigAudioMode = AudioVideoConfiguration(audioMode: .stereo48K)
        XCTAssertEqual(audioVideoConfigAudioMode.audioMode, .stereo48K)
        XCTAssertEqual(audioVideoConfigAudioMode.callKitEnabled, false)

        let audioVideoConfigAudioModeMono48K = AudioVideoConfiguration(audioMode: .mono48K)
        XCTAssertEqual(audioVideoConfigAudioModeMono48K.audioMode, .mono48K)
        XCTAssertEqual(audioVideoConfigAudioModeMono48K.callKitEnabled, false)

        let audioVideoConfigAudioModeMono16K = AudioVideoConfiguration(audioMode: .mono16K)
        XCTAssertEqual(audioVideoConfigAudioModeMono16K.audioMode, .mono16K)
        XCTAssertEqual(audioVideoConfigAudioModeMono16K.callKitEnabled, false)

        let audioVideoConfigCallkit = AudioVideoConfiguration(callKitEnabled: true)
        XCTAssertEqual(audioVideoConfigCallkit.audioMode, .stereo48K)
        XCTAssertEqual(audioVideoConfigCallkit.callKitEnabled, true)

        let audioVideoConfigAudioModeNoDevice = AudioVideoConfiguration(audioMode: .nodevice)
        XCTAssertEqual(audioVideoConfigAudioModeNoDevice.audioMode, .nodevice)
        XCTAssertEqual(audioVideoConfigAudioModeNoDevice.callKitEnabled, false)
    }
}


//
//  DefaultAudioVideoControllerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AVFoundation
import Mockingbird
import XCTest

class DefaultAudioVideoControllerTests: CommonTestCase {
    var audioClientControllerMock: AudioClientControllerMock!
    var audioClientObserverMock: AudioClientObserverMock!
    var clientMetricsCollectorMock: ClientMetricsCollectorMock!
    var videoClientControllerMock: VideoClientControllerMock!
    var videoTileControllerMock: VideoTileControllerMock!
    var defaultAudioVideoController: DefaultAudioVideoController!

    override func setUp() {
        super.setUp()

        audioClientControllerMock = mock(AudioClientController.self)
        audioClientObserverMock = mock(AudioClientObserver.self)
        clientMetricsCollectorMock = mock(ClientMetricsCollector.self)
        videoClientControllerMock = mock(VideoClientController.self)
        videoTileControllerMock = mock(VideoTileController.self)

        defaultAudioVideoController = DefaultAudioVideoController(audioClientController: audioClientControllerMock,
                                                                  audioClientObserver: audioClientObserverMock,
                                                                  clientMetricsCollector: clientMetricsCollectorMock,
                                                                  videoClientController: videoClientControllerMock,
                                                                  videoTileController: videoTileControllerMock,
                                                                  configuration: meetingSessionConfigurationMock,
                                                                  logger: loggerMock)
    }

    func testStart() {
        XCTAssertNoThrow(try defaultAudioVideoController.start())

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: false,
            audioMode: .stereo48K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_callKitEnabled() {
        let callKitEnabled = true
        XCTAssertNoThrow(try defaultAudioVideoController.start(callKitEnabled: callKitEnabled))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: callKitEnabled,
            audioMode: .stereo48K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_mono48K_callKitDisabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .mono48K, callKitEnabled: false)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: false,
            audioMode: .mono48K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_mono48K_callKitEnabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .mono48K, callKitEnabled: true)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: true,
            audioMode: .mono48K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_nodevice_callKitDisabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .nodevice, callKitEnabled: false)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: false,
            audioMode: .nodevice
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_nodevice_callKitEnabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .nodevice, callKitEnabled: true)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: true,
            audioMode: .nodevice
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_mono16K_callKitDisabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .mono16K, callKitEnabled: false)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: false,
            audioMode: .mono16K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStart_mono16K_callKitEnabled() {
        XCTAssertNoThrow(try defaultAudioVideoController.start(audioVideoConfiguration: AudioVideoConfiguration(audioMode: .mono16K, callKitEnabled: true)))

        verify(audioClientControllerMock.start(
            audioFallbackUrl: self.meetingSessionConfigurationMock.urls.audioFallbackUrl,
            audioHostUrl: self.meetingSessionConfigurationMock.urls.audioHostUrl,
            meetingId: self.meetingSessionConfigurationMock.meetingId,
            attendeeId: self.meetingSessionConfigurationMock.credentials.attendeeId,
            joinToken: self.meetingSessionConfigurationMock.credentials.joinToken,
            callKitEnabled: true,
            audioMode: .mono16K
        )).wasCalled()
        verify(videoClientControllerMock.start()).wasCalled()
    }

    func testStop() {
        defaultAudioVideoController.stop()

        verify(audioClientControllerMock.stop()).wasCalled()
        verify(videoClientControllerMock.stopAndDestroy()).wasCalled()
    }

    func testAddAudioVideoObserver() {
        let audioVideoObserverMock: AudioVideoObserverMock = mock(AudioVideoObserver.self)
        defaultAudioVideoController.addAudioVideoObserver(observer: audioVideoObserverMock)

        verify(audioClientObserverMock.subscribeToAudioClientStateChange(observer: audioVideoObserverMock)).wasCalled()
        verify(videoClientControllerMock.subscribeToVideoClientStateChange(observer: audioVideoObserverMock))
            .wasCalled()
    }

    func testRemoveAudioVideoObserver() {
        let audioVideoObserverMock: AudioVideoObserverMock = mock(AudioVideoObserver.self)
        defaultAudioVideoController.removeAudioVideoObserver(observer: audioVideoObserverMock)

        verify(audioClientObserverMock.unsubscribeFromAudioClientStateChange(observer: audioVideoObserverMock))
            .wasCalled()
        verify(videoClientControllerMock.unsubscribeFromVideoClientStateChange(observer: audioVideoObserverMock))
            .wasCalled()
    }

    func testAddMetricsObserver() {
        let metricsObserverMock: MetricsObserverMock = mock(MetricsObserver.self)
        defaultAudioVideoController.addMetricsObserver(observer: metricsObserverMock)

        verify(clientMetricsCollectorMock.subscribeToMetrics(observer: metricsObserverMock)).wasCalled()
    }

    func testRemoveMetricsObserver() {
        let metricsObserverMock: MetricsObserverMock = mock(MetricsObserver.self)
        defaultAudioVideoController.removeMetricsObserver(observer: metricsObserverMock)

        verify(clientMetricsCollectorMock.unsubscribeFromMetrics(observer: metricsObserverMock)).wasCalled()
    }

    func testStartLocalVideo() {
        XCTAssertNoThrow(try defaultAudioVideoController.startLocalVideo())

        verify(videoClientControllerMock.startLocalVideo()).wasCalled()
    }

    func testStartLocalVideoWithConfig() {
        let config = LocalVideoConfiguration()
        XCTAssertNoThrow(try defaultAudioVideoController.startLocalVideo(config: config))

        verify(videoClientControllerMock.startLocalVideo(config: config)).wasCalled()
    }

    func testStartLocalVideoWithSource() {
        let cameraCaptureSourceMock: CameraCaptureSourceMock = mock(CameraCaptureSource.self)
        defaultAudioVideoController.startLocalVideo(source: cameraCaptureSourceMock)

        verify(videoClientControllerMock.startLocalVideo(source: cameraCaptureSourceMock)).wasCalled()
    }

    func testStartLocalVideoWithSourceAndConfig() {
        let config = LocalVideoConfiguration()
        let cameraCaptureSourceMock: CameraCaptureSourceMock = mock(CameraCaptureSource.self)
        defaultAudioVideoController.startLocalVideo(source: cameraCaptureSourceMock, config: config)

        verify(videoClientControllerMock.startLocalVideo(source: cameraCaptureSourceMock, config: config)).wasCalled()
    }

    func testStopLocalVideo() {
        defaultAudioVideoController.stopLocalVideo()

        verify(videoClientControllerMock.stopLocalVideo()).wasCalled()
    }

    func testStartRemoteVideo() {
        defaultAudioVideoController.startRemoteVideo()

        verify(videoClientControllerMock.startRemoteVideo()).wasCalled()
    }

    func testStopRemoteVideo() {
        defaultAudioVideoController.stopRemoteVideo()

        verify(videoClientControllerMock.stopRemoteVideo()).wasCalled()
    }
}


//
//  DefaultAudioVideoFacadeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Mockingbird
import XCTest

class DefaultAudioVideoFacadeTests: CommonTestCase {
    var audioVideoControllerMock: AudioVideoControllerFacadeMock!
    var realtimeControllerMock: RealtimeControllerFacadeMock!
    var deviceControllerMock: DeviceControllerMock!
    var videoTileControllerMock: VideoTileControllerMock!
    var activeSpeakerDetectorMock: ActiveSpeakerDetectorFacadeMock!
    var contentShareControllerMock: ContentShareControllerMock!
    var eventAnalyticsControllerMock: EventAnalyticsControllerMock!
    var meetingStatsCollectorMock: MeetingStatsCollectorMock!

    var defaultAudioVideoFacade: DefaultAudioVideoFacade!

    override func setUp() {
        super.setUp()

        audioVideoControllerMock = mock(AudioVideoControllerFacade.self)
        realtimeControllerMock = mock(RealtimeControllerFacade.self)
        deviceControllerMock = mock(DeviceController.self)
        videoTileControllerMock = mock(VideoTileController.self)
        activeSpeakerDetectorMock = mock(ActiveSpeakerDetectorFacade.self)
        contentShareControllerMock = mock(ContentShareController.self)
        eventAnalyticsControllerMock = mock(EventAnalyticsController.self)
        meetingStatsCollectorMock = mock(MeetingStatsCollector.self)

        var valueProvider = ValueProvider()
        valueProvider.register(loggerMock, for: Logger.self)
        valueProvider.register(meetingSessionConfigurationMock, for: MeetingSessionConfiguration.self)
        audioVideoControllerMock.useDefaultValues(from: valueProvider)

        defaultAudioVideoFacade = DefaultAudioVideoFacade(audioVideoController: audioVideoControllerMock,
                                                          realtimeController: realtimeControllerMock,
                                                          deviceController: deviceControllerMock,
                                                          videoTileController: videoTileControllerMock,
                                                          activeSpeakerDetector: activeSpeakerDetectorMock,
                                                          contentShareController: contentShareControllerMock,
                                                          eventAnalyticsController: eventAnalyticsControllerMock,
                                                          meetingStatsCollector: meetingStatsCollectorMock)
    }

    func testStart_WithConfigArgs() {
        let audioVideoConfiguration = AudioVideoConfiguration(audioMode: .mono48K, callKitEnabled: true)
        given(audioVideoControllerMock.start(audioVideoConfiguration: any())).willReturn()

        XCTAssertNoThrow(try defaultAudioVideoFacade.start(audioVideoConfiguration: audioVideoConfiguration))

        verify(audioVideoControllerMock.start(audioVideoConfiguration: audioVideoConfiguration)).wasCalled()
    }

    func testStart_WithCallKitArgs() {
        given(audioVideoControllerMock.start(audioVideoConfiguration: any())).willReturn()

        XCTAssertNoThrow(try defaultAudioVideoFacade.start(callKitEnabled: true))

        verify(audioVideoControllerMock.start(audioVideoConfiguration: any(where: { $0.audioMode == .stereo48K && $0.callKitEnabled == true }))).wasCalled()
    }

    func testStart_WithNoArgs() {
        given(audioVideoControllerMock.start(audioVideoConfiguration: any())).willReturn()

        XCTAssertNoThrow(try defaultAudioVideoFacade.start())

        verify(audioVideoControllerMock.start(audioVideoConfiguration: any(where: { $0.audioMode == .stereo48K && $0.callKitEnabled == false }))).wasCalled()
    }

    func testStartLocalVideo() {
        XCTAssertNoThrow(try defaultAudioVideoFacade.startLocalVideo())

        verify(audioVideoControllerMock.startLocalVideo()).wasCalled()
    }

    func testStartLocalVideoWithConfig() {
        let config = LocalVideoConfiguration()
        XCTAssertNoThrow(try defaultAudioVideoFacade.startLocalVideo(config: config))

        verify(audioVideoControllerMock.startLocalVideo(config: config)).wasCalled()
    }

    func testStartLocalVideoWithSource() {
        let cameraCaptureSourceMock: CameraCaptureSourceMock = mock(CameraCaptureSource.self)
        defaultAudioVideoFacade.startLocalVideo(source: cameraCaptureSourceMock)

        verify(audioVideoControllerMock.startLocalVideo(source: cameraCaptureSourceMock)).wasCalled()
    }

    func testStartLocalVideoWithSourceAndConfig() {
        let config = LocalVideoConfiguration()
        let cameraCaptureSourceMock: CameraCaptureSourceMock = mock(CameraCaptureSource.self)
        defaultAudioVideoFacade.startLocalVideo(source: cameraCaptureSourceMock, config: config)

        verify(audioVideoControllerMock.startLocalVideo(source: cameraCaptureSourceMock, config: config)).wasCalled()
    }

    func testStartContentShare() {
        let source = ContentShareSource()
        defaultAudioVideoFacade.startContentShare(source: source)

        verify(contentShareControllerMock.startContentShare(source: source)).wasCalled()
    }

    func testStartContentSharewithConfig() {
        let source = ContentShareSource()
        let config = LocalVideoConfiguration()
        defaultAudioVideoFacade.startContentShare(source: source, config: config)

        verify(contentShareControllerMock.startContentShare(source: source, config: config)).wasCalled()
    }
}


//
//  SignalStrengthTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class SignalStrengthTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(SignalStrength.none.description, "none")
        XCTAssertEqual(SignalStrength.low.description, "low")
        XCTAssertEqual(SignalStrength.high.description, "high")
    }
}


//
//  VolumeLevelTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VolumeLevelTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(VolumeLevel.muted.description, "muted")
        XCTAssertEqual(VolumeLevel.notSpeaking.description, "notSpeaking")
        XCTAssertEqual(VolumeLevel.low.description, "low")
        XCTAssertEqual(VolumeLevel.medium.description, "medium")
        XCTAssertEqual(VolumeLevel.high.description, "high")
    }
}


//
//  ActiveSpeakerDetectorTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

@testable import AmazonChimeSDK
import XCTest

class ActiveSpeakerDetectorTests: XCTestCase, ActiveSpeakerPolicy, ActiveSpeakerObserver {
    let scores = [0.1, 0.2, 0.3, 0.4, 0.5]
    let volumes: [VolumeLevel] = [.muted, .notSpeaking, .low, .medium, .high]
    var attendees = [AttendeeInfo]()
    var volumeUpdates = [VolumeUpdate]()
    var attendeesReceived: [AttendeeInfo] = [AttendeeInfo(attendeeId: "", externalUserId: "")]
    var scoreChangeAttendees: [AttendeeInfo: Double] = [AttendeeInfo(attendeeId: "", externalUserId: ""): 0.0]
    var scoreIndex = 0
    var activeSpeakerDetector = DefaultActiveSpeakerDetector(selfAttendeeId: "")

    private let emptyAttendeesReceivedExpectation = XCTestExpectation(
        description: "Is fulfilled when received no attendees in callback")
    private let calculateScoreExpectation = XCTestExpectation(
        description: "Is fulfilled when calculateScore is called")
    private let prioritizeBandwidthExpectation = XCTestExpectation(
        description: "Is fulfilled when prioritizeVideoSendBandwidthForActiveSpeaker is called")
    private let activeSpeakerDidDetectExpectation = XCTestExpectation(
        description: "Is fulfilled when activeSpeakerDidDetect is called")
    private let activeSpeakerScoreDidChangeExpectation = XCTestExpectation(
        description: "Is fulfilled when activeSpeakerScoreDidChange is called")
    private let oneMilliSecondInSeconds = 0.001
    private let twoHundredMilliSecondsInSeconds = 0.2
    private let halfSeconds = 0.5

    override func setUp() {
        for index in 0 ... 4 {
            attendees.append(AttendeeInfo(
                attendeeId: "attendee" + String(index),
                externalUserId: "attendee" + String(index)
            ))
            volumeUpdates.append(VolumeUpdate(attendeeInfo: attendees[index], volumeLevel: volumes[index]))
        }
        calculateScoreExpectation.expectedFulfillmentCount = 5
        prioritizeBandwidthExpectation.assertForOverFulfill = true
        activeSpeakerDetector = DefaultActiveSpeakerDetector(selfAttendeeId: "attendee0")
        activeSpeakerDetector.addActiveSpeakerObserver(policy: self, observer: self)
        activeSpeakerDetector.attendeesDidJoin(attendeeInfo: attendees)
    }

    override func tearDown() {
        activeSpeakerDetector.removeActiveSpeakerObserver(observer: self)
    }

    func testActiveSpeakerDetectorShouldOnAttendeesJoinMakeExpectedCallbacks() {
        // received no attendees in callback because scores are not calculated until volumes are received
        wait(for: [emptyAttendeesReceivedExpectation], timeout: oneMilliSecondInSeconds)
        XCTAssertEqual(attendeesReceived.isEmpty, true)
        calculateScoreExpectation.isInverted = true
        wait(for: [calculateScoreExpectation], timeout: oneMilliSecondInSeconds)
        prioritizeBandwidthExpectation.isInverted = true
        wait(for: [prioritizeBandwidthExpectation], timeout: oneMilliSecondInSeconds)
        wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds)
    }

    func testActiveSpeakerDetectorShouldOnVolumeChangeMakeExpectedCallbacks() {
        activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates)
        calculateScoreExpectation.expectedFulfillmentCount = 2
        wait(for: [calculateScoreExpectation], timeout: twoHundredMilliSecondsInSeconds)
        wait(for: [prioritizeBandwidthExpectation], timeout: oneMilliSecondInSeconds)
        wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds)
        XCTAssertEqual(attendeesReceived, attendees.reversed())
    }

    func testActiveSpeakerDetectorMakesScoresCallback() {
        activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates)
        wait(for: [activeSpeakerScoreDidChangeExpectation], timeout: TimeInterval(scoresCallbackIntervalMs))
        XCTAssertEqual(scoreChangeAttendees.map { $0.key }.sorted(), attendees.sorted())
    }

    func testActiveSpeakerDetectorShouldOnAttendeesLeaveReceiveCorrectActiveSpeakers() {
        activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates)
        wait(for: [activeSpeakerScoreDidChangeExpectation], timeout: twoHundredMilliSecondsInSeconds)
        XCTAssertEqual(attendeesReceived, attendees.reversed())

        activeSpeakerDetector.attendeesDidLeave(attendeeInfo: [attendees[0], attendees[1]])
        let newVolumeUpdates = [volumeUpdates[2], volumeUpdates[3], volumeUpdates[4]]
        scoreIndex = 0
        activeSpeakerDetector.volumeDidChange(volumeUpdates: newVolumeUpdates)
        let adhocWaitingExpectation = XCTestExpectation(description: "Is fulfilled after adhoc waiting")
        Timer.scheduledTimer(withTimeInterval: twoHundredMilliSecondsInSeconds, repeats: false, block: { _ in
            adhocWaitingExpectation.fulfill()
        })
        wait(for: [adhocWaitingExpectation], timeout: halfSeconds)
        XCTAssertEqual(attendeesReceived, [attendees[4], attendees[3], attendees[2]])
    }

    func testActiveSpeakerDetectorShouldRemoveActiveSpeakerObserver() {
        activeSpeakerDetector.removeActiveSpeakerObserver(observer: self)
        activeSpeakerDetector.attendeesDidJoin(attendeeInfo: attendees)
        activeSpeakerDetector.volumeDidChange(volumeUpdates: volumeUpdates)
        wait(for: [activeSpeakerDidDetectExpectation], timeout: oneMilliSecondInSeconds)
        XCTAssertEqual(attendeesReceived, [])
    }

    func calculateScore(attendeeInfo: AttendeeInfo, volume: VolumeLevel) -> Double {
        calculateScoreExpectation.fulfill()
        let returnScore = scoreIndex < 5 ? scores[scoreIndex] : 0.0
        scoreIndex += 1
        return returnScore
    }

    func prioritizeVideoSendBandwidthForActiveSpeaker() -> Bool {
        prioritizeBandwidthExpectation.fulfill()
        return true
    }

    var observerId: String = "fakeObserverId"

    func activeSpeakerDidDetect(attendeeInfo: [AttendeeInfo]) {
        activeSpeakerDidDetectExpectation.fulfill()
        if attendeeInfo.isEmpty {
            emptyAttendeesReceivedExpectation.fulfill()
        }
        attendeesReceived = attendeeInfo
    }

    var scoresCallbackIntervalMs: Int = 1

    func activeSpeakerScoreDidChange(scores: [AttendeeInfo: Double]) {
        activeSpeakerScoreDidChangeExpectation.fulfill()
        scoreChangeAttendees = scores
    }
}


//
//  ActiveSpeakerPolicyTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

@testable import AmazonChimeSDK
import XCTest

class ActiveSpeakerPolicyTests: XCTestCase {
    private var activeSpeakerPolicy: DefaultActiveSpeakerPolicy = DefaultActiveSpeakerPolicy(
        speakerWeight: 0.9, cutoffThreshold: 0.01, takeoverRate: 0.2
    )
    private var attendeeA: AttendeeInfo = AttendeeInfo(
        attendeeId: "fakeAttendeeIdA", externalUserId: "fakeExternalUserIdA"
    )
    private var attendeeB: AttendeeInfo = AttendeeInfo(
        attendeeId: "fakeAttendeeIdB", externalUserId: "fakeExternalUserIdB"
    )

    private var speakerWeight = 0.9
    private var cutoffThreshold = 0.01
    private var takeoverRate = 0.2

    override func setUp() {
        super.setUp()
        activeSpeakerPolicy = DefaultActiveSpeakerPolicy(
            speakerWeight: speakerWeight,
            cutoffThreshold: cutoffThreshold,
            takeoverRate: takeoverRate
        )
    }

    func testActiveSpeakerShouldGetBandwidthPriority() {
        let prioritizeBandwidth = activeSpeakerPolicy.prioritizeVideoSendBandwidthForActiveSpeaker()
        XCTAssertEqual(prioritizeBandwidth, true)
    }

    func testActiveSpeakerShouldCalculateScoreWhenNotSpeakingOrMuted() {
        var score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .muted)
        XCTAssertEqual(score, 0.0)

        score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .notSpeaking)
        XCTAssertEqual(score, 0.0)
    }

    func testScoreShouldIncreaseAtExpectedRate() {
        let firstScore = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        XCTAssertEqual(firstScore, 1 - speakerWeight)

        let secondScore = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .low)
        XCTAssertEqual(secondScore, firstScore * speakerWeight + (1.0 - speakerWeight))

        let thirdScore = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .high)
        XCTAssertEqual(thirdScore, secondScore * speakerWeight + (1.0 - speakerWeight))
    }

    func testScoreShouldDecreaseAtExpectedRate() {
        var previousScore = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        var score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .muted)
        XCTAssertEqual(score, previousScore * speakerWeight)

        previousScore = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .notSpeaking)
        XCTAssertEqual(score, previousScore * speakerWeight)
    }

    func testActiveSpeakerScoreShouldTakeOver() {
        _ = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        _ = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        var scoreA = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .medium)
        _ = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeB, volume: .medium)
        scoreA -= takeoverRate
        let score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .muted)
        XCTAssertEqual(score, scoreA * speakerWeight)
    }

    func testcutoffThresholdShouldDecreaseScoreToZero() {
        cutoffThreshold = 0.1
        activeSpeakerPolicy = DefaultActiveSpeakerPolicy(
            speakerWeight: speakerWeight,
            cutoffThreshold: cutoffThreshold,
            takeoverRate: takeoverRate
        )
        let score = activeSpeakerPolicy.calculateScore(attendeeInfo: attendeeA, volume: .high)
        XCTAssertEqual(score, 0.0)
    }
}


//
//  AudioModeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//
@testable import AmazonChimeSDK
import XCTest

class AudioModeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(AudioMode.mono16K.description, "mono16K")
        XCTAssertEqual(AudioMode.mono48K.description, "mono48K")
        XCTAssertEqual(AudioMode.stereo48K.description, "stereo48K")
        XCTAssertEqual(AudioMode.nodevice.description, "nodevice")
    }
}


//
//  SchedulerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import Foundation

@testable import AmazonChimeSDK
import XCTest

class SchedulerTests: XCTestCase {
    private var timer: IntervalScheduler = IntervalScheduler(intervalMs: 1, callback: {})
    private var callback = {}

    private let intervalMs = 1
    private let testTimeoutSecs = 0.01
    private let expectedCount = 5
    private let expectedTestDurationNanos: UInt64 = 5_000_000

    private let expectation = XCTestExpectation(
        description: "Callback is called once every millisecond for 5 milliseconds")

    override func setUp() {
        super.setUp()
        callback = {
            self.expectation.fulfill()
        }
        timer = IntervalScheduler(intervalMs: intervalMs, callback: callback)
        expectation.expectedFulfillmentCount = 5
        expectation.assertForOverFulfill = true
    }

    func testTimerShouldMakeCallback() {
        timer.start()
        let start = DispatchTime.now()
        // Must finish all executions before timeout
        wait(for: [expectation], timeout: testTimeoutSecs)
        let stop = DispatchTime.now()
        // Must take no less than expected (mininal) tasks duration
        XCTAssertGreaterThanOrEqual(stop.uptimeNanoseconds - start.uptimeNanoseconds, expectedTestDurationNanos)
    }

    func testTimerShouldStopMakingCallback() {
        timer.start()
        timer.stop()
        expectation.isInverted = true
        wait(for: [expectation], timeout: testTimeoutSecs)
    }

    func testStartShouldBeIdempotent() {
        timer.start()
        timer.start()
        let start = DispatchTime.now()
        // Must finish all executions before timeout
        wait(for: [expectation], timeout: testTimeoutSecs)
        let stop = DispatchTime.now()
        // Must take no less than expected (mininal) tasks duration
        XCTAssertGreaterThanOrEqual(stop.uptimeNanoseconds - start.uptimeNanoseconds, expectedTestDurationNanos)
    }

    func testStopShouldBeIdempotent() {
        timer.start()
        timer.stop()
        timer.stop()
        expectation.isInverted = true
        wait(for: [expectation], timeout: testTimeoutSecs)
    }

    override func tearDown() {
        timer.stop()
    }
}


//
//  ContentShareStatusCodeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class ContentShareStatusCodeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(ContentShareStatusCode.ok.description, "ok")
        XCTAssertEqual(ContentShareStatusCode.videoServiceFailed.description, "videoServiceFailed")
    }
}


//
//  DefaultContentShareControllerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Mockingbird
import XCTest

class DefaultContentShareControllerTests: XCTestCase {
    var contentShareVideoClientControllerMock: ContentShareVideoClientControllerMock!
    var defaultContentShareController: DefaultContentShareController!
    var videoSourceMock: VideoSourceMock!

    override func setUp() {
        videoSourceMock = mock(VideoSource.self)
        contentShareVideoClientControllerMock = mock(ContentShareVideoClientController.self)
        defaultContentShareController = DefaultContentShareController(contentShareVideoClientController: contentShareVideoClientControllerMock)
    }

    func testStartContentShareWithValidSource() throws {
        let contentShareSource = ContentShareSource()
        contentShareSource.videoSource = videoSourceMock

        defaultContentShareController.startContentShare(source: contentShareSource)

        verify(contentShareVideoClientControllerMock.startVideoShare(source: self.videoSourceMock)).wasCalled()
    }

    func testStartContentShareWithInvalidSource() throws {
        let contentShareSource = ContentShareSource()

        defaultContentShareController.startContentShare(source: contentShareSource)

        verify(contentShareVideoClientControllerMock.startVideoShare(source: any())).wasNeverCalled()
    }

    func testStartContentShareWithConfig() {
        let config = LocalVideoConfiguration()
        let contentShareSource = ContentShareSource()
        contentShareSource.videoSource = videoSourceMock

        defaultContentShareController.startContentShare(source: contentShareSource, config: config)

        verify(contentShareVideoClientControllerMock.startVideoShare(source: self.videoSourceMock, config: config)).wasCalled()
    }

    func testStopContentShare() throws {
        defaultContentShareController.stopContentShare()

        verify(contentShareVideoClientControllerMock.stopVideoShare()).wasCalled()
    }

    func testAddContentShareObserver() {
        let contentShareObserverMock: ContentShareObserverMock = mock(ContentShareObserver.self)

        defaultContentShareController.addContentShareObserver(observer: contentShareObserverMock)

        verify(contentShareVideoClientControllerMock.subscribeToVideoClientStateChange(observer: contentShareObserverMock)).wasCalled()
    }

    func testRemoveContentShareObserver() {
        let contentShareObserverMock: ContentShareObserverMock = mock(ContentShareObserver.self)

        defaultContentShareController.removeContentShareObserver(observer: contentShareObserverMock)

        verify(contentShareVideoClientControllerMock.unsubscribeFromVideoClientStateChange(observer: contentShareObserverMock)).wasCalled()
    }
}


//
//  AttendeeInfoTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class AttendeeInfoTests: XCTestCase {
    private let attendeeId = "attendeeId"
    private let externalUserId = ""

    func testAttendeeInfoShouldBeInitialized() {
        let attendeeInfo = AttendeeInfo(attendeeId: attendeeId, externalUserId: externalUserId)

        XCTAssertEqual(attendeeId, attendeeInfo.attendeeId)
        XCTAssertEqual(externalUserId, attendeeInfo.externalUserId)
    }

    func testAttendeeInfoShouldBeEqualWhenInitializedWithSameParameters() {
        let attendeeInfo1 = AttendeeInfo(attendeeId: attendeeId, externalUserId: externalUserId)
        let attendeeInfo2 = AttendeeInfo(attendeeId: attendeeId, externalUserId: externalUserId)

        XCTAssertEqual(attendeeInfo1, attendeeInfo2)
    }

    func testEqualizationShouldReturnFalseWhenTestAttendeeInfoAgainstOtherType() {
        let attendeeInfo = AttendeeInfo(attendeeId: attendeeId, externalUserId: externalUserId)
        let volumeUpdate = VolumeUpdate(attendeeInfo: attendeeInfo, volumeLevel: .muted)

        XCTAssertNotEqual(attendeeInfo, volumeUpdate)
    }

    func testAttendeeInfoShouldBeOrderedAlphabetically() {
        let attendeeInfo1 = AttendeeInfo(attendeeId: attendeeId, externalUserId: externalUserId)
        let attendeeInfo2 = AttendeeInfo(attendeeId: "attendeeId2", externalUserId: externalUserId)

        XCTAssertTrue(attendeeInfo1 < attendeeInfo2)
    }
}


//
//  DataMessageTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class DataMessageTests: XCTestCase {
    let topic = "AValidTopic"
    let data = Data()
    let senderAttendeeId = "123456"
    let senderExternalAttendeeId = "1234#external"

    func testDataMessageShouldBeInitialized() {
        let dataMessage = DataMessage(topic: topic,
                                      data: data,
                                      senderAttendeeId: senderAttendeeId,
                                      senderExternalUserId: senderExternalAttendeeId,
                                      timestampMs: 1,
                                      throttled: false)

        XCTAssertEqual(topic, dataMessage.topic)
        XCTAssertEqual(data, dataMessage.data)
        XCTAssertEqual(senderAttendeeId, dataMessage.senderAttendeeId)
        XCTAssertEqual(senderExternalAttendeeId, dataMessage.senderExternalUserId)
        XCTAssertEqual(1, dataMessage.timestampMs)
        XCTAssertEqual(false, dataMessage.throttled)
    }

    func testDataMessageText() {
        let anotherString = "AnotherString"

        let dataMessage = DataMessage(topic: topic,
                                      data: anotherString.data(using: .utf8, allowLossyConversion: true)!,
                                      senderAttendeeId: senderAttendeeId,
                                      senderExternalUserId: senderExternalAttendeeId,
                                      timestampMs: 1,
                                      throttled: false)
        XCTAssertEqual(anotherString, dataMessage.text())
    }

    func testDataMessageTextNil() {
        var data = Data()
        data.append(contentsOf: [255, 255])
        let dataMessage = DataMessage(topic: topic,
                                      data: data,
                                      senderAttendeeId: senderAttendeeId,
                                      senderExternalUserId: senderExternalAttendeeId,
                                      timestampMs: 1,
                                      throttled: false)
        XCTAssertNil(dataMessage.text())
    }

    func testDataMessageFromJSON() {
        let anotherString = "{\"ABC\":\"CBA\"}"
        let jsonObject = ["ABC": "CBA"]
        let dataMessage = DataMessage(topic: topic,
                                      data: anotherString.data(using: .utf8, allowLossyConversion: true)!,
                                      senderAttendeeId: senderAttendeeId,
                                      senderExternalUserId: senderExternalAttendeeId,
                                      timestampMs: 1,
                                      throttled: false)
        let returnJson = dataMessage.fromJSON() as? [String: String]
        XCTAssertEqual(jsonObject, returnJson)
    }

    func testDataMessageFromJSONNil() {
        let anotherString = "AnotherString"

        let dataMessage = DataMessage(topic: topic,
                                      data: anotherString.data(using: .utf8, allowLossyConversion: true)!,
                                      senderAttendeeId: senderAttendeeId,
                                      senderExternalUserId: senderExternalAttendeeId,
                                      timestampMs: 1,
                                      throttled: false)
        XCTAssertNil(dataMessage.fromJSON())
    }
}


//
//  SignalUpdateTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class SignalUpdateTests: XCTestCase {
    private let attendeeInfo = AttendeeInfo(attendeeId: "attendeeId", externalUserId: "externalUserId")
    private let signalStrength = SignalStrength.none

    func testVolumeUpdateShouldBeInitialized() {
        let signalUpdate = SignalUpdate(attendeeInfo: attendeeInfo, signalStrength: signalStrength)

        XCTAssertEqual(attendeeInfo, signalUpdate.attendeeInfo)
        XCTAssertEqual(signalStrength, signalUpdate.signalStrength)
    }
}


//
//  VolumeUpdateTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VolumeUpdateTests: XCTestCase {
    private let attendeeInfo = AttendeeInfo(attendeeId: "attendeeId", externalUserId: "externalUserId")
    private let volumeLevel = VolumeLevel.muted

    func testVolumeUpdateShouldBeInitialized() {
        let volumeUpdate = VolumeUpdate(attendeeInfo: attendeeInfo, volumeLevel: volumeLevel)

        XCTAssertEqual(attendeeInfo, volumeUpdate.attendeeInfo)
        XCTAssertEqual(volumeLevel, volumeUpdate.volumeLevel)
    }
}


//
//  TranscriptAlternativeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptAlternativeTests: XCTestCase {
    let transcript = "test"

    func testTranscriptAlternativeShouldInitialize() {
        let transcriptAlternative = TranscriptAlternative(items: [], transcript: transcript, entities: [])

        XCTAssertNotNil(transcriptAlternative)
        XCTAssertEqual(transcriptAlternative.items, [])
        XCTAssertEqual(transcriptAlternative.transcript, transcript)
        XCTAssertEqual(transcriptAlternative.entities, [])
    }
}


//
//  TranscriptEntityTest.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptEntityTests: XCTestCase {
    let type = "PII"
    let category = "ALL"
    let content = "test"
    let confidence = 0.0
    let startTimeMs: Int64 = 1632087029249
    let endTimeMs: Int64 = 1632087029250

    func testTranscriptEntityShouldInitialize() {
        let transcriptEntity = TranscriptEntity(type: type,
                                                content: content,
                                            category: category,
                                            confidence: confidence,
                                            startTimeMs: startTimeMs,
                                            endTimeMs: endTimeMs)

        XCTAssertNotNil(transcriptEntity)
        XCTAssertEqual(transcriptEntity.type, type)
        XCTAssertEqual(transcriptEntity.content, content)
        XCTAssertEqual(transcriptEntity.category, category)
        XCTAssertEqual(transcriptEntity.confidence, confidence)
        XCTAssertEqual(transcriptEntity.startTimeMs, startTimeMs)
        XCTAssertEqual(transcriptEntity.endTimeMs, endTimeMs)
    }
}


//
//  TranscriptionStatusTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptionStatusTests: XCTestCase {
    let eventTimeMs: Int64 = 1632087029249
    let transcriptionRegion = "us-east-1"
    let transcriptionConfiguration = "transcription-configuration"
    let message = "Internal server error"

    func testTranscriptionStatusShouldInitialize() {
        let transcriptionStatus = TranscriptionStatus(type: .failed,
                                                      eventTimeMs: eventTimeMs,
                                                      transcriptionRegion: transcriptionRegion,
                                                      transcriptionConfiguration: transcriptionConfiguration,
                                                      message: message)

        XCTAssertNotNil(transcriptionStatus)
        XCTAssertEqual(transcriptionStatus.type, .failed)
        XCTAssertEqual(transcriptionStatus.eventTimeMs, eventTimeMs)
        XCTAssertEqual(transcriptionStatus.transcriptionRegion, transcriptionRegion)
        XCTAssertEqual(transcriptionStatus.transcriptionConfiguration, transcriptionConfiguration)
        XCTAssertEqual(transcriptionStatus.message, message)
    }
}


//
//  TranscriptionStatusTypeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptionStatusTypeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(TranscriptionStatusType.started.description, "started")
        XCTAssertEqual(TranscriptionStatusType.interrupted.description, "interrupted")
        XCTAssertEqual(TranscriptionStatusType.resumed.description, "resumed")
        XCTAssertEqual(TranscriptionStatusType.stopped.description, "stopped")
        XCTAssertEqual(TranscriptionStatusType.failed.description, "failed")
        XCTAssertEqual(TranscriptionStatusType.unknown.description, "unknown")
    }
}


//
//  TranscriptItemTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptItemTests: XCTestCase {
    let startTimeMs: Int64 = 1632087029249
    let endTimeMs: Int64 = 1632087029250
    let attendee = AttendeeInfo(attendeeId: "attendee-id", externalUserId: "external-user-id")
    let content = "test"
    let stable = false
    let confidence = 0.0

    func testTranscriptItemShouldInitialize() {
        let transcriptItem = TranscriptItem(type: .punctuation,
                                            startTimeMs: startTimeMs,
                                            endTimeMs: endTimeMs,
                                            attendee: attendee,
                                            content: content,
                                            vocabularyFilterMatch: true,
                                            stable: stable,
                                            confidence: confidence)

        XCTAssertNotNil(transcriptItem)
        XCTAssertEqual(transcriptItem.type, .punctuation)
        XCTAssertEqual(transcriptItem.startTimeMs, startTimeMs)
        XCTAssertEqual(transcriptItem.endTimeMs, endTimeMs)
        XCTAssertEqual(transcriptItem.attendee, attendee)
        XCTAssertEqual(transcriptItem.content, content)
        XCTAssertEqual(transcriptItem.vocabularyFilterMatch, true)
        XCTAssertEqual(transcriptItem.stable, stable)
        XCTAssertEqual(transcriptItem.confidence, confidence)
    }
}


//
//  TranscriptItemTypeTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptItemTypeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(TranscriptItemType.pronunciation.description, "pronunciation")
        XCTAssertEqual(TranscriptItemType.punctuation.description, "punctuation")
        XCTAssertEqual(TranscriptItemType.unknown.description, "unknown")
    }
}


//
//  TranscriptLanguageWithScoreTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptLanguageWithScoreTests: XCTestCase {
    let languageCode = "en-US"
    let score = 0.0

    func testTranscriptLanguageWithScoreShouldInitialize() {
        let transcriptLanguageWithScore = TranscriptLanguageWithScore(languageCode: languageCode, score: score)

        XCTAssertNotNil(transcriptLanguageWithScore)
        XCTAssertEqual(transcriptLanguageWithScore.languageCode, languageCode)
        XCTAssertEqual(transcriptLanguageWithScore.score, score)
    }
}


//
//  TranscriptResultTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptResultTests: XCTestCase {
    let resultId = "result-id"
    let channelId = "channel-id"
    let startTimeMs: Int64 = 1632087029249
    let endTimeMs: Int64 = 1632087029250
    let languageCode = "en-US"

    func testTranscriptResultShouldInitialize() {
        let transcriptResult = TranscriptResult(resultId: "result-id",
                                                channelId: "channel-id",
                                                isPartial: true,
                                                startTimeMs: startTimeMs,
                                                endTimeMs: endTimeMs,
                                                alternatives: [],
                                                languageCode: languageCode,
                                                languageIdentification: [])

        XCTAssertNotNil(transcriptResult)
        XCTAssertEqual(transcriptResult.resultId, resultId)
        XCTAssertEqual(transcriptResult.channelId, channelId)
        XCTAssertEqual(transcriptResult.isPartial, true)
        XCTAssertEqual(transcriptResult.startTimeMs, startTimeMs)
        XCTAssertEqual(transcriptResult.endTimeMs, endTimeMs)
        XCTAssertEqual(transcriptResult.alternatives, [])
        XCTAssertEqual(transcriptResult.languageCode, languageCode)
        XCTAssertEqual(transcriptResult.languageIdentification, [])
    }
}


//
//  TranscriptTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class TranscriptTests: XCTestCase {
    func testTranscriptShouldBeInitialized() {
        let transcript = Transcript(results: [])

        XCTAssertNotNil(transcript)
        XCTAssertEqual(transcript.results, [])
    }
}


//
//  ObservableMetricTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class ObservableMetricTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(ObservableMetric.audioReceivePacketLossPercent.description,
                       "audioReceivePacketLossPercent")
        XCTAssertEqual(ObservableMetric.audioSendPacketLossPercent.description,
                       "audioSendPacketLossPercent")
        XCTAssertEqual(ObservableMetric.videoAvailableSendBandwidth.description, "videoAvailableSendBandwidth")
        XCTAssertEqual(ObservableMetric.videoAvailableReceiveBandwidth.description, "videoAvailableReceiveBandwidth")
        XCTAssertEqual(ObservableMetric.videoSendBitrate.description, "videoSendBitrate")
        XCTAssertEqual(ObservableMetric.videoSendPacketLossPercent.description, "videoSendPacketLossPercent")
        XCTAssertEqual(ObservableMetric.videoSendFps.description, "videoSendFps")
        XCTAssertEqual(ObservableMetric.videoSendRttMs.description, "videoSendRttMs")
        XCTAssertEqual(ObservableMetric.videoReceiveBitrate.description, "videoReceiveBitrate")
        XCTAssertEqual(ObservableMetric.videoReceivePacketLossPercent.description, "videoReceivePacketLossPercent")
        XCTAssertEqual(ObservableMetric.contentShareVideoSendBitrate.description, "contentShareVideoSendBitrate")
        XCTAssertEqual(ObservableMetric.contentShareVideoSendPacketLossPercent.description, "contentShareVideoSendPacketLossPercent")
        XCTAssertEqual(ObservableMetric.contentShareVideoSendFps.description, "contentShareVideoSendFps")
        XCTAssertEqual(ObservableMetric.contentShareVideoSendRttMs.description, "contentShareVideoSendRttMs")
    }

    func testIsContentShareMetric() {
        XCTAssertFalse(ObservableMetric.audioReceivePacketLossPercent.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.audioSendPacketLossPercent.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoAvailableSendBandwidth.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoAvailableReceiveBandwidth.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoSendBitrate.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoSendPacketLossPercent.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoSendFps.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoSendRttMs.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoReceiveBitrate.isContentShareMetric)
        XCTAssertFalse(ObservableMetric.videoReceivePacketLossPercent.isContentShareMetric)
        XCTAssertTrue(ObservableMetric.contentShareVideoSendBitrate.isContentShareMetric)
        XCTAssertTrue(ObservableMetric.contentShareVideoSendPacketLossPercent.isContentShareMetric)
        XCTAssertTrue(ObservableMetric.contentShareVideoSendFps.isContentShareMetric)
        XCTAssertTrue(ObservableMetric.contentShareVideoSendRttMs.isContentShareMetric)
    }
}


//
//  DefaultVideoTileTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import Mockingbird
import XCTest

class DefaultVideoTileTests: XCTestCase {
    let tileId = 123
    let attendeeId = "123-456-789"
    let videoStreamContentWidth = 1080
    let videoStreamContentHeight = 1920
    let isLocalTile = false

    var loggerMock: LoggerMock!
    var videoRenderViewMock: VideoRenderViewMock!
    var defaultVideoTitle: DefaultVideoTile!

    override func setUp() {
        loggerMock = mock(Logger.self)
        defaultVideoTitle = DefaultVideoTile(tileId: tileId,
                                             attendeeId: attendeeId,
                                             videoStreamContentWidth: videoStreamContentWidth,
                                             videoStreamContentHeight: videoStreamContentHeight,
                                             isLocalTile: isLocalTile,
                                             logger: loggerMock)
    }

    func testBind() {
        videoRenderViewMock = mock(VideoRenderView.self)
        defaultVideoTitle.bind(videoRenderView: videoRenderViewMock)

        verify(loggerMock.info(msg: "Binding the view to tile: tileId: \(tileId), attendeeId: \(attendeeId)")).wasCalled()
        XCTAssert(videoRenderViewMock === defaultVideoTitle.videoRenderView)
    }

    func testRenderFrame() {
        var cVPPixelBuffer: CVPixelBuffer?
        CVPixelBufferCreate(kCFAllocatorDefault, 3840, 2160, kCVPixelFormatType_32ARGB, nil, &cVPPixelBuffer)
        let buffer = VideoFramePixelBuffer(pixelBuffer: cVPPixelBuffer!)
        let frame = VideoFrame(timestampNs: 0, rotation: .rotation0, buffer: buffer)
        videoRenderViewMock = mock(VideoRenderView.self)
        defaultVideoTitle.bind(videoRenderView: videoRenderViewMock)
        defaultVideoTitle.onVideoFrameReceived(frame: frame)

        verify(videoRenderViewMock.onVideoFrameReceived(frame: frame)).wasCalled()
    }

    func testUnbind() {
        defaultVideoTitle.unbind()

        verify(loggerMock.info(msg: "Unbinding the view from tile: tileId: \(tileId), attendeeId: \(attendeeId)")).wasCalled()
        XCTAssertNil(defaultVideoTitle.videoRenderView)
    }

    func testSetPauseState() {
        defaultVideoTitle.setPauseState(pauseState: VideoPauseState.pausedByUserRequest)
        XCTAssertEqual(VideoPauseState.pausedByUserRequest, defaultVideoTitle.state.pauseState)
    }
}


//
//  VideoContentHintTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import XCTest

class VideoContentHintTests: XCTestCase {
    func testToInternalShouldMatchVideoContentHintInternal() {
        XCTAssertEqual(VideoContentHint.none.toInternal, VideoContentHintInternal.none)
        XCTAssertEqual(VideoContentHint.motion.toInternal, VideoContentHintInternal.motion)
        XCTAssertEqual(VideoContentHint.detail.toInternal, VideoContentHintInternal.detailed)
        XCTAssertEqual(VideoContentHint.text.toInternal, VideoContentHintInternal.text)
    }

    func testDescriptionShouldMatch() {
        XCTAssertEqual(VideoContentHint.none.description, "none")
        XCTAssertEqual(VideoContentHint.motion.description, "motion")
        XCTAssertEqual(VideoContentHint.detail.description, "detail")
        XCTAssertEqual(VideoContentHint.text.description, "text")
    }
}


//
//  VideoFrameTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VideoFrameTests: XCTestCase {
    func testInitShoudPopulateProperties() {
        var cVPPixelBuffer: CVPixelBuffer?
        CVPixelBufferCreate(kCFAllocatorDefault, 3840, 2160, kCVPixelFormatType_32ARGB, nil, &cVPPixelBuffer)
        let buffer = VideoFramePixelBuffer(pixelBuffer: cVPPixelBuffer!)
        let frame = VideoFrame(timestampNs: Int64.max, rotation: .rotation0, buffer: buffer)

        XCTAssertEqual(frame.width, 3840)
        XCTAssertEqual(frame.height, 2160)
        XCTAssertEqual(frame.rotation, .rotation0)
        XCTAssertEqual(frame.timestampNs, 9223372036854775807)
    }
}


//
//  VideoPauseStateTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VideoPauseStateTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(VideoPauseState.unpaused.description, "unpaused")
        XCTAssertEqual(VideoPauseState.pausedByUserRequest.description, "pausedByUserRequest")
        XCTAssertEqual(VideoPauseState.pausedForPoorConnection.description, "pausedForPoorConnection")
    }
}


//
//  VideoRotationTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import XCTest

class VideoRotationTests: XCTestCase {
    func testRawValueShouldMatch() {
        XCTAssertEqual(VideoRotation.rotation0.rawValue, 0)
        XCTAssertEqual(VideoRotation.rotation90.rawValue, 90)
        XCTAssertEqual(VideoRotation.rotation180.rawValue, 180)
        XCTAssertEqual(VideoRotation.rotation270.rawValue, 270)
    }

    func testToInternalShouldMatchVideoRotationInternal() {
        XCTAssertEqual(VideoRotation.rotation0.toInternal, VideoRotationInternal.rotation0)
        XCTAssertEqual(VideoRotation.rotation90.toInternal, VideoRotationInternal.rotation90)
        XCTAssertEqual(VideoRotation.rotation180.toInternal, VideoRotationInternal.rotation180)
        XCTAssertEqual(VideoRotation.rotation270.toInternal, VideoRotationInternal.rotation270)
    }

    func testDescriptionShouldMatch() {
        XCTAssertEqual(VideoRotation.rotation0.description, "rotation_0")
        XCTAssertEqual(VideoRotation.rotation90.description, "rotation_90")
        XCTAssertEqual(VideoRotation.rotation180.description, "rotation_180")
        XCTAssertEqual(VideoRotation.rotation270.description, "rotation_270")
    }
}


//
//  VideoTileStateTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AmazonChimeSDK
import XCTest

class VideoTileStateTests: XCTestCase {
    private let tileId = 0
    private let attendeeId = "attendeeId"
    private let screenShareAttendeeId = "attendeeId#content"
    private let pauseState = VideoPauseState.unpaused
    private let videoStreamContentWidth = 720
    private let videoStreamContentHeight = 1280
    private var videoTileState: VideoTileState?

    func testVideoTileStateShouldBeInitialized() {
        videoTileState = VideoTileState(tileId: tileId,
                                        attendeeId: attendeeId,
                                        videoStreamContentWidth: videoStreamContentWidth,
                                        videoStreamContentHeight: videoStreamContentHeight,
                                        pauseState: pauseState,
                                        isLocalTile: false)
        XCTAssertEqual(videoTileState?.tileId, tileId)
        XCTAssertEqual(videoTileState?.attendeeId, attendeeId)
        XCTAssertEqual(videoTileState?.videoStreamContentWidth, videoStreamContentWidth)
        XCTAssertEqual(videoTileState?.videoStreamContentHeight, videoStreamContentHeight)
        XCTAssertEqual(videoTileState?.pauseState, pauseState)
        XCTAssertEqual(videoTileState?.isLocalTile, false)
        XCTAssertEqual(videoTileState?.isContent, false)
    }

    func testVideoTileStateShouldBeInitializedAsLocalVideoViewWhenAttendeeIdIsEmpty() {
        videoTileState = VideoTileState(tileId: tileId,
                                        attendeeId: attendeeId,
                                        videoStreamContentWidth: videoStreamContentWidth,
                                        videoStreamContentHeight: videoStreamContentHeight,
                                        pauseState: pauseState,
                                        isLocalTile: true)

        XCTAssertEqual(videoTileState?.tileId, tileId)
        XCTAssertEqual(videoTileState?.pauseState, pauseState)
        XCTAssertEqual(videoTileState?.isLocalTile, true)
        XCTAssertEqual(videoTileState?.isContent, false)
    }

    func testVideoTileStateShouldBeInitializedAsScreenShareViewWhenAttendeeIdContainsContentKeyword() {
        videoTileState = VideoTileState(tileId: tileId,
                                        attendeeId: screenShareAttendeeId,
                                        videoStreamContentWidth: videoStreamContentWidth,
                                        videoStreamContentHeight: videoStreamContentHeight,
                                        pauseState: pauseState,
                                        isLocalTile: false)

        XCTAssertEqual(videoTileState?.tileId, tileId)
        XCTAssertEqual(videoTileState?.attendeeId, screenShareAttendeeId)
        XCTAssertEqual(videoTileState?.pauseState, pauseState)
        XCTAssertEqual(videoTileState?.isLocalTile, false)
        XCTAssertEqual(videoTileState?.isContent, true)
    }
}


//
//  BackgroundFilterTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
#if canImport(AmazonChimeSDKMachineLearning)
@testable import AmazonChimeSDKMachineLearning
#endif
import Mockingbird
import XCTest
import CommonCrypto

/// XCTest file to test `BackgroundBlurVideoFrameProcessor` and `BackgroundReplacementVideoFrameProcessor`.
class BackgroundFilterTests: XCTestCase {
    var loggerMock: LoggerMock!
    var testImage: UIImage?
    var expectedBlurImage: UIImage?
    var expectedReplacementImage: UIImage?

    /// Expected similarity match for images.
    let expectedMatchPercentage = 0.95

    /// Maximum pixel difference allowed when comparing similarity.
    let expectedPixelMatchThreshold = Int(255 * 0.05)

    /// Downscaled size used for testing. Note that we prefer downscaling
    /// because it's more efficient for the test. Otherwise, we end up doing
    /// a lot more comparisons and the test takes longer.
    let downscaledSize = CGSize(width: 144, height: 256)

    let context = CIContext(options: [.cacheIntermediates: false])

    override func setUp() {
        loggerMock = mock(Logger.self)
        /// Load test image that will be used by the tests.
        guard let testImage = UIImage(named: "background-ml-test-image.jpeg",
                                      in: Bundle(for: type(of: self)),
                                      compatibleWith: nil) else {
            XCTFail("Failed to load test image.")
            return
        }
        guard let expectedBlurImage = UIImage(named: "expected-blur-test-image.png",
                                              in: Bundle(for: type(of: self)),
                                              compatibleWith: nil) else {
            XCTFail("Failed to load expected blur image.")
            return
        }
        guard let expectedReplacementImage = UIImage(named: "expected-replacement-test-image.png",
                                                     in: Bundle(for: type(of: self)),
                                                     compatibleWith: nil) else {
            XCTFail("Failed to load expected replacement image.")
            return
        }

        self.testImage = testImage
        self.expectedBlurImage = expectedBlurImage
        self.expectedReplacementImage = expectedReplacementImage
    }

    /// Test `BackgroundBlurVideoFrameProcessor` functionalities  on the testImage frame.
    func testBackgroundBlurVideoFrameProcessor() {
        #if canImport(AmazonChimeSDKMachineLearning)
        guard let frame = generateVideoFrame() else {
            return
        }

        let videoSinkMock = mock(VideoSink.self)
        var processedImage: UIImage?
        var videoFrameReceivedExpectation: XCTestExpectation?

        given(videoSinkMock.onVideoFrameReceived(frame: any())) ~> { [self] videoFrame in
            let result = self.processImage(frame: videoFrame)
            processedImage = result.image
            videoFrameReceivedExpectation!.fulfill()
        }

        let backgroundBlurConfigurations = BackgroundBlurConfiguration(
            logger: ConsoleLogger(name: "testBackgroundBlurVideoFrameProcessor"))
        let processor = BackgroundBlurVideoFrameProcessor(backgroundBlurConfiguration: backgroundBlurConfigurations)

        processor.addVideoSink(sink: videoSinkMock)

        // Verify the checksum for the different blur strengths.
        let blurList = [BackgroundBlurStrength.high]
        for index in 0...(blurList.count - 1) {
            processedImage = nil
            videoFrameReceivedExpectation = expectation(description: "Video frame is received for index \(index)")

            processor.setBlurStrength(newBlurStrength: blurList[index])
            processor.onVideoFrameReceived(frame: frame)

            // Wait for the image to generate before proceeding to avoid non-determinism.
            wait(for: [videoFrameReceivedExpectation!], timeout: 1)
            XCTAssert(processedImage != nil)

            guard let gotCgImage = self.resize(image: processedImage!, to: downscaledSize).cgImage,
                let gotCgImageData = gotCgImage.dataProvider?.data,
                let gotCgImageBytes = CFDataGetBytePtr(gotCgImageData) else {
                XCTFail("Couldn't access CGImage data")
                return
            }
            guard let expectedCgImage = self.resize(image: self.expectedBlurImage!, to: downscaledSize).cgImage,
                let expectedCgImageData = expectedCgImage.dataProvider?.data,
                let expectedCgImageBytes = CFDataGetBytePtr(expectedCgImageData) else {
                XCTFail("Couldn't access CGImage data")
                return
            }
            XCTAssert(gotCgImage.colorSpace?.model == .rgb)
            XCTAssert(expectedCgImage.colorSpace?.model == .rgb)
            XCTAssertEqual(expectedCgImage.bitsPerPixel, gotCgImage.bitsPerPixel)
            XCTAssertEqual(expectedCgImage.bitsPerComponent, gotCgImage.bitsPerComponent)
            XCTAssertEqual(expectedCgImage.height, gotCgImage.height)
            XCTAssertEqual(expectedCgImage.width, gotCgImage.width)

            let matchPercentage = self.getCGImageMatchPercentage(
                expectedCgImage: expectedCgImage, gotCgImage: gotCgImage,
                expectedCgImageBytes: expectedCgImageBytes, gotCgImageBytes: gotCgImageBytes)
            XCTAssert(matchPercentage >= expectedMatchPercentage,
                      "Expected match percentage \(matchPercentage) to be >= \(expectedMatchPercentage)")
        }
        #else
        XCTFail("AmazonChimeSDKMachineLearning could not be imported.")
        #endif
    }

    /// Test `BackgroundReplacementVideoFrameProcessor` functionalities on the testImage frame.
    func testBackgroundReplacementVideoFrameProcessor() {
        #if canImport(AmazonChimeSDKMachineLearning)
        guard let frame = generateVideoFrame() else {
            return
        }

        let videoSinkMock = mock(VideoSink.self)
        var processedImage: UIImage?
        var videoFrameReceivedExpectation: XCTestExpectation?

        given(videoSinkMock.onVideoFrameReceived(frame: any())) ~> { videoFrame in
            let result = self.processImage(frame: videoFrame)
            processedImage = result.image
            videoFrameReceivedExpectation!.fulfill()
        }

        let replacementImageColors = [UIColor.red]
        var backgroundReplacementImage = generateBackgroundImage(width: frame.width,
                                                                 height: frame.height,
                                                                 color: replacementImageColors[0])
        let logger = ConsoleLogger(name: "testBackgroundReplacementVideoFrameProcessor")
        let backgroundReplacementConfigurations = BackgroundReplacementConfiguration(
            logger: logger,
            backgroundReplacementImage: backgroundReplacementImage
        )

        let processor = BackgroundReplacementVideoFrameProcessor(
            backgroundReplacementConfiguration: backgroundReplacementConfigurations)

        processor.addVideoSink(sink: videoSinkMock)

        // Loop through the different generated backgrounds images and verify checksum.
        for index in 0...replacementImageColors.count - 1 {
            processedImage = nil
            videoFrameReceivedExpectation = expectation(description: "Video frame is received for index \(index)")

            if index > 0 {
                backgroundReplacementImage = generateBackgroundImage(width: frame.width,
                                                                     height: frame.height,
                                                                     color: replacementImageColors[index])
                processor.setBackgroundImage(newBackgroundReplacementImage: backgroundReplacementImage)
            }
            processor.onVideoFrameReceived(frame: frame)

            // Wait for the image to generate before proceeding to avoid non-determinism.
            wait(for: [videoFrameReceivedExpectation!], timeout: 1)
            XCTAssert(processedImage != nil)

            guard let gotCgImage = self.resize(image: processedImage!, to: downscaledSize).cgImage,
                let gotCgImageData = gotCgImage.dataProvider?.data,
                let gotCgImageBytes = CFDataGetBytePtr(gotCgImageData) else {
                XCTFail("Couldn't access CGImage data")
                return
            }
            guard let expectedCgImage = self.resize(image: self.expectedReplacementImage!, to: downscaledSize).cgImage,
                let expectedCgImageData = expectedCgImage.dataProvider?.data,
                let expectedCgImageBytes = CFDataGetBytePtr(expectedCgImageData) else {
                XCTFail("Couldn't access CGImage data")
                return
            }
            XCTAssert(gotCgImage.colorSpace?.model == .rgb)
            XCTAssert(expectedCgImage.colorSpace?.model == .rgb)
            XCTAssertEqual(expectedCgImage.bitsPerPixel, gotCgImage.bitsPerPixel)
            XCTAssertEqual(expectedCgImage.bitsPerComponent, gotCgImage.bitsPerComponent)
            XCTAssertEqual(expectedCgImage.height, gotCgImage.height)
            XCTAssertEqual(expectedCgImage.width, gotCgImage.width)

            let matchPercentage = self.getCGImageMatchPercentage(
                expectedCgImage: expectedCgImage, gotCgImage: gotCgImage,
                expectedCgImageBytes: expectedCgImageBytes, gotCgImageBytes: gotCgImageBytes)
            XCTAssert(matchPercentage >= expectedMatchPercentage,
                      "Expected match percentage \(matchPercentage) to be >= \(expectedMatchPercentage)")
        }
        #else
        XCTFail("AmazonChimeSDKMachineLearning could not be imported.")
        #endif
    }

    /// Test `NoopSegmentationProcessor` for coverage purposes.
    func testNoopSegmentationProcessor() {
        let segmentationProcessor = NoopSegmentationProcessor()
        let initializeResult = segmentationProcessor.initialize(144, width: 256, channels: 4)
        XCTAssertTrue(initializeResult)

        let predictResult = segmentationProcessor.predict()
        XCTAssertTrue(predictResult)

        let modelStateResult = segmentationProcessor.getModelState()
        XCTAssertEqual(CwtModelState.LOADED.rawValue, UInt(modelStateResult))

        let inputBuffer = segmentationProcessor.getInputBuffer()
        XCTAssertNotNil(inputBuffer)

        let outputBuffer = segmentationProcessor.getOutputBuffer()
        XCTAssertNotNil(outputBuffer)
    }

    /// Generates a blue background image used as a background replacement image
    /// for `BackgroundReplacementVideoFrameProcessor` test.
    ///
    /// - Parameters:
    ///   - width: width of the replacement image.
    ///   - height: height of the replacement image.
    ///   - color;  Background UIColor.
    func generateBackgroundImage(width: Int, height: Int, color: UIColor) -> UIImage {
        let rect = CGRect(x: 0,
                          y: 0,
                          width: width,
                          height: height)
        UIGraphicsBeginImageContextWithOptions(CGSize(width: width,
                                                      height: height),
                                               false, 0)
        color.setFill()
        UIRectFill(rect)
        let backgroundReplacementImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return backgroundReplacementImage
    }

    /// Generates a `VideoFrame` for the test image.
    func generateVideoFrame() -> VideoFrame? {
        guard let uiImage = testImage else {
            return nil
        }
        guard let testCGImage = uiImage.cgImage else {
            return nil
        }
        let height = testCGImage.height
        let width = testCGImage.width

        var cvPixelBuffer: CVPixelBuffer?
        CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32ARGB, nil, &cvPixelBuffer)
        context.render(CIImage(cgImage: testCGImage), to: cvPixelBuffer!)

        let buffer = VideoFramePixelBuffer(pixelBuffer: cvPixelBuffer!)
        let frame = VideoFrame(timestampNs: 0, rotation: .rotation0, buffer: buffer)
        return frame
    }

    /// Generate the check sum of a video frame and return processed UIImage. This function should only be used in the
    /// context of a test as the generated image will be attached for reference.
    ///
    /// - Parameters:
    ///   - frame: `background-ml-test-image.jpg` video frame.
    func processImage(frame: VideoFrame) -> (hash: String, image: UIImage?) {
        guard let pixelBuffer = frame.buffer as? VideoFramePixelBuffer else {
            return ("", nil)
        }
        CVPixelBufferLockBaseAddress(pixelBuffer.pixelBuffer, CVPixelBufferLockFlags.readOnly)
        guard let address = CVPixelBufferGetBaseAddress(pixelBuffer.pixelBuffer) else {
            XCTFail("Failed to retrieve frame buffer address.")
            return ("", nil)
        }
        let contextDataPointer: UnsafeMutablePointer<UInt8> = address.bindMemory(
            to: UInt8.self,
            capacity: frame.width * frame.height * 4)

        let imageCfData = CFDataCreate(nil, contextDataPointer, frame.width * frame.height * 4)
        let cgProvider = CGDataProvider.init(data: imageCfData!)!
        var hashArray = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        let nsImageData = NSData(data: (cgProvider.data as Data?)!)

        // Attach images for manual testing and debugging purposes.
        // In the test view, right click the test and use "Jump to Report"
        // to see the attached images.
        let ciImage = CIImage(cvImageBuffer: pixelBuffer.pixelBuffer)
        let context = CIContext(options: [.cacheIntermediates: false])
        guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
            XCTFail("Error creating CGImage of input frame.")
            return ("", nil)
        }
        let image = UIImage(cgImage: cgImage)
        let attachment = XCTAttachment(image: image)
        attachment.lifetime = .keepAlways
        self.add(attachment)

        CC_SHA256(nsImageData.bytes, UInt32(nsImageData.count), &hashArray)

        let finalData = NSData(bytes: hashArray, length: Int(CC_SHA256_DIGEST_LENGTH))

        var bytes = [UInt8](repeating: 0, count: finalData.length)
        finalData.getBytes(&bytes, length: finalData.length)

        var hexString = ""
        for byte in bytes {
            hexString += String(format: "%02x", UInt8(byte))
        }

        CVPixelBufferUnlockBaseAddress(pixelBuffer.pixelBuffer, CVPixelBufferLockFlags.readOnly)

        return (hexString, image)
    }

    /// Returns the match percentage associated with two CGImages and their byte pointers.
    ///
    /// - Parameters:
    ///   - expectedCgImage: First image to compare against..
    ///   - gotCgImage: Second CGImage to compare against.
    ///   - expectedCgImageBytes: First CGImage byte pointer to compare against.
    ///   - gotCgImageBytes: Second CGImage byte pointer to compare against
    func getCGImageMatchPercentage(
        expectedCgImage: CGImage, gotCgImage: CGImage,
        expectedCgImageBytes: UnsafePointer<UInt8>, gotCgImageBytes: UnsafePointer<UInt8>
    ) -> Double {
        var matchCount = 0
        let totalPixels = gotCgImage.height * gotCgImage.width
        let bytesPerPixel = gotCgImage.bitsPerPixel / gotCgImage.bitsPerComponent
        for row in 0 ..< gotCgImage.height {
            for col in 0 ..< gotCgImage.width {
                let offset = (row * gotCgImage.bytesPerRow) + (col * bytesPerPixel)
                let match = self.getRGBMatch(offset: offset,
                                             expectedImageBytes: expectedCgImageBytes, gotImageBytes: gotCgImageBytes)
                if match {
                    matchCount += 1
                }
            }
        }
        let matchPercentage = Double(matchCount) / Double(totalPixels)
        return matchPercentage
    }

    /// Returns whether the RGB pixel value at a given offset matches for two CGImages.
    ///
    /// - Parameters:
    ///   - offset: Index in the expectedImageBytes and gotImageBytes.
    ///   - expectedImageBytes: First CGImage byte pointer to compare against.
    ///   - gotImageBytes: Second CGImage byte pointer to compare against.
    func getRGBMatch(
        offset: Int,
        expectedImageBytes: UnsafePointer<UInt8>,
        gotImageBytes: UnsafePointer<UInt8>
    ) -> Bool {
        let rDiff = Int32(expectedImageBytes[offset]) - Int32(gotImageBytes[offset])
        let gDiff = Int32(expectedImageBytes[offset+1]) - Int32(gotImageBytes[offset+1])
        let bDiff = Int32(expectedImageBytes[offset+2]) - Int32(gotImageBytes[offset+2])
        let rMatch = abs(rDiff) < expectedPixelMatchThreshold
        let gMatch = abs(gDiff) < expectedPixelMatchThreshold
        let bMatch = abs(bDiff) < expectedPixelMatchThreshold
        return rMatch && gMatch && bMatch
    }

    /// Resize and rescale images. This is particularly useful for upsampling and downsampling.
    ///
    /// - Parameters:
    ///   - image: Input image  to resize..
    ///   - to: New size of the input image..
    func resize(image: UIImage, to newSize: CGSize) -> UIImage {
        return UIGraphicsImageRenderer(size: newSize).image { _ in
            let hScale = newSize.height / image.size.height
            let vScale = newSize.width / image.size.width
            let scale = max(hScale, vScale)
            let resizeSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
            var middle = CGPoint.zero
            if resizeSize.width > newSize.width {
                middle.x -= (resizeSize.width - newSize.width) / 2.0
            }
            if resizeSize.height > newSize.height {
                middle.y -= (resizeSize.height - newSize.height) / 2.0
            }
            image.draw(in: CGRect(origin: middle, size: resizeSize))
        }
    }
}


//
//  CaptureSourceErrorTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class CaptureSourceErrorTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(CaptureSourceError.unknown.description, "unknown")
        XCTAssertEqual(CaptureSourceError.systemFailure.description, "systemFailure")
        XCTAssertEqual(CaptureSourceError.configurationFailure.description, "configurationFailure")
        XCTAssertEqual(CaptureSourceError.invalidFrame.description, "invalidFrame")
    }
}


//
//  DefautCameraCaptureSourceTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

import AVFoundation
@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import XCTest
import Mockingbird

class DefaultCameraCaptureSourceTests: XCTestCase {
    var defaultCameraCaptureSource: DefaultCameraCaptureSource!
    var mockSourceObserver: CaptureSourceObserverMock!
    var eventControllerMock: EventAnalyticsControllerMock!
    let defaultTimeout = 0.1

    override func setUp() {
        AVCaptureDevice.swizzle()
        AVCaptureSession.swizzle()
        eventControllerMock = mock(EventAnalyticsController.self)
        let loggerMock = mock(Logger.self)
        mockSourceObserver = mock(CaptureSourceObserver.self)

        defaultCameraCaptureSource = DefaultCameraCaptureSource(logger: loggerMock)
        defaultCameraCaptureSource.setEventAnalyticsController(eventAnalyticsController: eventControllerMock)

        defaultCameraCaptureSource.addCaptureSourceObserver(observer: mockSourceObserver)
    }

    override func tearDown() {
        // swizzle is exchange so it should return back
        AVCaptureDevice.swizzle()
        AVCaptureSession.swizzle()
    }

    func testStop_captureDidStop() {
        defaultCameraCaptureSource.stop()

        let expect = eventually {
            verify(mockSourceObserver.captureDidStop()).wasCalled()
        }

        wait(for: [expect], timeout: defaultTimeout)
    }

    func testStop_captureDidStart_Failed() {
        AVCaptureSession.swizzleCanAddFalse()
        defaultCameraCaptureSource.start()

        let expect = eventually {
            verify(mockSourceObserver.captureDidFail(error: .configurationFailure)).wasCalled()
            verify(eventControllerMock.publishEvent(name: .videoInputFailed, attributes: any())).wasCalled()
        }

        wait(for: [expect], timeout: defaultTimeout)
        AVCaptureSession.swizzleCanAddFalse()
    }

    func testClosestFormat() {
        XCTAssertEqual(defaultCameraCaptureSource.closestFormat(
            formatA: VideoCaptureFormat(width: 1280, height: 720, maxFrameRate: 30),
            formatB: VideoCaptureFormat(width: 1280, height: 720, maxFrameRate: 15)
        ), true)
    }
}

extension AVCaptureSession {
    class func swizzle() {
        [(#selector(AVCaptureSession.addInput(_:)), #selector(AVCaptureSession.mockAddInput(_:)))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }

        [(#selector(AVCaptureSession.canAddInput(_:)), #selector(AVCaptureSession.mockCanAddInputTrue(_:)))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }

        [(#selector(AVCaptureSession.init(mock:)), #selector(NSObject.init))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_setImplementation(original!, method_getImplementation(mock!))
        }
    }

    class func swizzleCanAddFalse() {
        [(#selector(AVCaptureSession.canAddOutput(_:)),
          #selector(AVCaptureSession.mockCanAddOutputFalse(_:)))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }
    }

    @objc convenience init(mock: String) {
        fatalError("should never be called because this is fake method replaced by original init")
    }

    @objc func mockCanAddOutputFalse(_ output: AVCaptureOutput) -> Bool {
        return false
    }

    @objc func mockAddInput(_ deviceInput: AVCaptureInput) {
        return
    }

    @objc func mockCanAddInputTrue(_ deviceInput: AVCaptureInput) -> Bool {
        return true
    }
}

extension AVCaptureDevice {
    class func swizzle() {
        [(#selector(AVCaptureDevice.default(_:for:position:)),
          #selector(AVCaptureDevice.mockDefaultDevice(_:for:position:)))].forEach {
            let original = class_getClassMethod(self, $0)
            let mock = class_getClassMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }
        [(#selector(AVCaptureDevice.hasMediaType(_:)), #selector(AVCaptureDevice.mockHasMediaType(_:))),
         (#selector(AVCaptureDevice.supportsSessionPreset),
          #selector(AVCaptureDevice.mockSupportsAVCaptureSessionPreset)),
         (#selector(AVCaptureDevice.isTorchModeSupported(_:)), #selector(AVCaptureDevice.mockIsTorchModeSupported)),
         (#selector(getter: AVCaptureDevice.torchMode), #selector(getter: AVCaptureDevice.mockTorchMode)),
         (#selector(setter: AVCaptureDevice.torchMode), #selector(setter: AVCaptureDevice.mockTorchMode))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }
        [(#selector(AVCaptureDevice.init(mock:)), #selector(NSObject.init))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_setImplementation(original!, method_getImplementation(mock!))
        }
        [(#selector(AVCaptureDevice.lockForConfiguration),
          #selector(AVCaptureDevice.mocklockForConfiguration)),
         (#selector(AVCaptureDevice.unlockForConfiguration),
          #selector(AVCaptureDevice.mockUnlockForConfiguration))].forEach {
            let original = class_getInstanceMethod(self, $0)
            let mock = class_getInstanceMethod(self, $1)
            method_exchangeImplementations(original!, mock!)
        }

    }

    @objc convenience init(mock: String) {
        fatalError("should never be called because this is fake method replaced by original init")
    }

    @objc class func mockDefaultDevice(_ deviceType: AVCaptureDevice.DeviceType,
                                       for mediaType: AVMediaType?,
                                       position: AVCaptureDevice.Position) -> AVCaptureDevice? {
        return AVCaptureDevice(mock: "")
    }

    @objc func mocklockForConfiguration() {
        return
    }
    @objc func mockUnlockForConfiguration() {
        return
    }

    @objc func mockHasMediaType(_ mediaType: String!) -> Bool {
        return true
    }

    @objc func mockSupportsAVCaptureSessionPreset(_ preset: String!) -> Bool {
        return true
    }

    @objc func mockIsTorchModeSupported(_ torchMode: AVCaptureDevice.TorchMode) -> Bool {
        return UI_USER_INTERFACE_IDIOM() != .pad
    }

    @objc var mockTorchMode: AVCaptureDevice.TorchMode {
        get {return .off}
        set {}
    }
}


//
//  VideoCaptureFormatTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class VideoCaptureFormatTests: XCTestCase {
    func testInitShouldPopulateproperties() {
        let format = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)

        XCTAssertEqual(format.width, 16)
        XCTAssertEqual(format.height, 9)
        XCTAssertEqual(format.maxFrameRate, 60)
    }

    func testEqualShouldReturnTrueWhenAllFieldsMatch() {
        let format1 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)
        let format2 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)

        XCTAssertEqual(format1, format2)
    }

    func testEqualShouldReturnFalseWhenMaxFrameRateIsDifferent() {
        let format1 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)
        let format2 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 30)

        XCTAssertNotEqual(format1, format2)
    }

    func testEqualShouldReturnFalseWhenWidthIsDifferent() {
        let format1 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)
        let format2 = VideoCaptureFormat(width: 15, height: 9, maxFrameRate: 60)

        XCTAssertNotEqual(format1, format2)
    }

    func testEqualShouldReturnFalseWhenHeightIsDifferent() {
        let format1 = VideoCaptureFormat(width: 16, height: 9, maxFrameRate: 60)
        let format2 = VideoCaptureFormat(width: 16, height: 8, maxFrameRate: 60)

        XCTAssertNotEqual(format1, format2)
    }
}


//
//  CommonTestCase.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import XCTest

class CommonTestCase: XCTestCase {
    let externalMeetingId = "external-meeting-id"
    let audioFallbackUrl = "audioFallbackUrl"
    let audioHostUrlWithPort = "audio-host-url:2020"
    let audioHostUrl = "audio-host-url"
    let signalingUrl = "signalingUrl"
    let turnControlUrl = "turnControlUrl"
    let mediaRegion = "us-east-1"
    let meetingId = "meeting-id"
    let attendeeId = "attendee-id"
    let externalUserId = "externalUserId"
    let joinToken = "join-token"

    var meetingSessionConfigurationMock: MeetingSessionConfigurationMock!
    var loggerMock: LoggerMock!

    override func setUp() {
        let mediaPlacementMock: MediaPlacementMock = mock(MediaPlacement.self)
            .initialize(audioFallbackUrl: audioFallbackUrl,
                        audioHostUrl: audioHostUrlWithPort,
                        signalingUrl: signalingUrl,
                        turnControlUrl: turnControlUrl, eventIngestionUrl: nil)
        let meetingMock: MeetingMock = mock(Meeting.self).initialize(externalMeetingId: externalMeetingId,
                                                                     mediaPlacement: mediaPlacementMock,
                                                                     mediaRegion: mediaRegion,
                                                                     meetingId: meetingId,
                                                                     primaryMeetingId: nil)
        let createMeetingResponseMock: CreateMeetingResponseMock = mock(CreateMeetingResponse.self)
            .initialize(meeting: meetingMock)

        let attendeeMock: AttendeeMock = mock(Attendee.self).initialize(attendeeId: attendeeId,
                                                                        externalUserId: externalUserId,
                                                                        joinToken: joinToken)
        let createAttendeeResponseMock: CreateAttendeeResponseMock = mock(CreateAttendeeResponse.self)
            .initialize(attendee: attendeeMock)
        meetingSessionConfigurationMock = mock(MeetingSessionConfiguration.self)
            .initialize(createMeetingResponse: createMeetingResponseMock,
                        createAttendeeResponse: createAttendeeResponseMock,
                        urlRewriter: URLRewriterUtils.defaultUrlRewriter)
        loggerMock = mock(Logger.self)
    }
}


//
//  DefaultDeviceControllerTests.swift
//  AmazonChimeSDK
//
//  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//  SPDX-License-Identifier: Apache-2.0
//

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import AVFoundation
import Mockingbird
import XCTest

class DefaultDeviceControllerTests: XCTestCase {
    var audioSessionMock: AudioSessionMock!
    var videoClientControllerMock: VideoClientControllerMock!
    var loggerMock: LoggerMock!
    var defaultDeviceController: DefaultDeviceController!
    var eventAnalyticsControllerMock: EventAnalyticsControllerMock!

    override func setUp() {
        videoClientControllerMock = mock(VideoClientController.self)
        eventAnalyticsControllerMock = mock(EventAnalyticsController.self)
        loggerMock = mock(Logger.self)
        let route = AVAudioSession.sharedInstance().currentRoute
        audioSessionMock = mock(AudioSession.self)
        given(audioSessionMock.getCurrentRoute()).willReturn(route)
        defaultDeviceController = DefaultDeviceController(audioSession: audioSessionMock,
                                                          videoClientController: videoClientControllerMock,
                                                          eventAnalyticsController: eventAnalyticsControllerMock,
                                                          logger: loggerMock)
    }

    func testListAudioDevices() {
        let availableInputs = AVAudioSession.sharedInstance().availableInputs
        given(audioSessionMock.getAvailableInputs()).willReturn(availableInputs)

        let audioDevices = defaultDeviceController.listAudioDevices()
        XCTAssertTrue(!audioDevices.isEmpty)
        XCTAssertEqual(audioDevices[1].type, MediaDeviceType.audioBuiltInSpeaker)
        XCTAssertEqual(audioDevices[1].label, "Built-in Speaker")
    }

    func testListAudioDevicesShouldDedupe() {
        let bt = MockedAudioSessionPortDescription(portType: AVAudioSession.Port.bluetoothHFP, portName: "BT")
        let btUnkown = MockedAudioSessionPortDescription(portType: AVAudioSession.Port.init(rawValue: "Unkown"), portName: "BT")
        let availableInputs = [bt, btUnkown]
        given(audioSessionMock.getAvailableInputs()).willReturn(availableInputs)

        let audioDevices = defaultDeviceController.listAudioDevices()

        // contains 1 bt and 1 loud speaker
        XCTAssertEqual(2, audioDevices.count)
        XCTAssertEqual(audioDevices[0].type, MediaDeviceType.audioBluetooth)
        XCTAssertEqual(audioDevices[0].label, "BT")
        XCTAssertEqual(audioDevices[1].type, MediaDeviceType.audioBuiltInSpeaker)
        XCTAssertEqual(audioDevices[1].label, "Built-in Speaker")
    }

    func testChooseAudioDevice_speaker() {
        let speakerDevice = MediaDevice(label: "Built-in Speaker")
        defaultDeviceController.chooseAudioDevice(mediaDevice: speakerDevice)

        verify(audioSessionMock.overrideOutputAudioPort(.speaker)).wasCalled()
    }

    func testChooseAudioDevice_nonSpeaker() {
        let availableInputs = AVAudioSession.sharedInstance().availableInputs
        let nonSpeakerDevice = MediaDevice.fromAVSessionPort(port: (availableInputs?[0])!)
        defaultDeviceController.chooseAudioDevice(mediaDevice: nonSpeakerDevice)

        verify(audioSessionMock.setPreferredInput(nonSpeakerDevice.port)).wasCalled()
    }

    func testSwitchCamera() {
        defaultDeviceController.switchCamera()

        verify(videoClientControllerMock.switchCamera()).wasCalled()
    }

    func testGetCurrentAudioDevice() {
        let currentDevice = defaultDeviceController.getActiveAudioDevice()
        let route = AVAudioSession.sharedInstance().currentRoute
        var expected: MediaDevice?
        if route.outputs.count > 0 {
            if route.outputs[0].portType == .builtInSpeaker {
                expected = MediaDevice(label: "Built-in Speaker", type: MediaDeviceType.audioBuiltInSpeaker)
            } else if route.inputs.count > 0 {
                expected = MediaDevice.fromAVSessionPort(port: route.inputs[0])
            }
        }

        verify(audioSessionMock.getCurrentRoute()).wasCalled(2)
        XCTAssertEqual(currentDevice?.label, expected?.label)
        XCTAssertEqual(currentDevice?.type, expected?.type)
    }
}