diff --git a/IONFilesystemLib/IONFILEManager.swift b/IONFilesystemLib/IONFILEManager.swift index 71a2e65..fe707de 100644 --- a/IONFilesystemLib/IONFILEManager.swift +++ b/IONFilesystemLib/IONFILEManager.swift @@ -27,6 +27,9 @@ extension IONFILEManager: IONFILEDirectoryManager { public func removeDirectory(atURL pathURL: URL, includeIntermediateDirectories: Bool) throws { try withSecurityScopedAccess(to: pathURL) { + guard fileManager.fileExists(atPath: pathURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: pathURL.urlPath) + } if !includeIntermediateDirectories { let directoryContents = try listDirectory(atURL: pathURL) if !directoryContents.isEmpty { @@ -40,7 +43,10 @@ extension IONFILEManager: IONFILEDirectoryManager { public func listDirectory(atURL pathURL: URL) throws -> [URL] { try withSecurityScopedAccess(to: pathURL) { - try fileManager.contentsOfDirectory(at: pathURL, includingPropertiesForKeys: nil) + guard fileManager.fileExists(atPath: pathURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: pathURL.urlPath) + } + return try fileManager.contentsOfDirectory(at: pathURL, includingPropertiesForKeys: nil) } } } @@ -56,8 +62,11 @@ extension IONFILEManager: IONFILEFileManager { public func readEntireFile(atURL fileURL: URL, withEncoding encoding: IONFILEEncoding, andOffset offset: Int, andLength length: Int) throws -> IONFILEEncodingValueMapper { try withSecurityScopedAccess(to: fileURL) { + guard fileManager.fileExists(atPath: fileURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: fileURL.urlPath) + } let result: IONFILEEncodingValueMapper - if (offset > 0 || length > 0) { + if offset > 0 || length > 0 { result = try readPartialFile(fileURL, encoding, offset, length) } else { switch encoding { @@ -75,7 +84,10 @@ extension IONFILEManager: IONFILEFileManager { public func readFileInChunks(atURL fileURL: URL, withEncoding encoding: IONFILEEncoding, andChunkSize chunkSize: Int, andOffset offset: Int, andLength length: Int) throws -> IONFILEChunkPublisher { try withSecurityScopedAccess(to: fileURL) { - .init(fileURL, chunkSize, encoding, offset, length) + guard fileManager.fileExists(atPath: fileURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: fileURL.urlPath) + } + return .init(fileURL, chunkSize, encoding, offset, length) } } @@ -147,6 +159,9 @@ extension IONFILEManager: IONFILEFileManager { public func getItemAttributes(atURL url: URL) throws -> IONFILEItemAttributeModel { try withSecurityScopedAccess(to: url) { + guard fileManager.fileExists(atPath: url.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: url.urlPath) + } let attributesDictionary = try fileManager.attributesOfItem(atPath: url.urlPath) return .create(from: attributesDictionary) } @@ -154,6 +169,9 @@ extension IONFILEManager: IONFILEFileManager { public func renameItem(fromURL originURL: URL, toURL destinationURL: URL) throws { try withSecurityScopedAccess(to: originURL) { + guard fileManager.fileExists(atPath: originURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: originURL.urlPath) + } try withSecurityScopedAccess(to: destinationURL) { guard try shouldPerformDualPathOperation(fromURL: originURL, toURL: destinationURL) else { return @@ -165,6 +183,9 @@ extension IONFILEManager: IONFILEFileManager { public func copyItem(fromURL originURL: URL, toURL destinationURL: URL) throws { try withSecurityScopedAccess(to: originURL) { + guard fileManager.fileExists(atPath: originURL.urlPath) else { + throw IONFILEFileManagerError.fileNotFound(atPath: originURL.urlPath) + } try withSecurityScopedAccess(to: destinationURL) { guard try shouldPerformDualPathOperation(fromURL: originURL, toURL: destinationURL) else { return @@ -271,7 +292,7 @@ private extension IONFILEManager { with: "/", options: .regularExpression ) - if (urlToReturn.absoluteString.contains(":///")) { + if urlToReturn.absoluteString.contains(":///") { // the regex may ommit a slash after ://, which is incorrect because it breaks in case of an absolute file path urlStringWithoutDuplicateSeparators = urlStringWithoutDuplicateSeparators.replacingOccurrences(of: "://", with: ":///") } diff --git a/IONFilesystemLibTests/IONFILEDirectoryManagerTests.swift b/IONFilesystemLibTests/IONFILEDirectoryManagerTests.swift index d35a267..8c29abf 100644 --- a/IONFilesystemLibTests/IONFILEDirectoryManagerTests.swift +++ b/IONFilesystemLibTests/IONFILEDirectoryManagerTests.swift @@ -69,7 +69,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { // MARK: - 'removeDirectory' tests func test_removeDirectory_butFails_shouldReturnAnError() { let error = MockFileManagerError.deleteDirectoryError - createFileManager(with: error) + createFileManager(with: error, exclusions: []) let testDirectory = URL(filePath: "/test/directory") let shouldIncludeIntermediateDirectories = true @@ -81,10 +81,24 @@ final class IONFILEDirectoryManagerTests: XCTestCase { XCTAssertEqual($0 as? MockFileManagerError, error) } } + + func test_removeDirectory_butFails_shouldReturnFileNotFound() { + createFileManager(fileExists: false, exclusions: []) + let testDirectory = URL(filePath: "/test/directory") + let shouldIncludeIntermediateDirectories = true + + // When + XCTAssertThrowsError( + try sut.removeDirectory(atURL: testDirectory, includeIntermediateDirectories: shouldIncludeIntermediateDirectories) + ) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError,.fileNotFound(atPath: testDirectory.urlPath)) + } + } func test_removeDirectory_includingIntermediateDirectories_shouldBeSuccessful() throws { // Given - let fileManager = createFileManager() + let fileManager = createFileManager(exclusions: []) let testDirectory = URL(filePath: "/test/directory") let shouldIncludeIntermediateDirectories = true @@ -97,7 +111,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { func test_removeDirectory_excludingIntermediateDirectories_directoryDoesntHaveContent_shouldBeSuccessful() throws { // Given - let fileManager = createFileManager() + let fileManager = createFileManager(exclusions: []) let testDirectory = URL(filePath: "/test/directory") let shouldIncludeIntermediateDirectories = false @@ -109,7 +123,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { } func test_removeDirectory_excludingIntermediateDirectories_directoryHasContent_shouldReturnAnError() { - createFileManager(shouldDirectoryHaveContent: true) + createFileManager(shouldDirectoryHaveContent: true, exclusions: []) let testDirectory = URL(filePath: "/test/directory") let shouldIncludeIntermediateDirectories = false @@ -125,7 +139,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { // MARK: - 'listDirectory' tests func test_listDirectory_withNoContent_shouldReturnEmptyArray() throws { // Given - let fileManager = createFileManager() + let fileManager = createFileManager(exclusions: []) let testDirectory = URL(filePath: "/test/directory") // When @@ -139,7 +153,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { // MARK: - 'listDirectory' tests func test_listDirectory_withContent_shouldReturnNotEmptyArray() throws { // Given - let fileManager = createFileManager(shouldDirectoryHaveContent: true) + let fileManager = createFileManager(shouldDirectoryHaveContent: true, exclusions: []) let testDirectory = URL(filePath: "/test/directory") // When @@ -153,7 +167,7 @@ final class IONFILEDirectoryManagerTests: XCTestCase { func test_listDirectory_butFails_shouldReturnAnError() { // Given let error = MockFileManagerError.readDirectoryError - createFileManager(with: error) + createFileManager(with: error, exclusions: []) let testDirectory = URL(filePath: "/test/directory") // When @@ -164,6 +178,20 @@ final class IONFILEDirectoryManagerTests: XCTestCase { XCTAssertEqual($0 as? MockFileManagerError, error) } } + + func test_listDirectory_butFails_shouldReturnFileNotFound() { + // Given + createFileManager(fileExists: false, exclusions: []) + let testDirectory = URL(filePath: "/test/directory") + + // When + XCTAssertThrowsError( + try sut.listDirectory(atURL: testDirectory) + ) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: testDirectory.urlPath)) + } + } } private extension IONFILEDirectoryManagerTests { diff --git a/IONFilesystemLibTests/IONFILEFileManagerTests.swift b/IONFilesystemLibTests/IONFILEFileManagerTests.swift index 654e48c..0b069a1 100644 --- a/IONFilesystemLibTests/IONFILEFileManagerTests.swift +++ b/IONFilesystemLibTests/IONFILEFileManagerTests.swift @@ -54,6 +54,18 @@ extension IONFILEFileManagerTests { XCTAssertThrowsError(try fetchEntireContent(forURL: fileURL, withEncoding: .string(encoding: .utf8))) } + func test_readEntireFile_thatDoesntExist_returnsFileNotFound() throws { + // Given + createFileManager(fileExists: false) + let fileURL = URL(filePath: "/file/directory") + + // When + XCTAssertThrowsError(try sut.readEntireFile(atURL: fileURL, withEncoding: .string(encoding: .utf8))) { + //Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: fileURL.urlPath)) + } + } + func test_readEntireFile_offsetAndLengthWithExampleEncoding_returnsCorrectData() throws { // Given createFileManager() @@ -104,7 +116,7 @@ extension IONFILEFileManagerTests { let result = try sut.readEntireFile(atURL: fileURL, withEncoding: .string(encoding: encoding), andOffset: offset, andLength: length) // Then - guard case .string(let resultEncoding, let resultValue) = result else { + guard case .string(_, let resultValue) = result else { XCTFail("Wrong result type") return } @@ -162,6 +174,18 @@ extension IONFILEFileManagerTests { XCTAssertThrowsError(try fetchChunkedContent(forURL: fileURL, withEncoding: .string(encoding: .utf8))) } + func test_readFileInChunks_thatDoesntExist_returnsFileNotFound() throws { + // Given + createFileManager(fileExists: false) + let fileURL = URL(filePath: "/file/directory") + + // When + XCTAssertThrowsError(try fetchChunkedContent(forURL: fileURL, withEncoding: .string(encoding: .utf8))) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: fileURL.urlPath)) + } + } + func test_readFileInChunks_withOffsetAndLength_returnsContentSuccessfully() throws { // Given createFileManager() @@ -505,11 +529,11 @@ extension IONFILEFileManagerTests { // Then XCTAssertEqual(fileManager.capturedIntermediateDirectories, shouldIncludeIntermediateDirectories) XCTAssertEqual(fileManager.capturedPath, parentFolderURL) - + + fileManager.fileExists = true let savedFileContent = try fetchEntireContent(forURL: fileURL, withEncoding: .string(encoding: stringEncoding)) XCTAssertEqual(savedFileContent, contentToSave) - - fileManager.fileExists = true + try sut.deleteFile(atURL: fileURL) // keep things clean by deleting created file } @@ -610,11 +634,11 @@ extension IONFILEFileManagerTests { XCTAssertEqual(fileManager.capturedIntermediateDirectories, shouldIncludeIntermediateDirectories) // Then + fileManager.fileExists = true let savedFileContent = try fetchEntireContent(forURL: fileURL, withEncoding: .string(encoding: stringEncoding)) XCTAssertEqual(savedFileContent, contentToAdd) - fileManager.fileExists = true try sut.deleteFile(atURL: fileURL) // keep things clean by deleting created file } @@ -731,13 +755,25 @@ extension IONFILEFileManagerTests { XCTAssertEqual($0 as? MockFileManagerError, error) } } + + func test_getItemAttributes_fileDoesntExist_returnsFileNotFound() { + // Given + createFileManager(fileExists: false) + let testURL = URL(filePath: "/test/missing-file") + + // When + XCTAssertThrowsError(try sut.getItemAttributes(atURL: testURL)) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: testURL.urlPath)) + } + } } // MARK: - 'renameItem' tests extension IONFILEFileManagerTests { func test_renameItem_shouldBeSuccessful() throws { // Given - let fileManager = createFileManager(fileExists: false) + let fileManager = createFileManager() let originPath = URL(filePath: "/test/origin") let destinationPath = URL(filePath: "/test/destination") @@ -751,7 +787,7 @@ extension IONFILEFileManagerTests { func test_renameItem_sameOriginAndDestination_shouldDoNothing() throws { // Given - let fileManager = createFileManager(fileExists: false) + let fileManager = createFileManager() let originPath = URL(filePath: "/test/origin") let destinationPath = URL(filePath: "/test/origin") @@ -762,6 +798,19 @@ extension IONFILEFileManagerTests { XCTAssertNil(fileManager.capturedOriginPath) XCTAssertNil(fileManager.capturedDestinationPath) } + + func test_renameItem_fileDoesntExist_returnsFileNotFound() { + // Given + createFileManager(fileExists: false) + let originPath = URL(filePath: "/test/origin") + let destinationPath = URL(filePath: "/test/destination") + + // When + XCTAssertThrowsError(try sut.renameItem(fromURL: originPath, toURL: destinationPath)) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: originPath.urlPath)) + } + } func test_renameDirectory_alreadyExisting_shouldBeSuccessful() throws { // Given @@ -811,7 +860,7 @@ extension IONFILEFileManagerTests { extension IONFILEFileManagerTests { func test_copyItem_shouldBeSuccessful() throws { // Given - let fileManager = createFileManager(fileExists: false) + let fileManager = createFileManager() let originPath = URL(filePath: "/test/origin") let destinationPath = URL(filePath: "/test/destination") @@ -825,7 +874,7 @@ extension IONFILEFileManagerTests { func test_copyItem_sameOriginAndDestination_shouldDoNothing() throws { // Given - let fileManager = createFileManager(fileExists: false) + let fileManager = createFileManager() let originPath = URL(filePath: "/test/origin") let destinationPath = URL(filePath: "/test/origin") @@ -837,6 +886,19 @@ extension IONFILEFileManagerTests { XCTAssertNil(fileManager.capturedDestinationPath) } + + func test_copyItem_fileDoesntExist_returnsFileNotFound() { + // Given + createFileManager(fileExists: false) + let originPath = URL(filePath: "/test/origin") + let destinationPath = URL(filePath: "/test/destination") + + // When + XCTAssertThrowsError(try sut.copyItem(fromURL: originPath, toURL: destinationPath)) { + // Then + XCTAssertEqual($0 as? IONFILEFileManagerError, .fileNotFound(atPath: originPath.urlPath)) + } + } func test_copyDirectory_alreadyExisting_shouldBeSuccessful() throws { // Given