408 lines
14 KiB
Swift
408 lines
14 KiB
Swift
import HAKit
|
|
import PromiseKit
|
|
@testable import Shared
|
|
import Version
|
|
import XCTest
|
|
|
|
class LocalPushManagerTests: XCTestCase {
|
|
private var manager: LocalPushManager!
|
|
private var api: FakeHomeAssistantAPI!
|
|
private var apiConnection: HAMockConnection!
|
|
private var attachmentManager: FakeNotificationAttachmentManager!
|
|
|
|
private var added: [(UNNotificationRequest, Resolver<Void>)] = []
|
|
private var addedChanged: (() -> Void)?
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
attachmentManager = FakeNotificationAttachmentManager()
|
|
Current.notificationAttachmentManager = attachmentManager
|
|
|
|
let server: Server
|
|
|
|
let fakeServers = FakeServerManager()
|
|
Current.servers = fakeServers
|
|
|
|
server = fakeServers.addFake()
|
|
|
|
api = FakeHomeAssistantAPI(server: server)
|
|
apiConnection = HAMockConnection()
|
|
api.connection = apiConnection
|
|
|
|
Current.cachedApis[server.identifier] = api
|
|
|
|
added = []
|
|
}
|
|
|
|
override func tearDown() {
|
|
super.tearDown()
|
|
|
|
weak var weakManager = manager
|
|
manager = nil
|
|
XCTAssertNil(weakManager)
|
|
|
|
for sub in apiConnection.pendingSubscriptions {
|
|
XCTAssertTrue(sub.cancellable.wasCancelled)
|
|
}
|
|
}
|
|
|
|
private func setUpManager(webhookID: String, version: Version? = nil) {
|
|
api.server.info.connection.webhookID = webhookID
|
|
if let version {
|
|
api.server.info.version = version
|
|
}
|
|
|
|
manager = LocalPushManager(server: api.server)
|
|
manager.add = { [weak self] request in
|
|
let (promise, resolver) = Promise<Void>.pending()
|
|
self?.added.append((request, resolver))
|
|
DispatchQueue.main.async { self?.addedChanged?() }
|
|
return promise
|
|
}
|
|
}
|
|
|
|
private func fireConnectionChange() {
|
|
api.server.info.connection.internalHardwareAddresses = [UUID().uuidString]
|
|
let expectation = expectation(description: "loop")
|
|
DispatchQueue.main.async {
|
|
expectation.fulfill()
|
|
}
|
|
wait(for: [expectation], timeout: 10)
|
|
}
|
|
|
|
func testStateInitialSuccessful() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
XCTAssertEqual(manager.state, .establishing)
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.initiated(.success(.empty))
|
|
XCTAssertEqual(manager.state, .available(received: 0))
|
|
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
]))
|
|
XCTAssertEqual(manager.state, .available(received: 1))
|
|
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
]))
|
|
XCTAssertEqual(manager.state, .available(received: 2))
|
|
}
|
|
|
|
func testStateInitialFailure() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
XCTAssertEqual(manager.state, .establishing)
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.initiated(.failure(.internal(debugDescription: "unit-test")))
|
|
XCTAssertEqual(manager.state, .unavailable)
|
|
|
|
// pretend like a future connection made it work
|
|
sub.initiated(.success(.empty))
|
|
XCTAssertEqual(manager.state, .available(received: 0))
|
|
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
]))
|
|
XCTAssertEqual(manager.state, .available(received: 1))
|
|
}
|
|
|
|
func testSubscriptionAtStart() throws {
|
|
setUpManager(webhookID: "webhook1", version: .init(major: 2021, minor: 9))
|
|
|
|
let sub1 = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
XCTAssertEqual(sub1.request.type, "mobile_app/push_notification_channel")
|
|
XCTAssertEqual(sub1.request.data["webhook_id"] as? String, "webhook1")
|
|
XCTAssertNil(sub1.request.data["support_confirm"])
|
|
|
|
sub1.initiated(.success(.empty))
|
|
|
|
apiConnection.pendingSubscriptions.removeAll()
|
|
fireConnectionChange()
|
|
XCTAssertTrue(apiConnection.pendingSubscriptions.isEmpty, "same id")
|
|
|
|
api.server.info.version = .init(major: 2021, minor: 10)
|
|
|
|
// change webhookID
|
|
api.server.info.connection.webhookID = "webhook2"
|
|
fireConnectionChange()
|
|
|
|
XCTAssertTrue(sub1.cancellable.wasCancelled)
|
|
|
|
let sub2 = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
XCTAssertEqual(sub2.request.type, "mobile_app/push_notification_channel")
|
|
XCTAssertEqual(sub2.request.data["webhook_id"] as? String, "webhook2")
|
|
XCTAssertEqual(sub2.request.data["support_confirm"] as? Bool, true)
|
|
|
|
// fail the subscription
|
|
sub2.initiated(.failure(.internal(debugDescription: "unit-test")))
|
|
fireConnectionChange()
|
|
|
|
// now succeed it (e.g. reconnect happened)
|
|
sub2.initiated(.success(.empty))
|
|
}
|
|
|
|
func testInvalidate() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
manager.invalidate()
|
|
XCTAssertTrue(sub.cancellable.wasCancelled)
|
|
}
|
|
|
|
func testEventSuccessfullyAddedWithoutConfirmId() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
|
|
let expectation1 = expectation(description: "contentRequestsChanged")
|
|
attachmentManager.contentRequestsChanged = {
|
|
expectation1.fulfill()
|
|
}
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
"data": [
|
|
"tag": "test_tag",
|
|
],
|
|
]))
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let req = try XCTUnwrap(attachmentManager.contentRequests.first)
|
|
XCTAssertEqual(req.0.body, "test_message")
|
|
req.1(with(UNMutableNotificationContent()) {
|
|
$0.body = "test_message_modified"
|
|
})
|
|
|
|
let expectation2 = expectation(description: "addedChanged")
|
|
addedChanged = {
|
|
expectation2.fulfill()
|
|
}
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let final = try XCTUnwrap(added.first)
|
|
XCTAssertEqual(final.0.content.body, "test_message_modified")
|
|
XCTAssertEqual(final.0.identifier, "test_tag")
|
|
final.1.fulfill(())
|
|
|
|
XCTAssertFalse(
|
|
apiConnection.pendingRequests
|
|
.contains(where: { $0.request.type == "mobile_app/push_notification_confirm" })
|
|
)
|
|
}
|
|
|
|
func testEventSuccessfullyAddedWithConfirmIdSuccessfullyConfirm() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
|
|
let expectation1 = expectation(description: "contentRequestsChanged")
|
|
attachmentManager.contentRequestsChanged = {
|
|
expectation1.fulfill()
|
|
}
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
"hass_confirm_id": "test_confirm_id",
|
|
"data": [
|
|
"tag": "test_tag",
|
|
],
|
|
]))
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let req = try XCTUnwrap(attachmentManager.contentRequests.first)
|
|
XCTAssertEqual(req.0.body, "test_message")
|
|
req.1(with(UNMutableNotificationContent()) {
|
|
$0.body = "test_message_modified"
|
|
})
|
|
|
|
let expectation2 = expectation(description: "addedChanged")
|
|
addedChanged = {
|
|
expectation2.fulfill()
|
|
}
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let final = try XCTUnwrap(added.first)
|
|
XCTAssertEqual(final.0.content.body, "test_message_modified")
|
|
XCTAssertEqual(final.0.identifier, "test_tag")
|
|
final.1.fulfill(())
|
|
|
|
let expectation3 = expectation(description: "run loop")
|
|
DispatchQueue.main.async(execute: expectation3.fulfill)
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let pendingRequest = try XCTUnwrap(
|
|
apiConnection.pendingRequests
|
|
.first(where: { $0.request.type == "mobile_app/push_notification_confirm" })
|
|
)
|
|
XCTAssertEqual(pendingRequest.request.data["webhook_id"] as? String, "webhook1")
|
|
XCTAssertEqual(pendingRequest.request.data["confirm_id"] as? String, "test_confirm_id")
|
|
|
|
// just making sure this doesn't have a runtime problem
|
|
pendingRequest.completion(.success(.empty))
|
|
}
|
|
|
|
func testEventSuccessfullyAddedWithConfirmIdFailsToConfirm() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
|
|
let expectation1 = expectation(description: "contentRequestsChanged")
|
|
attachmentManager.contentRequestsChanged = {
|
|
expectation1.fulfill()
|
|
}
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
"hass_confirm_id": "test_confirm_id",
|
|
"data": [
|
|
"tag": "test_tag",
|
|
],
|
|
]))
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let req = try XCTUnwrap(attachmentManager.contentRequests.first)
|
|
XCTAssertEqual(req.0.body, "test_message")
|
|
req.1(with(UNMutableNotificationContent()) {
|
|
$0.body = "test_message_modified"
|
|
})
|
|
|
|
let expectation2 = expectation(description: "addedChanged")
|
|
addedChanged = {
|
|
expectation2.fulfill()
|
|
}
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let final = try XCTUnwrap(added.first)
|
|
XCTAssertEqual(final.0.content.body, "test_message_modified")
|
|
XCTAssertEqual(final.0.identifier, "test_tag")
|
|
final.1.fulfill(())
|
|
|
|
let expectation3 = expectation(description: "run loop")
|
|
DispatchQueue.main.async(execute: expectation3.fulfill)
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let pendingRequest = try XCTUnwrap(
|
|
apiConnection.pendingRequests
|
|
.first(where: { $0.request.type == "mobile_app/push_notification_confirm" })
|
|
)
|
|
XCTAssertEqual(pendingRequest.request.data["webhook_id"] as? String, "webhook1")
|
|
XCTAssertEqual(pendingRequest.request.data["confirm_id"] as? String, "test_confirm_id")
|
|
|
|
// just making sure this doesn't have a runtime problem
|
|
pendingRequest.completion(.failure(.internal(debugDescription: "unit-test")))
|
|
}
|
|
|
|
func testEventAddFailsWithoutConfirmId() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
|
|
let expectation1 = expectation(description: "contentRequestsChanged")
|
|
attachmentManager.contentRequestsChanged = {
|
|
expectation1.fulfill()
|
|
}
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
"data": [
|
|
"tag": "test_tag",
|
|
],
|
|
]))
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let req = try XCTUnwrap(attachmentManager.contentRequests.first)
|
|
XCTAssertEqual(req.0.body, "test_message")
|
|
req.1(with(UNMutableNotificationContent()) {
|
|
$0.body = "test_message_modified"
|
|
})
|
|
|
|
let expectation2 = expectation(description: "addedChanged")
|
|
addedChanged = {
|
|
expectation2.fulfill()
|
|
}
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let final = try XCTUnwrap(added.first)
|
|
XCTAssertEqual(final.0.content.body, "test_message_modified")
|
|
XCTAssertEqual(final.0.identifier, "test_tag")
|
|
enum TestError: Error { case any }
|
|
final.1.reject(TestError.any)
|
|
}
|
|
|
|
func testEventAddFailsWithConfirmId() throws {
|
|
setUpManager(webhookID: "webhook1")
|
|
|
|
let expectation1 = expectation(description: "contentRequestsChanged")
|
|
attachmentManager.contentRequestsChanged = {
|
|
expectation1.fulfill()
|
|
}
|
|
|
|
let sub = try XCTUnwrap(apiConnection.pendingSubscriptions.first)
|
|
sub.handler(sub.cancellable, .dictionary([
|
|
"message": "test_message",
|
|
"data": [
|
|
"tag": "test_tag",
|
|
],
|
|
"hass_confirm_id": "test_confirm_id",
|
|
]))
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let req = try XCTUnwrap(attachmentManager.contentRequests.first)
|
|
XCTAssertEqual(req.0.body, "test_message")
|
|
req.1(with(UNMutableNotificationContent()) {
|
|
$0.body = "test_message_modified"
|
|
})
|
|
|
|
let expectation2 = expectation(description: "addedChanged")
|
|
addedChanged = {
|
|
expectation2.fulfill()
|
|
}
|
|
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
let final = try XCTUnwrap(added.first)
|
|
XCTAssertEqual(final.0.content.body, "test_message_modified")
|
|
XCTAssertEqual(final.0.identifier, "test_tag")
|
|
enum TestError: Error { case any }
|
|
final.1.reject(TestError.any)
|
|
|
|
let expectation3 = expectation(description: "run loop")
|
|
DispatchQueue.main.async(execute: expectation3.fulfill)
|
|
waitForExpectations(timeout: 10.0)
|
|
|
|
XCTAssertFalse(
|
|
apiConnection.pendingRequests
|
|
.contains(where: { $0.request.type == "mobile_app/push_notification_confirm" })
|
|
)
|
|
}
|
|
}
|
|
|
|
private class FakeNotificationAttachmentManager: NotificationAttachmentManager {
|
|
var contentRequests: [(UNNotificationContent, (UNNotificationContent) -> Void)] = []
|
|
var contentRequestsChanged: (() -> Void)?
|
|
|
|
func content(
|
|
from originalContent: UNNotificationContent,
|
|
api: HomeAssistantAPI
|
|
) -> Guarantee<UNNotificationContent> {
|
|
let (guarantee, seal) = Guarantee<UNNotificationContent>.pending()
|
|
contentRequests.append((originalContent, seal))
|
|
DispatchQueue.main.async { [contentRequestsChanged] in
|
|
contentRequestsChanged?()
|
|
}
|
|
return guarantee
|
|
}
|
|
|
|
func downloadAttachment(from originalContent: UNNotificationContent, api: HomeAssistantAPI) -> Promise<URL> {
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
private class FakeHomeAssistantAPI: HomeAssistantAPI {}
|