mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 08:25:32 +00:00
579 lines
24 KiB
Swift
579 lines
24 KiB
Swift
//
|
|
// ResourceUpdateTests.swift
|
|
// KeymanEngineTests
|
|
//
|
|
// Created by Joshua Horton on 7/15/20.
|
|
// Copyright © 2020 SIL International. All rights reserved.
|
|
//
|
|
|
|
import XCTest
|
|
import os.log
|
|
@testable import KeymanEngine
|
|
|
|
class ResourceUpdateTests: XCTestCase {
|
|
var mockedURLSession: TestUtils.Downloading.URLSessionMock!
|
|
var downloadManager: ResourceDownloadManager!
|
|
|
|
override func setUp() {
|
|
TestUtils.setupAndDeinitManager()
|
|
|
|
mockedURLSession = TestUtils.Downloading.URLSessionMock()
|
|
downloadManager = ResourceDownloadManager(session: mockedURLSession!, autoExecute: false)
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
// Resets resource directories for a clean slate.
|
|
TestUtils.standardTearDown()
|
|
|
|
let queueWasCleared = mockedURLSession!.queueIsEmpty
|
|
mockedURLSession = nil
|
|
|
|
if !queueWasCleared {
|
|
throw NSError(domain: "Keyman",
|
|
code: 4,
|
|
userInfo: [NSLocalizedDescriptionKey: "A test did not fully utilize its queued mock results!"])
|
|
}
|
|
}
|
|
|
|
// Designed for use in a 'temporary' test to produce test fixtures for ResourceUpdateTests.
|
|
// Will receive an XCTAttachment with the generated bundle in .zip form.
|
|
func setupUpdateCacheValidityTest() throws {
|
|
// Install a package
|
|
guard let kh_package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) as? KeyboardKeymanPackage else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.khmer_angkor.fullID, from: kh_package)
|
|
|
|
// Set up package-version query mocking.
|
|
let mockedResult = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedResult))
|
|
|
|
let queryExpectation = XCTestExpectation(description: "Package-version query completed.")
|
|
// Run a package-version query on it.
|
|
KeymanPackage.queryCurrentVersions(for: [TestUtils.Keyboards.khmer_angkor.packageKey], withSession: mockedURLSession) { _, _ in
|
|
queryExpectation.fulfill()
|
|
}
|
|
|
|
wait(for: [queryExpectation], timeout: 5)
|
|
|
|
// Uses the XCTest 'attachment' system to retrieve the desired file.
|
|
self.add(try TestUtils.EngineStateBundler.createBundle(withName: "khmer_angkor update base"))
|
|
|
|
os_log("Bundle archived and attached to test's report.", log:KeymanEngineLogger.resources, type: .info)
|
|
}
|
|
|
|
func testCacheCurrent() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
guard let timestamp = UserDefaults.standard.cachedPackageQueryResult(forPackageKey: khmerAngkorKey)?.timestampForLastQuery else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
let originalDate = Date.init(timeIntervalSince1970: timestamp)
|
|
|
|
downloadManager.unitTestCurrentDate = originalDate
|
|
XCTAssertTrue(downloadManager.updateCacheIsCurrent)
|
|
|
|
let timestampPastThreshold = timestamp + ResourceDownloadManager.DISTRIBUTION_CACHE_VALIDITY_THRESHOLD + 1
|
|
let needsUpdateDate = Date.init(timeIntervalSince1970: timestampPastThreshold)
|
|
|
|
downloadManager.unitTestCurrentDate = needsUpdateDate
|
|
XCTAssertFalse(downloadManager.updateCacheIsCurrent)
|
|
}
|
|
|
|
func testGetKeysForUpdatablePackagesSimple() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
|
|
// Case 1: up-to-date query, version is current.
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
guard let timestamp = UserDefaults.standard.cachedPackageQueryResult(forPackageKey: khmerAngkorKey)?.timestampForLastQuery else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
let originalDate = Date.init(timeIntervalSince1970: timestamp)
|
|
|
|
downloadManager.unitTestCurrentDate = originalDate
|
|
XCTAssertEqual(downloadManager.getKeysForUpdatablePackages().count, 0)
|
|
|
|
// Case 2: query out of date, version still current.
|
|
let timestampPastThreshold = timestamp + ResourceDownloadManager.DISTRIBUTION_CACHE_VALIDITY_THRESHOLD + 1
|
|
let needsUpdateDate = Date.init(timeIntervalSince1970: timestampPastThreshold)
|
|
|
|
downloadManager.unitTestCurrentDate = needsUpdateDate
|
|
XCTAssertEqual(downloadManager.getKeysForUpdatablePackages().count, 0)
|
|
|
|
// Now to run it under an actual "updatable" condition.
|
|
// Case 3: fresh query with available update.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: nil) // For Step 5.
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
|
|
let queryExpectation = XCTestExpectation(description: "Version query should complete successfully.")
|
|
|
|
KeymanPackage.queryCurrentVersions(for: [khmerAngkorKey], withSession: mockedURLSession) { _, _ in
|
|
|
|
var keys = self.downloadManager.getKeysForUpdatablePackages()
|
|
XCTAssertEqual(keys.count, 1)
|
|
if keys.count > 0 {
|
|
XCTAssertTrue(keys.contains(khmerAngkorKey))
|
|
}
|
|
|
|
// Case 4: we now make _this_ fetch look out-of-date.
|
|
guard let package = ResourceFileManager.shared.getInstalledPackage(withKey: khmerAngkorKey),
|
|
let timestamp = package.versionQueryCache.timestampForLastQuery else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
|
|
let needsUpdateTimestamp = timestamp + ResourceDownloadManager.DISTRIBUTION_CACHE_VALIDITY_THRESHOLD + 1
|
|
self.downloadManager.unitTestCurrentDate = Date(timeIntervalSince1970: needsUpdateTimestamp)
|
|
|
|
keys = self.downloadManager.getKeysForUpdatablePackages()
|
|
XCTAssertEqual(keys.count, 1)
|
|
if keys.count > 0 {
|
|
XCTAssertTrue(keys.contains(khmerAngkorKey))
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
}
|
|
|
|
wait(for: [queryExpectation], timeout: 5)
|
|
}
|
|
|
|
func testQueryKeysForUpdatablePackagesSimple() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
|
|
// Case 1: fresh query reports 'version is current'.
|
|
let mockedVersionIsCurrent = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsCurrent))
|
|
|
|
let queryExpectation1 = XCTestExpectation(description: "Version query should complete successfully.")
|
|
|
|
downloadManager.queryKeysForUpdatablePackages { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
queryExpectation1.fulfill()
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(results.count, 0)
|
|
|
|
queryExpectation1.fulfill()
|
|
}
|
|
|
|
// Case 2: fresh query reports 'version is outdated'.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
|
|
let queryExpectation2 = XCTestExpectation(description: "Version query should complete successfully.")
|
|
|
|
downloadManager.queryKeysForUpdatablePackages { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
queryExpectation2.fulfill()
|
|
return
|
|
}
|
|
XCTAssertEqual(results.count, 1)
|
|
if results.count > 0 {
|
|
XCTAssertTrue(results.contains(khmerAngkorKey))
|
|
}
|
|
|
|
queryExpectation2.fulfill()
|
|
}
|
|
|
|
// Since we're relying on the queries' local parameters, we can safely run them in parallel!
|
|
wait(for: [queryExpectation1, queryExpectation2], timeout: 5)
|
|
}
|
|
|
|
func testQueryKeysForUpdatablePackagesPostMigration() throws {
|
|
// Features 7 keyboards and 6 lexical models, most of which use autogenerated kmp.jsons.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.cloud_to_kmp_13)
|
|
try Migrations.migrateCloudResourcesToKMPFormat()
|
|
|
|
let allPackages = ResourceFileManager.shared.installedPackages
|
|
|
|
// Query generated from actual api.keyman.com query on 17 Jul 2020.
|
|
/* Original request URL: https://api.keyman.com/package-version?keyboard=sil_sahu&keyboard=sil_euro_latin&keyboard=balochi_latin&keyboard=sil_cameroon_qwerty&keyboard=fv_sencoten&keyboard=indigenous_nt&model=nrc.en.mtnt&model=nrc.str.sencoten&model=sil.bcc-latn.upp_ptwl1&platform=ios
|
|
*/
|
|
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_post_migration, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedQuery))
|
|
|
|
let expectation = XCTestExpectation(description: "package-version query should return cleanly")
|
|
|
|
func assertAutogeneratedPackagesWillUpdate(packageSet: Set<KeymanPackage.Key>) {
|
|
// Any "package" with an autogenerated wrapper should ALWAYS be auto-updated if possible.
|
|
// Note: all installed resources were cloud-distributed at the time that the mocked
|
|
// query was created.
|
|
allPackages.forEach { package in
|
|
if package.metadata.isAutogeneratedWrapper {
|
|
XCTAssertTrue(packageSet.contains(package.key), "Key for autogenerated package \(package.id) not returned for update")
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertFalse(downloadManager.updateCacheIsCurrent)
|
|
downloadManager.queryKeysForUpdatablePackages { keys, error in
|
|
guard error == nil, let keys = keys else {
|
|
XCTFail()
|
|
expectation.fulfill()
|
|
return
|
|
}
|
|
|
|
assertAutogeneratedPackagesWillUpdate(packageSet: keys)
|
|
expectation.fulfill()
|
|
}
|
|
|
|
wait(for: [expectation], timeout: 5)
|
|
|
|
// Now, check to ensure we've cached the results.
|
|
XCTAssertTrue(downloadManager.updateCacheIsCurrent)
|
|
|
|
let set = downloadManager.getKeysForUpdatablePackages()
|
|
assertAutogeneratedPackagesWillUpdate(packageSet: set)
|
|
}
|
|
|
|
func testPerformBatchUpdateSimple() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
|
|
// Establish the query's cache.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: nil)
|
|
let mockedKeyboardUpdate = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
mockedURLSession.queueMockResult(.download(mockedKeyboardUpdate))
|
|
|
|
// For this test, it's easier to let the queue automatically run.
|
|
downloadManager.downloader.autoExecute = true
|
|
|
|
let queryExpectation = XCTestExpectation(description: "Version query should complete successfully.")
|
|
let updateExpectation = XCTestExpectation(description: "The keyboard update should complete.")
|
|
|
|
downloadManager.queryKeysForUpdatablePackages { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
XCTAssertEqual(results.count, 1)
|
|
if results.count > 0 {
|
|
XCTAssertTrue(results.contains(khmerAngkorKey))
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
|
|
self.downloadManager.performBatchUpdate(forPackageKeys: results, withNotifications: false) { successes, failures in
|
|
|
|
if successes.count == 1 {
|
|
XCTAssertEqual(successes[0], khmerAngkorKey)
|
|
} else {
|
|
XCTAssertEqual(successes.count, 1)
|
|
}
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
}
|
|
|
|
// Since we're relying on the queries' local parameters, we can safely run them in parallel!
|
|
wait(for: [queryExpectation, updateExpectation], timeout: 5)
|
|
}
|
|
|
|
func testPerformBatchUpdateSimpleWithDownloadError() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
|
|
// Establish the query's cache.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: nil)
|
|
let mockedKeyboardUpdate = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: NSError(domain: "Keyman", code: 0, userInfo: nil))
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
mockedURLSession.queueMockResult(.download(mockedKeyboardUpdate))
|
|
|
|
// For this test, it's easier to let the queue automatically run.
|
|
downloadManager.downloader.autoExecute = true
|
|
|
|
let queryExpectation = XCTestExpectation(description: "Version query should complete successfully.")
|
|
let updateExpectation = XCTestExpectation(description: "The keyboard update should complete.")
|
|
|
|
downloadManager.queryKeysForUpdatablePackages { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
XCTAssertEqual(results.count, 1)
|
|
if results.count > 0 {
|
|
XCTAssertTrue(results.contains(khmerAngkorKey))
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
|
|
self.downloadManager.performBatchUpdate(forPackageKeys: [khmerAngkorKey], withNotifications: false) { successes, failures in
|
|
|
|
if failures.count == 1 {
|
|
XCTAssertEqual(failures[0].0, khmerAngkorKey)
|
|
} else {
|
|
XCTAssertEqual(failures.count, 1)
|
|
}
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
}
|
|
|
|
// Since we're relying on the queries' local parameters, we can safely run them in parallel!
|
|
wait(for: [queryExpectation, updateExpectation], timeout: 5)
|
|
}
|
|
|
|
func testPerformBatchUpdateSimpleWithQueryError() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
let khmerAngkorKey = TestUtils.Keyboards.khmer_angkor.packageKey
|
|
|
|
// Establish the query's cache.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: NSError(domain: "Keyman", code: 0, userInfo: nil))
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
|
|
// For this test, it's easier to let the queue automatically run.
|
|
downloadManager.downloader.autoExecute = true
|
|
|
|
let queryExpectation = XCTestExpectation(description: "Version query should complete successfully.")
|
|
let updateExpectation = XCTestExpectation(description: "The keyboard update should complete.")
|
|
|
|
downloadManager.queryKeysForUpdatablePackages { results, error in
|
|
guard let _ = error, results == nil else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
|
|
self.downloadManager.performBatchUpdate(forPackageKeys: [khmerAngkorKey], withNotifications: false) { successes, failures in
|
|
|
|
if failures.count == 1 {
|
|
XCTAssertEqual(failures[0].0, khmerAngkorKey)
|
|
} else {
|
|
XCTAssertEqual(failures.count, 1)
|
|
}
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
}
|
|
|
|
// Since we're relying on the queries' local parameters, we can safely run them in parallel!
|
|
wait(for: [queryExpectation, updateExpectation], timeout: 5)
|
|
}
|
|
|
|
func testPerformBatchUpdateInvalidKey() {
|
|
// Load save-state: khmer_angkor installed, cached query holds timestamp for 15 Jul 2020.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Updates.km_base_state)
|
|
let badKey = KeymanPackage.Key(id: "foo", type: .keyboard)
|
|
|
|
let updateExpectation = XCTestExpectation(description: "The keyboard update should complete.")
|
|
|
|
self.downloadManager.performBatchUpdate(forPackageKeys: [badKey], withNotifications: false) { successes, failures in
|
|
|
|
if failures.count == 1 {
|
|
XCTAssertEqual(failures[0].0, badKey)
|
|
} else {
|
|
XCTAssertEqual(failures.count, 1)
|
|
}
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
|
|
// Since we're relying on the queries' local parameters, we can safely run them in parallel!
|
|
wait(for: [updateExpectation], timeout: 5)
|
|
}
|
|
|
|
func testPerformBatchUpdatePostMigration() throws {
|
|
// Features 7 keyboards and 6 lexical models, most of which use autogenerated kmp.jsons.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.cloud_to_kmp_13)
|
|
try Migrations.migrateCloudResourcesToKMPFormat()
|
|
|
|
// Step 1 - get the package-version query results in place.
|
|
|
|
// Query generated from actual api.keyman.com query on 17 Jul 2020.
|
|
/* Original request URL: https://api.keyman.com/package-version?keyboard=sil_sahu&keyboard=sil_euro_latin&keyboard=balochi_latin&keyboard=sil_cameroon_qwerty&keyboard=fv_sencoten&keyboard=indigenous_nt&model=nrc.en.mtnt&model=nrc.str.sencoten&model=sil.bcc-latn.upp_ptwl1&platform=ios
|
|
*/
|
|
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_post_migration, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedQuery))
|
|
|
|
let queryExpectation = XCTestExpectation(description: "package-version query should return cleanly")
|
|
|
|
XCTAssertFalse(downloadManager.updateCacheIsCurrent)
|
|
downloadManager.queryKeysForUpdatablePackages { keys, error in
|
|
guard error == nil, keys != nil else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
}
|
|
|
|
wait(for: [queryExpectation], timeout: 5)
|
|
|
|
// Now, check to ensure we've cached the results.
|
|
XCTAssertTrue(downloadManager.updateCacheIsCurrent)
|
|
|
|
// Step 2: prepare to run the batch update!
|
|
let updateExpectation = XCTestExpectation(description: "performBatchUpdate should return cleanly")
|
|
|
|
let updateSet = downloadManager.getKeysForUpdatablePackages()
|
|
downloadManager.performBatchUpdate(forPackageKeys: updateSet, withNotifications: false) { successes, failures in
|
|
XCTAssertEqual(failures.count, 0)
|
|
XCTAssertEqual(successes.count, updateSet.count)
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
|
|
// Step 2.5: ensure all mocking is properly established.
|
|
|
|
// At this stage, the downloading queue is paused. We need to inspect the batch ordering in order
|
|
// to get the mocking right.
|
|
//
|
|
// Swift implements 'secure hashing' behavior, which means the order is technically
|
|
// inconsistent between runs. (In order to prevent "hash flooding" attacks.)
|
|
// Reference: https://www.hackingwithswift.com/articles/115/swift-4-2-improves-hashable-with-a-new-hasher-struct
|
|
guard case let .compositeBatch(updateBatch) = downloadManager.downloader.topLevelNodes()[0] else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
// Now that we are examining the batch node, we can determine the order in which KeymanEngine wants to
|
|
// download the updates and mock accordingly.
|
|
updateBatch.batchQueue.forEach { entry in
|
|
// Create the mock corresponding to this key and add it to the mocking queue.
|
|
let packageLocation = TestUtils.Packages.getURLForPackage(withKey: entry.0.packageKeys[0])!
|
|
let batchForKey = TestUtils.Downloading.MockResult(location: packageLocation, error: nil)
|
|
mockedURLSession.queueMockResult(.download(batchForKey))
|
|
}
|
|
|
|
// Step 3: Let the updater do its thing.
|
|
|
|
downloadManager.downloader.autoExecute = true // We'll let it auto-run for this test.
|
|
downloadManager.downloader.step() // It will auto-run after this.
|
|
|
|
wait(for: [updateExpectation], timeout: 15)
|
|
|
|
// Step 4: Assertion time.
|
|
|
|
let packages = ResourceFileManager.shared.installedPackages
|
|
packages.forEach { package in
|
|
// Baseline - the kmp.json files should no longer be the old, auto-generated versions.
|
|
XCTAssertFalse(package.metadata.isAutogeneratedWrapper, "\(package.key.id) @ \(package.sourceFolder)")
|
|
XCTAssertNotEqual(package.version, Version.freshInstall)
|
|
|
|
// Most of the packages have author information included within their metadata.
|
|
// This is never autogenerated.
|
|
if package.key != TestUtils.Packages.Keys.indigenous_nt &&
|
|
package.key != TestUtils.Packages.Keys.sil_euro_latin {
|
|
XCTAssertNotNil(package.metadata.info?.author, package.key.id)
|
|
}
|
|
|
|
if let folderContents = try? FileManager.default.contentsOfDirectory(atPath: package.sourceFolder.path) {
|
|
if package.resourceType() == .keyboard {
|
|
// At it turns out, all of the keyboard packages handled by this case also contain a
|
|
// 'desktop'-oriented keyboard!
|
|
XCTAssertTrue(folderContents.contains(where: { $0.hasSuffix(".kmx") }))
|
|
// As it turns out, all of the keyboard packages handled by this case have welcome.htm files!
|
|
XCTAssertTrue(folderContents.contains(where: { $0.hasSuffix("welcome.htm") }))
|
|
}
|
|
} else {
|
|
XCTFail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func testPerformBatchUpdatePostMigrationWithErrors() throws {
|
|
// Features 7 keyboards and 6 lexical models, most of which use autogenerated kmp.jsons.
|
|
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.cloud_to_kmp_13)
|
|
try Migrations.migrateCloudResourcesToKMPFormat()
|
|
|
|
// Step 1 - get the package-version query results in place.
|
|
|
|
// Query generated from actual api.keyman.com query on 17 Jul 2020.
|
|
/* Original request URL: https://api.keyman.com/package-version?keyboard=sil_sahu&keyboard=sil_euro_latin&keyboard=balochi_latin&keyboard=sil_cameroon_qwerty&keyboard=fv_sencoten&keyboard=indigenous_nt&model=nrc.en.mtnt&model=nrc.str.sencoten&model=sil.bcc-latn.upp_ptwl1&platform=ios
|
|
*/
|
|
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_post_migration, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedQuery))
|
|
|
|
let queryExpectation = XCTestExpectation(description: "package-version query should return cleanly")
|
|
|
|
XCTAssertFalse(downloadManager.updateCacheIsCurrent)
|
|
downloadManager.queryKeysForUpdatablePackages { keys, error in
|
|
guard error == nil, keys != nil else {
|
|
XCTFail()
|
|
queryExpectation.fulfill()
|
|
return
|
|
}
|
|
|
|
queryExpectation.fulfill()
|
|
}
|
|
|
|
wait(for: [queryExpectation], timeout: 5)
|
|
|
|
// Now, check to ensure we've cached the results.
|
|
XCTAssertTrue(downloadManager.updateCacheIsCurrent)
|
|
|
|
// Step 2: prepare to run the batch update!
|
|
let updateExpectation = XCTestExpectation(description: "performBatchUpdate should return cleanly")
|
|
|
|
let updateSet = downloadManager.getKeysForUpdatablePackages()
|
|
downloadManager.performBatchUpdate(forPackageKeys: updateSet, withNotifications: false) { successes, failures in
|
|
XCTAssertEqual(failures.count, 2)
|
|
XCTAssertEqual(successes.count, updateSet.count-2)
|
|
|
|
updateExpectation.fulfill()
|
|
}
|
|
|
|
// Step 2.5: ensure all mocking is properly established.
|
|
|
|
// At this stage, the downloading queue is paused. We need to inspect the batch ordering in order
|
|
// to get the mocking right.
|
|
//
|
|
// Swift implements 'secure hashing' behavior, which means the order is technically
|
|
// inconsistent between runs. (In order to prevent "hash flooding" attacks.)
|
|
// Reference: https://www.hackingwithswift.com/articles/115/swift-4-2-improves-hashable-with-a-new-hasher-struct
|
|
guard case let .compositeBatch(updateBatch) = downloadManager.downloader.topLevelNodes()[0] else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
// Now that we are examining the batch node, we can determine the order in which KeymanEngine wants to
|
|
// download the updates and mock accordingly.
|
|
updateBatch.batchQueue.forEach { entry in
|
|
// Create the mock corresponding to this key and add it to the mocking queue.
|
|
let packageKey = entry.0.packageKeys[0]
|
|
let packageLocation = TestUtils.Packages.getURLForPackage(withKey: packageKey)!
|
|
var batchForKey: TestUtils.Downloading.MockResult
|
|
|
|
// One's a lexical model, one's a keyboard.
|
|
if(packageKey == TestUtils.Packages.Keys.nrc_en_mtnt ||
|
|
packageKey == TestUtils.Packages.Keys.fv_sencoten) {
|
|
batchForKey = TestUtils.Downloading.MockResult(location: packageLocation, error: TestUtils.mockedError)
|
|
} else {
|
|
batchForKey = TestUtils.Downloading.MockResult(location: packageLocation, error: nil)
|
|
}
|
|
mockedURLSession.queueMockResult(.download(batchForKey))
|
|
}
|
|
|
|
// Step 3: Let the updater do its thing.
|
|
|
|
downloadManager.downloader.autoExecute = true // We'll let it auto-run for this test.
|
|
downloadManager.downloader.step() // It will auto-run after this.
|
|
|
|
// This test's goal: not crashing due to errors / failures during update ops.
|
|
wait(for: [updateExpectation], timeout: 15)
|
|
}
|
|
}
|