diff --git a/Modules/CommonsLib/Sources/CommonsLib/Constants.swift b/Modules/CommonsLib/Sources/CommonsLib/Constants.swift index bc4f8d4d..22d23fa1 100644 --- a/Modules/CommonsLib/Sources/CommonsLib/Constants.swift +++ b/Modules/CommonsLib/Sources/CommonsLib/Constants.swift @@ -91,7 +91,7 @@ public struct Constants { public static let Shared = "shareddownloads" public static let SavedFiles = "savedfiles" public static let LDAPCerts = "LDAPCerts" - public static let Logs = "logfiles" + public static let Logs = "logs" public static let SiVaCert = "sivacert" public static let TSACert = "tsacert" public static let EncryptionKeyTransferCert = "keytransfercert" diff --git a/Modules/CommonsLib/Tests/CommonsLibTests/Logging/LoggableTests.swift b/Modules/CommonsLib/Tests/CommonsLibTests/Logging/LoggableTests.swift new file mode 100644 index 00000000..ac3737ff --- /dev/null +++ b/Modules/CommonsLib/Tests/CommonsLibTests/Logging/LoggableTests.swift @@ -0,0 +1,32 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Testing +import CommonsLib +import CommonsLibMocks + +@MainActor +struct LoggableTests { + @Test + func logger_createsLoggerWithCorrectCategory() { + let expectedCategory = "LoggableMock" + let category = LoggableMock.category + #expect(category == expectedCategory) + } +} diff --git a/Modules/UtilsLib/Sources/UtilsLib/File/Directories.swift b/Modules/UtilsLib/Sources/UtilsLib/File/Directories.swift index b869b1d8..32285fa3 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/File/Directories.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/File/Directories.swift @@ -95,7 +95,7 @@ public struct Directories { fileManager: FileManagerProtocol ) throws -> URL? { let baseDirectory = try directory ?? getCacheDirectory(fileManager: fileManager) - let logsDirectory = baseDirectory.appending(path: "logs") + let logsDirectory = baseDirectory.appending(path: CommonsLib.Constants.Folder.Logs) try createDirectoryIfNeeded(at: logsDirectory, fileManager: fileManager) return logsDirectory.appending(path: Constants.File.LibDigidocLog) } diff --git a/Modules/UtilsLib/Sources/UtilsLib/File/FileUtil.swift b/Modules/UtilsLib/Sources/UtilsLib/File/FileUtil.swift index 7ad32684..2499f913 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/File/FileUtil.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/File/FileUtil.swift @@ -312,10 +312,7 @@ public struct FileUtil: FileUtilProtocol, Loggable { FileUtil.logger().error("Unable to locate library directory") return } - let directory = libraryDirectory.appendingPathComponent( - "logs", - isDirectory: true - ) + let directory = libraryDirectory.appending(path: CommonsLib.Constants.Folder.Logs) removeDirectory(at: directory) } diff --git a/Modules/UtilsLib/Sources/UtilsLib/Logging/Loggable.swift b/Modules/UtilsLib/Sources/UtilsLib/Logging/Loggable.swift index 90f91201..f95da3de 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/Logging/Loggable.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/Logging/Loggable.swift @@ -20,6 +20,7 @@ import Foundation import OSLog +/// @mockable public protocol Loggable: Sendable {} public extension Loggable { diff --git a/Modules/UtilsLib/Tests/UtilsLibTests/File/DirectoriesTests.swift b/Modules/UtilsLib/Tests/UtilsLibTests/File/DirectoriesTests.swift index e39fbc43..d23d9bb1 100644 --- a/Modules/UtilsLib/Tests/UtilsLibTests/File/DirectoriesTests.swift +++ b/Modules/UtilsLib/Tests/UtilsLibTests/File/DirectoriesTests.swift @@ -233,7 +233,7 @@ struct DirectoriesTests { @Test func getLogsDirectory_primaryDirectoryExists() throws { let mockDirectory = URL(fileURLWithPath: "/path/to/primary/directory") - let expectedDirectory = mockDirectory.appending(path: "logs") + let expectedDirectory = mockDirectory.appending(path: CommonsLib.Constants.Folder.Logs) let trimmedExpectedDirectoryPath = expectedDirectory.resolvedPath.hasPrefix("/") ? String( expectedDirectory.resolvedPath.dropFirst() diff --git a/Modules/UtilsLib/Tests/UtilsLibTests/File/FileUtilTests.swift b/Modules/UtilsLib/Tests/UtilsLibTests/File/FileUtilTests.swift index d6a85b29..24408a73 100644 --- a/Modules/UtilsLib/Tests/UtilsLibTests/File/FileUtilTests.swift +++ b/Modules/UtilsLib/Tests/UtilsLibTests/File/FileUtilTests.swift @@ -324,4 +324,45 @@ struct FileUtilTests { #expect(!mockFileManager.fileExists(atPath: nonExistentDirectory.resolvedPath)) } + + @Test + func removeCacheLogsDirectory_successWhenDirectoryExists() async throws { + fileUtil.removeCacheLogsDirectory() + + #expect(mockFileManager.removeItemCallCount == 1) + } + + @Test + func removeCacheLogsDirectory_doesNotThrowWhenDirectoryCreationFails() async throws { + mockFileManager.createDirectoryHandler = { _, _, _ in + throw NSError(domain: "TestError", code: 1) + } + + #expect(throws: Never.self) { + fileUtil.removeCacheLogsDirectory() + } + + #expect(mockFileManager.removeItemCallCount == 0) + } + + @Test + func removeCacheLogsDirectory_doesNotThrowErrorWhenRemovingDirectoryAndItDoesntExist() async throws { + mockFileManager.removeItemHandler = { _ in + throw NSError(domain: "TestError", code: 1) + } + + #expect(throws: Never.self) { + fileUtil.removeCacheLogsDirectory() + } + + #expect(mockFileManager.removeItemCallCount == 1) + } + + @Test + func removeLibraryLogsDirectory_successWhenDirectoryExists() async throws { + let testDirectory = URL(fileURLWithPath: "/tmp") + fileUtil.removeLibraryLogsDirectory(directory: testDirectory) + + #expect(mockFileManager.removeItemCallCount == 1) + } } diff --git a/RIADigiDoc/UI/Component/DiagnosticsView.swift b/RIADigiDoc/UI/Component/DiagnosticsView.swift index 43569a6d..fa2bdae9 100644 --- a/RIADigiDoc/UI/Component/DiagnosticsView.swift +++ b/RIADigiDoc/UI/Component/DiagnosticsView.swift @@ -135,7 +135,9 @@ struct DiagnosticsView: View { fileURL: tempFileURL, languageSettings: languageSettings, onComplete: { - handleFileSaverCompletion() + Task { + await handleFileSaverCompletion() + } }, isFileSaved: $isFileSaved ) @@ -193,16 +195,14 @@ struct DiagnosticsView: View { } } - private func handleFileSaverCompletion() { + private func handleFileSaverCompletion() async { guard let type = activeExportType else { return } switch type { case .diagnosticsFile: viewModel.onDiagnosticsFileSavingComplete() case .logFile: - Task { - await viewModel.onLogFileSavingComplete() - } + await viewModel.onLogFileSavingComplete() } activeExportType = nil diff --git a/RIADigiDocTests/Domain/Preferences/DataStoreTests.swift b/RIADigiDocTests/Domain/Preferences/DataStoreTests.swift index 51003372..75efd00f 100644 --- a/RIADigiDocTests/Domain/Preferences/DataStoreTests.swift +++ b/RIADigiDocTests/Domain/Preferences/DataStoreTests.swift @@ -449,4 +449,45 @@ final class DataStoreTests { #expect(inputData.personalCode == input.personalCode) #expect(inputData.rememberMe == input.rememberMe) } + + // MARK: - Logging + + @Test + func getEnableLoggingNextSession_success() async throws { + #expect(!(await dataStore.getEnableLoggingNextSession())) + } + + @Test + func setEnableLoggingNextSession_success() async throws { + await dataStore.setEnableLoggingNextSession(true) + #expect(await dataStore.getEnableLoggingNextSession()) + await dataStore.setEnableLoggingNextSession(false) + #expect(!(await dataStore.getEnableLoggingNextSession())) + } + + @Test + func getEnableLoggingThisSession_success() async throws { + #expect(!(await dataStore.getEnableLoggingThisSession())) + } + + @Test + func setEnableLoggingThisSession_success() async throws { + await dataStore.setEnableLoggingThisSession(true) + #expect(await dataStore.getEnableLoggingThisSession()) + await dataStore.setEnableLoggingThisSession(false) + #expect(!(await dataStore.getEnableLoggingThisSession())) + } + + @Test + func getIsLogFileSaved_success() async throws { + #expect(!(await dataStore.getIsLogFileSaved())) + } + + @Test + func setIsLogFileSaved_success() async throws { + await dataStore.setIsLogFileSaved(true) + #expect(await dataStore.getIsLogFileSaved()) + await dataStore.setIsLogFileSaved(false) + #expect(!(await dataStore.getIsLogFileSaved())) + } } diff --git a/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift b/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift index c2d106b4..6f680a3c 100644 --- a/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift +++ b/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift @@ -341,4 +341,140 @@ final class DiagnosticsViewModelTests { await self.viewModel.observeConfigurationUpdates() } } + + // MARK: - onEnableOneTimeLogGenerationChange Tests + + @Test + func onEnableOneTimeLogGenerationChange_falseToFalseDoesNotUpdate() async throws { + mockDataStore.getEnableLoggingNextSessionHandler = { + false + } + + await viewModel.onEnableOneTimeLogGenerationChange(false) + + #expect(mockDataStore.setEnableLoggingNextSessionCallCount == 0) + #expect(mockDataStore.setEnableLoggingThisSessionCallCount == 0) + } + + @Test + func onEnableOneTimeLogGenerationChange_trueToTrueDoesNotUpdate() async throws { + mockDataStore.getEnableLoggingNextSessionHandler = { + true + } + + await viewModel.onEnableOneTimeLogGenerationChange(true) + + #expect(mockDataStore.setEnableLoggingNextSessionCallCount == 0) + #expect(mockDataStore.setEnableLoggingThisSessionCallCount == 0) + } + + @Test + func onEnableOneTimeLogGenerationChange_falseToTrueSuccess() async throws { + mockDataStore.getEnableLoggingNextSessionHandler = { + false + } + + await viewModel.onEnableOneTimeLogGenerationChange(true) + + let nextSessionArgumentValue = mockDataStore.setEnableLoggingNextSessionArgValues.first + guard let value = nextSessionArgumentValue else { + Issue.record("Expected valid enable logging next session argument value") + return + } + #expect(value) + + #expect(mockDataStore.setEnableLoggingNextSessionCallCount == 1) + #expect(mockDataStore.setEnableLoggingThisSessionCallCount == 0) + #expect(viewModel.showRestartActivateAlert) + } + + @Test + func onEnableOneTimeLogGenerationChange_TrueToFalseSuccess() async throws { + mockDataStore.getEnableLoggingNextSessionHandler = { + true + } + + await viewModel.onEnableOneTimeLogGenerationChange(false) + + let nextSessionArgumentValue = mockDataStore.setEnableLoggingNextSessionArgValues.first + guard let value = nextSessionArgumentValue else { + Issue.record("Expected valid enable logging next session argument value") + return + } + #expect(!value) + let thisSessionArgumentValue = mockDataStore.setEnableLoggingThisSessionArgValues.first + guard let value = thisSessionArgumentValue else { + Issue.record("Expected valid enable logging this session argument value") + return + } + #expect(!value) + + #expect(mockDataStore.setEnableLoggingNextSessionCallCount == 1) + #expect(mockDataStore.setEnableLoggingThisSessionCallCount == 1) + #expect(!viewModel.showRestartActivateAlert) + #expect(!viewModel.showSaveLogButton) + } + + // MARK: - createLogFile Tests + + @Test + func createLogFile_success() async throws { + let tempDirectoryURL = TestFileUtil.getTemporaryDirectory(subfolder: "logfiles") + + mockFileManager.urlHandler = { _, _, _, _ in tempDirectoryURL } + mockFileManager.fileExistsHandler = { _ in true } + mockFileManager.copyItemHandler = { _, _ in } + + defer { + try? FileManager.default.removeItem(at: tempDirectoryURL) + } + + if let logFileUrl = await viewModel.createLogFile( + directory: tempDirectoryURL + ) { + #expect(!logFileUrl.resolvedPath.isEmpty) + } + + } + + @Test + func createLogFile_doesNotThrowWhenFails() async throws { + await #expect(throws: Never.self) { + let result = await viewModel.createLogFile() + + #expect(result == nil) + } + } + + // MARK: - onLogFileSavingComplete Tests + + @Test + func onLogFileSavingComplete_success() async throws { + await viewModel.onLogFileSavingComplete() + + let argumentValue = mockDataStore.setEnableLoggingNextSessionArgValues.first + + guard let value = argumentValue else { + Issue.record("Expected valid enable logging next session argument value") + return + } + #expect(!value) + + #expect(mockFileUtil.removeCacheLogsDirectoryCallCount == 1) + #expect(mockFileUtil.removeLibraryLogsDirectoryCallCount == 1) + #expect(mockDataStore.setEnableLoggingNextSessionCallCount == 1) + #expect(!viewModel.enableOneTimeLogGeneration) + #expect(!viewModel.showSaveLogButton) + #expect(viewModel.showRestartDeactivateAlert) + } + + @Test + func onLogFileSavingComplete_doesNotThrowWhenFails() async throws { + mockFileManager.removeItemHandler = { _ in + throw NSError(domain: "TestError", code: 1, userInfo: nil) + } + await #expect(throws: Never.self) { + await viewModel.onLogFileSavingComplete() + } + } }