// // Copyright Amazon.com Inc. or its affiliates. // All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // import XCTest @testable import Amplify @testable import AmplifyTestCommon class AuthCategoryConfigurationTests: XCTestCase { override func setUp() async throws { await Amplify.reset() } /// Test if we can add a new auth plugin /// /// - Given: UnConfigured Amplify framework /// - When: /// - I add a new Auth plugin to Amplify /// - Then: /// - Plugin should be added without throwing any error /// func testCanAddPlugin() throws { let plugin = MockAuthCategoryPlugin() XCTAssertNoThrow(try Amplify.add(plugin: plugin)) } /// Test if Auth plugin can be configured /// /// - Given: UnConfigured Amplify framework /// - When: /// - I add a new Auth plugin and add configuration for the plugin /// - Then: /// - Auth plugin should be configured correctly /// func testCanConfigureCategory() throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) XCTAssertNotNil(Amplify.Auth) XCTAssertNotNil(try Amplify.Auth.getPlugin(for: "MockAuthCategoryPlugin")) } /// Test if resetting Auth category works /// /// - Given: Amplify framework configured with Auth plugin /// - When: /// - I call await Amplify.reset() /// - Then: /// - The plugin should invoke the reset method. /// func testCanResetCategory() async throws { let plugin = MockAuthCategoryPlugin() let resetWasInvoked = expectation(description: "reset() was invoked") plugin.listeners.append { message in if message == "reset" { resetWasInvoked.fulfill() } } try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) await Amplify.reset() await fulfillment(of: [resetWasInvoked], timeout: 1.0) } /// Test whether calling reset removes the plugin added /// /// - Given: Amplify framework configured with Auth plugin /// - When: /// - I call Amplify.reset /// - Then: /// - Auth plugin should no longer work /// func testResetRemovesAddedPlugin() async throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) await Amplify.reset() XCTAssertThrowsError(try Amplify.Auth.getPlugin(for: "MockAuthCategoryPlugin"), "Getting a plugin after reset() should throw") { error in guard case AuthError.configuration = error else { XCTFail("Expected PluginError.noSuchPlugin") return } } } /// Test if we can register multiple plugins /// /// - Given: UnConfigured Amplify framework /// - When: /// - I configure Amplify with multiple plugins for Auth /// - Then: /// - I should be able to access individual plugins I added. /// func testCanRegisterMultiplePlugins() throws { let plugin1 = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin1) let plugin2 = MockSecondAuthCategoryPlugin() try Amplify.add(plugin: plugin2) let config = AuthCategoryConfiguration( plugins: [ "MockAuthCategoryPlugin": true, "MockSecondAuthCategoryPlugin": true ] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) XCTAssertNotNil(try Amplify.Auth.getPlugin(for: "MockAuthCategoryPlugin")) XCTAssertNotNil(try Amplify.Auth.getPlugin(for: "MockSecondAuthCategoryPlugin")) } /// Test if the default plugin works /// /// - Given: Amplify configured with Auth plugin /// - When: /// - I invoke a API from Auth plugin with default invocation /// - Then: /// - API should complete without error /// func testCanUseDefaultPluginIfOnlyOnePlugin() async throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration(plugins: ["MockAuthCategoryPlugin": true]) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) _ = try await Amplify.Auth.update(oldPassword: "current", to: "new") } /// Test if I can pick a specific plugin /// /// - Given: Amplify configured with multiple Auth plugins /// - When: /// - I choose one plugin and call one of the Auth API /// - Then: /// - API should complete without error for one plugin /// func testCanUseSpecifiedPlugin() async throws { let defaultPlugin = MockAuthCategoryPlugin() defaultPlugin.listeners.append { message in if message == "changePassword" { XCTFail("test method should not be invoked on default plugin") } } try Amplify.add(plugin: defaultPlugin) let anotherPlugin = MockSecondAuthCategoryPlugin() let methodShouldBeInvokedOnSecondPlugin = expectation(description: "test method should be invoked on second plugin") anotherPlugin.listeners.append { message in if message == "changePassword" { methodShouldBeInvokedOnSecondPlugin.fulfill() } } try Amplify.add(plugin: anotherPlugin) let config = AuthCategoryConfiguration( plugins: [ "MockAuthCategoryPlugin": true, "MockSecondAuthCategoryPlugin": true ] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) _ = try await Amplify.Auth.getPlugin(for: "MockSecondAuthCategoryPlugin") .update(oldPassword: "current", to: "new", options: nil) await waitForExpectations(timeout: 1.0) } /// Test if we get error when trying default plugin when multiple plugin added. /// /// - Given: Amplify configured with multiple auth plugin /// - When: /// - I try to invoke an API with default plugin /// - Then: /// - Should throw an exception /// func testPreconditionFailureInvokingWithMultiplePlugins() async throws { let plugin1 = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin1) let plugin2 = MockSecondAuthCategoryPlugin() try Amplify.add(plugin: plugin2) let config = AuthCategoryConfiguration( plugins: [ "MockAuthCategoryPlugin": true, "MockSecondAuthCategoryPlugin": true ] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) let registry = TypeRegistry.register(type: AuthCategoryPlugin.self) { _ in MockAuthCategoryPlugin() } _ = try await Amplify.Auth.update(oldPassword: "current", to: "new") XCTAssertGreaterThan(registry.messages.count, 0) } /// Test if configuration Auth plugin with getPlugin() works /// /// - Given: Amplify with Auth plugin configured /// - When: /// - I try to add a new configuration to the same plugin /// - Then: /// - Should work without any error. /// func testCanConfigurePluginDirectly() throws { let plugin = MockAuthCategoryPlugin() let configureShouldBeInvokedFromCategory = expectation(description: "Configure should be invoked by Amplify.configure()") let configureShouldBeInvokedDirectly = expectation(description: "Configure should be invoked by getPlugin().configure()") var invocationCount = 0 plugin.listeners.append { message in if message == "configure(using:)" { invocationCount += 1 switch invocationCount { case 1: configureShouldBeInvokedFromCategory.fulfill() case 2: configureShouldBeInvokedDirectly.fulfill() default: XCTFail("Expected configure() to be called only two times, but got \(invocationCount)") } } } try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) try Amplify.Auth.getPlugin(for: "MockAuthCategoryPlugin").configure(using: true) waitForExpectations(timeout: 1.0) } /// Test if unconfigured plugin throws an error /// /// - Given: An unconfigured Amplify framework with Auth plugin added /// - When: /// - Invoke an API in auth /// - Then: /// - I should get an exception /// func testPreconditionFailureInvokingBeforeConfig() async throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let registry = TypeRegistry.register(type: AuthCategoryPlugin.self) { _ in MockAuthCategoryPlugin() } // Remember, this test must be invoked with a category that doesn't include an Amplify-supplied default plugin _ = try await Amplify.Auth.update(oldPassword: "current", to: "new") XCTAssertGreaterThan(registry.messages.count, 0) } // MARK: - Test internal config behavior guarantees /// Test if configuring twice throws an exception /// /// - Given: Amplify with Auth plugin configured /// - When: /// - I try to configure Auth again /// - Then: /// - Should throw an exception /// func testThrowsConfiguringTwice() throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) XCTAssertNoThrow(try Amplify.Auth.configure(using: config)) XCTAssertThrowsError(try Amplify.Auth.configure(using: config), "configure() an already configured plugin should throw") { error in guard case ConfigurationError.amplifyAlreadyConfigured = error else { XCTFail("Expected ConfigurationError.amplifyAlreadyConfigured") return } } } /// Test if configuring after reset works /// /// - Given: Amplify framework with Auth configured /// - When: /// - I reset Amplify and then configure again /// - Then: /// - Should not throw any error /// func testCanConfigureAfterReset() async throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) XCTAssertNoThrow(try Amplify.Auth.configure(using: config)) await Amplify.Auth.reset() XCTAssertNoThrow(try Amplify.Auth.configure(using: config)) let registry = TypeRegistry.register(type: AuthCategoryPlugin.self) { _ in MockAuthCategoryPlugin() } _ = try await Amplify.Auth.update(oldPassword: "current", to: "new") XCTAssertEqual(registry.messages.count, 0) } /// Test that Amplify logs a warning if it encounters a plugin configuration key without a corresponding plugin /// /// - Given: /// - A configuration with a nonexistent plugin key specified /// - When: /// - I invoke `Amplify.configure()` /// - Then: /// - I should see a log warning /// func testWarnsOnMissingPlugin() throws { let warningReceived = expectation(description: "Warning message received") let loggingPlugin = MockLoggingCategoryPlugin() loggingPlugin.listeners.append { message in if message.starts(with: "warn(_:): No plugin found") { warningReceived.fulfill() } } let loggingConfig = LoggingCategoryConfiguration( plugins: [loggingPlugin.key: true] ) try Amplify.add(plugin: loggingPlugin) let categoryConfig = AuthCategoryConfiguration( plugins: ["NonExistentPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: categoryConfig, logging: loggingConfig) try Amplify.configure(amplifyConfig) waitForExpectations(timeout: 0.1) } /// Test that Amplify throws an `AuthError` if it encounters a plugin without a key /// /// - Given: Unconfigured Amplify Framework /// - When: /// - I add an Auth plugin without a key /// - Then: /// - An `AuthError` is thrown /// func testThrowsPluginWithoutKey() { let plugin = MockAuthCategoryPluginWithoutKey() XCTAssertThrowsError(try Amplify.add(plugin: plugin)) { error in XCTAssertNotNil(error as? AuthError) } } /// Test if adding a plugin after configuration throws an error /// /// - Given: Amplify is configured /// - When: /// - Add is called for Auth category /// - Then: /// - Should throw an exception /// func testAddAfterConfigureThrowsError() throws { let plugin = MockAuthCategoryPlugin() try Amplify.add(plugin: plugin) let config = AuthCategoryConfiguration( plugins: ["MockAuthCategoryPlugin": true] ) let amplifyConfig = AmplifyConfiguration(auth: config) try Amplify.configure(amplifyConfig) XCTAssertThrowsError(try Amplify.add(plugin: plugin), "configure() an already configured plugin should throw") { error in guard case ConfigurationError.amplifyAlreadyConfigured = error else { XCTFail("Expected ConfigurationError.amplifyAlreadyConfigured") return } } } }