iOS/Tests/Shared/Sensors/SensorContainer.test.swift

455 lines
16 KiB
Swift

import Foundation
import PromiseKit
@testable import Shared
import Version
import XCTest
class SensorContainerTests: XCTestCase {
private var observer: MockSensorObserver!
private var container: SensorContainer!
private var server1: Server!
private var server2: Server!
private enum TestError: Error {
case anyError
}
override func setUp() {
super.setUp()
let servers = FakeServerManager()
server1 = servers.addFake()
server2 = servers.addFake()
Current.servers = servers
observer = MockSensorObserver()
container = SensorContainer()
}
func testNoProvidersNoCachedDoesntNotify() {
XCTAssertTrue(observer.updates.isEmpty)
container.register(observer: observer)
XCTAssertTrue(observer.updates.isEmpty)
}
func testInitialRegistrationOfProvider() throws {
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
]),
]
container.register(provider: MockSensorProvider.self)
container.register(observer: observer)
XCTAssertEqual(observer.updates.count, 0)
}
func testMultipleProvidersFlattensAndNotifies() throws {
container.register(observer: observer)
container.register(provider: MockSensorProvider.self)
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b", uniqueID: "test1b"),
]),
.value([
WebhookSensor(name: "test2a", uniqueID: "test2a"),
WebhookSensor(name: "test2b", uniqueID: "test2b"),
]),
]
let date = Date()
Current.date = { date }
let promise = container.sensors(reason: .trigger("unit-test"), server: server1)
let result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b", "test2a", "test2b",
]))
XCTAssertEqual(observer.updates.count, 1)
if let update = observer.updates.first {
let updateResult = try hang(Promise(update.sensors))
XCTAssertEqual(
updateResult.map(\.asTestEquatableForSensorContainer),
result.sensors.sorted().map(\.asTestEquatableForSensorContainer)
)
XCTAssertEqual(update.on, date)
}
}
func testMultipleButContainingErrorsReturnsSuccessful() throws {
container.register(provider: MockSensorProvider.self)
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
Promise(error: TestError.anyError),
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b", uniqueID: "test1b"),
]),
]
let promise = container.sensors(reason: .trigger("unit-test"), server: server1)
let result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b",
]))
}
func testRegistrationDoesntOverrideCache() throws {
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b", uniqueID: "test1b"),
]),
]
let date1 = Date(timeIntervalSinceNow: -200)
Current.date = { date1 }
let promise1 = container.sensors(reason: .trigger("unit-test"), server: server1)
let result1 = try hang(Promise(promise1))
XCTAssertEqual(Set(result1.sensors.map(\.Name)), Set([
"test1a", "test1b",
]))
container.register(observer: observer)
XCTAssertEqual(observer.updates.count, 1)
if let update = observer.updates.first {
let updateResult = try hang(Promise(update.sensors))
XCTAssertEqual(
updateResult.map(\.asTestEquatableForSensorContainer),
result1.sensors.map(\.asTestEquatableForSensorContainer)
)
XCTAssertEqual(update.on, date1)
}
let date2 = Date(timeIntervalSinceNow: -100)
Current.date = { date2 }
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"), // same
WebhookSensor(name: "test1b_mod", uniqueID: "test1b"), // changed value, ignored for cache
WebhookSensor(name: "test1c", uniqueID: "test1c"), // new sensor
]),
]
let promise2 = container.sensors(reason: .registration, server: server1)
let result2 = try hang(Promise(promise2))
// registration doesn't do any filtering
XCTAssertEqual(Set(result2.sensors.map(\.Name)), Set([
"test1a", "test1b_mod", "test1c",
]))
XCTAssertEqual(observer.updates.count, 2)
if observer.updates.count > 1 {
let update = observer.updates[1]
let updateResult = try hang(Promise(update.sensors))
XCTAssertEqual(
updateResult.map(\.Name),
["test1a", "test1b", "test1c"]
)
XCTAssertEqual(update.on, date2)
}
}
func testTriggerDoesCache() throws {
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b", uniqueID: "test1b"),
]),
]
let date = Date(timeIntervalSinceNow: -200)
Current.date = { date }
let promise = container.sensors(reason: .trigger("unit-test"), server: server1)
let result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b",
]))
container.register(observer: observer)
XCTAssertEqual(observer.updates.count, 1)
if let update = observer.updates.first {
let updateResult = try hang(Promise(update.sensors))
XCTAssertEqual(
updateResult.map(\.asTestEquatableForSensorContainer),
result.sensors.map(\.asTestEquatableForSensorContainer)
)
XCTAssertEqual(update.on, date)
}
}
func testUnregisteredObserverIsntNotified() {
container.register(observer: observer)
container.unregister(observer: observer)
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
.value([WebhookSensor(name: "test", uniqueID: "test")]),
]
_ = container.sensors(reason: .trigger("unit-test"), server: server1)
XCTAssertTrue(observer.updates.isEmpty)
}
func testEmptySensorsFlowsThrough() throws {
container.register(provider: MockSensorProvider.self)
MockSensorProvider.returnedPromises = [
.value([]),
.value([]),
.value([WebhookSensor(name: "test", uniqueID: "test")]),
]
let promise = container.sensors(reason: .trigger("unit-test"), server: server1)
let result = try hang(Promise(promise))
XCTAssertEqual(result.sensors.map(\.UniqueID), ["test"])
}
func testDependenciesInformsUpdate() throws {
container.register(provider: MockSensorProvider.self)
container.register(observer: observer)
MockSensorProvider.returnedPromises = [
.value([]),
]
let promise = container.sensors(reason: .trigger("unit-test"), server: server1)
_ = try hang(Promise(promise))
guard let lastCreated = MockSensorProvider.lastCreated else {
XCTFail("expected a provider to have been created")
return
}
XCTAssertEqual(observer.updateSignalCount, 0)
let info: MockUpdateSignaler = lastCreated
.request
.dependencies
.updateSignaler(for: lastCreated)
info.signal()
XCTAssertEqual(observer.updateSignalCount, 1)
}
func testCachingSensorValues() throws {
container.register(provider: MockSensorProvider.self)
container.register(provider: MockSensorProvider.self)
let initialValues: [Promise<[WebhookSensor]>] = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b", uniqueID: "test1b"),
]),
.value([
WebhookSensor(name: "test2a", uniqueID: "test2a"),
WebhookSensor(name: "test2b", uniqueID: "test2b"),
]),
]
MockSensorProvider.returnedPromises = initialValues
var promise: Guarantee<SensorResponse>
var result: SensorResponse
promise = container.sensors(reason: .trigger("unit-test"), server: server1)
result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b", "test2a", "test2b",
]))
MockSensorProvider.returnedPromises = initialValues
promise = container.sensors(reason: .trigger("unit-test"), server: server1)
result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b", "test2a", "test2b",
]))
// now try a couple changed things
MockSensorProvider.returnedPromises = [
.value([
WebhookSensor(name: "test1a", uniqueID: "test1a"),
WebhookSensor(name: "test1b-mod", uniqueID: "test1b"),
]),
.value([
WebhookSensor(name: "test2a", uniqueID: "test2a"),
WebhookSensor(name: "test2b", uniqueID: "test2b"),
WebhookSensor(name: "test2c-new", uniqueID: "test2c"),
]),
]
promise = container.sensors(reason: .trigger("unit-test"), server: server1)
result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set([
"test1a", "test1b", "test2a", "test2b", "test2c",
]))
// now return nothing, should get nothing
MockSensorProvider.returnedPromises = [.value([]), .value([])]
promise = container.sensors(reason: .trigger("unit-test"), server: server1)
result = try hang(Promise(promise))
XCTAssertTrue(result.sensors.isEmpty)
// now let's see what the current 'last update' state is
container.register(observer: observer)
XCTAssertFalse(observer.updates.isEmpty)
if let last = observer.updates.last?.sensors {
let observerResult = try hang(Promise(last))
XCTAssertEqual(Set(observerResult.map(\.UniqueID)), Set([
"test1a", "test1b", "test2a", "test2b", "test2c",
]))
}
}
func testDisabledSensorRedacted() throws {
container.register(provider: MockSensorProvider.self)
let underlying = with(WebhookSensor(name: "test1a", uniqueID: "testDisabled")) {
$0.State = "state"
$0.Attributes = ["test": true]
}
container.setEnabled(false, for: underlying)
XCTAssertFalse(container.isEnabled(sensor: underlying))
let promises: [Promise<[WebhookSensor]>] = [.value([underlying])]
MockSensorProvider.returnedPromises = promises
let promise1 = container.sensors(reason: .trigger("unit-test"), server: server1)
let result1 = try hang(Promise(promise1))
let result1sensor = try XCTUnwrap(result1.sensors.first)
XCTAssertEqual(result1sensor.UniqueID, underlying.UniqueID)
XCTAssertEqual(result1sensor.State as? String, "unavailable")
XCTAssertNil(result1sensor.Attributes)
XCTAssertEqual(result1sensor.Name, underlying.Name)
XCTAssertEqual(result1sensor.Icon, "mdi:dots-square")
container.setEnabled(true, for: underlying)
XCTAssertTrue(container.isEnabled(sensor: underlying))
MockSensorProvider.returnedPromises = promises
let promise2 = container.sensors(reason: .trigger("unit-test"), server: server1)
let result2 = try hang(Promise(promise2))
let result2sensor = try XCTUnwrap(result2.sensors.first)
XCTAssertEqual(result2sensor, underlying)
}
func testDisabledServersRedacted() throws {
container.register(provider: MockSensorProvider.self)
let underlying = with(WebhookSensor(name: "test1a", uniqueID: "testDisabled")) {
$0.State = "state"
$0.Attributes = ["test": true]
}
server1.info.setSetting(value: ServerSensorPrivacy.none, for: .sensorPrivacy)
MockSensorProvider.returnedPromises = [.value([underlying])]
let promiseS1 = container.sensors(reason: .trigger("unit-test"), server: server1)
let resultS1 = try hang(Promise(promiseS1))
MockSensorProvider.returnedPromises = [.value([underlying])]
let promiseS2 = container.sensors(reason: .trigger("unit-test"), server: server2)
let resultS2 = try hang(Promise(promiseS2))
let sensorS1 = try XCTUnwrap(resultS1.sensors.first)
XCTAssertEqual(sensorS1.UniqueID, underlying.UniqueID)
XCTAssertEqual(sensorS1.State as? String, "unavailable")
XCTAssertNil(sensorS1.Attributes)
XCTAssertEqual(sensorS1.Name, underlying.Name)
XCTAssertEqual(sensorS1.Icon, "mdi:dots-square")
let sensorS2 = try XCTUnwrap(resultS2.sensors.first)
XCTAssertEqual(sensorS2.UniqueID, underlying.UniqueID)
XCTAssertEqual(sensorS2.State as? String, "state")
XCTAssertEqual(sensorS2.Attributes?["test"] as? Bool, true)
XCTAssertEqual(sensorS2.Name, underlying.Name)
}
func testSensorsLimitedTo() throws {
container.register(provider: MockSensorProvider.self)
container.register(provider: MockSensorProviderLimitedTo.self)
let expected = WebhookSensor(name: "included", uniqueID: "included")
let promises: [Promise<[WebhookSensor]>] = [.value([expected])]
MockSensorProvider.returnedPromises = promises
let promise = container.sensors(
reason: .registration,
limitedTo: [MockSensorProvider.self],
location: nil,
server: server1
)
let result = try hang(Promise(promise))
XCTAssertEqual(Set(result.sensors.map(\.UniqueID)), Set(["included"]))
}
}
private extension WebhookSensor {
var asTestEquatableForSensorContainer: [String] {
[UniqueID ?? "missing", Name ?? "missing"]
}
}
private class MockSensorObserver: SensorObserver {
var updates: [SensorObserverUpdate] = []
var updateSignalCount: Int = 0
func sensorContainer(
_ container: SensorContainer,
didUpdate update: SensorObserverUpdate
) {
updates.append(update)
}
func sensorContainer(_ container: SensorContainer, didSignalForUpdateBecause reason: SensorContainerUpdateReason) {
updateSignalCount += 1
}
}
private class MockSensorProvider: SensorProvider {
static var returnedPromises: [Promise<[WebhookSensor]>] = []
static var lastCreated: MockSensorProvider?
let request: SensorProviderRequest
let returnedPromise: Promise<[WebhookSensor]>
required init(request: SensorProviderRequest) {
self.request = request
self.returnedPromise = Self.returnedPromises.popLast() ?? .init(error: InvalidTest.noPromiseProvided)
Self.lastCreated = self
}
enum InvalidTest: Error {
case noPromiseProvided
}
func sensors() -> Promise<[WebhookSensor]> {
returnedPromise
}
}
private class MockSensorProviderLimitedTo: SensorProvider {
required init(request: SensorProviderRequest) {
//
}
func sensors() -> Promise<[WebhookSensor]> {
XCTFail("expected to not be called")
return .value([])
}
}
private class MockUpdateSignaler: SensorProviderUpdateSignaler {
let signal: () -> Void
required init(signal: @escaping () -> Void) {
self.signal = signal
}
}