Implemented the "createFile(at: with: )" function for the FileService service in the library target.

This commit is contained in:
Javier Cicchelli 2025-02-07 21:50:50 +01:00
parent 3dcb110de1
commit 33ae67fc58
5 changed files with 78 additions and 9 deletions

View File

@ -9,6 +9,7 @@ public protocol FileServicing {
// MARK: Functions // MARK: Functions
func copyFile(from source: URL, to destination: URL) async throws (FileServiceError) func copyFile(from source: URL, to destination: URL) async throws (FileServiceError)
func createFile(at location: URL, with data: Data) async throws (FileServiceError)
func createFolder(at location: URL) async throws (FileServiceError) func createFolder(at location: URL) async throws (FileServiceError)
func deleteItem(at location: URL) async throws (FileServiceError) func deleteItem(at location: URL) async throws (FileServiceError)
func isItemExists(at location: URL) async throws (FileServiceError) -> Bool func isItemExists(at location: URL) async throws (FileServiceError) -> Bool
@ -18,6 +19,8 @@ public protocol FileServicing {
// MARK: - Errors // MARK: - Errors
public enum FileServiceError: Error, Equatable { public enum FileServiceError: Error, Equatable {
case fileDataIsEmpty
case fileNotCreated
case folderNotCreated case folderNotCreated
case itemAlreadyExists case itemAlreadyExists
case itemEmptyData case itemEmptyData

View File

@ -30,7 +30,7 @@ extension FileService: FileServicing {
public func copyFile(from source: URL, to destination: URL) async throws (FileServiceError) { public func copyFile(from source: URL, to destination: URL) async throws (FileServiceError) {
guard try await !isItemExists(at: destination) else { guard try await !isItemExists(at: destination) else {
throw FileServiceError.itemAlreadyExists throw .itemAlreadyExists
} }
var itemData: Data? var itemData: Data?
@ -38,43 +38,59 @@ extension FileService: FileServicing {
do { do {
itemData = try Data(contentsOf: source) itemData = try Data(contentsOf: source)
} catch { } catch {
throw FileServiceError.itemEmptyData throw .itemEmptyData
} }
do { do {
try itemData?.write(to: destination, options: .atomic) try itemData?.write(to: destination, options: .atomic)
} catch { } catch {
throw FileServiceError.itemNotCopied throw .itemNotCopied
}
}
public func createFile(at location: URL, with data: Data) async throws (FileServiceError) {
guard try await !isItemExists(at: location) else {
throw .itemAlreadyExists
}
guard !data.isEmpty else {
throw .fileDataIsEmpty
}
do {
try data.write(to: location, options: .atomic)
} catch {
throw .fileNotCreated
} }
} }
public func createFolder(at location: URL) async throws (FileServiceError) { public func createFolder(at location: URL) async throws (FileServiceError) {
guard try await !isItemExists(at: location) else { guard try await !isItemExists(at: location) else {
throw FileServiceError.itemAlreadyExists throw .itemAlreadyExists
} }
do { do {
try fileManager.createDirectory(at: location, withIntermediateDirectories: true) try fileManager.createDirectory(at: location, withIntermediateDirectories: true)
} catch { } catch {
throw FileServiceError.folderNotCreated throw .folderNotCreated
} }
} }
public func deleteItem(at location: URL) async throws (FileServiceError) { public func deleteItem(at location: URL) async throws (FileServiceError) {
guard try await isItemExists(at: location) else { guard try await isItemExists(at: location) else {
throw FileServiceError.itemNotExists throw .itemNotExists
} }
do { do {
try fileManager.removeItem(at: location) try fileManager.removeItem(at: location)
} catch { } catch {
throw FileServiceError.itemNotDeleted throw .itemNotDeleted
} }
} }
public func isItemExists(at location: URL) async throws (FileServiceError) -> Bool { public func isItemExists(at location: URL) async throws (FileServiceError) -> Bool {
guard location.isFileURL else { guard location.isFileURL else {
throw FileServiceError.itemNotFileURL throw .itemNotFileURL
} }
let filePath = location.pathString let filePath = location.pathString

View File

@ -35,7 +35,7 @@ struct FileServiceTests {
// WHEN // WHEN
try await service.copyFile(from: source, to: destination) try await service.copyFile(from: source, to: destination)
// THENn // THEN
#expect(spy.actions.count == 1) #expect(spy.actions.count == 1)
let action = try #require(spy.actions.last) let action = try #require(spy.actions.last)
@ -57,6 +57,37 @@ struct FileServiceTests {
#expect(spy.actions.isEmpty == true) #expect(spy.actions.isEmpty == true)
} }
@Test(arguments: zip([URL.someNewFile],
[Data("some data goes here...".utf8)]))
func createFile(with location: URL, and data: Data) async throws {
// GIVEN
let service = service(action: .createFile(location, data))
// WHEN
try await service.createFile(at: location, with: data)
// THEN
#expect(spy.actions.count == 1)
let action = try #require(spy.actions.last)
#expect(action == .fileCreated(location, data))
}
@Test(arguments: [FileServiceError.itemAlreadyExists, .fileDataIsEmpty, .fileNotCreated])
func createFile(throws error: FileServiceError) async throws {
// GIVEN
let service = service(action: .error(error))
// WHEN
// THEN
await #expect(throws: error) {
try await service.createFile(at: .someNewFile, with: .init())
}
#expect(spy.actions.isEmpty == true)
}
@Test(arguments: [URL.someNewFolder, .someNewFile]) @Test(arguments: [URL.someNewFolder, .someNewFile])
func createFolder(with location: URL) async throws { func createFolder(with location: URL) async throws {
// GIVEN // GIVEN

View File

@ -64,6 +64,19 @@ extension FileServiceMock: FileServicing {
} }
} }
func createFile(at location: URL, with data: Data) async throws (FileServiceError) {
guard let nextAction else { return }
switch nextAction {
case .error(let error):
throw error
case let .createFile(location, data):
try await spy?.createFile(at: location, with: data)
default:
break
}
}
func createFolder(at location: URL) async throws (FileServiceError) { func createFolder(at location: URL) async throws (FileServiceError) {
guard let nextAction else { return } guard let nextAction else { return }
@ -127,6 +140,7 @@ private extension FileServiceMock {
extension FileServiceMock { extension FileServiceMock {
enum Action { enum Action {
case copyFile(URL, URL) case copyFile(URL, URL)
case createFile(URL, Data)
case createFolder(URL) case createFolder(URL)
case deleteItem(URL) case deleteItem(URL)
case error(FileServiceError) case error(FileServiceError)

View File

@ -21,6 +21,10 @@ extension FileServiceSpy: FileServicing {
actions.append(.fileCopied(source, destination)) actions.append(.fileCopied(source, destination))
} }
func createFile(at location: URL, with data: Data) async throws (FileServiceError) {
actions.append(.fileCreated(location, data))
}
func createFolder(at location: URL) async throws (FileServiceError) { func createFolder(at location: URL) async throws (FileServiceError) {
actions.append(.folderCreated(location)) actions.append(.folderCreated(location))
} }
@ -42,6 +46,7 @@ extension FileServiceSpy: FileServicing {
extension FileServiceSpy { extension FileServiceSpy {
enum Action: Equatable { enum Action: Equatable {
case fileCreated(_ location: URL, _ data: Data)
case fileCopied(_ source: URL, _ destination: URL) case fileCopied(_ source: URL, _ destination: URL)
case folderCreated(_ location: URL) case folderCreated(_ location: URL)
case itemDeleted(_ location: URL) case itemDeleted(_ location: URL)