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

import AmazonChimeSDK
import AVFoundation
import XCTest

class MediaDeviceTests: XCTestCase {
    private var mediaDevice: MediaDevice?

    override func setUp() {
        super.setUp()
        mediaDevice = MediaDevice(label: "mediaDevice")
    }

    func testMediaDeviceShouldBeInitialized() {
        XCTAssertEqual(mediaDevice?.label, "mediaDevice")
        XCTAssertNil(mediaDevice?.port)
        XCTAssertEqual(mediaDevice?.type, .other)
        XCTAssertEqual(mediaDevice?.description, "mediaDevice - other")
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class MediaDeviceTypeTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(MediaDeviceType.audioBluetooth.description, "audioBluetooth")
        XCTAssertEqual(MediaDeviceType.audioWiredHeadset.description, "audioWiredHeadset")
        XCTAssertEqual(MediaDeviceType.audioBuiltInSpeaker.description, "audioBuiltInSpeaker")
        XCTAssertEqual(MediaDeviceType.audioHandset.description, "audioHandset")
        XCTAssertEqual(MediaDeviceType.videoFrontCamera.description, "videoFrontCamera")
        XCTAssertEqual(MediaDeviceType.videoBackCamera.description, "videoBackCamera")
        XCTAssertEqual(MediaDeviceType.other.description, "other")
    }
}


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

import Foundation
import AVFoundation

class MockedAudioSessionPortDescription: AVAudioSessionPortDescription {
    var mockPortType: AVAudioSession.Port
    var mockPortName: String
    var mockUid: String
    var mockHasHardwareVoiceCallProcessing: Bool

    init(portType: AVAudioSession.Port,
         portName: String) {
        self.mockPortType = portType
        self.mockPortName = portName
        self.mockUid = UUID().uuidString
        self.mockHasHardwareVoiceCallProcessing = false
        super.init()
    }

    override var portType: AVAudioSession.Port {
        mockPortType
    }

    override var portName: String {
        mockPortName
    }

    override var uid: String {
        mockUid
    }

    override  var hasHardwareVoiceCallProcessing: Bool {
        mockHasHardwareVoiceCallProcessing
    }

    override var channels: [AVAudioSessionChannelDescription]? {
        nil
    }

    override var dataSources: [AVAudioSessionDataSourceDescription]? {
        nil
    }

    override var selectedDataSource: AVAudioSessionDataSourceDescription? {
        nil
    }

    override var preferredDataSource: AVAudioSessionDataSourceDescription? {
        nil
    }

    override func setPreferredDataSource(_ dataSource: AVAudioSessionDataSourceDescription?) throws {}
}


//
//  DefaultEventBufferTests.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 DefaultEventBufferTests: XCTestCase {
    private var eventSqliteBuffer: DefaultEventBuffer!
    private var eventDao: EventDaoMock!
    private var dirtyEventDao: DirtyEventDaoMock!
    private var converter: IngestionEventConverterMock!
    private var ingestionConfiguration: IngestionConfiguration!
    private var eventSender: EventSenderMock!
    private var logger: LoggerMock!

    private let meetingEvent = SDKEvent(eventName: EventName.meetingEnded, eventAttributes: [EventAttributeName.poorConnectionCount: 0])

    private let ingestionEvent = IngestionMeetingEvent(name: String(describing: EventName.meetingEnded),
                                                       eventAttributes: [:])
    private let meetingEventItem = MeetingEventItem(id: "sdfdf",
                                                    data: IngestionMeetingEvent(name: String(describing: EventName.meetingEnded),
                                                                                eventAttributes: [:]))
    private let ingestionRecord = IngestionRecord(metadata: [:],
                                                  events: [IngestionEvent(type: "Meet",
                                                                          metadata: [:],
                                                                          payloads: [IngestionPayload(name: "aeeee", ts: 1232132)])])
    override func setUp() {
        ingestionConfiguration = IngestionConfigurationBuilder().build(disabled: false,
                                                                       ingestionUrl: "a",
                                                                       clientConiguration: MeetingEventClientConfiguration(eventClientJoinToken: "",
                                                                                meetingId: "",
                                                                                attendeeId: ""))
        converter = mock(IngestionEventConverter.self).initialize()
        eventDao = mock(EventDao.self)
        dirtyEventDao = mock(DirtyEventDao.self)
        eventSender = mock(EventSender.self)
        logger = mock(Logger.self)
        given(converter.toIngestionRecord(meetingEvents: any(), ingestionConfiguration: any())).willReturn(ingestionRecord)
        given(converter.toIngestionRecord(dirtyMeetingEvents: any(), ingestionConfiguration: any())).willReturn(ingestionRecord)
        given(dirtyEventDao.queryDirtyMeetingEventItems(size: any())).willReturn([DirtyMeetingEventItem(id: "aa", data: ingestionEvent, ttl: 11123)])

        given(converter.toIngestionMeetingEvent(event: any(), ingestionConfiguration: any())).willReturn(IngestionMeetingEvent(name: "dsfdsf", eventAttributes: [:]))
        eventSqliteBuffer = DefaultEventBuffer(ingestionConfiguration: ingestionConfiguration,
                                              eventDao: eventDao,
                                              dirtyEventDao: dirtyEventDao,
                                              converter: converter,
                                              eventSender: eventSender,
                                              logger: logger)
    }

    func testAddShouldInvokeInsertMeetingEvent() {
        given(eventDao.insertMeetingEvent(event: any())).willReturn(true)

        eventSqliteBuffer.add(item: meetingEvent)

        verify(eventDao.insertMeetingEvent(event: any())).wasCalled(1)
    }

    func testProcessShouldInvokeInsertMeetingEvent() {
        given(eventDao.queryMeetingEventItems(size: any())).willReturn([meetingEventItem])
        given(eventSender.sendEvents(ingestionRecord: any(), completionHandler: any())).willReturn()

        eventSqliteBuffer.process()

        verify(eventDao.queryMeetingEventItems(size: any())).wasCalled(1)
        verify(eventSender.sendEvents(ingestionRecord: any(), completionHandler: any())).wasCalled(2)
        verify(converter.toIngestionRecord(meetingEvents: any(), ingestionConfiguration: any())).wasCalled(1)
    }
}


//
//  DefaultEventReporterTests.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 DefaultEventReporterTests: XCTestCase {
    private var eventBuffer: EventBufferMock!
    private var logger: LoggerMock!
    private var timer: SchedulerMock!

    private let clientConfigurationMock = MeetingEventClientConfiguration(eventClientJoinToken: "", meetingId: "meetingId", attendeeId: "attendeeId")
    private let ingestionUrl = "ingestionUrl"
    private let ingestionRecord = IngestionRecord(metadata: [:],
                                                  events: [IngestionEvent(type: "Meet",
                                                                          metadata: [:],
                                                                          payloads: [IngestionPayload(name: "aeeee", ts: 1232132)])])
    private let emptyIngestionRecord = IngestionRecord(metadata: [:], events: [])

    override func setUp() {
        eventBuffer = mock(EventBuffer.self)
        logger = mock(Logger.self)
        timer = mock(Scheduler.self)
        given(eventBuffer.process()).willReturn()
    }

    func testDefaultEventReporterShouldCallIntervalSchedulerStartIfDisabledIsTrue() {
        let ingestionUrl = "ingestionUrl"
        let ingestionConfiguration = IngestionConfigurationBuilder().build(disabled: true,
                                                                           ingestionUrl: ingestionUrl,
                                                                           clientConiguration: clientConfigurationMock)
        DefaultEventReporter(ingestionConfiguration: ingestionConfiguration,
                             eventBuffer: eventBuffer,
                             logger: logger)

        verify(eventBuffer.process()).wasNeverCalled()
    }

    func testDefaultEventReporterShouldCallIntervalSchedulerStartIfDisabledIsFalse() {
        let ingestionConfiguration = IngestionConfigurationBuilder().build(disabled: false,
                                                                           ingestionUrl: ingestionUrl,
                                                                           clientConiguration: clientConfigurationMock)

        DefaultEventReporter(ingestionConfiguration: ingestionConfiguration,
                             eventBuffer: eventBuffer,
                             logger: logger,
                             timer: timer)

        verify(timer.start()).wasCalled(1)
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class IngestionConfigurationBuilderTests: XCTestCase {
    private let clientConfigurationMock = MeetingEventClientConfiguration(eventClientJoinToken: "", meetingId: "meetingId", attendeeId: "attendeeId")
    private let ingestionUrl = "url"
    private let disabled = false
    private let bufferSize = 10
    private let flushSize = 10
    private let flushIntervalMs = Int64(500)
    private let retryCountLimit = 1

    func testIngestionConfigurationBuilderShouldBeAbleToInitializedIngestionConfiguration() {
        let ingestionConfiguration = IngestionConfigurationBuilder()
            .setFlushIntervalMs(flushIntervalMs: flushIntervalMs)
            .setFlushSize(flushSize: flushSize)
            .setRetryCountLimit(retryCountLimit: retryCountLimit)
            .build(disabled: true, ingestionUrl: ingestionUrl, clientConiguration: clientConfigurationMock)

        XCTAssertNotNil(ingestionConfiguration)
        XCTAssertEqual(ingestionUrl, ingestionConfiguration.ingestionUrl)
        XCTAssertEqual(true, ingestionConfiguration.disabled)
        XCTAssertEqual(flushSize, ingestionConfiguration.flushSize)
        XCTAssertEqual(flushIntervalMs, ingestionConfiguration.flushIntervalMs)
        XCTAssertEqual(retryCountLimit, ingestionConfiguration.retryCountLimit)
    }

    func testIngestionConfigurationBuilderShouldReturnNilIfIngestionUrlOrClientConfigurationIsNotPassed() {
        let ingestionConfiguration = IngestionConfigurationBuilder().build(disabled: true,
                                                                           ingestionUrl: ingestionUrl,
                                                                           clientConiguration: clientConfigurationMock)

        XCTAssertNotNil(ingestionConfiguration)
    }

    func testIngestionConfigurationShouldDefaultSomeValuesForOptionalParameters() {
        let ingestionConfiguration = IngestionConfigurationBuilder().build(disabled: false, ingestionUrl: ingestionUrl,
                                                                           clientConiguration: clientConfigurationMock)

        XCTAssertGreaterThan(ingestionConfiguration.flushSize, 0)
        XCTAssertGreaterThan(ingestionConfiguration.retryCountLimit, 0)
        XCTAssertEqual(ingestionConfiguration.disabled, false)
        XCTAssertGreaterThan(ingestionConfiguration.flushIntervalMs, 0)
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class IngestionConfigurationTests: XCTestCase {
    private let clientConfigurationMock = MeetingEventClientConfiguration(eventClientJoinToken: "", meetingId: "meetingId", attendeeId: "attendeeId")
    private let ingestionUrl = ""
    private let disabled = false
    private let bufferSize = 50
    private let flushSize = 20
    private let flushIntervalMs = Int64(500)
    private let retryCountLimit = 2

    func testIngestionConfigurationShouldBeInitialized() {
        let ingestionConfiguration = IngestionConfiguration(clientConfiguration: clientConfigurationMock,
                                                            ingestionUrl: ingestionUrl,
                                                            disabled: disabled,
                                                            flushSize: flushSize,
                                                            flushIntervalMs: flushIntervalMs,
                                                            retryCountLimit: retryCountLimit)

        XCTAssertEqual(ingestionUrl, ingestionConfiguration.ingestionUrl)
        XCTAssertEqual(disabled, ingestionConfiguration.disabled)
        XCTAssertEqual(flushSize, ingestionConfiguration.flushSize)
        XCTAssertEqual(flushIntervalMs, ingestionConfiguration.flushIntervalMs)
        XCTAssertEqual(retryCountLimit, ingestionConfiguration.retryCountLimit)
    }

    func testIngestionConfigurationShouldDefaultToCertainValueWhenNegative() {
        let ingestionConfiguration = IngestionConfiguration(clientConfiguration: clientConfigurationMock,
                                                            ingestionUrl: ingestionUrl,
                                                            disabled: disabled,
                                                            flushSize: -1,
                                                            flushIntervalMs: -1,
                                                            retryCountLimit: -1)

        XCTAssertGreaterThan(ingestionConfiguration.flushSize, 0)
        XCTAssertGreaterThan(ingestionConfiguration.flushIntervalMs, 0)
        XCTAssertGreaterThan(ingestionConfiguration.retryCountLimit, 0)
    }

    func testIngestionConfigurationShouldDefaultToCertainValueWhenGivenValuesAreLarge() {
        let largeValue = 1000000000
        let ingestionConfiguration = IngestionConfiguration(clientConfiguration: clientConfigurationMock,
                                                            ingestionUrl: ingestionUrl,
                                                            disabled: disabled,
                                                            flushSize: largeValue,
                                                            flushIntervalMs: Int64(largeValue),
                                                            retryCountLimit: largeValue)

        XCTAssertLessThan(ingestionConfiguration.flushSize, largeValue)
        XCTAssertLessThan(ingestionConfiguration.retryCountLimit, largeValue)
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class MeetingEventClientConfigurationTests: XCTestCase {

    func testConfigShouldPutMeetingIdAndAttendeeIdInMetadataAttributes() {
        let testMeetingId = "meeting_id_123"
        let testAttendeeId = "attendee_id_123"
        let config = MeetingEventClientConfiguration(eventClientJoinToken: "",
                                                     meetingId: testMeetingId,
                                                     attendeeId: testAttendeeId)
        guard let meetingId = config.metadataAttributes[EventAttributeName.meetingId.description] as? String else {
            XCTFail("`meetingId` is nil")
            return
        }
        guard let attendeeId = config.metadataAttributes[EventAttributeName.attendeeId.description] as? String else {
            XCTFail("`attendeeId` is nil")
            return
        }
        XCTAssertEqual(testMeetingId, meetingId)
        XCTAssertEqual(testAttendeeId, attendeeId)
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class AttendeeStatusTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(AttendeeStatus.joined.description, "joined")
        XCTAssertEqual(AttendeeStatus.left.description, "left")
        XCTAssertEqual(AttendeeStatus.dropped.description, "dropped")
    }
}


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

import Foundation
@testable import AmazonChimeSDK
import Mockingbird
import XCTest

class DefaultEventAnalyticsControllerTests: CommonTestCase {
    var eventAnalyticsController: DefaultEventAnalyticsController!
    var meetingStatsCollectorMock: MeetingStatsCollectorMock!
    var eventReporterMock: EventReporterMock!

    override func setUp() {
        super.setUp()
        eventReporterMock = mock(EventReporter.self)
        meetingStatsCollectorMock = mock(MeetingStatsCollector.self)
        given(meetingStatsCollectorMock.getMeetingStats()).willReturn([AnyHashable: Any]())
        eventAnalyticsController = DefaultEventAnalyticsController(meetingSessionConfig: meetingSessionConfigurationMock,
                                                                   meetingStatsCollector: meetingStatsCollectorMock,
                                                                   logger: loggerMock,
                                                                   eventReporter: eventReporterMock)
    }

    func testPublishEvent_eventDidReceive() {
        let mockObserver = mock(EventAnalyticsObserver.self)
        eventAnalyticsController.addEventAnalyticsObserver(observer: mockObserver)
        eventAnalyticsController.publishEvent(name: .meetingStartRequested)
        let expectation = eventually {
            verify(mockObserver.eventDidReceive(name: .meetingStartRequested, attributes: any())).wasCalled()
        }
        wait(for: [expectation], timeout: 1.0)
    }

    func testPublishEvent_eventReporter_report() {
        let mockObserver = mock(EventAnalyticsObserver.self)
        eventAnalyticsController.addEventAnalyticsObserver(observer: mockObserver)
        eventAnalyticsController.publishEvent(name: .meetingStartRequested)

        verify(eventReporterMock.report(event: any())).wasCalled(1)
    }

    func testPushHistoryState_eventReporter_report() {
        let mockObserver = mock(EventAnalyticsObserver.self)
        eventAnalyticsController.addEventAnalyticsObserver(observer: mockObserver)
        eventAnalyticsController.pushHistory(historyEventName: .meetingReconnected)

        verify(eventReporterMock.report(event: any())).wasCalled(1)
    }
}


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

import Foundation
@testable import AmazonChimeSDK
import Mockingbird
import XCTest

class DefaultMeetingStatsCollectorTests: CommonTestCase {
    var meetingStatsCollector: DefaultMeetingStatsCollector!

    override func setUp() {
        super.setUp()
        meetingStatsCollector = DefaultMeetingStatsCollector(logger: loggerMock)
    }

    func testIncrementRetryCount() {
        let retryCount = meetingStatsCollector.getMeetingStats()[EventAttributeName.retryCount] as? Int
        meetingStatsCollector.incrementRetryCount()
        let updatedRetryCount = meetingStatsCollector.getMeetingStats()[EventAttributeName.retryCount] as? Int
        if let expected = retryCount, let result = updatedRetryCount {
            XCTAssertEqual(expected + 1, result)
        } else {
            XCTFail("The type conversion failed")
        }
    }

    func testIncrementPoorConnectionCount() {
        let poorConnectionCount = meetingStatsCollector.getMeetingStats()[EventAttributeName.poorConnectionCount] as? Int
        meetingStatsCollector.incrementPoorConnectionCount()
        let updatedPoorConnectionCount = meetingStatsCollector.getMeetingStats()[EventAttributeName.poorConnectionCount] as? Int
        if let expected = poorConnectionCount, let result = updatedPoorConnectionCount {
            XCTAssertEqual(expected + 1, result)
        } else {
            XCTFail("The type conversion failed")
        }
    }

    func testResetMeetingStats() {
        meetingStatsCollector.resetMeetingStats()
        let stats = meetingStatsCollector.getMeetingStats()
        XCTAssertEqual(stats[EventAttributeName.poorConnectionCount] as? Int, 0)
        XCTAssertEqual(stats[EventAttributeName.maxVideoTileCount] as? Int, 0)
        XCTAssertEqual(stats[EventAttributeName.retryCount] as? Int, 0)
        XCTAssertEqual(stats[EventAttributeName.meetingDurationMs] as? Int, 0)
    }

}


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

@testable import AmazonChimeSDK
import XCTest

class AudioClientStateTests: XCTestCase {
    func testDescriptionShouldMatch() {
        XCTAssertEqual(AudioClientState.initialized.description, "initialized")
        XCTAssertEqual(AudioClientState.started.description, "started")
        XCTAssertEqual(AudioClientState.stopped.description, "stopped")
    }
}


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

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

class DefaultAudioClientControllerTests: CommonTestCase {
    let callKitEnabled = false

    var audioClientMock: AudioClientProtocolMock!
    var audioClientObserverMock: AudioClientObserverMock!
    var audioSessionMock: AudioSessionMock!
    var audioLockMock: AudioLockMock!
    var activeSpeakerMock: ActiveSpeakerDetectorFacadeMock!

    var eventAnalyticsControllerMock: EventAnalyticsControllerMock!
    var meetingStatsCollectorMock: MeetingStatsCollectorMock!

    var defaultAudioClientController: DefaultAudioClientController!

    override func setUp() {
        super.setUp()

        audioClientMock = mock(AudioClientProtocol.self)
        audioClientObserverMock = mock(AudioClientObserver.self)
        audioSessionMock = mock(AudioSession.self)
        audioLockMock = mock(AudioLock.self)
        eventAnalyticsControllerMock = mock(EventAnalyticsController.self)
        meetingStatsCollectorMock = mock(MeetingStatsCollector.self)
        activeSpeakerMock = mock(ActiveSpeakerDetectorFacade.self)

        given(meetingStatsCollectorMock.getMeetingStats()).will { [AnyHashable: Any]() }

        given(audioSessionMock.getRecordPermission()).willReturn(.granted)
        given(audioClientMock.startSession(any(),
                                           basePort: any(),
                                           callId: any(),
                                           profileId: any(),
                                           microphoneMute: any(),
                                           speakerMute: any(),
                                           isPresenter: any(),
                                           sessionToken: any(),
                                           audioWsUrl: any(),
                                           callKitEnabled: any(),
                                           appInfo: any(),
                                           audioMode: any())).willReturn(AUDIO_CLIENT_OK)

        defaultAudioClientController = DefaultAudioClientController(audioClient: audioClientMock,
                                                                    audioClientObserver: audioClientObserverMock,
                                                                    audioSession: audioSessionMock,
                                                                    audioClientLock: audioLockMock,
                                                                    eventAnalyticsController: eventAnalyticsControllerMock,
                                                                    meetingStatsCollector: meetingStatsCollectorMock,
                                                                    activeSpeakerDetector: activeSpeakerMock,
                                                                    logger: loggerMock)
    }

    func testSetMute_stateInitialized() {
        DefaultAudioClientController.state = AudioClientState.initialized

        XCTAssertFalse(defaultAudioClientController.setMute(mute: true))
    }

    func testSetMute_stateStarted() {
        DefaultAudioClientController.state = AudioClientState.started
        given(audioClientMock.setMicrophoneMuted(any(Bool.self))).willReturn(Int(AUDIO_CLIENT_OK.rawValue))

        XCTAssertTrue(defaultAudioClientController.setMute(mute: true))
    }

    func testStart_recordPermissionNotGranted() {
        given(audioSessionMock.getRecordPermission()).willReturn(.denied)

        XCTAssertThrowsError(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                    audioHostUrl: audioHostUrlWithPort,
                                                                    meetingId: meetingId,
                                                                    attendeeId: attendeeId,
                                                                    joinToken: joinToken,
                                                                    callKitEnabled: callKitEnabled,
                                                                    audioMode: .stereo48K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStart_alreadyStarted() {
        DefaultAudioClientController.state = .started
        given(audioSessionMock.getRecordPermission()).willReturn(.granted)

        XCTAssertThrowsError(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                    audioHostUrl: audioHostUrlWithPort,
                                                                    meetingId: meetingId,
                                                                    attendeeId: attendeeId,
                                                                    joinToken: joinToken,
                                                                    callKitEnabled: callKitEnabled,
                                                                    audioMode: .stereo48K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStop_stoppedOk() {
        DefaultAudioClientController.state = .started
        given(audioClientMock.stopSession()).willReturn(Int(AUDIO_CLIENT_OK.rawValue))

        defaultAudioClientController.stop()

        let expect = eventually {
            verify(audioLockMock.lock()).wasCalled()
            verify(audioLockMock.unlock()).wasCalled()
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingEnded, attributes: any())).wasCalled()
            verify(meetingStatsCollectorMock.resetMeetingStats()).wasCalled()
        }

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

    func testStart_startedOk() {
        DefaultAudioClientController.state = .initialized

        XCTAssertNoThrow(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                audioHostUrl: audioHostUrlWithPort,
                                                                meetingId: meetingId,
                                                                attendeeId: attendeeId,
                                                                joinToken: joinToken,
                                                                callKitEnabled: callKitEnabled,
                                                                audioMode: .stereo48K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioClientObserverMock.notifyAudioClientObserver(observerFunction: any())).wasCalled()
        verify(audioClientMock.startSession(self.audioHostUrl,
                                            basePort: 1820,
                                            callId: self.meetingId,
                                            profileId: self.attendeeId,
                                            microphoneMute: false,
                                            speakerMute: false,
                                            isPresenter: true,
                                            sessionToken: self.joinToken,
                                            audioWsUrl: self.audioFallbackUrl,
                                            callKitEnabled: false,
                                            appInfo: any(),
                                            audioMode: .Stereo48K)).wasCalled()
        verify(eventAnalyticsControllerMock.publishEvent(name: .meetingStartRequested)).wasCalled()
        XCTAssertEqual(.started, DefaultAudioClientController.state)
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStartWithMono48K_startedOk() {
        DefaultAudioClientController.state = .initialized

        XCTAssertNoThrow(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                audioHostUrl: audioHostUrlWithPort,
                                                                meetingId: meetingId,
                                                                attendeeId: attendeeId,
                                                                joinToken: joinToken,
                                                                callKitEnabled: callKitEnabled,
                                                                audioMode: .mono48K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioClientObserverMock.notifyAudioClientObserver(observerFunction: any())).wasCalled()
        verify(audioClientMock.startSession(self.audioHostUrl,
                                            basePort: 1820,
                                            callId: self.meetingId,
                                            profileId: self.attendeeId,
                                            microphoneMute: false,
                                            speakerMute: false,
                                            isPresenter: true,
                                            sessionToken: self.joinToken,
                                            audioWsUrl: self.audioFallbackUrl,
                                            callKitEnabled: false,
                                            appInfo: any(),
                                            audioMode: .Mono48K)).wasCalled()
        verify(eventAnalyticsControllerMock.publishEvent(name: .meetingStartRequested)).wasCalled()
        XCTAssertEqual(.started, DefaultAudioClientController.state)
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStartWithMono16K_startedOk() {
        DefaultAudioClientController.state = .initialized

        XCTAssertNoThrow(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                audioHostUrl: audioHostUrlWithPort,
                                                                meetingId: meetingId,
                                                                attendeeId: attendeeId,
                                                                joinToken: joinToken,
                                                                callKitEnabled: callKitEnabled,
                                                                audioMode: .mono16K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioClientObserverMock.notifyAudioClientObserver(observerFunction: any())).wasCalled()
        verify(audioClientMock.startSession(self.audioHostUrl,
                                            basePort: 1820,
                                            callId: self.meetingId,
                                            profileId: self.attendeeId,
                                            microphoneMute: false,
                                            speakerMute: false,
                                            isPresenter: true,
                                            sessionToken: self.joinToken,
                                            audioWsUrl: self.audioFallbackUrl,
                                            callKitEnabled: false,
                                            appInfo: any(),
                                            audioMode: .Mono16K)).wasCalled()
        verify(eventAnalyticsControllerMock.publishEvent(name: .meetingStartRequested)).wasCalled()
        XCTAssertEqual(.started, DefaultAudioClientController.state)
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStartWithNoDevice_startedOk() {
        DefaultAudioClientController.state = .initialized

        XCTAssertNoThrow(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                audioHostUrl: audioHostUrlWithPort,
                                                                meetingId: meetingId,
                                                                attendeeId: attendeeId,
                                                                joinToken: joinToken,
                                                                callKitEnabled: callKitEnabled,
                                                                audioMode: .nodevice))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioClientObserverMock.notifyAudioClientObserver(observerFunction: any())).wasCalled()
        verify(audioClientMock.startSession(self.audioHostUrl,
                                            basePort: 1820,
                                            callId: self.meetingId,
                                            profileId: self.attendeeId,
                                            microphoneMute: false,
                                            speakerMute: false,
                                            isPresenter: true,
                                            sessionToken: self.joinToken,
                                            audioWsUrl: self.audioFallbackUrl,
                                            callKitEnabled: false,
                                            appInfo: any(),
                                            audioMode: .NoDevice)).wasCalled()
        verify(eventAnalyticsControllerMock.publishEvent(name: .meetingStartRequested)).wasCalled()
        XCTAssertEqual(.started, DefaultAudioClientController.state)
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testStart_failedToStart() {
        DefaultAudioClientController.state = .initialized
        given(audioClientMock.startSession(any(),
                                           basePort: any(),
                                           callId: any(),
                                           profileId: any(),
                                           microphoneMute: any(),
                                           speakerMute: any(),
                                           isPresenter: any(),
                                           sessionToken: any(),
                                           audioWsUrl: any(),
                                           callKitEnabled: any(),
                                           appInfo: any(),
                                           audioMode: any())).willReturn(AUDIO_CLIENT_ERR)

        XCTAssertThrowsError(try defaultAudioClientController.start(audioFallbackUrl: audioFallbackUrl,
                                                                    audioHostUrl: audioHostUrlWithPort,
                                                                    meetingId: meetingId,
                                                                    attendeeId: attendeeId,
                                                                    joinToken: joinToken,
                                                                    callKitEnabled: callKitEnabled,
                                                                    audioMode: .stereo48K))
        verify(audioLockMock.lock()).wasCalled()
        verify(audioClientObserverMock.notifyAudioClientObserver(observerFunction: any())).wasCalled()
        verify(audioClientMock.startSession(self.audioHostUrl,
                                            basePort: 1820,
                                            callId: self.meetingId,
                                            profileId: self.attendeeId,
                                            microphoneMute: false,
                                            speakerMute: false,
                                            isPresenter: true,
                                            sessionToken: self.joinToken,
                                            audioWsUrl: self.audioFallbackUrl,
                                            callKitEnabled: false,
                                            appInfo: any(),
                                            audioMode: .Stereo48K)).wasCalled()
        XCTAssertEqual(.initialized, DefaultAudioClientController.state)
        verify(audioLockMock.unlock()).wasCalled()
    }

    func testSetVoiceFocusEnabled_success() {
        DefaultAudioClientController.state = .started

        given(audioClientMock.setBliteNSSelected(any())).willReturn(Int(AUDIO_CLIENT_OK.rawValue))

        XCTAssertTrue(defaultAudioClientController.setVoiceFocusEnabled(enabled: true))
        verify(audioClientMock.setBliteNSSelected(true)).wasCalled()

        XCTAssertTrue(defaultAudioClientController.setVoiceFocusEnabled(enabled: false))
        verify(audioClientMock.setBliteNSSelected(false)).wasCalled()
    }

    func testSetVoiceFocusEnabled_failure_audioClientNotStarted() {
        DefaultAudioClientController.state = .initialized

        XCTAssertFalse(defaultAudioClientController.setVoiceFocusEnabled(enabled: true))
        verify(audioClientMock.setBliteNSSelected(any())).wasNeverCalled()

        XCTAssertFalse(defaultAudioClientController.setVoiceFocusEnabled(enabled: false))
        verify(audioClientMock.setBliteNSSelected(any())).wasNeverCalled()
    }

    func testSetVoiceFocusEnabled_failure_mediaFailure() {
        DefaultAudioClientController.state = .started

        given(audioClientMock.setBliteNSSelected(any())).willReturn(Int(AUDIO_CLIENT_ERR.rawValue))

        XCTAssertFalse(defaultAudioClientController.setVoiceFocusEnabled(enabled: true))
        verify(audioClientMock.setBliteNSSelected(true)).wasCalled()

        XCTAssertFalse(defaultAudioClientController.setVoiceFocusEnabled(enabled: false))
        verify(audioClientMock.setBliteNSSelected(false)).wasCalled()
    }

    func testIsVoiceFocusEnabled_success() {
        DefaultAudioClientController.state = .started

        given(audioClientMock.isBliteNSSelected()).willReturn(true)
        XCTAssertTrue(defaultAudioClientController.isVoiceFocusEnabled())
        verify(audioClientMock.isBliteNSSelected()).wasCalled()

        given(audioClientMock.isBliteNSSelected()).willReturn(false)
        XCTAssertFalse(defaultAudioClientController.isVoiceFocusEnabled())
        verify(audioClientMock.isBliteNSSelected()).wasCalled(2)
    }

    func testIsVoiceFocusEnabled_failure_audioClientNotStarted() {
        DefaultAudioClientController.state = .initialized

        XCTAssertFalse(defaultAudioClientController.isVoiceFocusEnabled())
        verify(audioClientMock.isBliteNSSelected()).wasNeverCalled()
    }
}


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

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import Mockingbird
import XCTest

class DefaultAudioClientObserverTests: XCTestCase {
    var config: MeetingSessionConfiguration!
    let externalMeetingId = "external-meeting-id"
    let audioFallbackUrl = "audioFallbackUrl"
    let audioHostUrl = "audioHostUrl"
    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"
    let timestampMs: Int64 = 1632087029249
    let transcriptionRegion = "us-east-1"
    let transcriptionConfiguration = "transcription-configuration"
    let failedMessage = "Internal server error"
    var audioClientMock: AudioClientProtocolMock!
    var clientMetricsCollectorMock: ClientMetricsCollectorMock!
    var audioSessionMock: AudioSessionMock!
    var audioLockMock: AudioLockMock!
    var eventAnalyticsControllerMock: EventAnalyticsControllerMock!
    var loggerMock: LoggerMock!
    var defaultAudioClientObserver: DefaultAudioClientObserver!
    var mockAudioVideoObserver: AudioVideoObserverMock!
    var mockRealTimeObserver: RealtimeObserverMock!
    var meetingStatsCollectorMock: MeetingStatsCollectorMock!
    var transcriptEventObserverMock: TranscriptEventObserverMock!

    let defaultTimeout = 1.0

    override func setUp() {
        mockAudioVideoObserver = mock(AudioVideoObserver.self)
        mockRealTimeObserver = mock(RealtimeObserver.self)
        audioClientMock = mock(AudioClientProtocol.self)
        clientMetricsCollectorMock = mock(ClientMetricsCollector.self)
        eventAnalyticsControllerMock = mock(EventAnalyticsController.self)
        audioLockMock = mock(AudioLock.self)
        meetingStatsCollectorMock = mock(MeetingStatsCollector.self)
        transcriptEventObserverMock = mock(TranscriptEventObserver.self)
        loggerMock = mock(Logger.self)

        given(meetingStatsCollectorMock.getMeetingStats()).will { [AnyHashable: Any]() }

        let mediaPlacementMock: MediaPlacementMock = mock(MediaPlacement.self)
            .initialize(audioFallbackUrl: audioFallbackUrl,
                        audioHostUrl: audioHostUrl,
                        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)
        let clientConfig = MeetingEventClientConfiguration(eventClientJoinToken: joinToken, meetingId: meetingId, attendeeId: attendeeId)

        config = mock(MeetingSessionConfiguration.self).initialize(createMeetingResponse: createMeetingResponseMock,
                                                                   createAttendeeResponse: createAttendeeResponseMock,
                                                                   urlRewriter: URLRewriterUtils.defaultUrlRewriter)
        defaultAudioClientObserver = DefaultAudioClientObserver(audioClient: audioClientMock,
                                                                clientMetricsCollector: clientMetricsCollectorMock,
                                                                audioClientLock: audioLockMock,
                                                                configuration: config,
                                                                logger: loggerMock,
                                                                eventAnalyticsController: eventAnalyticsControllerMock,
                                                                meetingStatsCollector: meetingStatsCollectorMock)
        defaultAudioClientObserver.subscribeToAudioClientStateChange(observer: mockAudioVideoObserver)
        defaultAudioClientObserver.subscribeToRealTimeEvents(observer: mockRealTimeObserver)
        defaultAudioClientObserver.subscribeToTranscriptEvent(observer: transcriptEventObserverMock)
    }

    func testAudioClientStateChanged_Connected() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTING,
                                                           status: audio_client_status_t.init(0))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(0))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidStart(reconnecting: false)).wasCalled()
        }

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

    func testAudioClientStateChanged_Reconnected() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_RECONNECTING,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidStart(reconnecting: true)).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionRecover() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.networkBecomePoor.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.connectionDidRecover()).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionBecomePoor() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.networkBecomePoor.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.connectionDidBecomePoor()).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionDrop() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_RECONNECTING,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidDrop()).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionCancelReconnect() {
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_RECONNECTING,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_NORMAL,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidCancelReconnect()).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionFailedFromConnecting() {
        given(audioClientMock.stopSession()).willReturn(0)
        DefaultAudioClientController.state = .started
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTING,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.audioServerHungup.rawValue))
        let expect = eventually {
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingFailed, attributes: any())).wasCalled()
            verify(mockAudioVideoObserver.audioSessionDidStopWithStatus(sessionStatus: any())).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionFailedFromConnected() {
        given(audioClientMock.stopSession()).willReturn(0)
        DefaultAudioClientController.state = .started
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.audioServerHungup.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidStopWithStatus(sessionStatus: any())).wasCalled()
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingFailed, attributes: any())).wasCalled()
            verify(meetingStatsCollectorMock.resetMeetingStats()).wasCalled()
        }

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

    func testAudioClientStateChanged_NotifiesOfInputDeviceFailure() {
        given(audioClientMock.stopSession()).willReturn(0)
        DefaultAudioClientController.state = .started
        let statusCode = MeetingSessionStatusCode.audioInputDeviceNotResponding.rawValue
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL,
                                                           status: audio_client_status_t.init(statusCode))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidStopWithStatus(sessionStatus: any(MeetingSessionStatus.self,
                                                                        where: { $0.statusCode.rawValue == statusCode }))).wasCalled()
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingFailed, attributes: any())).wasCalled()
            verify(meetingStatsCollectorMock.resetMeetingStats()).wasCalled()
        }

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

    func testAudioClientStateChanged_NotifiesOfOutputDeviceFailure() {
        given(audioClientMock.stopSession()).willReturn(0)
        DefaultAudioClientController.state = .started
        let statusCode = MeetingSessionStatusCode.audioOutputDeviceNotResponding.rawValue
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_CONNECTED,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL,
                                                           status: audio_client_status_t.init(statusCode))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidStopWithStatus(sessionStatus: any(MeetingSessionStatus.self,
                                                                        where: { $0.statusCode.rawValue == statusCode }))).wasCalled()
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingFailed, attributes: any())).wasCalled()
            verify(meetingStatsCollectorMock.resetMeetingStats()).wasCalled()
        }

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

    func testAudioClientStateChanged_ConnectionCancelledReconnect() {
        given(audioClientMock.stopSession()).willReturn(0)
        DefaultAudioClientController.state = .started
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_RECONNECTING,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.ok.rawValue))
        defaultAudioClientObserver.audioClientStateChanged(AUDIO_CLIENT_STATE_DISCONNECTED_ABNORMAL,
                                                           status: audio_client_status_t.init(MeetingSessionStatusCode.audioServerHungup.rawValue))
        let expect = eventually {
            verify(mockAudioVideoObserver.audioSessionDidCancelReconnect()).wasCalled()
            verify(mockAudioVideoObserver.audioSessionDidStopWithStatus(sessionStatus: any())).wasCalled()
            verify(eventAnalyticsControllerMock.publishEvent(name: .meetingFailed, attributes: any())).wasCalled()
            verify(meetingStatsCollectorMock.resetMeetingStats()).wasCalled()
        }

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

    func testAudioMetricChanged_processAudioClientMetrics() {
        var metrics = [AnyHashable: Any]()
        metrics[ObservableMetric.audioSendPacketLossPercent] = 50
        defaultAudioClientObserver.audioMetricsChanged(metrics)
        verify(clientMetricsCollectorMock.processAudioClientMetrics(metrics: any())).wasCalled()
    }
}

// MARK: - Signal Strength Tests
extension DefaultAudioClientObserverTests{
    func testSignalStrengthChanged_signalStrengthDidChange() {
        let signals = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 0)]
        defaultAudioClientObserver.signalStrengthChanged(signals as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.signalStrengthDidChange(signalUpdates: any())).wasCalled()
        }

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

    func testSignalStrengthChanged_signalStrengthDidChange_noDuplicate() {
        let signals = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 0)]
        defaultAudioClientObserver.signalStrengthChanged(signals as [Any])
        defaultAudioClientObserver.signalStrengthChanged(signals as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.signalStrengthDidChange(signalUpdates: any())).wasCalled(1)
        }

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

    func testSignalStrengthChanged_signalStrengthDidChange_differentSignalLevel() {
        let signals = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 0)]
        let signals2 = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        defaultAudioClientObserver.signalStrengthChanged(signals as [Any])
        defaultAudioClientObserver.signalStrengthChanged(signals2 as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.signalStrengthDidChange(signalUpdates: any())).wasCalled(2)
        }

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

// MARK: - Volume State Tests
extension DefaultAudioClientObserverTests{
    func testVolumeStateChanged_volumeDidChange() {
        let volumes = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 2)]
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.volumeDidChange(volumeUpdates: any())).wasCalled()
        }

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

    func testVolumeStateChanged_volumeDidChange_noDuplicate() {
        let volumes = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 2)]
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.volumeDidChange(volumeUpdates: any())).wasCalled(1)
        }

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

    func testVolumeStateChanged_volumeDidChange_differentVolumeLevel() {
        let volumes = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 2)]
        let volumes2 = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        defaultAudioClientObserver.volumeStateChanged(volumes2 as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.volumeDidChange(volumeUpdates: any())).wasCalled(2)
        }

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

    func testVolumeStateChanged_attendeesDidMute() {
        let volumes = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: -1)]
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidMute(attendeeInfo: any())).wasCalled()
        }

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

    func testVolumeStateChanged_attendeesDidUnMute() {
        let volumes = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: -1)]
        let volumes2 = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 2)]
        defaultAudioClientObserver.volumeStateChanged(volumes as [Any])
        defaultAudioClientObserver.volumeStateChanged(volumes2 as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidMute(attendeeInfo: any())).wasCalled()
            verify(mockRealTimeObserver.attendeesDidUnmute(attendeeInfo: any())).wasCalled()
        }

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

// MARK: - Attendees Presence Tests
extension DefaultAudioClientObserverTests{
    func testAttendeesPresenceChanged_attendeesDidJoin() {
        let attendees = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        defaultAudioClientObserver.attendeesPresenceChanged(attendees as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidJoin(attendeeInfo: any())).wasCalled()
        }

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

    func testAttendeesPresenceChanged_attendeesDidJoinDuplicate() {
        let attendees = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        defaultAudioClientObserver.attendeesPresenceChanged(attendees as [Any])
        defaultAudioClientObserver.attendeesPresenceChanged(attendees as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidJoin(attendeeInfo: any())).wasCalled(1)
        }

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

    func testAttendeesPresenceChanged_attendeesDidLeave() {
        let attendees = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        let attendees2 = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 2)]
        defaultAudioClientObserver.attendeesPresenceChanged(attendees as [Any])
        defaultAudioClientObserver.attendeesPresenceChanged(attendees2 as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidJoin(attendeeInfo: any())).wasCalled()
            verify(mockRealTimeObserver.attendeesDidLeave(attendeeInfo: any())).wasCalled()
        }

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

    func testAttendeesPresenceChanged_attendeesDidDrop() {
        let attendees = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 1)]
        let attendees2 = [AttendeeUpdate(profileId: attendeeId, externalUserId: externalUserId, data: 3)]
        defaultAudioClientObserver.attendeesPresenceChanged(attendees as [Any])
        defaultAudioClientObserver.attendeesPresenceChanged(attendees2 as [Any])
        let expect = eventually {
            verify(mockRealTimeObserver.attendeesDidJoin(attendeeInfo: any())).wasCalled()
            verify(mockRealTimeObserver.attendeesDidDrop(attendeeInfo: any())).wasCalled()
        }

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

// MARK: - Transcript Event Tests
extension DefaultAudioClientObserverTests{
    func testTranscriptEventsReceived_receivedTranscriptionStatus() {
        let statusStarted = TranscriptionStatusInternal(type: TranscriptionStatusTypeInternal.started,
                                                        eventTimeMs: timestampMs,
                                                        transcriptionRegion: transcriptionRegion,
                                                        transcriptionConfiguration: transcriptionConfiguration,
                                                        message: "")
        let statusFailed = TranscriptionStatusInternal(type: TranscriptionStatusTypeInternal.failed,
                                                       eventTimeMs: timestampMs,
                                                       transcriptionRegion: transcriptionRegion,
                                                       transcriptionConfiguration: transcriptionConfiguration,
                                                       message: failedMessage)
        let events = [statusStarted, statusFailed]
        defaultAudioClientObserver.transcriptEventsReceived(events as [Any])
        let expect = eventually {
            verify(transcriptEventObserverMock.transcriptEventDidReceive(transcriptEvent: any())).wasCalled(2)
        }

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

    func testTranscriptEventsReceived_receivedTranscript() {
        let item = TranscriptItemInternal(type: TranscriptItemTypeInternal.pronunciation,
                                          startTimeMs: timestampMs,
                                          endTimeMs: timestampMs,
                                          attendee: AttendeeInfoInternal(attendeeId: "attendee-id",
                                                                         externalUserId: "external-user-id"),
                                          content: "test",
                                          vocabularyFilterMatch: true,
                                          stable: false,
                                          confidence: 0.0)!
        let alternative = TranscriptAlternativeInternal(items: [item], entities: [], transcript: "test")!
        let result = TranscriptResultInternal(resultId: "result-id",
                                              channelId: "",
                                              isPartial: true,
                                              startTimeMs: timestampMs,
                                              endTimeMs: timestampMs,
                                              alternatives: [alternative],
                                              languageCode: "en-US",
                                              languageIdentification: [])!
        let transcript = TranscriptInternal(results: [result])
        let events = [transcript]
        defaultAudioClientObserver.transcriptEventsReceived(events as [Any])
        let expect = eventually {
            verify(transcriptEventObserverMock.transcriptEventDidReceive(transcriptEvent: any())).wasCalled()
        }

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

    func testTranscriptEventsReceived_receivedTranscriptWithNilEntities() {
        let item = TranscriptItemInternal(type: TranscriptItemTypeInternal.pronunciation,
                                          startTimeMs: timestampMs,
                                          endTimeMs: timestampMs,
                                          attendee: AttendeeInfoInternal(attendeeId: "attendee-id",
                                                                         externalUserId: "external-user-id"),
                                          content: "test",
                                          vocabularyFilterMatch: true,
                                          stable: false,
                                          confidence: 0.0)!
        let alternative = TranscriptAlternativeInternal(items: [item], entities: nil, transcript: "test") ?? TranscriptAlternativeInternal()
        let result = TranscriptResultInternal(resultId: "result-id",
                                              channelId: "",
                                              isPartial: true,
                                              startTimeMs: timestampMs,
                                              endTimeMs: timestampMs,
                                              alternatives: [alternative],
                                              languageCode: "en-US",
                                              languageIdentification: [])!
        let transcript = TranscriptInternal(results: [result])
        let events = [transcript]
        defaultAudioClientObserver.transcriptEventsReceived(events as [Any])
        let expect = eventually {
            verify(transcriptEventObserverMock.transcriptEventDidReceive(transcriptEvent: any())).wasCalled()
        }

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

    func testTranscriptEventsReceived_receivedTranscriptWithNilLanguageIdentification() {
        let item = TranscriptItemInternal(type: TranscriptItemTypeInternal.pronunciation,
                                          startTimeMs: timestampMs,
                                          endTimeMs: timestampMs,
                                          attendee: AttendeeInfoInternal(attendeeId: "attendee-id",
                                                                         externalUserId: "external-user-id"),
                                          content: "test",
                                          vocabularyFilterMatch: true,
                                          stable: false,
                                          confidence: 0.0)!
        let alternative = TranscriptAlternativeInternal(items: [item], entities: [], transcript: "test")!
        let result = TranscriptResultInternal(resultId: "result-id",
                                              channelId: "",
                                              isPartial: true,
                                              startTimeMs: timestampMs,
                                              endTimeMs: timestampMs,
                                              alternatives: [alternative],
                                              languageCode: "en-US",
                                              languageIdentification: nil) ?? TranscriptResultInternal()
        let transcript = TranscriptInternal(results: [result])
        let events = [transcript]
        defaultAudioClientObserver.transcriptEventsReceived(events as [Any])
        let expect = eventually {
            verify(transcriptEventObserverMock.transcriptEventDidReceive(transcriptEvent: any())).wasCalled()
        }

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


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

@testable import AmazonChimeSDK
import AmazonChimeSDKMedia
import Mockingbird
import XCTest

class DefaultAudioClientTests: XCTestCase {
    var loggerMock: LoggerMock!
    var defaultAudioClient: DefaultAudioClient!

    override func setUp() {
        loggerMock = mock(Logger.self)
        defaultAudioClient = DefaultAudioClient.shared(logger: loggerMock)
    }

    func testShared() {
        XCTAssertNotNil(DefaultAudioClient.shared(logger: loggerMock))
    }

    func testAudioLogCallBack_errorLogLevel() {
        let someErrorMessage = "some error message"
        defaultAudioClient.audioLogCallBack(loglevel_t(rawValue: Constants.fatalLevel), msg: someErrorMessage)

        verify(loggerMock.error(msg: someErrorMessage)).wasCalled()
    }

    func testAudioLogCallBack_fatalLogLevel() {
        let someFatalMessage = "some fatal message"
        defaultAudioClient.audioLogCallBack(loglevel_t(rawValue: Constants.errorLevel), msg: someFatalMessage)

        verify(loggerMock.error(msg: someFatalMessage)).wasCalled()
    }

    func testAudioLogCallBack_otherLogLevel() {
        let someMessage = "some message"
        defaultAudioClient.audioLogCallBack(loglevel_t(rawValue: 3), msg: someMessage)

        verify(loggerMock.info(msg: someMessage)).wasNeverCalled()
    }
}


//
//  DefaultContentShareVideoClientControllerTests.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 DefaultContentShareVideoClientControllerTests: CommonTestCase {
    var videoClientMock: VideoClientProtocolMock!
    var videoSourceMock: VideoSourceMock!
    var clientMetricsLollectorMock: ClientMetricsCollectorMock!
    var defaultContentShareVideoClientController: DefaultContentShareVideoClientController!

    override func setUp() {
        super.setUp()

        videoClientMock = mock(VideoClientProtocol.self)
        videoSourceMock = mock(VideoSource.self)
        loggerMock = mock(Logger.self)
        clientMetricsLollectorMock = mock(ClientMetricsCollector.self)
        defaultContentShareVideoClientController =
            DefaultContentShareVideoClientController(videoClient: videoClientMock,
                                                     configuration: meetingSessionConfigurationMock,
                                                     logger: loggerMock,
                                                     clientMetricsCollector: clientMetricsLollectorMock)

        given(videoSourceMock.getVideoContentHint()).willReturn(VideoContentHint.text)
    }

    func testStartVideoShareFirstTime() {
        defaultContentShareVideoClientController.startVideoShare(source: videoSourceMock)

        verify(videoClientMock.start(self.meetingId,
                                     token: self.joinToken,
                                     sending: false,
                                     config: any(),
                                     appInfo: any(),
                                     signalingUrl: any())).wasCalled()
        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
    }

    func testStartVideoShareWithConfig() {
        let config = LocalVideoConfiguration(maxBitRateKbps: 300)
        defaultContentShareVideoClientController.startVideoShare(source: videoSourceMock, config: config)

        verify(videoClientMock.setExternalVideoSource(any())).wasCalled()
        verify(videoClientMock.setSending(true)).wasCalled()
        verify(videoClientMock.setMaxBitRateKbps(300)).wasCalled()
    }

    func testStartVideoShareAfterStart() {
        given(videoClientMock.start(any(),
                                    token: any(),
                                    sending: any(),
                                    config: any(),
                                    appInfo: any(),
                                    signalingUrl: any())).will {_, _, _, _, _, _ in
            self.defaultContentShareVideoClientController.videoClientDidConnect(nil, controlStatus: 1)
        }
        given(videoClientMock.stop()).will {
            self.defaultContentShareVideoClientController.videoClientDidStop(nil)
        }

        defaultContentShareVideoClientController.startVideoShare(source: videoSourceMock)
        defaultContentShareVideoClientController.startVideoShare(source: videoSourceMock)

        verify(videoClientMock.start(self.meetingId,
                                     token: self.joinToken,
                                     sending: false,
                                     config: any(),
                                     appInfo: any(),
                                     signalingUrl: any())).wasCalled()
        verify(videoClientMock.setExternalVideoSource(any())).wasCalled(2)
        verify(videoClientMock.setSending(true)).wasCalled(2)
    }

    func testStopVideoShareAfterStart() {
        given(videoClientMock.start(any(),
                                    token: any(),
                                    sending: any(),
                                    config: any(),
                                    appInfo: any(),
                                    signalingUrl: any())).will {_, _, _, _, _, _ in
            self.defaultContentShareVideoClientController.videoClientDidConnect(nil, controlStatus: 1)
        }

        defaultContentShareVideoClientController.startVideoShare(source: videoSourceMock)
        defaultContentShareVideoClientController.stopVideoShare()

        verify(videoClientMock.setSending(false)).wasCalled()
        verify(videoClientMock.stop()).wasCalled()
    }

    func testStopVideoShareBeforeStart() {
        defaultContentShareVideoClientController.stopVideoShare()

        verify(videoClientMock.setSending(false)).wasNeverCalled()
        verify(videoClientMock.stop()).wasNeverCalled()
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class AnyCodableTests: XCTestCase {

    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()
}

// MARK: Decoder tests
extension AnyCodableTests{

    func testAnyCodableCanInitBoolFromDecoder() {
        let data = true
        let codableData = AnyCodable(data)
        guard let decodedData = encodeAndDecode(data: codableData) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data, decodedData.value as? Bool)
    }

    func testAnyCodableCanInitIntFromDecoder() {
        let data = Int(123)
        let codableData = AnyCodable(data)
        guard let decodedData = encodeAndDecode(data: codableData) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data, decodedData.value as? Int)
    }

    func testAnyCodableCanInitDoubleFromDecoder() {
        let data = Double(123.456)
        let codableData = AnyCodable(data)
        guard let decodedData = encodeAndDecode(data: codableData) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data, decodedData.value as? Double)
    }

    func testAnyCodableCanInitStringFromDecoder() {
        let data = String("test string")
        let codableData = AnyCodable(data)
        guard let decodedData = encodeAndDecode(data: codableData) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data, decodedData.value as? String)
    }

    func testAnyCodableCanInitArrayFromDecoder() {
        let data:[AnyCodable] = [1,2,3]
        guard let decodedData = encodeAndDecode(data: data) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data[0].value as? Int, decodedData[0].value as? Int)
    }

    func testAnyCodableCanInitDictFromDecoder() {
        let data:[String: AnyCodable] = ["a":1, "b":2, "c":3]
        guard let decodedData = encodeAndDecode(data: data) else {
            XCTFail("Failed to encode/decode data")
            return
        }
        XCTAssertEqual(data["a"]?.value as? Int, decodedData["a"]?.value as? Int)
    }

    private func encodeAndDecode(data: AnyCodable) -> AnyCodable? {
        guard let encodedData = try? encoder.encode(data) else {
            return nil
        }
        guard let decodedData = try? decoder.decode(AnyCodable.self, from: encodedData) else {
            return nil
        }
        return decodedData
    }

    private func encodeAndDecode(data: [AnyCodable]) -> [AnyCodable]? {
        guard let encodedData = try? encoder.encode(data) else {
            return nil
        }
        guard let decodedData = try? decoder.decode([AnyCodable].self, from: encodedData) else {
            return nil
        }
        return decodedData
    }

    private func encodeAndDecode(data: [String: AnyCodable]) -> [String: AnyCodable]? {
        guard let encodedData = try? encoder.encode(data) else {
            return nil
        }
        guard let decodedData = try? decoder.decode([String: AnyCodable].self, from: encodedData) else {
            return nil
        }
        return decodedData
    }
}

// MARK: Init tests
extension AnyCodableTests {

    func testInitBoolLiteral() {
        let data = true
        let codableData = AnyCodable(booleanLiteral: data)
        XCTAssertEqual(codableData.value as? Bool, data)
    }

    func testInitFloatLiteral() {
        let data = Double(123.456)
        let codableData = AnyCodable(floatLiteral: data)
        XCTAssertEqual(codableData.value as? Double, data)
    }

    func testInitIntLiteral() {
        let data = Int(123)
        let codableData = AnyCodable(integerLiteral: data)
        XCTAssertEqual(codableData.value as? Int, data)
    }

    func testInitStringLiteral() {
        let data = String("abc")
        let codableData = AnyCodable(stringLiteral: data)
        XCTAssertEqual(codableData.value as? String, data)
    }

    func testInitArrayLiteral() {
        let codableData = AnyCodable(arrayLiteral: "a", "b", "c")
        let value = codableData.value as? [AnyCodable]
        XCTAssertEqual(value?[0].value as? String, "a")
        XCTAssertEqual(value?[1].value as? String, "b")
        XCTAssertEqual(value?[2].value as? String, "c")
    }

    func testInitDictLiteral() {
        let codableData = AnyCodable(dictionaryLiteral:
            ("a", AnyCodable(1)),
            ("b", AnyCodable(2)),
            ("c", AnyCodable(3))
        )
        let value = codableData.value as? [String: AnyCodable]
        XCTAssertEqual(value?["a"]?.value as? Int, 1)
        XCTAssertEqual(value?["b"]?.value as? Int, 2)
        XCTAssertEqual(value?["c"]?.value as? Int, 3)
    }

    func testBoolAnyInitShouldInitialize() {
        let data: Any? = true
        XCTAssertNotNil(AnyCodable(data))
    }

    func testDoubleAnyInitShouldInitialize() {
        let data: Any? = Float(123.456)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testFloatAnyInitShouldInitialize() {
        let data: Any? = Float(123.456)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testIntAnyInitShouldInitialize() {
        let data: Any? = Int(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testInt8AnyInitShouldInitialize() {
        let data: Any? = Int8(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testInt16AnyInitShouldInitialize() {
        let data: Any? = Int16(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testInt32AnyInitShouldInitialize() {
        let data: Any? = Int32(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testInt64AnyInitShouldInitialize() {
        let data: Any? = Int64(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testUIntAnyInitShouldInitialize() {
        let data: Any? = UInt(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testUInt8AnyInitShouldInitialize() {
        let data: Any? = UInt8(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testUInt16AnyInitShouldInitialize() {
        let data: Any? = UInt16(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testUInt32AnyInitShouldInitialize() {
        let data: Any? = UInt32(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testUInt64AnyInitShouldInitialize() {
        let data: Any? = UInt64(123)
        XCTAssertNotNil(AnyCodable(data))
    }

    func testStringAnyInitShouldInitialize() {
        let data: Any? = String("abc")
        XCTAssertNotNil(AnyCodable(data))
    }

    func testArrayAnyInitShouldInitialize() {
        let data: Any? = [AnyCodable("a"), AnyCodable("b"), AnyCodable("c")]
        XCTAssertNotNil(AnyCodable(data))
    }

    func testDictAnyInitShouldInitialize() {
        let data: Any? = ["a": AnyCodable(1), "b": AnyCodable(2), "c": AnyCodable(3)]
        XCTAssertNotNil(AnyCodable(data))
    }
}

// MARK: Int cast helper method tests
extension AnyCodableTests {

    func testInt64ValueShouldReturnValueIfAnyCodableIsInt64() {
        let data = Int64(123)
        let codableData = AnyCodable(data)
        XCTAssertEqual(data, codableData.int64Value)
    }

    func testInt64ValueShouldReturnValueIfAnyCodableIsInt() {
        let data = Int(123)
        let codableData = AnyCodable(data)
        XCTAssertEqual(Int64(data), codableData.int64Value)
    }

    func testInt64ValueShouldReturnNilIfAnyCodableIsNotInt64OrInt() {
        let data = String("123")
        let codableData = AnyCodable(data)
        XCTAssertNil(codableData.int64Value)
    }
}


//
//  DirtyEventSQLiteDaoTests.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 DirtyEventSQLiteDaoTests: XCTestCase {
    private var dirtyEventDao: DirtyEventSQLiteDao!
    private var sqliteManagerMock: DatabaseManagerMock!
    private let tableName = "DirtyEvents"
    private let mockMap = [
        "id": "6b1d60db-bfa3-41fd-8448-7737f961cf3d",
        "data": "{\"name\":\"meetingEnded\",\"eventAttributes\":{\"meetingStatus\":\"ok\"}}",
        "ttl": Int64(1000299292)
    ] as [String: Any]
    private let uuid = UUID(uuidString: "6b1d60db-bfa3-41fd-8448-7737f961cf3d")?.uuidString
    private let mockDirtyMeetingEventItem = DirtyMeetingEventItem(id: UUID(uuidString: "6b1d60db-bfa3-41fd-8448-7737f961cf3d")!.uuidString,
                                                                  data: IngestionMeetingEvent(name: String(describing: EventName.meetingEnded),
                                                                                eventAttributes: [:]),
                                                                  ttl: Int64(1000299292))

    override func setUp() {
        sqliteManagerMock = mock(DatabaseManager.self)
        let loggerMock = mock(Logger.self)

        given(sqliteManagerMock.query(tableName: any(), size: any())).willReturn([mockMap])
        given(sqliteManagerMock.insert(tableName: any(), contentValue: any())).willReturn(true)
        given(sqliteManagerMock.insertMultiples(tableName: any(), contentValues: any())).willReturn(true)
        given(sqliteManagerMock.delete(tableName: any(), ids: any())).willReturn(true)

        dirtyEventDao = DirtyEventSQLiteDao(sqliteManager: sqliteManagerMock, logger: loggerMock)
    }

    func testQueryShouldCallDatabaseClientQuery() {
        dirtyEventDao.queryDirtyMeetingEventItems(size: 10)
        verify(sqliteManagerMock.query(tableName: self.tableName, size: 10)).wasCalled(1)
    }

    func testQueryShouldReturnMeetingEventItem() {
        let items = dirtyEventDao.queryDirtyMeetingEventItems(size: 10)

        XCTAssertNotNil(items)
        XCTAssertEqual(1, items.count)
        XCTAssertEqual(String(describing: EventName.meetingEnded), items[0].data.name)
        XCTAssertEqual(mockMap["ttl"] as? Int64, items[0].ttl)
    }

    func testInsertShouldCallDatabaseClientWrite() {
        dirtyEventDao.insertDirtyMeetingEventItems(dirtyEvents: [mockDirtyMeetingEventItem])
        verify(sqliteManagerMock.insertMultiples(tableName: self.tableName, contentValues: any())).wasCalled(1)
    }

    func testDeleteShouldCallDatabaseClientWrite() {
        dirtyEventDao.deleteDirtyMeetingEventsByIds(ids: [uuid!])
        verify(sqliteManagerMock.delete(tableName: self.tableName, ids: any())).wasCalled(1)
    }
}


//
//  EventSQLiteDaoTests.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 EventSQLiteDaoTests: XCTestCase {
    private var eventDao: EventSQLiteDao!
    private let tableName = "Events"
    private var sqliteManagerMock: DatabaseManagerMock!
    private let mockMap = [
        "id": "6b1d60db-bfa3-41fd-8448-7737f961cf3d",
        "data": "{\"name\":\"meetingEnded\",\"eventAttributes\":{\"meetingStatus\":\"ok\"}}"
    ]
    private let uuid = UUID(uuidString: "6b1d60db-bfa3-41fd-8448-7737f961cf3d")?.uuidString
    private let mockMeetingEventItem = MeetingEventItem(id: UUID(uuidString: "6b1d60db-bfa3-41fd-8448-7737f961cf3d")!.uuidString,
                                                        data: IngestionMeetingEvent(name: String(describing: EventName.meetingEnded),
                                                                                eventAttributes: [:]))
    override func setUp() {
        sqliteManagerMock = mock(DatabaseManager.self)
        let loggerMock = mock(Logger.self)
        given(sqliteManagerMock.query(tableName: any(), size: any())).willReturn([mockMap])
        given(sqliteManagerMock.insert(tableName: any(), contentValue: any())).willReturn(true)
        given(sqliteManagerMock.insertMultiples(tableName: any(), contentValues: any())).willReturn(true)
        given(sqliteManagerMock.delete(tableName: any(), ids: any())).willReturn(true)
        eventDao = EventSQLiteDao(sqliteManager: sqliteManagerMock, logger: loggerMock)
    }

    func testQueryShouldCallDatabaseManagerQuery() {
        eventDao.queryMeetingEventItems(size: 10)
        verify(sqliteManagerMock.query(tableName: self.tableName, size: 10)).wasCalled(1)
    }

    func testQueryShouldReturnMeetingEventItem() {
        let items = eventDao.queryMeetingEventItems(size: 10)

        XCTAssertNotNil(items)
        XCTAssertGreaterThan(items.count, 0)
        XCTAssertEqual(String(describing: EventName.meetingEnded), items[0].data.name)
    }

    func testInsertShouldCallDatabaseManagerWrite() {
        eventDao.insertMeetingEvent(event: mockMeetingEventItem)
        verify(sqliteManagerMock.insert(tableName: self.tableName, contentValue: any())).wasCalled(1)
    }

    func testDeleteShouldCallDatabaseManagerWrite() {
        eventDao.deleteMeetingEventsByIds(ids: [uuid!])
        verify(sqliteManagerMock.delete(tableName: self.tableName, ids: any())).wasCalled(1)
    }

    func testConstructorShouldCallDatabaseManagerExecute() {
        eventDao.deleteMeetingEventsByIds(ids: [uuid!])
        verify(sqliteManagerMock.delete(tableName: self.tableName, ids: any())).wasCalled(1)
    }
}


//
//  SQLiteDatabaseManagerTests.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 SQLiteDatabaseManagerTests: XCTestCase {
    private var sqliteDatabaseManager: SQLiteDatabaseManager!
    private var sqliteClient: DatabaseClientMock!
    private let contentValue = [
        "id": "hello",
        "data": "world"
    ]
    private let tableName = "test"

    override func setUp() {
        sqliteClient = mock(DatabaseClient.self)
        given(sqliteClient.query(statement: any(), params: any())).willReturn([])
        given(sqliteClient.write(statement: any(), params: any())).willReturn(true)
        sqliteDatabaseManager = SQLiteDatabaseManager(sqliteClient: sqliteClient)
    }

    func testInsertShouldInvokeClientWrite() {
        sqliteDatabaseManager.insert(tableName: tableName, contentValue: contentValue)
        verify(sqliteClient.write(statement: any(), params: any())).wasCalled(1)
    }

    func testExecuteShouldInvokeClientWrite() {
        sqliteDatabaseManager.execute(statement: "example statement")
        verify(sqliteClient.write(statement: any(), params: any())).wasCalled(1)
    }

    func testInsertMultipleShouldInvokeClientWrite() {
        sqliteDatabaseManager.insertMultiples(tableName: tableName, contentValues: [contentValue])
        verify(sqliteClient.write(statement: any(), params: any())).wasCalled(1)
    }

    func testQueryShouldInvokeClientQuery() {
        sqliteDatabaseManager.query(tableName: tableName, size: 5)
        verify(sqliteClient.query(statement: any(), params:  any())).wasCalled(1)
    }
}


//
//  ClientMetricsCollectorTests.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 ClientMetricsCollectorTests: XCTestCase, MetricsObserver {
    // Replicated here as to confirm values don't change and to avoid exposing in actual code
    private let clientMicDeviceFramesLostPercent = 0
    private let clientPostJbSpk1sPacketsLostPercent = 7
    private let serverPostJbMic1sPacketsLostPercent = 3
    private var receivedMetrics: [AnyHashable: Any] = [:]

    private let metricsReceivedExpectation = XCTestExpectation(
        description: "Is fulfilled after metrics were received")
    private let metricsReceivedTimeoutMs = 10.0

    private var clientMetricsCollector: DefaultClientMetricsCollector?

    override func setUp() {
        super.setUp()
        clientMetricsCollector = DefaultClientMetricsCollector()
        clientMetricsCollector?.subscribeToMetrics(observer: self)
    }

    func testOnMetricsReceiveShouldNotBeCalledBeforeInterval() {
        let audioClientMetrics = [
            serverPostJbMic1sPacketsLostPercent: 1,
            clientPostJbSpk1sPacketsLostPercent: 2
        ]
        clientMetricsCollector?.processAudioClientMetrics(metrics: audioClientMetrics)

        // No callback should have occurred
        XCTAssertEqual(receivedMetrics.count, 0)
    }

    func testOnMetricsReceiveShouldBeCalledAfterInterval() {
        let audioClientMetrics = [
            serverPostJbMic1sPacketsLostPercent: 1,
            clientPostJbSpk1sPacketsLostPercent: 2
        ]

        // Wait at least a second and next time we process metrics we should receive the callback
        Thread.sleep(forTimeInterval: 1)
        clientMetricsCollector?.processAudioClientMetrics(metrics: audioClientMetrics)
        wait(for: [metricsReceivedExpectation], timeout: metricsReceivedTimeoutMs)

        XCTAssertEqual(receivedMetrics.count, 2)
        XCTAssertEqual(receivedMetrics[ObservableMetric.audioSendPacketLossPercent] as? Int, 1)
        XCTAssertEqual(receivedMetrics[ObservableMetric.audioReceivePacketLossPercent] as? Int, 2)
    }

    func testNonObservableMetricShouldNotBeEmitted() {
        let audioClientMetrics = [
            clientMicDeviceFramesLostPercent: 1
        ]

        // Wait at least a second and next time we process metrics we should receive the callback
        Thread.sleep(forTimeInterval: 1)
        clientMetricsCollector?.processAudioClientMetrics(metrics: audioClientMetrics)

        XCTAssertEqual(receivedMetrics.count, 0)
    }

    func testInvalidMetricShouldNotBeEmitted() {
        let audioClientMetrics = [
            999: 1
        ]

        // Wait at least a second and next time we process metrics we should receive the callback
        Thread.sleep(forTimeInterval: 1)
        clientMetricsCollector?.processAudioClientMetrics(metrics: audioClientMetrics)

        XCTAssertEqual(receivedMetrics.count, 0)
    }

    func metricsDidReceive(metrics: [AnyHashable: Any]) {
        metricsReceivedExpectation.fulfill()
        receivedMetrics = metrics
    }
}


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

@testable import AmazonChimeSDK
import XCTest

class ConcurrentDictionaryTests: XCTestCase {
    private var dict: ConcurrentDictionary<String, Int>!

    override func setUp() {
        super.setUp()
        self.dict = ConcurrentDictionary()
    }

    func testPutNonNilAndGetShouldWork() {
        dict["1+1="] = 2
        XCTAssertEqual(dict.getShallowDictCopy().count, 1)
        XCTAssertEqual(dict["1+1="], 2)
    }

    func testPutNilAndGetShouldWork() {
        dict["?+?="] = nil
        XCTAssertEqual(dict.getShallowDictCopy().count, 0)
        XCTAssertNil(dict["?+?="])
    }

    func testForEachShouldWork() {
        dict["1+0="] = 1
        dict["10+0="] = 10
        dict["100+0="] = 100

        var sum: Int = 0
        var count: Int = 0
        dict.forEach { _, value in
            count += 1
            sum += value
        }

        XCTAssertEqual(sum, 111)
        XCTAssertEqual(count, 3)
    }

    func testSortedShouldWork() {
        dict["1+0="] = 1
        dict["1+1="] = 2

        let sortedAscending = dict.sorted(by: { $0.value > $1.value })
        XCTAssertEqual(sortedAscending[0].value, 2)
        XCTAssertEqual(sortedAscending[1].value, 1)

        let sortedDecending = dict.sorted(by: { $0.value < $1.value })
        XCTAssertEqual(sortedDecending[0].value, 1)
        XCTAssertEqual(sortedDecending[1].value, 2)
    }

    func testGetShallowDictCopyShouldReturnShallowCopy() {
        dict["1+1="] = 1
        var dictCopy = dict.getShallowDictCopy()
        dictCopy["1+1="] = 2

        XCTAssertEqual(dict["1+1="], 1)
        XCTAssertEqual(dictCopy["1+1="], 2)
    }

    func testThreadSafety() {
        dict["?"] = 0
        let backgroundThreadEndedExpectation = XCTestExpectation(
            description: "The background thread was ended")
        let mainThreadEndedExpectation = XCTestExpectation(
            description: "The main thread was ended")

        // The quality-of-service (QoS) - '.userInteractive' has higher priority than '.background', which is performed more quickly and with more resources than lower priority work.
        // In order to pass tests, we give them higher priority to perform quickly.
        DispatchQueue.global(qos: .userInteractive).async {
            self.dict.forEach { _ in
                sleep(2)
                self.dict["?"] = 1
            }
            backgroundThreadEndedExpectation.fulfill()
        }
        DispatchQueue.main.async {
            sleep(1)
            self.dict["?"] = 2
            mainThreadEndedExpectation.fulfill()
        }

        wait(for: [backgroundThreadEndedExpectation, mainThreadEndedExpectation], timeout: 5)
        XCTAssertEqual(self.dict["?"], 2)
    }

    func testThreadSafetyShouldFailForNormalDict() {
        var normalDict = ["?": 0]
        let backgroundThreadEndedExpectation = XCTestExpectation(
            description: "The background thread was ended")
        let mainThreadEndedExpectation = XCTestExpectation(
            description: "The main thread was ended")

        DispatchQueue.global(qos: .background).async {
            normalDict.forEach { _ in
                sleep(2)
                normalDict["?"] = 1
            }
            backgroundThreadEndedExpectation.fulfill()
        }
        DispatchQueue.main.async {
            sleep(1)
            normalDict["?"] = 2
            mainThreadEndedExpectation.fulfill()
        }

        wait(for: [backgroundThreadEndedExpectation, mainThreadEndedExpectation], timeout: 5)
        XCTAssertEqual(normalDict["?"], 1)
    }
}