NetNewsWire/Account/Tests/AccountTests/Feedly/FeedlyLogoutOperationTests.swift

218 lines
6.4 KiB
Swift

//
// FeedlyLogoutOperationTests.swift
// AccountTests
//
// Created by Kiel Gillard on 15/11/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import XCTest
@testable import Account
import RSCore
import Secrets
class FeedlyLogoutOperationTests: 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()
}
private func getTokens(for account: Account) throws -> (accessToken: Credentials, refreshToken: Credentials) {
guard let accessToken = try account.retrieveCredentials(type: .oauthAccessToken), let refreshToken = try account.retrieveCredentials(type: .oauthRefreshToken) else {
XCTFail("Unable to retrieve access and/or refresh token from account.")
throw CredentialsError.incompleteCredentials
}
return (accessToken, refreshToken)
}
class TestFeedlyLogoutService: FeedlyLogoutService {
var mockResult: Result<Void, Error>?
var logoutExpectation: XCTestExpectation?
func logout(completion: @escaping (Result<Void, Error>) -> ()) {
guard let result = mockResult else {
XCTFail("Missing mock result. Test may time out because the completion will not be called.")
return
}
DispatchQueue.main.async {
completion(result)
self.logoutExpectation?.fulfill()
}
}
}
func testCancel() {
let service = TestFeedlyLogoutService()
service.logoutExpectation = expectation(description: "Did Call Logout")
service.logoutExpectation?.isInverted = true
let accessToken: Credentials
let refreshToken: Credentials
do {
(accessToken, refreshToken) = try getTokens(for: account)
} catch {
XCTFail("Could not retrieve credentials to verify their integrity later.")
return
}
let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
logout.completionBlock = { _ in
completionExpectation.fulfill()
}
MainThreadOperationQueue.shared.add(logout)
MainThreadOperationQueue.shared.cancelOperations([logout])
waitForExpectations(timeout: 1)
XCTAssertTrue(logout.isCanceled)
do {
let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken)
let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken)
XCTAssertEqual(accountAccessToken, accessToken)
XCTAssertEqual(accountRefreshToken, refreshToken)
} catch {
XCTFail("Could not verify tokens were left intact. Did the operation delete them?")
}
}
func testLogoutSuccess() {
let service = TestFeedlyLogoutService()
service.logoutExpectation = expectation(description: "Did Call Logout")
service.mockResult = .success(())
let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
logout.completionBlock = { _ in
completionExpectation.fulfill()
}
MainThreadOperationQueue.shared.add(logout)
waitForExpectations(timeout: 1)
XCTAssertFalse(logout.isCanceled)
do {
let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken)
let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken)
XCTAssertNil(accountAccessToken)
XCTAssertNil(accountRefreshToken)
} catch {
XCTFail("Could not verify tokens were deleted.")
}
}
class TestLogoutDelegate: FeedlyOperationDelegate {
var error: Error?
var didFailExpectation: XCTestExpectation?
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
self.error = error
didFailExpectation?.fulfill()
}
}
func testLogoutMissingAccessToken() {
support.removeCredentials(matching: .oauthAccessToken, from: account)
let (_, service) = support.makeMockNetworkStack()
service.credentials = nil
let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log)
let delegate = TestLogoutDelegate()
delegate.didFailExpectation = expectation(description: "Did Fail")
logout.delegate = delegate
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
logout.completionBlock = { _ in
completionExpectation.fulfill()
}
MainThreadOperationQueue.shared.add(logout)
waitForExpectations(timeout: 1)
XCTAssertFalse(logout.isCanceled)
do {
let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken)
XCTAssertNil(accountAccessToken)
} catch {
XCTFail("Could not verify tokens were deleted.")
}
XCTAssertNotNil(delegate.error, "Should have failed with error.")
if let error = delegate.error {
switch error {
case CredentialsError.incompleteCredentials:
break
default:
XCTFail("Expected \(CredentialsError.incompleteCredentials)")
}
}
}
func testLogoutFailure() {
let service = TestFeedlyLogoutService()
service.logoutExpectation = expectation(description: "Did Call Logout")
service.mockResult = .failure(URLError(.timedOut))
let accessToken: Credentials
let refreshToken: Credentials
do {
(accessToken, refreshToken) = try getTokens(for: account)
} catch {
XCTFail("Could not retrieve credentials to verify their integrity later.")
return
}
let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log)
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
let completionExpectation = expectation(description: "Did Finish")
logout.completionBlock = { _ in
completionExpectation.fulfill()
}
MainThreadOperationQueue.shared.add(logout)
waitForExpectations(timeout: 1)
XCTAssertFalse(logout.isCanceled)
do {
let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken)
let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken)
XCTAssertEqual(accountAccessToken, accessToken)
XCTAssertEqual(accountRefreshToken, refreshToken)
} catch {
XCTFail("Could not verify tokens were left intact. Did the operation delete them?")
}
}
}