mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 17:46:07 -06:00
484 lines
18 KiB
Swift
484 lines
18 KiB
Swift
import BitwardenKit
|
|
import BitwardenKitMocks
|
|
import BitwardenSdk
|
|
import TestHelpers
|
|
import XCTest
|
|
|
|
@testable import BitwardenShared
|
|
|
|
// swiftlint:disable file_length
|
|
|
|
final class ClientServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_length
|
|
var clientBuilder: MockClientBuilder!
|
|
var configService: MockConfigService!
|
|
var errorReporter: MockErrorReporter!
|
|
var sdkRepositoryFactory: MockSdkRepositoryFactory!
|
|
var stateService: MockStateService!
|
|
var subject: DefaultClientService!
|
|
var vaultTimeoutService: MockVaultTimeoutService!
|
|
|
|
// MARK: Setup and Teardown
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
clientBuilder = MockClientBuilder()
|
|
configService = MockConfigService()
|
|
errorReporter = MockErrorReporter()
|
|
sdkRepositoryFactory = MockSdkRepositoryFactory()
|
|
sdkRepositoryFactory.makeCipherRepositoryReturnValue = MockSdkCipherRepository()
|
|
stateService = MockStateService()
|
|
subject = DefaultClientService(
|
|
clientBuilder: clientBuilder,
|
|
configService: configService,
|
|
errorReporter: errorReporter,
|
|
sdkRepositoryFactory: sdkRepositoryFactory,
|
|
stateService: stateService
|
|
)
|
|
vaultTimeoutService = MockVaultTimeoutService()
|
|
}
|
|
|
|
override func tearDown() {
|
|
super.tearDown()
|
|
|
|
clientBuilder = nil
|
|
configService = nil
|
|
errorReporter = nil
|
|
sdkRepositoryFactory = nil
|
|
stateService = nil
|
|
subject = nil
|
|
vaultTimeoutService = nil
|
|
}
|
|
|
|
// MARK: Tests
|
|
|
|
/// `auth(for:)` returns a new `AuthClientProtocol` for every user.
|
|
func test_auth() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let auth = try await subject.auth()
|
|
XCTAssertIdentical(auth, clientBuilder.clients.first?.authClient)
|
|
|
|
let user2Auth = try await subject.auth(for: "2")
|
|
XCTAssertNotIdentical(auth, user2Auth)
|
|
}
|
|
|
|
/// `auth(for:)` logs an error if there's no accounts and the `isPreAuth` flag isn't set.
|
|
func test_auth_noAccountsNotPreAuth() async throws {
|
|
let auth1 = try await subject.auth()
|
|
let auth2 = try await subject.auth()
|
|
XCTAssertNotIdentical(auth1, auth2)
|
|
|
|
XCTAssertEqual(errorReporter.errors.count, 2)
|
|
XCTAssertEqual((errorReporter.errors[0] as NSError).domain, "General Error: Missing isPreAuth")
|
|
XCTAssertEqual((errorReporter.errors[1] as NSError).domain, "General Error: Missing isPreAuth")
|
|
}
|
|
|
|
/// `client(for:)` called concurrently doesn't crash.
|
|
func test_client_calledConcurrently() async throws {
|
|
// Calling `client(for:)` concurrently shouldn't throw an exception due to simultaneous
|
|
// access to shared state. Since it's a race condition, running it repeatedly should expose
|
|
// the failure if it's going to fail.
|
|
for _ in 0 ..< 5 {
|
|
async let concurrentTask1 = subject.auth(for: "1")
|
|
async let concurrentTask2 = subject.auth(for: "1")
|
|
|
|
_ = try await (concurrentTask1, concurrentTask2)
|
|
}
|
|
}
|
|
|
|
/// Tests that `client(for:)` creates a new client if there is no active user/if there are no users.
|
|
/// Also tests that `client(for:)` returns a user's existing client.
|
|
/// Also tests that a `client(for:)` creates a new client if a user doesn't have one.
|
|
func test_client_multiple_users() async throws {
|
|
// No active user.
|
|
let noActiveUserAuth = try await subject.auth()
|
|
let auth = clientBuilder.clients.first?.authClient
|
|
XCTAssertIdentical(noActiveUserAuth, auth)
|
|
|
|
// Creates new client for user that doesn't have one.
|
|
let userAuth = try await subject.auth(for: "1")
|
|
XCTAssertNotIdentical(noActiveUserAuth, userAuth)
|
|
|
|
// Creates a new client for a different user.
|
|
let user2Auth = try await subject.auth(for: "2")
|
|
XCTAssertNotIdentical(noActiveUserAuth, user2Auth)
|
|
XCTAssertNotIdentical(userAuth, user2Auth)
|
|
|
|
// Returns a user's existing client.
|
|
let userExistingAuthClient = try await subject.auth(for: "1")
|
|
XCTAssertIdentical(userAuth, userExistingAuthClient)
|
|
}
|
|
|
|
/// `client(for:)` loads flags into the SDK.
|
|
@MainActor
|
|
func test_client_loadFlags() async throws {
|
|
configService.featureFlagsBool[.cipherKeyEncryption] = true
|
|
configService.configMocker.withResult(ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
))
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
["enableCipherKeyEncryption": true]
|
|
)
|
|
}
|
|
|
|
/// `client(for:)` loads `enableCipherKeyEncryption` flag as `false` into the SDK
|
|
/// when the server version is old.
|
|
@MainActor
|
|
func test_client_loadFlagsEnableCipherKeyEncryptionFalseBecauseOfServerVersion() async throws {
|
|
configService.featureFlagsBool[.cipherKeyEncryption] = true
|
|
configService.configMocker.withResult(ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.1.0"
|
|
)
|
|
))
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
["enableCipherKeyEncryption": false]
|
|
)
|
|
}
|
|
|
|
/// `client(for:)` loads `enableCipherKeyEncryption` flag as `false` into the SDK
|
|
/// when the server version is old.
|
|
@MainActor
|
|
func test_client_loadFlagsEnableCipherKeyEncryptionFalseBecauseOfFeatureFlag() async throws {
|
|
configService.featureFlagsBool[.cipherKeyEncryption] = false
|
|
configService.configMocker.withResult(ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
))
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
["enableCipherKeyEncryption": false]
|
|
)
|
|
}
|
|
|
|
/// `client(for:)` loading flags throws.
|
|
@MainActor
|
|
func test_client_loadFlagsThrows() async throws {
|
|
configService.configMocker.withResult(ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.6.0"
|
|
)
|
|
))
|
|
clientBuilder.setupClientOnCreation = { client in
|
|
client.platformClient.loadFlagsError = BitwardenTestError.example
|
|
}
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
|
|
XCTAssertEqual(errorReporter.errors as? [BitwardenTestError], [.example])
|
|
}
|
|
|
|
/// `client(for:)` does not load flags when config is `nil`.
|
|
@MainActor
|
|
func test_client_doesNotloadFlags() async throws {
|
|
configService.configMocker.withResult(nil)
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
[:]
|
|
)
|
|
}
|
|
|
|
/// `client(for:)` registers the SDK cipher repository.
|
|
func test_client_registersCipherRepository() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let auth = try await subject.auth()
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertIdentical(auth, client.authClient)
|
|
XCTAssertTrue(sdkRepositoryFactory.makeCipherRepositoryCalled)
|
|
XCTAssertNotNil(client.platformClient.stateMock.registerCipherRepositoryReceivedStore)
|
|
}
|
|
|
|
/// `configPublisher` loads flags into the SDK.
|
|
@MainActor
|
|
func test_configPublisher_loadFlags() async throws {
|
|
configService.featureFlagsBool[.cipherKeyEncryption] = true
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: false,
|
|
userId: "1",
|
|
serverConfig: ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: ["cipher-key-encryption": .bool(true)],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
try await waitForAsync {
|
|
guard !self.clientBuilder.clients.isEmpty else {
|
|
return false
|
|
}
|
|
let client = try? XCTUnwrap(self.clientBuilder.clients.first)
|
|
return client?.platformClient.featureFlags == ["enableCipherKeyEncryption": true]
|
|
}
|
|
}
|
|
|
|
/// `configPublisher` loads flags into the SDK on a already created client taking into account
|
|
/// changing the cipher-key-encryption feature flag.
|
|
@MainActor
|
|
func test_configPublisher_loadFlagsOverride() async throws { // swiftlint:disable:this function_body_length
|
|
configService.configMocker.withResult(ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238199",
|
|
server: nil,
|
|
version: "2024.1.0"
|
|
)
|
|
))
|
|
|
|
_ = try await subject.auth(for: "1")
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
["enableCipherKeyEncryption": false]
|
|
)
|
|
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: false,
|
|
userId: "1",
|
|
serverConfig: ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
try await waitForAsync {
|
|
let client = try? XCTUnwrap(self.clientBuilder.clients.first)
|
|
return client?.platformClient.featureFlags == ["enableCipherKeyEncryption": false]
|
|
}
|
|
XCTAssertEqual(clientBuilder.clients.count, 1)
|
|
|
|
configService.featureFlagsBool[.cipherKeyEncryption] = true
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: false,
|
|
userId: "1",
|
|
serverConfig: ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: ["cipher-key-encryption": .bool(true)],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
try await waitForAsync {
|
|
let client = try? XCTUnwrap(self.clientBuilder.clients.first)
|
|
return client?.platformClient.featureFlags == ["enableCipherKeyEncryption": true]
|
|
}
|
|
XCTAssertEqual(clientBuilder.clients.count, 1)
|
|
}
|
|
|
|
/// `configPublisher` does not load flags into the SDK when the config sent is pre authentication.
|
|
@MainActor
|
|
func test_configPublisher_doesNotloadFlagsWhenIsPreAuth() async throws {
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: true,
|
|
userId: "1",
|
|
serverConfig: ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
XCTAssertTrue(clientBuilder.clients.isEmpty)
|
|
}
|
|
|
|
/// `configPublisher` does not load flags into the SDK when the config sent doesn't have a user id.
|
|
@MainActor
|
|
func test_configPublisher_doesNotloadFlagsWhenUserIdIsNil() async throws {
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: false,
|
|
userId: nil,
|
|
serverConfig: ServerConfig(
|
|
date: Date(year: 2024, month: 2, day: 14, hour: 7, minute: 50, second: 0),
|
|
responseModel: ConfigResponseModel(
|
|
environment: nil,
|
|
featureStates: [:],
|
|
gitHash: "75238191",
|
|
server: nil,
|
|
version: "2024.4.0"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
XCTAssertTrue(clientBuilder.clients.isEmpty)
|
|
}
|
|
|
|
/// `configPublisher` does not load flags into the SDK when the config sent doesn't have a server config.
|
|
@MainActor
|
|
func test_configPublisher_doesNotloadFlagsWhenServerConfigIsNil() async throws {
|
|
configService.configSubject.send(
|
|
MetaServerConfig(
|
|
isPreAuth: false,
|
|
userId: "1",
|
|
serverConfig: nil
|
|
)
|
|
)
|
|
|
|
try await waitForAsync {
|
|
!self.clientBuilder.clients.isEmpty
|
|
}
|
|
|
|
let client = try XCTUnwrap(clientBuilder.clients.first)
|
|
XCTAssertEqual(
|
|
client.platformClient.featureFlags,
|
|
[:]
|
|
)
|
|
}
|
|
|
|
/// `crypto(for:)` returns a new `CryptoClientProtocol` for every user.
|
|
func test_crypto() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let crypto = try await subject.crypto()
|
|
|
|
XCTAssertIdentical(crypto, clientBuilder.clients.first?.cryptoClient)
|
|
let user2Crypto = try await subject.crypto(for: "2")
|
|
XCTAssertNotIdentical(crypto, user2Crypto)
|
|
}
|
|
|
|
/// `exporters(for:)` returns a new `ExporterClientProtocol` for every user.
|
|
func test_exporters() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let exporters = try await subject.exporters()
|
|
XCTAssertIdentical(exporters, clientBuilder.clients.first?.exporterClient)
|
|
|
|
let user2Exporters = try await subject.exporters(for: "2")
|
|
XCTAssertNotIdentical(exporters, user2Exporters)
|
|
}
|
|
|
|
/// `generators(for:)` returns a new `GeneratorClientsProtocol` for every user.
|
|
func test_generators() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let generators = try await subject.generators(isPreAuth: false)
|
|
XCTAssertIdentical(generators, clientBuilder.clients.first?.generatorClient)
|
|
|
|
let user2Generators = try await subject.generators(for: "2")
|
|
XCTAssertNotIdentical(generators, user2Generators)
|
|
}
|
|
|
|
/// `platform(for:)` returns a new `PlatformClientProtocol` for every user.
|
|
func test_platform() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let platform = try await subject.platform()
|
|
XCTAssertIdentical(platform, clientBuilder.clients.first?.platformClient)
|
|
|
|
let user2Platform = try await subject.platform(for: "2")
|
|
XCTAssertNotIdentical(platform, user2Platform)
|
|
}
|
|
|
|
/// `removeClient(for:)` removes a cached client for a user.
|
|
func test_removeClient() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let crypto = try await subject.crypto()
|
|
let crypto2 = try await subject.crypto()
|
|
// The same client should be returned for subsequent requests.
|
|
XCTAssertIdentical(crypto, crypto2)
|
|
|
|
try await subject.removeClient()
|
|
// After removing the client, a new client should be returned for the user.
|
|
let cryptoAfterRemoving = try await subject.crypto()
|
|
|
|
XCTAssertNotIdentical(crypto, cryptoAfterRemoving)
|
|
}
|
|
|
|
/// `sends(for:)` returns a new `VaultClientProtocol` for every user.
|
|
func test_sends() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let sends = try await subject.sends()
|
|
XCTAssertIdentical(sends, clientBuilder.clients.first?.sendClient)
|
|
|
|
let user2Sends = try await subject.sends(for: "2")
|
|
XCTAssertNotIdentical(sends, user2Sends)
|
|
}
|
|
|
|
/// `vault(for:)` returns a new `VaultClientProtocol` for every user.
|
|
func test_vault() async throws {
|
|
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
|
|
|
|
let vault = try await subject.vault()
|
|
XCTAssertIdentical(vault, clientBuilder.clients.first?.vaultClient)
|
|
|
|
let user2Vault = try await subject.vault(for: "2")
|
|
XCTAssertNotIdentical(vault, user2Vault)
|
|
}
|
|
}
|