iOS/Tests/Shared/Updater.test.swift

278 lines
10 KiB
Swift

import OHHTTPStubs
import PromiseKit
@testable import Shared
import Version
import XCTest
class UpdaterTest: XCTestCase {
private var stubDescriptors: [HTTPStubsDescriptor] = []
private var updater: Updater!
override func setUp() {
super.setUp()
updater = Updater()
}
override func tearDown() {
super.tearDown()
removeDescriptors()
}
private func removeDescriptors() {
for descriptor in stubDescriptors {
HTTPStubs.removeStub(descriptor)
}
}
private enum Expected {
case updateError(Updater.UpdateError)
case networkError(URLError.Code)
case hasUpdate(Int)
}
func testNetworkError() {
compare(
versions: .failure(URLError(.timedOut)),
currentVersion: .init(major: 2021, minor: 1, patch: 0, build: "1"),
expecting: .networkError(.timedOut)
)
}
func testNoVersions() {
compare(
versions: .success([]),
currentVersion: .init(major: 2021, minor: 1, patch: 0, build: "1"),
expecting: .updateError(.onLatestVersion)
)
}
func testLatestAvailableSameBuild() {
compare(
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "5"),
expecting: .updateError(.onLatestVersion)
)
}
func testLatestAvailableOlderBuilds() {
compare(
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "1"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "2"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "3"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "4"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "5"),
expecting: .updateError(.onLatestVersion)
)
}
func testLatestAvailableNewerBuild() {
compare(
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "1"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "2"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "3"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "4"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "3"),
expecting: .hasUpdate(3)
)
}
func testLatestAvailableNewerVersion() {
compare(
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "1"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "2"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "3"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "4"),
.init(hasAsset: true, prerelease: false, version: "2021.6", build: "1"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "3"),
expecting: .hasUpdate(4)
)
}
func testPrereleaseChecking() {
let versions: [AvailableUpdate] = [
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "1"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "2"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "3"),
.init(hasAsset: true, prerelease: true, version: "2021.5", build: "4"),
]
compare(
allowPrerelease: false,
versions: .success(versions),
currentVersion: .init(major: 2021, minor: 5, build: "2"),
expecting: .hasUpdate(2)
)
compare(
allowPrerelease: true,
versions: .success(versions),
currentVersion: .init(major: 2021, minor: 5, build: "2"),
expecting: .hasUpdate(3)
)
}
func testIgnoresWithoutAssets() {
compare(
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "4"),
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
.init(hasAsset: false, prerelease: false, version: "2021.5", build: "6"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "4"),
expecting: .hasUpdate(1)
)
}
func testUpdatePreference() {
compare(
dueToUserInteraction: false,
allowUpdates: false,
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "1"),
expecting: .updateError(.privacyDisabled)
)
compare(
dueToUserInteraction: true,
allowUpdates: false,
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "1"),
expecting: .hasUpdate(0)
)
}
func testAppStore() {
compare(
isAppStore: true,
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "1"),
expecting: .updateError(.unsupportedPlatform)
)
}
func testNotCatalyst() {
compare(
isCatalyst: false,
versions: .success([
.init(hasAsset: true, prerelease: false, version: "2021.5", build: "5"),
]),
currentVersion: .init(major: 2021, minor: 5, build: "1"),
expecting: .updateError(.unsupportedPlatform)
)
}
private func compare(
isCatalyst: Bool = true,
isAppStore: Bool = false,
dueToUserInteraction: Bool = false,
allowUpdates: Bool = true,
allowPrerelease: Bool = true,
versions: Swift.Result<[AvailableUpdate], Error>,
currentVersion: Version,
expecting: Expected
) {
Current.isCatalyst = isCatalyst
Current.isAppStore = isAppStore
Current.clientVersion = { currentVersion }
Current.settingsStore.privacy.updates = allowUpdates
Current.settingsStore.privacy.updatesIncludeBetas = allowPrerelease
let url = URL(string: "https://api.github.com/repos/home-assistant/ios/releases?per_page=25")!
removeDescriptors()
stubDescriptors.append(stub(condition: { $0.url == url }, response: { _ in
switch versions {
case let .success(versions):
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase
let stuffedVersions: [AvailableUpdate]
if versions.isEmpty == false {
// stuff a bunch of random older and invalid versions in, in random places
stuffedVersions = versions + [
.init(hasAsset: true, prerelease: true, version: "101.1", build: "1"),
.init(hasAsset: true, prerelease: false, version: "101.2", build: "1"),
.init(hasAsset: false, prerelease: true, version: "101.3", build: "1"),
.init(hasAsset: false, prerelease: false, version: "101.4", build: "1"),
.init(hasAsset: true, prerelease: true, version: "101.5", build: "1"),
.init(hasAsset: true, prerelease: false, version: "50.1", build: "1"),
.init(hasAsset: false, prerelease: true, version: "50.2", build: "1"),
.init(hasAsset: false, prerelease: false, version: "50.3", build: "1"),
.init(hasAsset: false, prerelease: false, version: "2019.1", build: "100000"),
.init(hasAsset: false, prerelease: false, version: "2019.100000", build: "100000"),
.init(hasAsset: false, prerelease: false, version: "-1", build: "-1"),
.init(hasAsset: false, prerelease: false, version: "", build: ""),
].shuffled()
} else {
stuffedVersions = versions
}
return HTTPStubsResponse(
data: try! encoder.encode(stuffedVersions),
statusCode: 200,
headers: [:]
)
case let .failure(error):
return HTTPStubsResponse(error: error)
}
}))
let promise = updater.check(dueToUserInteraction: dueToUserInteraction)
switch expecting {
case let .hasUpdate(version):
XCTAssertEqual(try hang(promise), try! versions.get()[version])
case let .updateError(expectedError):
XCTAssertThrowsError(try hang(promise)) { error in
XCTAssertEqual(error as? Updater.UpdateError, expectedError)
}
case let .networkError(code):
XCTAssertThrowsError(try hang(promise)) { error in
XCTAssertEqual((error as? URLError)?.code, code)
}
}
}
}
private extension AvailableUpdate {
init(
hasAsset: Bool,
prerelease: Bool,
version: String,
build: String
) {
var assets = [Asset]()
if hasAsset {
assets.append(.init(
browserDownloadUrl: URL(string: "https://example.com/downloadUrl")!,
name: "Asset Name"
))
}
self.init(
id: Int.random(in: 0 ..< Int.max),
htmlUrl: URL(string: "https://example.com/htmlUrl")!,
tagName: "release/\(version)/\(build)",
name: "\(version) (\(build))",
body: "example body",
prerelease: prerelease,
assets: assets
)
}
}