diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift index be5c18c00b..5647af3d38 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift @@ -29,6 +29,7 @@ actor LogActor { } private func write(_ data: Data) throws { + try rotation.ensureFileExists() if rotation.currentLogFile.hasSpace(for: data) { try rotation.currentLogFile.write(data: data) } else { diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift index 29b6d13f98..6fb4ad7f32 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift @@ -98,6 +98,12 @@ final class LogRotation { index: 0, fileSizeLimitInBytes: fileSizeLimitInBytes) } + + func ensureFileExists() throws { + if !FileManager.default.fileExists(atPath: currentLogFile.fileURL.relativePath) { + try rotate() + } + } /// - Returns: A UInt representing the best guess to which index to use /// next when the number of log files is less that the limit diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift index e4f7cbf863..ebd6db2d24 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift @@ -127,4 +127,24 @@ final class LogActorTests: XCTestCase { logs = try await systemUnderTest.getLogs() XCTAssertEqual(logs.count, 2) } + + /// Given: a Log file + /// When: LogActor writes to a log file that doesn't exist + /// Then: the log file is created and the log entry is recorded + func testLogActorCreatesLogFileIfItDoesNotExist() async throws { + let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) + let fileURL = try XCTUnwrap(files.first) + try FileManager.default.removeItem(at: fileURL) + var logs = try await systemUnderTest.getLogs() + XCTAssertEqual(logs.count, 0) + + let entry = LogEntry(category: "LogActorTests", namespace: nil, level: .error, message: UUID().uuidString, created: .init(timeIntervalSince1970: 0)) + try await systemUnderTest.record(entry) + try await systemUnderTest.synchronize() + + logs = try await systemUnderTest.getLogs() + XCTAssertEqual(logs.count, 1) + let contents = try XCTUnwrap(FileManager.default.contents(atPath: fileURL.path)) + XCTAssertNotNil(contents) + } } diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/AWSPinpointPushNotificationsPlugin+ClientBehaviour.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/AWSPinpointPushNotificationsPlugin+ClientBehaviour.swift index 1b09110c14..2c615a7cab 100644 --- a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/AWSPinpointPushNotificationsPlugin+ClientBehaviour.swift +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/AWSPinpointPushNotificationsPlugin+ClientBehaviour.swift @@ -6,6 +6,7 @@ // import Amplify +import AWSPinpoint import Foundation @_spi(InternalAWSPinpoint) import InternalAWSPinpoint import UserNotifications @@ -59,6 +60,13 @@ extension AWSPinpointPushNotificationsPlugin { } #endif + /// Retrieves the escape hatch to perform actions directly on PinpointClient. + /// + /// - Returns: PinpointClientProtocol instance + public func getEscapeHatch() -> PinpointClientProtocol { + pinpoint.pinpointClient + } + private func recordNotification(_ userInfo: [String: Any], applicationState: ApplicationState, action: PushNotification.Action) async { diff --git a/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginClientBehaviourTests.swift b/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginClientBehaviourTests.swift index fdf2bd72f9..f7ed2622d3 100644 --- a/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginClientBehaviourTests.swift +++ b/AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginClientBehaviourTests.swift @@ -6,6 +6,7 @@ // import Amplify +import AWSPinpoint @_spi(InternalAWSPinpoint) @testable import InternalAWSPinpoint @testable import AWSPinpointPushNotificationsPlugin import UserNotifications @@ -206,6 +207,19 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot } #endif + // - MARK: Escape Hatch tests + /// Given: A configured AWSPinpointPushNotificationsPlugin instance + /// When: The getEscapeHatch API is invoked + /// Then: The underlying PinpointClientProtocol instance is retrieved + func testGetEscapeHatch_shouldReturnPinpointClient() { + guard let escapeHatch = plugin.getEscapeHatch() as? PinpointClient else { + XCTFail("Unable to retrieve PinpointClient") + return + } + + XCTAssertTrue(escapeHatch === (mockPinpoint.pinpointClient as? PinpointClient)) + } + private func createUserInfo(for source: PushNotification.Source) -> Notifications.Push.UserInfo { return [ "data": [