diff --git a/Libraries/Sources/APIService/Actors/APIService.swift b/Libraries/Sources/APIService/Actors/APIService.swift new file mode 100644 index 0000000..47d59d4 --- /dev/null +++ b/Libraries/Sources/APIService/Actors/APIService.swift @@ -0,0 +1,112 @@ +// +// APIService.swift +// APIService +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation + +public actor APIService { + + // MARK: Properties + + private let client: Client + + // MARK: Initialisers + + public init(configuration: URLSessionConfiguration = .default) { + self.client = APIClient(configuration: configuration) + } + +} + +// MARK: - Service + +extension APIService: Service { + public func getUser( + credentials: Credentials + ) async throws -> Me { + try await client.request( + endpoint: GetMeEndpoint( + username: credentials.username, + password: credentials.password + ), + model: Me.self + ) + } + + public func getItems( + id: String, + credentials: Credentials + ) async throws -> [Item] { + try await client.request( + endpoint: GetItemsEndpoint( + itemId: id, + username: credentials.username, + password: credentials.password + ), + model: [Item].self + ) + } + + public func getData( + id: String, + credentials: Credentials + ) async throws -> Data { + try await client.request( + endpoint: GetDataEndpoint( + itemId: id, + username: credentials.username, + password: credentials.password + ) + ) + } + + public func createFolder( + id: String, + name: String, + credentials: Credentials + ) async throws -> Item { + try await client.request( + endpoint: CreateFolderEndpoint( + itemId: id, + folderName: name, + username: credentials.username, + password: credentials.password + ), + model: Item.self + ) + } + + public func uploadFile( + id: String, + file: File, + credentials: Credentials + ) async throws -> Item { + try await client.request( + endpoint: UploadFileEndpoint( + itemId: id, + fileName: file.name, + fileData: file.data, + username: credentials.username, + password: credentials.password + ), + model: Item.self + ) + } + + public func deleteItem( + id: String, + credentials: Credentials + ) async throws { + try await client.request( + endpoint: DeleteItemEndpoint( + itemId: id, + username: credentials.username, + password: credentials.password + ) + ) + } +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+CreateFolderTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+CreateFolderTests.swift new file mode 100644 index 0000000..68b2f22 --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+CreateFolderTests.swift @@ -0,0 +1,228 @@ +// +// APIService+CreateFolderTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceCreateFolderTests: XCTestCase { + + // MARK: Properties + + private let dateFormatter = DateFormatter.isoZulu + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let itemId = UUID().uuidString + private let url = URL(string: "http://163.172.147.216:8080/items/")! + + private var service: APIService! + private var data: Data! + private var result: Item! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withExistingParentFolderId_someFolderName_andRightCredentials() async throws { + // GIVEN + data = "{\"id\":\"058c53609cac8d8388b96792cbc42bea31c73def\",\"parentId\":\"\(itemId)\",\"name\":\"Some new folder name\",\"isDir\":true,\"modificationDate\":\"2022-12-04T20:54:12.513214855Z\"}".data(using: .utf8) + + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .created, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN + result = try await service.createFolder( + id: itemId, + name: "Some new folder name", + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertEqual(result, Item( + idParent: itemId, + id: "058c53609cac8d8388b96792cbc42bea31c73def", + name: "Some new folder name", + isDirectory: true, + lastModifiedAt: dateFormatter.date(from: "2022-12-04T20:54:12.513214855Z")!, + size: nil, + contentType: nil + )) + } + + func test_withExistingParentFolderId_someFolderName_andWrongCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: itemId, + name: "Some new folder name", + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withNonExistingParentFolderId_someFolderName_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: itemId, + name: "Some new folder name", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyParentFolderId_someFolderName_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("")] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: "", + name: "Some new folder name", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withExistingParentFolderId_someInvalidFolderName_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: itemId, + name: "./Some:new:folder:name\\", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withExistingParentFolderId_someExistingFolderName_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: itemId, + name: "Some new folder name", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withExistingParentFolderId_someEmptyFolderName_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.createFolder( + id: itemId, + name: "", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+DeleteItemTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+DeleteItemTests.swift new file mode 100644 index 0000000..b912e16 --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+DeleteItemTests.swift @@ -0,0 +1,136 @@ +// +// APIService+DeleteItemTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceDeleteItemTests: XCTestCase { + + // MARK: Properties + + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let itemId = UUID().uuidString + private let url = URL(string: "http://163.172.147.216:8080/items/")! + + private var service: APIService! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withExistingId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .noContent, + headers: [:], + data: nil + ) + + // WHEN + try await service.deleteItem( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertTrue(true) + } + + func test_withExistingId_andWrongCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + try await service.deleteItem( + id: itemId, + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withNonExistingId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + try await service.deleteItem( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("")] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + try await service.deleteItem( + id: "", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetDataTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetDataTests.swift new file mode 100644 index 0000000..26d063b --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetDataTests.swift @@ -0,0 +1,164 @@ +// +// APIService+GetDataTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceGetDataTests: XCTestCase { + + // MARK: Properties + + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let itemId = UUID().uuidString + private let url = URL(string: "http://163.172.147.216:8080/items/")! + + private var service: APIService! + private var data: Data! + private var result: Data! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withExistingFileId_andRightCredentials() async throws { + // GIVEN + data = "This is just a simple text for testing purposes.".data(using: .utf8) + + MockURLProtocol.mockData[url.appendingPathComponent(itemId + "/data")] = MockURLResponse( + status: .ok, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeOctet], + data: data + ) + + // WHEN + result = try await service.getData( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertEqual(result, data) + } + + func test_withExistingFileId_andWrongCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId + "/data")] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getData( + id: itemId, + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withNonExistingFileId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId + "/data")] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getData( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyFileId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("/data")] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getData( + id: "", + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemIsNotFile { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withSomeFolderId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId + "/data")] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getData( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemIsNotFile { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetItemsTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetItemsTests.swift new file mode 100644 index 0000000..2446f7a --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetItemsTests.swift @@ -0,0 +1,183 @@ +// +// APIService+GetItemsTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceGetItemsTests: XCTestCase { + + // MARK: Properties + + private let dateFormatter = DateFormatter.isoZulu + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let itemId = UUID().uuidString + private let url = URL(string: "http://163.172.147.216:8080/items/")! + + private var service: APIService! + private var data: Data! + private var result: [Item]! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withExistingId_andRightCredentials_whenDataArrayPopulated() async throws { + // GIVEN + data = "[{\"id\":\"a077432ceb69b4f2dcbd4932d3ec63c3a4f14784\",\"parentId\":\"4b8e41fd4a6a89712f15bbf102421b9338cfab11\",\"name\":\"Tokyo\",\"isDir\":true,\"modificationDate\":\"2022-11-25T17:33:57.095027128Z\"},{\"id\":\"f5d351f7e532cae7c7be28488564b567ffeb425a\",\"parentId\":\"4b8e41fd4a6a89712f15bbf102421b9338cfab11\",\"name\":\"Meme.jpg\",\"isDir\":false,\"size\":43144,\"contentType\":\"image/jpeg\",\"modificationDate\":\"2022-12-01T15:24:12.29816675Z\"}]\n".data(using: .utf8) + + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .ok, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN + result = try await service.getItems( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertEqual(result, [ + .init( + idParent: "4b8e41fd4a6a89712f15bbf102421b9338cfab11", + id: "a077432ceb69b4f2dcbd4932d3ec63c3a4f14784", + name: "Tokyo", + isDirectory: true, + lastModifiedAt: dateFormatter.date(from: "2022-11-25T17:33:57.095027128Z")!, + size: nil, + contentType: nil + ), + .init( + idParent: "4b8e41fd4a6a89712f15bbf102421b9338cfab11", + id: "f5d351f7e532cae7c7be28488564b567ffeb425a", + name: "Meme.jpg", + isDirectory: false, + lastModifiedAt: dateFormatter.date(from: "2022-12-01T15:24:12.29816675Z")!, + size: 43144, + contentType: "image/jpeg" + ), + ]) + } + + func test_withExistingId_andRightCredentials_whenDataArrayEmpty() async throws { + // GIVEN + data = "[]".data(using: .utf8) + + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .ok, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN + result = try await service.getItems( + id: itemId, + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertEqual(result, []) + } + + func test_withExistingId_andWrongCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getItems( + id: itemId, + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withNonExistingId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("xxx")] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getItems( + id: "xxx", + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyId_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("")] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getItems( + id: "", + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetUserTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetUserTests.swift new file mode 100644 index 0000000..b91c972 --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+GetUserTests.swift @@ -0,0 +1,120 @@ +// +// APIService+GetUserTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceGetUserTests: XCTestCase { + + // MARK: Properties + + private let dateFormatter = DateFormatter.iso8601 + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let url = URL(string: "http://163.172.147.216:8080/me")! + + private var service: APIService! + private var data: Data! + private var result: Me! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withRightCredentials() async throws { + // GIVEN + data = "{\"firstName\":\"Noel\",\"lastName\":\"Flantier\",\"rootItem\":{\"id\":\"4b8e41fd4a6a89712f15bbf102421b9338cfab11\",\"parentId\":\"\",\"name\":\"dossierTest\",\"isDir\":true,\"modificationDate\":\"2021-11-29T10:57:13Z\"}}\n".data(using: .utf8) + + MockURLProtocol.mockData[url] = MockURLResponse( + status: .ok, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN + result = try await service.getUser(credentials: .init( + username: "username", + password: "password" + )) + + // THEN + XCTAssertEqual(result, Me( + firstName: "Noel", + lastName: "Flantier", + rootItem: .init( + idParent: "", + id: "4b8e41fd4a6a89712f15bbf102421b9338cfab11", + name: "dossierTest", + isDirectory: true, + lastModifiedAt: dateFormatter.date(from: "2021-11-29T10:57:13Z")!, + size: nil, + contentType: nil + ) + )) + } + + func test_withWrongCredentials() async throws { + MockURLProtocol.mockData[url] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.getUser(credentials: .init( + username: "usrname", + password: "passwrd" + )) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withMalformedResponseData() async throws { + // GIVEN + data = "{\"firstName\":\"Noel\",\"rootItem\":{\"id\":\"4b8e41fd4a6a89712f15bbf102421b9338cfab11\"\"name\":\"dossierTest\",\"isDir\":true}}\n".data(using: .utf8) + + MockURLProtocol.mockData[url] = MockURLResponse( + status: .ok, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN & THEN + do { + _ = try await service.getUser(credentials: .init( + username: "usrname", + password: "passwrd" + )) + } catch is DecodingError { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +} diff --git a/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+UploadFileTests.swift b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+UploadFileTests.swift new file mode 100644 index 0000000..6b9890b --- /dev/null +++ b/Libraries/Tests/APIServiceTests/Cases/Actors/APIService+UploadFileTests.swift @@ -0,0 +1,247 @@ +// +// APIService+UploadFileTests.swift +// APIServiceTests +// +// Created by Javier Cicchelli on 04/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation +import XCTest + +@testable import APIService + +final class APIServiceUploadFileTests: XCTestCase { + + // MARK: Properties + + private let dateFormatter = DateFormatter.isoZulu + private let sessionConfiguration = { + let configuration = URLSessionConfiguration.default + + configuration.protocolClasses = [MockURLProtocol.self] + + return configuration + }() + + private let itemId = UUID().uuidString + private let url = URL(string: "http://163.172.147.216:8080/items/")! + + private var service: APIService! + private var data: Data! + private var result: Item! + + // MARK: Setup + + override func setUp() async throws { + service = .init(configuration: sessionConfiguration) + } + + override func tearDown() async throws { + service = nil + } + + // MARK: Test cases + + func test_withExistingParentFolderId_someFileNameAndData_andRightCredentials() async throws { + // GIVEN + data = "{\"id\":\"eb34443b0f1cd1b0a53dc889aa7ccb5a63edb2f8\",\"parentId\":\"\(itemId)\",\"name\":\"some-text-file.txt\",\"isDir\":false,\"size\":43,\"contentType\":\"text/plain; charset=utf-8\",\"modificationDate\":\"2022-12-04T21:20:01.218032276Z\"}".data(using: .utf8) + + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .created, + headers: [.Header.Keys.contentType: .Header.Values.contentTypeJSON], + data: data + ) + + // WHEN + result = try await service.uploadFile( + id: itemId, + file: .init( + name: "some-text-file.txt", + data: "This is just a dummy content in text format".data(using: .utf8)! + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + + // THEN + XCTAssertEqual(result, Item( + idParent: itemId, + id: "eb34443b0f1cd1b0a53dc889aa7ccb5a63edb2f8", + name: "some-text-file.txt", + isDirectory: false, + lastModifiedAt: dateFormatter.date(from: "2022-12-04T21:20:01.218032276Z")!, + size: 43, + contentType: "text/plain; charset=utf-8" + )) + } + + func test_withExistingParentFolderId_someFileNameAndData_andWrongCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .unauthorized, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: itemId, + file: .init( + name: "some-text-file.txt", + data: .init() + ), + credentials: .init( + username: "usrname", + password: "passwrd" + ) + ) + } catch APIClientError.authenticationFailed { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withNonExistingParentFolderId_someFileNameAndData_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .notFound, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: itemId, + file: .init( + name: "some-text-file.txt", + data: .init() + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemDoesNotExist { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyParentFolderId_someFileNameAndData_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent("")] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: "", + file: .init( + name: "some-text-file.txt", + data: .init() + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withExistingParentFolderId_someInvalidFileNameAndData_andRightCredentials() async throws { + // GIVEN + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: itemId, + file: .init( + name: "some-text-file.txt", + data: .init() + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyParentFolderId_someExistingNameAndData_andRightCredentials() async throws { + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: itemId, + file: .init( + name: "some-text-file.txt", + data: .init() + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + + func test_withEmptyParentFolderId_someEmptyFileNameAndData_andRightCredentials() async throws { + MockURLProtocol.mockData[url.appendingPathComponent(itemId)] = MockURLResponse( + status: .badRequest, + headers: [:], + data: nil + ) + + // WHEN & THEN + do { + _ = try await service.uploadFile( + id: itemId, + file: .init( + name: "", + data: .init() + ), + credentials: .init( + username: "username", + password: "password" + ) + ) + } catch APIClientError.itemNameInvalidOrDefined { + XCTAssertTrue(true) + } catch { + XCTAssertTrue(false) + } + } + +}