mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 00:42:29 -06:00
[PM-28339] Convert data store publisher tests to use async iterators (#2181)
This commit is contained in:
parent
c368f60889
commit
e9c25c9b06
@ -355,21 +355,15 @@ final class AuthenticatorBridgeItemServiceTests: AuthenticatorBridgeKitTestCase
|
||||
let initialItems = AuthenticatorBridgeItemDataView.fixtures().sorted { $0.id < $1.id }
|
||||
try await subject.insertItems(initialItems, forUserId: "userId")
|
||||
|
||||
var results: [[AuthenticatorBridgeItemDataView]] = []
|
||||
let publisher = try await subject.sharedItemsPublisher()
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { value in
|
||||
results.append(value)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = try await subject.sharedItemsPublisher().valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, initialItems)
|
||||
|
||||
try await subject.replaceAllItems(with: [], forUserId: "userId")
|
||||
|
||||
waitFor(results.count == 2)
|
||||
XCTAssertEqual(results[0], initialItems)
|
||||
XCTAssertEqual(results[1], [])
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, [])
|
||||
}
|
||||
|
||||
/// Verify that the shared items publisher publishes items that are inserted/replaced later.
|
||||
@ -378,22 +372,16 @@ final class AuthenticatorBridgeItemServiceTests: AuthenticatorBridgeKitTestCase
|
||||
let initialItems = AuthenticatorBridgeItemDataView.fixtures().sorted { $0.id < $1.id }
|
||||
try await subject.insertItems(initialItems, forUserId: "userId")
|
||||
|
||||
var results: [[AuthenticatorBridgeItemDataView]] = []
|
||||
let publisher = try await subject.sharedItemsPublisher()
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { value in
|
||||
results.append(value)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = try await subject.sharedItemsPublisher().valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, initialItems)
|
||||
|
||||
let replacedItems = [AuthenticatorBridgeItemDataView.fixture(name: "New Item")]
|
||||
try await subject.replaceAllItems(with: replacedItems, forUserId: "userId")
|
||||
|
||||
waitFor(results.count == 2)
|
||||
XCTAssertEqual(results[0], initialItems)
|
||||
XCTAssertEqual(results[1], replacedItems)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, replacedItems)
|
||||
}
|
||||
|
||||
/// The shared items publisher deletes items if the user is timed out.
|
||||
|
||||
@ -33,21 +33,15 @@ class AuthenticatorItemDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `authenticatorItemPublisher(userId:)` returns a publisher for a user's authenticatorItem objects.
|
||||
func test_authenticatorItemPublisher() async throws {
|
||||
var publishedValues = [[AuthenticatorItem]]()
|
||||
let publisher = subject.authenticatorItemPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.authenticatorItemPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceAuthenticatorItems(authenticatorItems, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], authenticatorItems)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, authenticatorItems)
|
||||
}
|
||||
|
||||
/// `deleteAllAuthenticatorItems(user:)` removes all objects for the user.
|
||||
|
||||
@ -120,28 +120,24 @@ class GeneratorDataStoreTests: BitwardenTestCase {
|
||||
/// `passwordHistoryPublisher(userId:)` returns a publisher for a user's password history objects.
|
||||
@MainActor
|
||||
func test_passwordHistoryPublisher() async throws {
|
||||
var publishedValues = [[PasswordHistory]]()
|
||||
let publisher = subject.passwordHistoryPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.passwordHistoryPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
let passwordHistory1 = PasswordHistory(password: "PASSWORD1", lastUsedDate: Date())
|
||||
try await subject.insertPasswordHistory(userId: "1", passwordHistory: passwordHistory1)
|
||||
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, [passwordHistory1])
|
||||
|
||||
let passwordHistoryOther = PasswordHistory(password: "PASSWORD_OTHER", lastUsedDate: Date())
|
||||
try await subject.insertPasswordHistory(userId: "2", passwordHistory: passwordHistoryOther)
|
||||
|
||||
let passwordHistory2 = PasswordHistory(password: "PASSWORD2", lastUsedDate: Date())
|
||||
try await subject.insertPasswordHistory(userId: "1", passwordHistory: passwordHistory2)
|
||||
|
||||
waitFor { publishedValues.count == 3 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], [passwordHistory1])
|
||||
XCTAssertEqual(publishedValues[2], [passwordHistory2, passwordHistory1])
|
||||
let thirdValue = try await iterator.next()
|
||||
XCTAssertEqual(thirdValue, [passwordHistory2, passwordHistory1])
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,40 +57,28 @@ class SendDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `sendPublisher(userId:)` returns a publisher for a single send.
|
||||
func test_sendPublisher() async throws {
|
||||
var publishedValues = [Send?]()
|
||||
let publisher = subject.sendPublisher(id: "1", userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { value in
|
||||
publishedValues.append(value)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.sendPublisher(id: "1", userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, .some(nil))
|
||||
|
||||
try await subject.replaceSends(sends, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertNil(publishedValues[0])
|
||||
XCTAssertEqual(publishedValues[1], Send.fixture(id: "1", name: "SEND1"))
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, Send.fixture(id: "1", name: "SEND1"))
|
||||
}
|
||||
|
||||
/// `sendsPublisher(userId:)` returns a publisher for a user's send objects.
|
||||
func test_sendsPublisher() async throws {
|
||||
var publishedValues = [[Send]]()
|
||||
let publisher = subject.sendsPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.sendsPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceSends(sends, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], sends)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, sends)
|
||||
}
|
||||
|
||||
/// `replaceSends(_:userId)` replaces the list of sends for the user.
|
||||
|
||||
@ -46,21 +46,15 @@ class CipherDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `cipherPublisher(userId:)` returns a publisher for a user's cipher objects.
|
||||
func test_cipherPublisher() async throws {
|
||||
var publishedValues = [[Cipher]]()
|
||||
let publisher = subject.cipherPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.cipherPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceCiphers(ciphers, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], ciphers)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, ciphers)
|
||||
}
|
||||
|
||||
/// `cipherChangesPublisher(userId:)` emits inserted ciphers for the user.
|
||||
|
||||
@ -34,21 +34,15 @@ class CollectionDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `collectionPublisher(userId:)` returns a publisher for a user's collection objects.
|
||||
func test_collectionPublisher() async throws {
|
||||
var publishedValues = [[Collection]]()
|
||||
let publisher = subject.collectionPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.collectionPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceCollections(collections, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], collections)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, collections)
|
||||
}
|
||||
|
||||
/// `deleteAllCollections(user:)` removes all objects for the user.
|
||||
|
||||
@ -68,21 +68,15 @@ class FolderDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `folderPublisher(userId:)` returns a publisher for a user's folder objects.
|
||||
func test_folderPublisher() async throws {
|
||||
var publishedValues = [[Folder]]()
|
||||
let publisher = subject.folderPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.folderPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceFolders(folders, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], folders)
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, folders)
|
||||
}
|
||||
|
||||
/// `replaceFolders(_:userId)` replaces the list of folders for the user.
|
||||
|
||||
@ -45,21 +45,15 @@ class OrganizationDataStoreTests: BitwardenTestCase {
|
||||
|
||||
/// `organizationPublisher(userId:)` returns a publisher for a user's organization objects.
|
||||
func test_organizationPublisher() async throws {
|
||||
var publishedValues = [[Organization]]()
|
||||
let publisher = subject.organizationPublisher(userId: "1")
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { values in
|
||||
publishedValues.append(values)
|
||||
},
|
||||
)
|
||||
defer { publisher.cancel() }
|
||||
var iterator = subject.organizationPublisher(userId: "1").valuesWithTimeout().makeAsyncIterator()
|
||||
|
||||
let firstValue = try await iterator.next()
|
||||
XCTAssertEqual(firstValue, [])
|
||||
|
||||
try await subject.replaceOrganizations(organizations, userId: "1")
|
||||
|
||||
waitFor { publishedValues.count == 2 }
|
||||
XCTAssertTrue(publishedValues[0].isEmpty)
|
||||
XCTAssertEqual(publishedValues[1], organizations.compactMap(Organization.init))
|
||||
let secondValue = try await iterator.next()
|
||||
XCTAssertEqual(secondValue, organizations.compactMap(Organization.init))
|
||||
}
|
||||
|
||||
/// `fetchAllOrganizations(userId:)` fetches all organizations for a user.
|
||||
|
||||
27
TestHelpers/Extensions/Publisher+ValuesWithTimeout.swift
Normal file
27
TestHelpers/Extensions/Publisher+ValuesWithTimeout.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
/// An error thrown when a publisher times out while awaiting values.
|
||||
public struct PublisherTimeoutError: Error {}
|
||||
|
||||
public extension Publisher where Failure == Error {
|
||||
/// Returns an async sequence of the publisher's values with a timeout.
|
||||
///
|
||||
/// This is useful in tests where you want to await publisher values without risking test hangs
|
||||
/// if the publisher never emits.
|
||||
///
|
||||
/// - Parameter timeout: The maximum time interval to wait for values, in seconds. Defaults to 10 seconds.
|
||||
/// - Returns: An async throwing publisher that emits values or throws `PublisherTimeoutError` on timeout.
|
||||
///
|
||||
func valuesWithTimeout(
|
||||
_ timeout: TimeInterval = 10,
|
||||
) -> AsyncThrowingPublisher<Publishers.Timeout<Self, DispatchQueue>> {
|
||||
self
|
||||
.timeout(
|
||||
.seconds(timeout),
|
||||
scheduler: DispatchQueue.main,
|
||||
customError: { PublisherTimeoutError() },
|
||||
)
|
||||
.values
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user