NetNewsWire/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests...

310 lines
13 KiB
Swift

//
// FeedlySyncAllOperationTests.swift
// AccountTests
//
// Created by Kiel Gillard on 30/10/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import XCTest
@testable import Account
import RSWeb
import RSCore
class FeedlySyncAllOperationTests: XCTestCase {
private var account: Account!
private let support = FeedlyTestSupport()
override func setUp() {
super.setUp()
account = support.makeTestAccount()
}
override func tearDown() {
if let account = account {
support.destroy(account)
}
super.tearDown()
}
func expectationForCompletion(of progress: DownloadProgress) -> XCTestExpectation {
return expectation(forNotification: .DownloadProgressDidChange, object: progress) { notification -> Bool in
guard let progress = notification.object as? DownloadProgress else {
return false
}
// We want to assert the progress completes.
if progress.isComplete {
return true
}
return false
}
}
func testCancel() {
let markArticlesService = TestMarkArticlesService()
markArticlesService.didMarkExpectation = expectation(description: "Set Article Statuses")
markArticlesService.didMarkExpectation?.isInverted = true
let getStreamIdsService = TestGetStreamIdsService()
getStreamIdsService.getStreamIdsExpectation = expectation(description: "Get Unread Article Identifiers")
getStreamIdsService.getStreamIdsExpectation?.isInverted = true
let getCollectionsService = TestGetCollectionsService()
getCollectionsService.getCollectionsExpectation = expectation(description: "Get User's Collections")
getCollectionsService.getCollectionsExpectation?.isInverted = true
let getGlobalStreamContents = TestGetStreamContentsService()
getGlobalStreamContents.getStreamContentsExpectation = expectation(description: "Get Contents of global.all")
getGlobalStreamContents.getStreamContentsExpectation?.isInverted = true
let getStarredIds = TestGetStreamIdsService()
getStarredIds.getStreamIdsExpectation = expectation(description: "Get Ids of global.saved")
getStarredIds.getStreamIdsExpectation?.isInverted = true
let getEntriesService = TestGetEntriesService()
getEntriesService.getEntriesExpectation = expectation(description: "Get Entries")
getEntriesService.getEntriesExpectation?.isInverted = true
let progress = DownloadProgress(numberOfTasks: 0)
let _ = expectationForCompletion(of: progress)
let container = support.makeTestDatabaseContainer()
let syncAll = FeedlySyncAllOperation(account: account,
credentials: support.accessToken,
lastSuccessfulFetchStartDate: nil,
markArticlesService: markArticlesService,
getUnreadService: getStreamIdsService,
getCollectionsService: getCollectionsService,
getStreamContentsService: getGlobalStreamContents,
getStarredService: getStarredIds,
getStreamIdsService: getStreamIdsService,
getEntriesService: getEntriesService,
database: container.database,
downloadProgress: progress,
log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
syncAll.completionBlock = { _ in
completionExpectation.fulfill()
}
let syncCompletionExpectation = expectation(description: "Did Finish Sync")
syncCompletionExpectation.isInverted = true
syncAll.syncCompletionHandler = { result in
switch result {
case .success:
XCTFail("Sync operation was cancelled, not successful.")
case .failure:
XCTFail("Sync operation should cancel silently.")
break
}
syncCompletionExpectation.fulfill()
}
MainThreadOperationQueue.shared.addOperation(syncAll)
XCTAssertTrue(progress.numberOfTasks > 1)
syncAll.cancel()
waitForExpectations(timeout: 2)
XCTAssertNil(syncAll.syncCompletionHandler, "Expected completion handler to be destroyed after completion.")
}
private var transport = TestTransport()
lazy var caller: FeedlyAPICaller = {
let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
caller.credentials = support.accessToken
return caller
}()
func testSyncing() throws {
performInitialSync()
try verifyInitialSync()
performChangeStatuses()
try verifyChangeStatuses()
performChangeStatusesAgain()
try verifyChangeStatusesAgain()
performAddFeedsAndFolders()
try verifyAddFeedsAndFolders()
}
// MARK: 1 - Initial Sync
private func loadMockData(inSubdirectoryNamed subdirectory: String) {
let provider = FeedlyMockResponseProvider(findingMocksIn: subdirectory)
transport.mockResponseFileUrlProvider = provider
let progress = DownloadProgress(numberOfTasks: 0)
let _ = expectationForCompletion(of: progress)
// lastSuccessfulFetchStartDate does not matter for the test, content will always be the same.
// It is tested in `FeedlyGetStreamContentsOperationTests`.
let syncAll = FeedlySyncAllOperation(account: account,
credentials: support.accessToken,
caller: caller,
database: databaseContainer.database,
lastSuccessfulFetchStartDate: nil,
downloadProgress: progress,
log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
syncAll.completionBlock = { _ in
completionExpectation.fulfill()
}
MainThreadOperationQueue.shared.addOperation(syncAll)
XCTAssertTrue(progress.numberOfTasks > 1)
waitForExpectations(timeout: 5)
XCTAssertTrue(progress.numberOfTasks == 0)
}
func performInitialSync() {
loadMockData(inSubdirectoryNamed: "feedly-1-initial")
}
func verifyInitialSync() throws {
let subdirectory = "feedly-1-initial"
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "collections", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all@MTZkOTdkZWQ1NzM6NTE2OjUzYjgyNmEy", subdirectory: subdirectory)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory, testCase: self)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds@MTZkOTRhOTNhZTQ6MzExOjUzYjgyNmEy", subdirectory: subdirectory, testCase: self)
support.checkStarredStatuses(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory, testCase: self)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory)
}
// MARK: 2 - Change Statuses
func performChangeStatuses() {
loadMockData(inSubdirectoryNamed: "feedly-2-changestatuses")
}
func verifyChangeStatuses() throws {
let subdirectory = "feedly-2-changestatuses"
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "collections", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all", subdirectory: subdirectory)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory, testCase: self)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds@MTZkOTJkNjIwM2Q6MTEzYjpkNDUwNjA3MQ==", subdirectory: subdirectory, testCase: self)
support.checkStarredStatuses(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory, testCase: self)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory)
}
// MARK: 3 - Change Statuses Again
func performChangeStatusesAgain() {
loadMockData(inSubdirectoryNamed: "feedly-3-changestatusesagain")
}
func verifyChangeStatusesAgain() throws {
let subdirectory = "feedly-3-changestatusesagain"
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "collections", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all", subdirectory: subdirectory)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory, testCase: self)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds@MTZkOGRlMjVmM2M6M2YyOmQ0NTA2MDcx", subdirectory: subdirectory, testCase: self)
support.checkStarredStatuses(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory, testCase: self)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory)
}
// MARK: 4 - Add Feeds and Folders
func performAddFeedsAndFolders() {
loadMockData(inSubdirectoryNamed: "feedly-4-addfeedsandfolders")
}
func verifyAddFeedsAndFolders() throws {
let subdirectory = "feedly-4-addfeedsandfolders"
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "collections", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all", subdirectory: subdirectory)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory, testCase: self)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds@MTZkOTE3YTRlMzQ6YWZjOmQ0NTA2MDcx", subdirectory: subdirectory, testCase: self)
support.checkStarredStatuses(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory, testCase: self)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory)
}
// MARK: 5 - Remove Feeds and Folders
func performRemoveFeedsAndFolders() {
loadMockData(inSubdirectoryNamed: "feedly-5-removefeedsandfolders")
}
func verifyRemoveFeedsAndFolders() throws {
let subdirectory = "feedly-5-removefeedsandfolders"
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "collections", subdirectory: subdirectory)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "global.all", subdirectory: subdirectory)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory, testCase: self)
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds@MTZkOGRlMjVmM2M6M2YxOmQ0NTA2MDcx", subdirectory: subdirectory, testCase: self)
support.checkStarredStatuses(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory, testCase: self)
try support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "starred", subdirectory: subdirectory)
}
// MARK: Downloading Test Data
var lastSuccessfulFetchStartDate: Date?
lazy var databaseContainer: FeedlyTestSupport.TestDatabaseContainer = {
return support.makeTestDatabaseContainer()
}()
func downloadTestData() {
let caller = FeedlyAPICaller(transport: URLSession.webserviceTransport(), api: .sandbox)
let credentials = Credentials(type: .oauthAccessToken, username: "<#USERNAME#>", secret: "<#SECRET#>")
caller.credentials = credentials
let progress = DownloadProgress(numberOfTasks: 0)
let _ = expectationForCompletion(of: progress)
let syncAll = FeedlySyncAllOperation(account: account, credentials: credentials, caller: caller, database: databaseContainer.database, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, downloadProgress: progress, log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
syncAll.completionBlock = { _ in
completionExpectation.fulfill()
}
lastSuccessfulFetchStartDate = Date()
MainThreadOperationQueue.shared.addOperation(syncAll)
XCTAssertTrue(progress.numberOfTasks > 1)
waitForExpectations(timeout: 60)
XCTAssertTrue(progress.numberOfTasks == 0)
}
// Prefix with "test" to manually run this particular function, e.g.: func test_getTestData()
func getTestData() {
// Add a breakpoint on the `print` statements and start a proxy server on your Mac.
// 1. In Feedly sandbox, perform the actions implied by the string in the print statement.
// 2. In the proxy server app, such as Charles, clear requests and responses and filter by "sandbox".
// 3. In Xcode, hit continue in the Debugger so the test requests the data.
// 4. Save the responses captured by the proxy.
print("Prepare for initial sync.")
downloadTestData()
assert(lastSuccessfulFetchStartDate != nil)
print("Read/unread, star and unstar some articles.")
downloadTestData()
print("Read/unread, star and unstar some articles again.")
downloadTestData()
print("Add Feeds and Folders.")
downloadTestData()
print("Rename and Remove Feeds and Folders.")
downloadTestData()
}
}