call: remove call screen for unhandled call

When reporting a new incoming call from notification extension
it is possible to create multiple pending calls for the same
jamiId. But when expiration timer called it will stop only one
call. This patch stops existing unhandled call before creating
A new one.

Gitlab: #248
Change-Id: Ic5ad5c3d25e75826bd25470f567d87653cbf75ab
diff --git a/Ring/RingTests/CallProviderDelegateTests.swift b/Ring/RingTests/CallProviderDelegateTests.swift
new file mode 100644
index 0000000..5877434
--- /dev/null
+++ b/Ring/RingTests/CallProviderDelegateTests.swift
@@ -0,0 +1,143 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import XCTest
+import CallKit
+@testable import Ring
+
+final class CallProviderDelegateTests: XCTestCase {
+    var callProviderService: CallsProviderService!
+    var unhandeledCalls: [UnhandeledCall]?
+    var systemCalls: [MockCall]?
+    var systemCallsHolder: MocSystemCalls!
+
+    override func setUpWithError() throws {
+        systemCallsHolder = MocSystemCalls()
+        let provider = MockCXProvider(systemCalls: systemCallsHolder)
+        let controller = MockCallController(systemCalls: systemCallsHolder)
+        callProviderService = CallsProviderService(provider: provider, controller: controller)
+        try super.setUpWithError()
+    }
+
+    override func tearDownWithError() throws {
+        try super.tearDownWithError()
+        callProviderService = nil
+        systemCallsHolder = nil
+        unhandeledCalls = nil
+        systemCalls = nil
+    }
+
+    func testStopCall_WhenPendingCallExists() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have no pending call and no system call")
+        // Act
+        callProviderService.previewPendingCall(peerId: jamiId1, withVideo: false, displayName: "", completion: nil)
+        let unhandeledCall = callProviderService.getUnhandeledCall(peerId: jamiId1)
+        XCTAssertNotNil(unhandeledCall)
+        callProviderService.stopCall(callUUID: unhandeledCall!.uuid, participant: jamiId1)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls?.count, 0)
+        XCTAssertEqual(unhandeledCalls?.count, 0)
+    }
+
+    func testStopCall_WhenPendingCallDoesNotExists() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have no pending call and no system call")
+        let account = AccountModel()
+        let call = CallModel()
+        call.participantUri = jamiId1
+        // Act
+        callProviderService.handleIncomingCall(account: account, call: call)
+        callProviderService.stopCall(callUUID: call.callUUID, participant: jamiId1)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls?.count, 0)
+        XCTAssertEqual(unhandeledCalls?.count, 0)
+    }
+
+    func testHandleIncomingCall_PendingExists() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have no pending call and one system call")
+        let account = AccountModel()
+        let call = CallModel()
+        call.participantUri = jamiId1
+        // Act
+        callProviderService.previewPendingCall(peerId: jamiId1, withVideo: false, displayName: "", completion: nil)
+        callProviderService.handleIncomingCall(account: account, call: call)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.count, 0)
+    }
+
+    func testHandleIncomingCall_PendingDoesNotExists() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have no pending call and one system call")
+        let account = AccountModel()
+        let call = CallModel()
+        call.participantUri = jamiId1
+        // Act
+        callProviderService.handleIncomingCall(account: account, call: call)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.count, 0)
+    }
+
+    func testPreviewPendingCall_WhenCalledTwice() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have one pending call and one system call")
+        // Act
+        callProviderService.previewPendingCall(peerId: jamiId1, withVideo: false, displayName: "", completion: nil)
+        callProviderService.previewPendingCall(peerId: jamiId1, withVideo: false, displayName: "", completion: nil)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.first?.uuid, systemCalls!.first?.uuid)
+    }
+
+    func testPreviewPendingCall_WhenCalledOnce() {
+        // Arrange
+        let expectation = self.expectation(description: "Should have one pending call and one system call")
+        // Act
+        callProviderService.previewPendingCall(peerId: jamiId1, withVideo: false, displayName: "", completion: nil)
+        updateCalls(expectation: expectation, jamiId: jamiId1)
+        waitForExpectations(timeout: 2, handler: nil)
+        // Assert
+        XCTAssertEqual(systemCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.count, 1)
+        XCTAssertEqual(unhandeledCalls!.first?.uuid, systemCalls!.first?.uuid)
+    }
+
+    func updateCalls(expectation: XCTestExpectation, jamiId: String) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+            self.unhandeledCalls = self.callProviderService.getUnhandeledCalls(peerId: jamiId)
+            self.systemCalls = self.systemCallsHolder.getCalls(jamiId: jamiId)
+            expectation.fulfill()
+        }
+    }
+}
diff --git a/Ring/RingTests/JamiSearchViewModelTests.swift b/Ring/RingTests/JamiSearchViewModelTests.swift
index 24a0276..ab48b9f 100644
--- a/Ring/RingTests/JamiSearchViewModelTests.swift
+++ b/Ring/RingTests/JamiSearchViewModelTests.swift
@@ -42,7 +42,7 @@
         let audioService = AudioService(withAudioAdapter: AudioAdapter())
         let systemService = SystemService(withSystemAdapter: SystemAdapter())
         let networkService = NetworkService()
-        let callsProvider: CallsProviderDelegate = CallsProviderDelegate()
+        let callsProvider: CallsProviderService = CallsProviderService(provider: CXProvider(configuration: CallsHelpers.providerConfiguration()), controller: CXCallController())
         let callService: CallsService = CallsService(withCallsAdapter: CallsAdapter(), dbManager: dBManager)
         let accountService: AccountsService = AccountsService(withAccountAdapter: AccountAdapter(), dbManager: dBManager)
         let contactsService: ContactsService = ContactsService(withContactsAdapter: ContactsAdapter(), dbManager: dBManager)
diff --git a/Ring/RingTests/SwarmInfoTests.swift b/Ring/RingTests/SwarmInfoTests.swift
index 1e8c745..7f85c3d 100644
--- a/Ring/RingTests/SwarmInfoTests.swift
+++ b/Ring/RingTests/SwarmInfoTests.swift
@@ -39,7 +39,7 @@
         let audioService = AudioService(withAudioAdapter: AudioAdapter())
         let systemService = SystemService(withSystemAdapter: SystemAdapter())
         let networkService = NetworkService()
-        let callsProvider: CallsProviderDelegate = CallsProviderDelegate()
+        let callsProvider: CallsProviderService = CallsProviderService(provider: CXProvider(configuration: CallsHelpers.providerConfiguration()), controller: CXCallController())
         let callService: CallsService = CallsService(withCallsAdapter: CallsAdapter(), dbManager: dBManager)
         let accountService: AccountsService = AccountsService(withAccountAdapter: AccountAdapter(), dbManager: dBManager)
         let contactsService: ContactsService = ContactsService(withContactsAdapter: ContactsAdapter(), dbManager: dBManager)
diff --git a/Ring/RingTests/TestableModels/MockCalls.swift b/Ring/RingTests/TestableModels/MockCalls.swift
new file mode 100644
index 0000000..ee9e9d6
--- /dev/null
+++ b/Ring/RingTests/TestableModels/MockCalls.swift
@@ -0,0 +1,99 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+@testable import Ring
+
+class MocSystemCalls {
+    var calls = [MockCall]()
+
+    func reportCall(call: MockCall) {
+        self.calls.append(call)
+    }
+
+    func removeCall(uuid: UUID) {
+        if let index = calls.firstIndex(where: { call in
+            call.uuid == uuid
+        }) {
+            calls.remove(at: index)
+        }
+    }
+
+    func getCalls(jamiId: String) -> [MockCall]? {
+        return self.calls.filter { call in
+            call.jamiId == jamiId
+        }
+    }
+
+    func getCalls() -> [MockCall] {
+        return self.calls
+    }
+}
+
+class MockCall {
+    let uuid: UUID
+    let jamiId: String
+
+    init(uuid: UUID, jamiId: String) {
+        self.uuid = uuid
+        self.jamiId = jamiId
+    }
+}
+
+class MockCXProvider: CXProvider {
+    var systemCalls: MocSystemCalls
+
+    init(systemCalls: MocSystemCalls) {
+        self.systemCalls = systemCalls
+        super.init(configuration: CallsHelpers.providerConfiguration())
+    }
+
+    override func reportNewIncomingCall(with UUID: UUID, update: CXCallUpdate, completion: ((Error?) -> Void)? = nil) {
+        if let handle = update.remoteHandle {
+            let call = MockCall(uuid: UUID, jamiId: handle.value)
+            self.systemCalls.reportCall(call: call)
+        }
+    }
+}
+
+class MockCallController: CXCallController {
+
+    var systemCalls: MocSystemCalls
+
+    init(systemCalls: MocSystemCalls) {
+        self.systemCalls = systemCalls
+        super.init(queue: DispatchQueue(label: "MockCallController"))
+    }
+
+    override func request(_ transaction: CXTransaction, completion: @escaping (Error?) -> Void) {
+        for action in transaction.actions {
+            if let startCallAction = action as? CXStartCallAction {
+                let uuid = startCallAction.callUUID
+                let jamiId = startCallAction.contactIdentifier!
+                let newCall = MockCall(uuid: uuid, jamiId: jamiId)
+                self.systemCalls.reportCall(call: newCall)
+            } else if let endCallAction = action as? CXEndCallAction {
+                let uuid = endCallAction.callUUID
+                self.systemCalls.removeCall(uuid: uuid)
+            }
+        }
+        completion(nil)
+    }
+}