diff --git a/README.md b/README.md index c0e70e7..bfc4644 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This is currently a work in progress. There is a list of currently supported fea - [x] Species - [x] Distribution Zones - [x] Operation support -- [ ] Combine support +- [x] Combine support - [ ] Full error handling - [x] Basic test suite - [ ] Deep test suite diff --git a/Source/Manager/Authentication.swift b/Source/Manager/Authentication.swift index 3e0e390..3717350 100644 --- a/Source/Manager/Authentication.swift +++ b/Source/Manager/Authentication.swift @@ -48,9 +48,9 @@ public extension Trefle { // MARK: - Token @discardableResult - internal static func claimToken(_ completed: @escaping (Result) -> Void) -> ClaimTokenOperation { + internal static func claimToken(_ completed: @escaping (Result) -> Void) -> JWTStateOperation { - let operation = ClaimTokenOperation(completed) + let operation = JWTStateOperation(completed) Trefle.operationQueue.addOperation(operation) return operation } diff --git a/Source/Manager/DistributionZonesManager.swift b/Source/Manager/DistributionZonesManager.swift index d910864..af8ba1f 100644 --- a/Source/Manager/DistributionZonesManager.swift +++ b/Source/Manager/DistributionZonesManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class DistributionZonesManager { +public class DistributionZonesManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/distributions" // MARK: - Distribution Zones URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class DistributionZonesManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } +} + +// MARK: - Operations + +public extension DistributionZonesManager { + // MARK: - Fetch Distribution Zones @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class DistributionZonesManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class DistributionZonesManager { // MARK: - Fetch Distribution Zone @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class DistributionZonesManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,25 @@ public class DistributionZonesManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension DistributionZonesManager { + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/DivisionClassesManager.swift b/Source/Manager/DivisionClassesManager.swift index 5325515..55f716c 100644 --- a/Source/Manager/DivisionClassesManager.swift +++ b/Source/Manager/DivisionClassesManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class DivisionClassesManager { +public class DivisionClassesManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/division_classes" // MARK: - Division Classes URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class DivisionClassesManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Divisions +} + +// MARK: - Operations + +public extension DivisionClassesManager { + + // MARK: - Fetch Division Class Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class DivisionClassesManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class DivisionClassesManager { // MARK: - Fetch Division Class @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class DivisionClassesManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,27 @@ public class DivisionClassesManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension DivisionClassesManager { + + // MARK: - Fetch Division Class Refs + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/DivisionOrdersManager.swift b/Source/Manager/DivisionOrdersManager.swift index b6388b2..816f1b6 100644 --- a/Source/Manager/DivisionOrdersManager.swift +++ b/Source/Manager/DivisionOrdersManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class DivisionOrdersManager { +public class DivisionOrdersManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/division_orders" // MARK: - Division Orders URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class DivisionOrdersManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Divisions +} + +// MARK: - Operations + +public extension DivisionOrdersManager { + + // MARK: - Fetch Division Order Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class DivisionOrdersManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class DivisionOrdersManager { // MARK: - Fetch Division Order @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class DivisionOrdersManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,27 @@ public class DivisionOrdersManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension DivisionOrdersManager { + + // MARK: - Fetch Division Order Refs + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/DivisionsManager.swift b/Source/Manager/DivisionsManager.swift index 14d9150..0f56425 100644 --- a/Source/Manager/DivisionsManager.swift +++ b/Source/Manager/DivisionsManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class DivisionsManager { +public class DivisionsManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/divisions" // MARK: - Divisions URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class DivisionsManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Divisions +} + +// MARK: - Operations + +public extension DivisionsManager { + + // MARK: - Fetch Division Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class DivisionsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class DivisionsManager { // MARK: - Fetch Division @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class DivisionsManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,27 @@ public class DivisionsManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension DivisionsManager { + + // MARK: - Fetch Division Refs + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/FamiliesManager.swift b/Source/Manager/FamiliesManager.swift index c03dd4d..683e0f6 100644 --- a/Source/Manager/FamiliesManager.swift +++ b/Source/Manager/FamiliesManager.swift @@ -7,8 +7,9 @@ // import Foundation +import Combine -public class FamiliesManager { +public class FamiliesManager: TrefleManagers { public typealias Filter = [FamilyFilter: [String]] public typealias SortOrder = [(field: FamilySortOrder, order: Order)] @@ -17,7 +18,7 @@ public class FamiliesManager { // MARK: - Families URLs - internal static func listURL(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil) -> URL? { + public static func listURL(filter: Filter?, order: SortOrder?, page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -43,16 +44,22 @@ public class FamiliesManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Families +} + +// MARK: - Operations + +public extension FamiliesManager { + + // MARK: - Fetch Family Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { - guard let url = listURL(page: page) else { + guard let url = listURL(filter: filter, order: order, page: page) else { completed(Result.failure(TrefleError.badURL)) return nil } @@ -65,7 +72,7 @@ public class FamiliesManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -75,7 +82,7 @@ public class FamiliesManager { // MARK: - Fetch Family @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -90,7 +97,7 @@ public class FamiliesManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -98,3 +105,27 @@ public class FamiliesManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension FamiliesManager { + + // MARK: - Fetch Family Refs + + static func fetchPublisher(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(filter: filter, order: order, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/GenusManager.swift b/Source/Manager/GenusManager.swift index fa56a1b..4bd3ff2 100644 --- a/Source/Manager/GenusManager.swift +++ b/Source/Manager/GenusManager.swift @@ -7,8 +7,9 @@ // import Foundation +import Combine -public class GenusManager { +public class GenusManager: TrefleManagers { public typealias Filter = [GenusFilter: [String]] public typealias SortOrder = [(field: GenusSortOrder, order: Order)] @@ -17,7 +18,7 @@ public class GenusManager { // MARK: - Genus URLs - internal static func listURL(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil) -> URL? { + public static func listURL(filter: Filter?, order: SortOrder?, page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -34,16 +35,22 @@ public class GenusManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Genus +} + +// MARK: - Operations + +public extension GenusManager { + + // MARK: - Fetch Genus Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { - guard let url = listURL(page: page) else { + guard let url = listURL(filter: filter, order: order, page: page) else { completed(Result.failure(TrefleError.badURL)) return nil } @@ -56,7 +63,7 @@ public class GenusManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -66,7 +73,7 @@ public class GenusManager { // MARK: - Fetch Genus @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -81,7 +88,7 @@ public class GenusManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -89,3 +96,27 @@ public class GenusManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension GenusManager { + + // MARK: - Fetch Genus Refs + + static func fetchPublisher(filter: Filter? = nil, order: SortOrder? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(filter: filter, order: order, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/KingdomsManager.swift b/Source/Manager/KingdomsManager.swift index e7916f2..421d037 100644 --- a/Source/Manager/KingdomsManager.swift +++ b/Source/Manager/KingdomsManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class KingdomsManager { +public class KingdomsManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/kingdoms" // MARK: - Kingdoms URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class KingdomsManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Kingdoms +} + +// MARK: - Operations + +public extension KingdomsManager { + + // MARK: - Fetch Kingdom Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class KingdomsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class KingdomsManager { // MARK: - Fetch Kingdom @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class KingdomsManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,27 @@ public class KingdomsManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension KingdomsManager { + + // MARK: - Fetch Kingdom Refs + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/PlantsManager.swift b/Source/Manager/PlantsManager.swift index d8a233a..a336c0e 100644 --- a/Source/Manager/PlantsManager.swift +++ b/Source/Manager/PlantsManager.swift @@ -7,8 +7,9 @@ // import Foundation +import Combine -public class PlantsManager { +public class PlantsManager: TrefleManagers { public typealias Filter = [PlantFilter: [String]] public typealias Exclude = [PlantExclude] @@ -19,7 +20,7 @@ public class PlantsManager { // MARK: - Plant URLs - internal static func listURL(query: String? = nil, zoneId: String? = nil, genusId: String? = nil, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> URL? { + public static func listURL(query: String? = nil, zoneId: String? = nil, genusId: String? = nil, filter: Filter?, exclude: Exclude?, order: SortOrder?, range: Range?, page: Int?) -> URL? { let urlString: String if query != nil { @@ -48,7 +49,7 @@ public class PlantsManager { } exclude?.forEach { (field) in - queryItems.append(URLQueryItem(name: "filter_not[\(field.rawValue)]", value: nil)) + queryItems.append(URLQueryItem(name: "filter_not[\(field.rawValue)]", value: "null")) } order?.forEach { (field, order) in @@ -68,14 +69,20 @@ public class PlantsManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Plants +} + +// MARK: - Operations + +public extension PlantsManager { + + // MARK: - Fetch Plant Refs @discardableResult - public static func fetch(filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(filter: filter, exclude: exclude, order: order, range: range, page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -90,7 +97,7 @@ public class PlantsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -100,7 +107,7 @@ public class PlantsManager { // MARK: - Search Plants @discardableResult - public static func search(query: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func search(query: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(query: query, filter: filter, exclude: exclude, order: order, range: range, page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -115,17 +122,17 @@ public class PlantsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) return listOperation } - // MARK: - Fetch Plants in Distribution Zone + // MARK: - Fetch Plant Refs in Distribution Zone @discardableResult - public static func fetchInDistributionZone(with zoneId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetchInDistributionZone(with zoneId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(zoneId: zoneId, filter: filter, exclude: exclude, order: order, range: range, page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -140,17 +147,17 @@ public class PlantsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) return listOperation } - // MARK: - Fetch Plants of a Genus + // MARK: - Fetch Plant Refs of a Genus @discardableResult - public static func fetchOfGenus(with genusId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetchOfGenus(with genusId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(genusId: genusId, filter: filter, exclude: exclude, order: order, range: range, page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -165,7 +172,7 @@ public class PlantsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -175,7 +182,7 @@ public class PlantsManager { // MARK: - Fetch Plant @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -190,7 +197,7 @@ public class PlantsManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -198,3 +205,78 @@ public class PlantsManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension PlantsManager { + + // MARK: - Fetch Plant Refs + + static func fetchPublisher(filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + + // MARK: - Search Plants + + static func searchPublisher(query: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(query: query, filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + + // MARK: - Fetch Plant Refs in Distribution Zone + + static func fetchInDistributionZonePublisher(with zoneId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(zoneId: zoneId, filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + + // MARK: - Fetch Plant Refs of a Genus + + static func fetchOfGenusPublisher(with genusId: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(genusId: genusId, filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/SpeciesManager.swift b/Source/Manager/SpeciesManager.swift index 9ac6be3..497e628 100644 --- a/Source/Manager/SpeciesManager.swift +++ b/Source/Manager/SpeciesManager.swift @@ -7,8 +7,9 @@ // import Foundation +import Combine -public class SpeciesManager { +public class SpeciesManager: TrefleManagers { public typealias Filter = [SpeciesFilter: [String]] public typealias Exclude = [SpeciesExclude] @@ -19,7 +20,7 @@ public class SpeciesManager { // MARK: - Species URLs - internal static func listURL(query: String? = nil, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> URL? { + public static func listURL(query: String? = nil, filter: Filter?, exclude: Exclude?, order: SortOrder?, range: Range?, page: Int?) -> URL? { let urlString: String if query != nil { @@ -44,7 +45,7 @@ public class SpeciesManager { } exclude?.forEach { (field) in - queryItems.append(URLQueryItem(name: "filter_not[\(field.rawValue)]", value: nil)) + queryItems.append(URLQueryItem(name: "filter_not[\(field.rawValue)]", value: "null")) } order?.forEach { (field, order) in @@ -64,16 +65,47 @@ public class SpeciesManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Species +} + +// MARK: - Operations + +public extension SpeciesManager { + + // MARK: - Fetch Species Refs + + @discardableResult + static func fetch(filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + + guard let url = listURL(filter: filter, exclude: exclude, order: order, range: range, page: page) else { + completed(Result.failure(TrefleError.badURL)) + return nil + } + + let listOperation = ListOperation(url: url, completionBlock: completed) + + guard Trefle.shared.isValid == false else { + + Trefle.operationQueue.addOperation(listOperation) + return listOperation + } + + let claimTokenOperation = JWTStateOperation() + listOperation.addDependency(claimTokenOperation) + + Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) + return listOperation + } + + // MARK: - Search Species @discardableResult - public static func fetch(query: String? = nil, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func search(query: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { - guard let url = listURL(page: page) else { + guard let url = listURL(query: query, filter: filter, exclude: exclude, order: order, range: range, page: page) else { completed(Result.failure(TrefleError.badURL)) return nil } @@ -86,7 +118,7 @@ public class SpeciesManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -96,7 +128,7 @@ public class SpeciesManager { // MARK: - Fetch Species @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -111,7 +143,7 @@ public class SpeciesManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -119,3 +151,44 @@ public class SpeciesManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension SpeciesManager { + + // MARK: - Fetch Species Refs + + static func fetchPublisher(filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + + // MARK: - Search Species + + static func searchPublisher(query: String, filter: Filter? = nil, exclude: Exclude? = nil, order: SortOrder? = nil, range: Range? = nil, page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(query: query, filter: filter, exclude: exclude, order: order, range: range, page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/SubkingdomsManager.swift b/Source/Manager/SubkingdomsManager.swift index c5d6982..ba078c2 100644 --- a/Source/Manager/SubkingdomsManager.swift +++ b/Source/Manager/SubkingdomsManager.swift @@ -7,14 +7,15 @@ // import Foundation +import Combine -public class SubkingdomsManager { +public class SubkingdomsManager: TrefleManagers { internal static let apiURL = "\(Trefle.baseAPIURL)/\(Trefle.apiVersion)/subkingdoms" // MARK: - Subkingdoms URLs - internal static func listURL(page: Int? = nil) -> URL? { + public static func listURL(page: Int?) -> URL? { guard var urlComponents = URLComponents(string: apiURL) else { return nil @@ -31,14 +32,20 @@ public class SubkingdomsManager { return urlComponents.url } - internal static func itemURL(identifier: String) -> URL? { + public static func itemURL(identifier: String) -> URL? { URL(string: "\(apiURL)/\(identifier)") } - // MARK: - Fetch Subkingdoms +} + +// MARK: - Operations + +public extension SubkingdomsManager { + + // MARK: - Fetch Subkingdom Refs @discardableResult - public static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { + static func fetch(page: Int? = nil, completed: @escaping (Result, Error>) -> Void) -> ListOperation? { guard let url = listURL(page: page) else { completed(Result.failure(TrefleError.badURL)) @@ -53,7 +60,7 @@ public class SubkingdomsManager { return listOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() listOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, listOperation], waitUntilFinished: false) @@ -63,7 +70,7 @@ public class SubkingdomsManager { // MARK: - Fetch Subkingdom @discardableResult - public static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { + static func fetchItem(identifier: String, completed: @escaping (Result, Error>) -> Void) -> ItemOperation? { guard let url = itemURL(identifier: identifier) else { completed(Result.failure(TrefleError.badURL)) @@ -78,7 +85,7 @@ public class SubkingdomsManager { return itemOperation } - let claimTokenOperation = ClaimTokenOperation() + let claimTokenOperation = JWTStateOperation() itemOperation.addDependency(claimTokenOperation) Trefle.operationQueue.addOperations([claimTokenOperation, itemOperation], waitUntilFinished: false) @@ -86,3 +93,27 @@ public class SubkingdomsManager { } } + +// MARK: - Publishers + +@available(iOS 13, *) +public extension SubkingdomsManager { + + // MARK: - Fetch Subkingdom Refs + + static func fetchPublisher(page: Int? = nil) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = listURL(page: page) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + fetchPublisher(url: url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Manager/Trefle.swift b/Source/Manager/Trefle.swift index 2c8cfb1..a0b899f 100644 --- a/Source/Manager/Trefle.swift +++ b/Source/Manager/Trefle.swift @@ -87,9 +87,11 @@ public class Trefle { } internal lazy var authStateDidChangeListeners = [UUID: ((authState: AuthState) -> Void)]() internal let authStateListenerQueue = DispatchQueue(label: "com.PigonaHill.TrefleSwiftSDK.AuthStateListenerQueue", attributes: .concurrent) + internal static let operationUnderlyingQueue = DispatchQueue(label: "com.PigonaHill.TrefleSwiftSDK.TrefleAPIQueue", attributes: .concurrent) public static let operationQueue: OperationQueue = { let queue = OperationQueue() queue.name = "com.TrefleSwiftSDK.OperationQueue" + queue.underlyingQueue = operationUnderlyingQueue return queue }() diff --git a/Source/Manager/TrefleError.swift b/Source/Manager/TrefleError.swift index 4fbdcc4..f12bc94 100644 --- a/Source/Manager/TrefleError.swift +++ b/Source/Manager/TrefleError.swift @@ -15,6 +15,7 @@ public enum TrefleError: Error, LocalizedError { case generalError case noAccessToken case noJWT + case invalidJWT public var errorDescription: String? { @@ -31,6 +32,8 @@ public enum TrefleError: Error, LocalizedError { return "No Access Token" case .noJWT: return "No JWT" + case .invalidJWT: + return "Invalid JWT" } } } diff --git a/Source/Manager/TrefleManagers.swift b/Source/Manager/TrefleManagers.swift new file mode 100644 index 0000000..dd51ae7 --- /dev/null +++ b/Source/Manager/TrefleManagers.swift @@ -0,0 +1,84 @@ +// +// TrefleManagers.swift +// TrefleSwiftSDK +// +// Created by James Barrow on 2020-10-16. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import Foundation +import Combine + +@available(iOS 13, *) +public typealias KingdomRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias KingdomPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias SubkingdomRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias SubkingdomPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionClassRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionClassPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionOrderRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias DivisionOrderPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias FamilyRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias FamilyPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias GenusRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias GenusPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias PlantRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias PlantPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias SpeciesRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias SpeciesPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias ZoneRefsPublisher = AnyPublisher, Error> +@available(iOS 13, *) +public typealias ZonePublisher = AnyPublisher, Error> + +public protocol TrefleManagers { + static func itemURL(identifier: String) -> URL? +} + +@available(iOS 13, *) +public extension TrefleManagers { + + // MARK: - Fetch List + + static func fetchPublisher(url: URL) -> AnyPublisher, Error> { + ResponseList.publisher(url) + .eraseToAnyPublisher() + } + + // MARK: - Fetch Item + + static func fetchItemPublisher(identifier: String) -> AnyPublisher, Error> { + + Future { (promise) in + if let url = itemURL(identifier: identifier) { + promise(.success(url)) + } else { + promise(.failure(TrefleError.badURL)) + } + } + .flatMap { (url) -> AnyPublisher, Error> in + ResponseItem.publisher(url) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Models/Authentication/JWTState.swift b/Source/Models/Authentication/JWTState.swift index b3aca1c..3aabd8b 100644 --- a/Source/Models/Authentication/JWTState.swift +++ b/Source/Models/Authentication/JWTState.swift @@ -24,4 +24,29 @@ struct JWTState: Codable { case expires = "expiration" } + // MARK: - URL + + internal static let urlString = "\(Trefle.baseAPIURL)/auth/claim" + internal static let url = URL(string: Self.urlString) + internal static var urlRequest: URLRequest? { + + guard var urlComponents = URLComponents(string: Self.urlString) else { + return nil + } + + urlComponents.queryItems = [ + URLQueryItem(name: "token", value: Trefle.shared.accessToken), + URLQueryItem(name: "origin", value: Trefle.shared.uri) + ] + + guard let url = urlComponents.url else { + return nil + } + + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = "POST" + + return urlRequest + } + } diff --git a/Source/Models/Base/Response.swift b/Source/Models/Base/Response.swift index 8e3a05e..643d16e 100644 --- a/Source/Models/Base/Response.swift +++ b/Source/Models/Base/Response.swift @@ -35,17 +35,17 @@ public struct ResponseItem: Decodable, CustomStringConvertible { // MARK: - Properties public let item: T - public let meta: Metadata + public let metadata: Metadata public var description: String { - "ResponseItem(data: \(item), meta: \(meta))" + "ResponseItem(data: \(item), meta: \(metadata))" } // MARK: - Coding private enum CodingKeys: String, CodingKey { case item = "data" - case meta + case metadata = "meta" } } diff --git a/Source/Operations/Authentication/ClaimTokenOperation.swift b/Source/Operations/Authentication/JWTStateOperation.swift similarity index 63% rename from Source/Operations/Authentication/ClaimTokenOperation.swift rename to Source/Operations/Authentication/JWTStateOperation.swift index 9e58fd1..57f61f4 100644 --- a/Source/Operations/Authentication/ClaimTokenOperation.swift +++ b/Source/Operations/Authentication/JWTStateOperation.swift @@ -1,5 +1,5 @@ // -// ClaimTokenOperation.swift +// JWTStateOperation.swift // TrefleSwiftSDK // // Created by James Barrow on 2020-10-11. @@ -8,7 +8,7 @@ import Foundation -public class ClaimTokenOperation: Operation { +public class JWTStateOperation: Operation { internal var claimTokenCompletionBlock: ((_ result: Result) -> Void)? @@ -62,32 +62,22 @@ public class ClaimTokenOperation: Operation { self._isFinished = true } - guard var urlComponents = URLComponents(string: "\(Trefle.baseAPIURL)/auth/claim") else { + guard let urlRequest = JWTState.urlRequest else { let error = TrefleError.badURL self.error = error - claimTokenCompletionBlock?(Result.failure(error)) // Finish - self._isExecuting = false - self._isFinished = true + _isExecuting = false + _isFinished = true return } - urlComponents.queryItems = [ - URLQueryItem(name: "token", value: Trefle.shared.accessToken), - URLQueryItem(name: "origin", value: Trefle.shared.uri) - ] - - guard let url = urlComponents.url else { - let error = TrefleError.badURL - self.error = error - - claimTokenCompletionBlock?(Result.failure(error)) + // Check if canceled, if so then return + if isCancelled == true { // Finish - self._isExecuting = false - self._isFinished = true + _isFinished = true return } @@ -95,8 +85,6 @@ public class ClaimTokenOperation: Operation { Trefle.shared.stateUUID = UUID().uuidString } - var urlRequest = URLRequest(url: url) - urlRequest.httpMethod = "POST" task = URLSession.shared.dataTask(with: urlRequest) { [weak self] (data, _, error) in guard let self = self else { @@ -112,43 +100,7 @@ public class ClaimTokenOperation: Operation { return } - if let error = error { - self.error = error - - self.claimTokenCompletionBlock?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - guard let data = data else { - let error = TrefleError.noData - self.error = error - - self.claimTokenCompletionBlock?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - let decoder = JSONDecoder.jwtJSONDecoder - let result: JWTState? - do { - result = try decoder.decode(JWTState.self, from: data) - } catch { - self.error = error - - self.claimTokenCompletionBlock?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } + let decodeResult = Self.decode(data: data, error: error) // Check if canceled, if so then return if self.isCancelled == true { @@ -159,15 +111,13 @@ public class ClaimTokenOperation: Operation { return } - guard let jwtState = result else { - let error = TrefleError.generalError + let jwtState: JWTState + switch decodeResult { + case .success(let response): + jwtState = response + case .failure(let error): self.error = error - self.claimTokenCompletionBlock?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true return } @@ -194,6 +144,31 @@ public class ClaimTokenOperation: Operation { task?.resume() } + internal static func decode(data: Data?, error: Error?) -> Result { + + if let error = error { + return Result.failure(error) + } + + guard let data = data else { + return Result.failure(TrefleError.noData) + } + + let decoder = JSONDecoder.jwtJSONDecoder + let result: JWTState? + do { + result = try decoder.decode(JWTState.self, from: data) + } catch { + return Result.failure(error) + } + + guard let jwtState = result else { + return Result.failure(TrefleError.generalError) + } + + return Result.success(jwtState) + } + public override func cancel() { super.cancel() diff --git a/Source/Operations/ItemOperation.swift b/Source/Operations/ItemOperation.swift index 6fe2022..20716f7 100644 --- a/Source/Operations/ItemOperation.swift +++ b/Source/Operations/ItemOperation.swift @@ -65,7 +65,7 @@ public class ItemOperation: Operation { } if jwt == Trefle.shared.jwt && Trefle.shared.isValid == false { - let error = TrefleError.noJWT + let error = TrefleError.invalidJWT self.error = error fetchItemCompleted?(Result.failure(error)) @@ -92,43 +92,7 @@ public class ItemOperation: Operation { return } - if let error = error { - self.error = error - - self.fetchItemCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - guard let data = data else { - let error = TrefleError.noData - self.error = error - - self.fetchItemCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - let decoder = JSONDecoder.customJSONDecoder - let result: ResponseItem? - do { - result = try decoder.decode(ResponseItem.self, from: data) - } catch { - self.error = error - - self.fetchItemCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } + let decodeResult = Self.decode(data: data, error: error) // Check if canceled, if so then return if self.isCancelled == true { @@ -139,22 +103,15 @@ public class ItemOperation: Operation { return } - guard let responseResult = result else { - let error = TrefleError.generalError + switch decodeResult { + case .success(let response): + self.response = response + self.fetchItemCompleted?(Result.success(response)) + case .failure(let error): self.error = error - self.fetchItemCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return } - self.response = responseResult - - self.fetchItemCompleted?(Result.success(responseResult)) - // Finish self._isExecuting = false self._isFinished = true @@ -162,6 +119,31 @@ public class ItemOperation: Operation { task?.resume() } + internal static func decode(data: Data?, error: Error?) -> Result, Error> { + + if let error = error { + return .failure(error) + } + + guard let data = data else { + return .failure(TrefleError.noData) + } + + let decoder = JSONDecoder.customJSONDecoder + let result: ResponseItem? + do { + result = try decoder.decode(ResponseItem.self, from: data) + } catch { + return .failure(error) + } + + guard let responseResult = result else { + return .failure(TrefleError.generalError) + } + + return .success(responseResult) + } + public override func cancel() { super.cancel() diff --git a/Source/Operations/ListOperation.swift b/Source/Operations/ListOperation.swift index 1b5977c..71828f6 100644 --- a/Source/Operations/ListOperation.swift +++ b/Source/Operations/ListOperation.swift @@ -65,7 +65,7 @@ public class ListOperation: Operation { } if jwt == Trefle.shared.jwt && Trefle.shared.isValid == false { - let error = TrefleError.noJWT + let error = TrefleError.invalidJWT self.error = error fetchCompleted?(Result.failure(error)) @@ -92,43 +92,7 @@ public class ListOperation: Operation { return } - if let error = error { - self.error = error - - self.fetchCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - guard let data = data else { - let error = TrefleError.noData - self.error = error - - self.fetchCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } - - let decoder = JSONDecoder.customJSONDecoder - let result: ResponseList? - do { - result = try decoder.decode(ResponseList.self, from: data) - } catch { - self.error = error - - self.fetchCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return - } + let decodeResult = Self.decode(data: data, error: error) // Check if canceled, if so then return if self.isCancelled == true { @@ -139,22 +103,15 @@ public class ListOperation: Operation { return } - guard let responseResult = result else { - let error = TrefleError.generalError + switch decodeResult { + case .success(let response): + self.response = response + self.fetchCompleted?(Result.success(response)) + case .failure(let error): self.error = error - self.fetchCompleted?(Result.failure(error)) - - // Finish - self._isExecuting = false - self._isFinished = true - return } - self.response = responseResult - - self.fetchCompleted?(Result.success(responseResult)) - // Finish self._isExecuting = false self._isFinished = true @@ -162,6 +119,31 @@ public class ListOperation: Operation { task?.resume() } + internal static func decode(data: Data?, error: Error?) -> Result, Error> { + + if let error = error { + return .failure(error) + } + + guard let data = data else { + return .failure(TrefleError.noData) + } + + let decoder = JSONDecoder.customJSONDecoder + let result: ResponseList? + do { + result = try decoder.decode(ResponseList.self, from: data) + } catch { + return .failure(error) + } + + guard let responseResult = result else { + return .failure(TrefleError.generalError) + } + + return .success(responseResult) + } + public override func cancel() { super.cancel() diff --git a/Source/Publishers/Authentication/JWTStatePublisher.swift b/Source/Publishers/Authentication/JWTStatePublisher.swift new file mode 100644 index 0000000..fad4431 --- /dev/null +++ b/Source/Publishers/Authentication/JWTStatePublisher.swift @@ -0,0 +1,50 @@ +// +// JWTStatePublisher.swift +// TrefleSwiftSDK +// +// Created by James Barrow on 2020-10-13. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import Foundation +import Combine + +@available(iOS 13, *) +internal extension JWTState { + + static func publisher(_ urlRequest: URLRequest) -> AnyPublisher { + URLSession.shared.dataTaskPublisher(for: urlRequest) + .map(\.data) + .decode(type: JWTState.self, decoder: JSONDecoder.jwtJSONDecoder) + .eraseToAnyPublisher() + } + + static func publisher() -> AnyPublisher { + + if Trefle.shared.isValid == true { + + return Future { (promise) in + if let jwtState = Trefle.shared.jwtState { + promise(.success(jwtState)) + } else { + promise(.failure(TrefleError.noJWT)) + } + } + .eraseToAnyPublisher() + + } else { + + return Future { promise in + if let urlRequest = JWTState.urlRequest { + promise(.success(urlRequest)) + } + promise(.failure(TrefleError.badURL)) + } + .flatMap { (urlRequest) -> AnyPublisher in + JWTState.publisher(urlRequest) + } + .eraseToAnyPublisher() + } + } + +} diff --git a/Source/Publishers/ResponseItemPublisher.swift b/Source/Publishers/ResponseItemPublisher.swift new file mode 100644 index 0000000..5b63db8 --- /dev/null +++ b/Source/Publishers/ResponseItemPublisher.swift @@ -0,0 +1,37 @@ +// +// ResponseItemPublisher.swift +// TrefleSwiftSDK +// +// Created by James Barrow on 2020-10-13. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import Foundation +import Combine + +@available(iOS 13, *) +public extension ResponseItem { + + static func publisher(_ urlRequest: URLRequest) -> AnyPublisher, Error> { + URLSession.shared.dataTaskPublisher(for: urlRequest) + .map(\.data) + .decode(type: ResponseItem.self, decoder: JSONDecoder.customJSONDecoder) + .eraseToAnyPublisher() + } + + internal static func publisher(_ url: URL) -> AnyPublisher, Error> { + + JWTState.publisher() + .tryMap { (jwtState) -> URLRequest in + if jwtState.isValid == false { + throw TrefleError.invalidJWT + } + return URLRequest.jsonRequest(url: url, jwt: jwtState.jwt) + } + .flatMap { (urlRequest) -> AnyPublisher, Error> in + ResponseItem.publisher(urlRequest) + } + .eraseToAnyPublisher() + } + +} diff --git a/Source/Publishers/ResponseListPublisher.swift b/Source/Publishers/ResponseListPublisher.swift new file mode 100644 index 0000000..6284c88 --- /dev/null +++ b/Source/Publishers/ResponseListPublisher.swift @@ -0,0 +1,37 @@ +// +// ResponseListPublisher.swift +// TrefleSwiftSDK +// +// Created by James Barrow on 2020-10-12. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import Foundation +import Combine + +@available(iOS 13, *) +public extension ResponseList { + + internal static func publisher(_ urlRequest: URLRequest) -> AnyPublisher, Error> { + URLSession.shared.dataTaskPublisher(for: urlRequest) + .map(\.data) + .decode(type: ResponseList.self, decoder: JSONDecoder.customJSONDecoder) + .eraseToAnyPublisher() + } + + static func publisher(_ url: URL) -> AnyPublisher, Error> { + + JWTState.publisher() + .tryMap { (jwtState) -> URLRequest in + if jwtState.isValid == false { + throw TrefleError.invalidJWT + } + return URLRequest.jsonRequest(url: url, jwt: jwtState.jwt) + } + .flatMap { (urlRequest) -> AnyPublisher, Error> in + ResponseList.publisher(urlRequest) + } + .eraseToAnyPublisher() + } + +} diff --git a/Tests/DistributionZonesPublisherTests.swift b/Tests/DistributionZonesPublisherTests.swift new file mode 100644 index 0000000..6184562 --- /dev/null +++ b/Tests/DistributionZonesPublisherTests.swift @@ -0,0 +1,79 @@ +// +// DistributionZonesPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-06. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class DistributionZonesPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDistributionZonesRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: ZoneRefsPublisher = DistributionZonesManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDistributionZonePublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: ZonePublisher = DistributionZonesManager.fetchItemPublisher(identifier: config.zoneId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.zoneId), "Returned item '\(response.item.identifier)' should match the fetched zone ID '\(config.zoneId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/DistributionZonesTests.swift b/Tests/DistributionZonesTests.swift index 59a46d6..af16a74 100644 --- a/Tests/DistributionZonesTests.swift +++ b/Tests/DistributionZonesTests.swift @@ -13,25 +13,30 @@ class DistributionZonesTests: XCTestCase { var config = try? TestConfig.load() - func testFetchDistributionZonesRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = DistributionZonesManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDistributionZonesRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DistributionZonesManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class DistributionZonesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class DistributionZonesTests: XCTestCase { return } - guard let url = DistributionZonesManager.itemURL(identifier: config.zoneId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DistributionZonesManager.fetchItem(identifier: config.zoneId) { (result) in switch result { case .success(let response): @@ -72,9 +72,9 @@ class DistributionZonesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/DivisionClassesPublisherTests.swift b/Tests/DivisionClassesPublisherTests.swift new file mode 100644 index 0000000..2b9c150 --- /dev/null +++ b/Tests/DivisionClassesPublisherTests.swift @@ -0,0 +1,106 @@ +// +// DivisionClassesPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-05. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class DivisionClassesPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionClassRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: DivisionClassRefsPublisher = DivisionClassesManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDivisionClassRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: DivisionClassRefsPublisher = DivisionClassesManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDivisionClassPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publusher: DivisionClassPublisher = DivisionClassesManager.fetchItemPublisher(identifier: config.divisionClassId) + let cancelable = publusher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.divisionClassId), "Returned item '\(response.item.identifier)' should match the fetched division class ID '\(config.divisionClassId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/DivisionClassesTests.swift b/Tests/DivisionClassesTests.swift index 8a6fce3..d058f37 100644 --- a/Tests/DivisionClassesTests.swift +++ b/Tests/DivisionClassesTests.swift @@ -13,25 +13,30 @@ class DivisionClassesTests: XCTestCase { var config = try? TestConfig.load() - func testFetchDivisionClassRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = DivisionClassesManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionClassRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionClassesManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,29 +44,20 @@ class DivisionClassesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } func testFetchDivisionClassRefsPage() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") - return - } - let requstedPage = 2 - guard let url = DivisionClassesManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") - return - } let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionClassesManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -79,9 +75,9 @@ class DivisionClassesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -93,14 +89,9 @@ class DivisionClassesTests: XCTestCase { return } - guard let url = DivisionClassesManager.itemURL(identifier: config.divisionClassId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionClassesManager.fetchItem(identifier: config.divisionClassId) { (result) in switch result { case .success(let response): @@ -112,9 +103,9 @@ class DivisionClassesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/DivisionOrdersPublisherTests.swift b/Tests/DivisionOrdersPublisherTests.swift new file mode 100644 index 0000000..5822fd3 --- /dev/null +++ b/Tests/DivisionOrdersPublisherTests.swift @@ -0,0 +1,106 @@ +// +// DivisionOrdersPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-05. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class DivisionOrdersPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionOrderRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: DivisionOrderRefsPublisher = DivisionOrdersManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDivisionOrderRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: DivisionOrderRefsPublisher = DivisionOrdersManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDivisionOrderPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: DivisionOrderPublisher = DivisionOrdersManager.fetchItemPublisher(identifier: config.divisionOrderId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.divisionOrderId), "Returned item '\(response.item.identifier)' should match the fetched division order ID '\(config.divisionOrderId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/DivisionOrdersTests.swift b/Tests/DivisionOrdersTests.swift index b32b2bb..6c8685c 100644 --- a/Tests/DivisionOrdersTests.swift +++ b/Tests/DivisionOrdersTests.swift @@ -13,25 +13,30 @@ class DivisionOrdersTests: XCTestCase { var config = try? TestConfig.load() - func testFetchDivisionOrderRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = DivisionOrdersManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionOrderRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionOrdersManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,29 +44,20 @@ class DivisionOrdersTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } func testFetchDivisionOrderRefsPage() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") - return - } - let requstedPage = 2 - guard let url = DivisionOrdersManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") - return - } let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionOrdersManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -79,9 +75,9 @@ class DivisionOrdersTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -93,14 +89,9 @@ class DivisionOrdersTests: XCTestCase { return } - guard let url = DivisionOrdersManager.itemURL(identifier: config.divisionOrderId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionOrdersManager.fetchItem(identifier: config.divisionOrderId) { (result) in switch result { case .success(let response): @@ -112,9 +103,9 @@ class DivisionOrdersTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/DivisionsPublisherTests.swift b/Tests/DivisionsPublisherTests.swift new file mode 100644 index 0000000..a1a4c21 --- /dev/null +++ b/Tests/DivisionsPublisherTests.swift @@ -0,0 +1,79 @@ +// +// DivisionsPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-04. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class DivisionsPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: DivisionRefsPublisher = DivisionsManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchDivisionPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: DivisionPublisher = DivisionsManager.fetchItemPublisher(identifier: config.divisionId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.divisionId), "Returned item '\(response.item.identifier)' should match the fetched division ID '\(config.divisionId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/DivisionsTests.swift b/Tests/DivisionsTests.swift index 92e21c1..cd0f563 100644 --- a/Tests/DivisionsTests.swift +++ b/Tests/DivisionsTests.swift @@ -13,25 +13,30 @@ class DivisionsTests: XCTestCase { var config = try? TestConfig.load() - func testFetchDivisionRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = DivisionsManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchDivisionRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionsManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class DivisionsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class DivisionsTests: XCTestCase { return } - guard let url = DivisionsManager.itemURL(identifier: config.divisionId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = DivisionsManager.fetchItem(identifier: config.divisionId) { (result) in switch result { case .success(let response): @@ -72,9 +72,9 @@ class DivisionsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/FamilyPublisherTests.swift b/Tests/FamilyPublisherTests.swift new file mode 100644 index 0000000..76fcb8a --- /dev/null +++ b/Tests/FamilyPublisherTests.swift @@ -0,0 +1,132 @@ +// +// FamilyPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-05. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class FamilyPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchFamilyRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: FamilyRefsPublisher = FamiliesManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchFamilyRefsPublisherFilter() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: FamilyRefsPublisher = FamiliesManager.fetchPublisher(filter: [.name: [config.familyName]]) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.name.lowercased() == config.familyName.lowercased() }), "Returned items should have the name of '\(config.familyName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchFamilyRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: FamilyRefsPublisher = FamiliesManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchFamilyPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: FamilyPublisher = FamiliesManager.fetchItemPublisher(identifier: config.familyId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.familyId), "Returned item '\(response.item.identifier)' should match the fetched division order ID '\(config.familyId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/FamiliesTests.swift b/Tests/FamilyTests.swift similarity index 69% rename from Tests/FamiliesTests.swift rename to Tests/FamilyTests.swift index 8086bb2..fb358cf 100644 --- a/Tests/FamiliesTests.swift +++ b/Tests/FamilyTests.swift @@ -1,5 +1,5 @@ // -// FamiliesTests.swift +// FamilyTests.swift // TrefleSwiftSDKTests // // Created by James Barrow on 2020-10-05. @@ -9,29 +9,34 @@ import XCTest @testable import TrefleSwiftSDK -class FamiliesTests: XCTestCase { +class FamilyTests: XCTestCase { var config = try? TestConfig.load() - func testFetchFamilyRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = FamiliesManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchFamilyRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = FamiliesManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class FamiliesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class FamiliesTests: XCTestCase { return } - guard let url = FamiliesManager.listURL(filter: [.name: [config.familyName]]) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = FamiliesManager.fetch(filter: [.name: [config.familyName]]) { (result) in switch result { case .success(let response): @@ -72,29 +72,20 @@ class FamiliesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } func testFetchFamilyRefsPage() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") - return - } - let requstedPage = 2 - guard let url = FamiliesManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") - return - } let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = FamiliesManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -112,9 +103,9 @@ class FamiliesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -126,14 +117,9 @@ class FamiliesTests: XCTestCase { return } - guard let url = FamiliesManager.itemURL(identifier: config.familyId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = FamiliesManager.fetchItem(identifier: config.familyId) { (result) in switch result { case .success(let response): @@ -145,9 +131,9 @@ class FamiliesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/GenusPublisherTests.swift b/Tests/GenusPublisherTests.swift new file mode 100644 index 0000000..bdf358c --- /dev/null +++ b/Tests/GenusPublisherTests.swift @@ -0,0 +1,132 @@ +// +// GenusPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-05. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class GenusPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchGenusRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: GenusRefsPublisher = GenusManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchGenusRefsPublisherFilter() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: GenusRefsPublisher = GenusManager.fetchPublisher(filter: [.name: [config.genusName]]) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.name.lowercased() == config.genusName.lowercased() }), "Returned items should have the name of '\(config.genusName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchGenusRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: GenusRefsPublisher = GenusManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchGenusPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: GenusPublisher = GenusManager.fetchItemPublisher(identifier: config.genusId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.genusId), "Returned item '\(response.item.identifier)' should match the fetched genus ID '\(config.genusId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/GenusTests.swift b/Tests/GenusTests.swift index 2399521..b70f92a 100644 --- a/Tests/GenusTests.swift +++ b/Tests/GenusTests.swift @@ -13,25 +13,30 @@ class GenusTests: XCTestCase { var config = try? TestConfig.load() - func testFetchGenusRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = GenusManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchGenusRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = GenusManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class GenusTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class GenusTests: XCTestCase { return } - guard let url = GenusManager.listURL(filter: [.name: [config.genusName]]) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = GenusManager.fetch(filter: [.name: [config.genusName]]) { (result) in switch result { case .success(let response): @@ -72,29 +72,20 @@ class GenusTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } func testFetchGenusRefsPage() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") - return - } - let requstedPage = 2 - guard let url = GenusManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") - return - } let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = GenusManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -112,9 +103,9 @@ class GenusTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -126,14 +117,9 @@ class GenusTests: XCTestCase { return } - guard let url = GenusManager.itemURL(identifier: config.genusId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = GenusManager.fetchItem(identifier: config.genusId) { (result) in switch result { case .success(let response): @@ -145,9 +131,9 @@ class GenusTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/KingdomsPublisherTests.swift b/Tests/KingdomsPublisherTests.swift new file mode 100644 index 0000000..eab3dc7 --- /dev/null +++ b/Tests/KingdomsPublisherTests.swift @@ -0,0 +1,84 @@ +// +// KingdomsPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-04. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class KingdomsPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchKingdomRefsPublisher() throws { + + guard let url = KingdomsManager.listURL(page: nil) else { + XCTFail("Failed to create URL!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: KingdomRefsPublisher = KingdomsManager.fetchPublisher(url: url) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchKingdomPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: KingdomPublisher = KingdomsManager.fetchItemPublisher(identifier: config.kingdomId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.kingdomId), "Returned item '\(response.item.identifier)' should match the fetched kingdom ID '\(config.kingdomId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/KingdomsTests.swift b/Tests/KingdomsTests.swift index 74e8aaf..c18bae5 100644 --- a/Tests/KingdomsTests.swift +++ b/Tests/KingdomsTests.swift @@ -13,25 +13,30 @@ class KingdomsTests: XCTestCase { var config = try? TestConfig.load() - func testFetchKingdomRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = KingdomsManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchKingdomRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = KingdomsManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class KingdomsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class KingdomsTests: XCTestCase { return } - guard let url = KingdomsManager.itemURL(identifier: config.kingdomId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = KingdomsManager.fetchItem(identifier: config.kingdomId) { (result) in switch result { case .success(let response): @@ -72,9 +72,9 @@ class KingdomsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/PlantsPublisherTests.swift b/Tests/PlantsPublisherTests.swift new file mode 100644 index 0000000..1e68f16 --- /dev/null +++ b/Tests/PlantsPublisherTests.swift @@ -0,0 +1,210 @@ +// +// PlantsPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-04-22. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class PlantsPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testGetPlantRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetPlantRefsPublisherFilter() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.fetchPublisher(filter: [.commonName: [config.commonName]]) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.commonName?.lowercased() == config.commonName.lowercased() }), "Returned items should have the common name of '\(config.commonName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetPlantRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testSearchPlantRefsPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.searchPublisher(query: config.commonName) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.commonName?.lowercased() == config.commonName.lowercased() }), "Returned items should have the common name of '\(config.commonName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetPlantRefsInDistributionZone() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.fetchInDistributionZonePublisher(with: config.twdgCode) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetPlantRefsOfGenus() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: PlantRefsPublisher = PlantsManager.fetchOfGenusPublisher(with: config.genusId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetPlant() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: PlantPublisher = PlantsManager.fetchItemPublisher(identifier: config.plantId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.mainSpeciesId == Int(config.plantId), "Returned item should match the fetched plant ID!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/PlantsTests.swift b/Tests/PlantsTests.swift index 6e9f670..866f8c3 100644 --- a/Tests/PlantsTests.swift +++ b/Tests/PlantsTests.swift @@ -13,25 +13,30 @@ class PlantsTests: XCTestCase { var config = try? TestConfig.load() - func testGetPlantRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = PlantsManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testGetPlantRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class PlantsTests: XCTestCase { return } - guard let url = PlantsManager.listURL(filter: [.commonName: [config.commonName]]) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetch(filter: [.commonName: [config.commonName]]) { (result) in switch result { case .success(let response): @@ -72,29 +72,42 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } - func testGetPlantRefsPage() throws { + func testGetPlantRefsExcludeURL() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") + guard let url = PlantsManager.listURL(filter: nil, exclude: [.toxicity], order: nil, range: nil, page: nil) else { + XCTFail("Failed to create URL!") return } + dump(url) - let requstedPage = 2 - guard let url = PlantsManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") + guard + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), + let queryItems = urlComponents.queryItems + else { + XCTAssert(false, "Couldn't get URL components from URL!") return } + let queryItem = URLQueryItem(name: "filter_not[\(PlantExclude.toxicity.rawValue)]", value: "null") + let item = queryItems.first(where: { $0 == queryItem }) + + XCTAssert(item != nil && item?.value == "null", "Query `filter_not` should contain `null` string as a value.") + } + + func testGetPlantRefsPage() throws { + + let requstedPage = 2 + let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -112,9 +125,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -126,14 +139,9 @@ class PlantsTests: XCTestCase { return } - guard let url = PlantsManager.listURL(query: config.commonName) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.search(query: config.commonName) { (result) in switch result { case .success(let response): @@ -145,9 +153,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -159,18 +167,13 @@ class PlantsTests: XCTestCase { return } - guard let url = PlantsManager.listURL(zoneId: config.twdgCode) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetchInDistributionZone(with: config.twdgCode) { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -178,9 +181,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -192,18 +195,13 @@ class PlantsTests: XCTestCase { return } - guard let url = PlantsManager.listURL(genusId: config.genusId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetchOfGenus(with: config.genusId) { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -211,9 +209,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -225,14 +223,9 @@ class PlantsTests: XCTestCase { return } - guard let url = PlantsManager.itemURL(identifier: config.plantId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = PlantsManager.fetchItem(identifier: config.plantId) { (result) in switch result { case .success(let response): @@ -244,9 +237,9 @@ class PlantsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/SpeciesPublisherTests.swift b/Tests/SpeciesPublisherTests.swift new file mode 100644 index 0000000..6b609b8 --- /dev/null +++ b/Tests/SpeciesPublisherTests.swift @@ -0,0 +1,158 @@ +// +// SpeciesPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-06. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class SpeciesPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchSpeciesRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: SpeciesRefsPublisher = SpeciesManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetSpeciesRefsPublisherFilter() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: SpeciesRefsPublisher = SpeciesManager.fetchPublisher(filter: [.commonName: [config.commonName]]) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.commonName?.lowercased() == config.commonName.lowercased() }), "Returned items should have the common name of '\(config.commonName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testGetSpeciesRefsPublisherPage() throws { + + let requstedPage = 2 + + let expectation = self.expectation(description: #function) + + let publisher: SpeciesRefsPublisher = SpeciesManager.fetchPublisher(page: requstedPage) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + guard let page = response.links.currentPage else { + XCTFail("Couldn't get page query from current URL link.") + return + } + XCTAssert(page == requstedPage, "Wrong page returned!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testSearchSpeciesRefsPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: SpeciesRefsPublisher = SpeciesManager.searchPublisher(query: config.commonName) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.contains(where: { $0.commonName?.lowercased() == config.commonName.lowercased() }), "Returned items should have the common name of '\(config.commonName)' but it wasn't found in '\(response.items)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchSpeciesPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: SpeciesPublisher = SpeciesManager.fetchItemPublisher(identifier: config.speciesId) + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.speciesId), "Returned item '\(response.item.identifier)' should match the fetched species ID '\(config.speciesId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/SpeciesTests.swift b/Tests/SpeciesTests.swift index ed09664..901444f 100644 --- a/Tests/SpeciesTests.swift +++ b/Tests/SpeciesTests.swift @@ -13,25 +13,30 @@ class SpeciesTests: XCTestCase { var config = try? TestConfig.load() - func testFetchSpeciesRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = SpeciesManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchSpeciesRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SpeciesManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class SpeciesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class SpeciesTests: XCTestCase { return } - guard let url = SpeciesManager.listURL(filter: [.commonName: [config.commonName]]) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SpeciesManager.fetch(filter: [.commonName: [config.commonName]]) { (result) in switch result { case .success(let response): @@ -72,29 +72,42 @@ class SpeciesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } - func testGetSpeciesRefsPage() throws { + func testGetSpeciesRefsExcludeURL() throws { - guard let config = self.config else { - XCTFail("Requires a test config to be setup before calling login!") + guard let url = SpeciesManager.listURL(filter: nil, exclude: [.toxicity], order: nil, range: nil, page: nil) else { + XCTFail("Failed to create URL!") return } + dump(url) - let requstedPage = 2 - guard let url = SpeciesManager.listURL(page: requstedPage) else { - XCTFail("Failed to create URL!") + guard + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), + let queryItems = urlComponents.queryItems + else { + XCTAssert(false, "Couldn't get URL components from URL!") return } + let queryItem = URLQueryItem(name: "filter_not[\(PlantExclude.toxicity.rawValue)]", value: "null") + let item = queryItems.first(where: { $0 == queryItem }) + + XCTAssert(item != nil && item?.value == "null", "Query `filter_not` should contain `null` string as a value.") + } + + func testGetSpeciesRefsPage() throws { + + let requstedPage = 2 + let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SpeciesManager.fetch(page: requstedPage) { (result) in switch result { case .success(let response): @@ -112,9 +125,9 @@ class SpeciesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -126,14 +139,9 @@ class SpeciesTests: XCTestCase { return } - guard let url = SpeciesManager.listURL(query: config.commonName) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SpeciesManager.search(query: config.commonName) { (result) in switch result { case .success(let response): @@ -145,9 +153,9 @@ class SpeciesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -159,14 +167,9 @@ class SpeciesTests: XCTestCase { return } - guard let url = SpeciesManager.itemURL(identifier: config.speciesId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SpeciesManager.fetchItem(identifier: config.speciesId) { (result) in switch result { case .success(let response): @@ -178,9 +181,9 @@ class SpeciesTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/SubkingdomsPublisherTests.swift b/Tests/SubkingdomsPublisherTests.swift new file mode 100644 index 0000000..65d4f89 --- /dev/null +++ b/Tests/SubkingdomsPublisherTests.swift @@ -0,0 +1,79 @@ +// +// SubkingdomsPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-10-04. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class SubkingdomsPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchSubkingdomRefsPublisher() throws { + + let expectation = self.expectation(description: #function) + + let publisher: SubkingdomRefsPublisher = KingdomsManager.fetchPublisher() + let cancelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.items.count > 0, "No returned items!") + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + + func testFetchSubkingdomPublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + let expectation = self.expectation(description: #function) + + let publisher: SubkingdomPublisher = KingdomsManager.fetchItemPublisher(identifier: config.kingdomId) + let cencelable = publisher + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (response) in + XCTAssert(response.item.identifier == Int(config.subkingdomId), "Returned item '\(response.item.identifier)' should match the fetched subkingdom ID '\(config.subkingdomId)'!") + } + + waitForExpectations(timeout: 60) { (error) in + cencelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/SubkingdomsTests.swift b/Tests/SubkingdomsTests.swift index d7038a0..15b1c9d 100644 --- a/Tests/SubkingdomsTests.swift +++ b/Tests/SubkingdomsTests.swift @@ -13,25 +13,30 @@ class SubkingdomsTests: XCTestCase { var config = try? TestConfig.load() - func testFetchSubkingdomRefs() throws { + override func setUp() { guard let config = self.config else { XCTFail("Requires a test config to be setup before calling login!") return } - guard let url = SubkingdomsManager.listURL() else { - XCTFail("Failed to create URL!") - return - } + let state = JWTState(jwt: config.accessToken, expires: Date.distantFuture) + Trefle.shared.jwtState = state + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testFetchSubkingdomRefs() throws { let expectation = self.expectation(description: #function) - let operation = ListOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SubkingdomsManager.fetch { (result) in switch result { - case .success(let page): - XCTAssert(page.items.count > 0, "No returned items!") + case .success(let response): + XCTAssert(response.items.count > 0, "No returned items!") case .failure(let error): XCTFail(error.localizedDescription) @@ -39,9 +44,9 @@ class SubkingdomsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } @@ -53,14 +58,9 @@ class SubkingdomsTests: XCTestCase { return } - guard let url = SubkingdomsManager.itemURL(identifier: config.kingdomId) else { - XCTFail("Failed to create URL!") - return - } - let expectation = self.expectation(description: #function) - let operation = ItemOperation(jwt: config.accessToken, url: url) { (result) in + let operation = SubkingdomsManager.fetchItem(identifier: config.kingdomId) { (result) in switch result { case .success(let response): @@ -72,9 +72,9 @@ class SubkingdomsTests: XCTestCase { expectation.fulfill() } - Trefle.operationQueue.addOperation(operation) waitForExpectations(timeout: 60) { (error) in + operation?.cancel() XCTAssertNil(error, error?.localizedDescription ?? "") } } diff --git a/Tests/TrefleAuthPublisherTests.swift b/Tests/TrefleAuthPublisherTests.swift new file mode 100644 index 0000000..80087d4 --- /dev/null +++ b/Tests/TrefleAuthPublisherTests.swift @@ -0,0 +1,71 @@ +// +// TrefleAuthPublisherTests.swift +// TrefleSwiftSDKTests +// +// Created by James Barrow on 2020-04-21. +// Copyright © 2020 Pig on a Hill Productions. All rights reserved. +// + +import XCTest +@testable import TrefleSwiftSDK + +@available(iOS 13, *) +class TrefleAuthPublisherTests: XCTestCase { + + var config = try? TestConfig.load() + + override func setUp() { + + guard let userDefaults = UserDefaults(suiteName: Trefle.userDefaultsSuiteName) else { + XCTFail("Failed to reset!") + return + } + + guard let stateUUID = userDefaults.object(forKey: Trefle.userDefaultsKeychainStateUUID) as? String else { + return + } + + userDefaults.removeObject(forKey: Trefle.userDefaultsKeychainStateUUID) + userDefaults.synchronize() + + try? KeychainPasswordItem(service: Trefle.keychainServiceName, account: stateUUID) + .deleteItem() + } + + override class func tearDown() { + Trefle.shared.jwtState = nil + } + + func testConfigurePublisher() throws { + + guard let config = self.config else { + XCTFail("Requires a test config to be setup before calling login!") + return + } + + Trefle.configure(accessToken: config.accessToken, uri: config.uri) + + guard Trefle.shared.isValid == false else { + XCTFail("Already valid") + return + } + + let expectation = self.expectation(description: #function) + + let cancelable = JWTState.publisher() + .sink { (completion) in + if let error = completion as? Error { + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } receiveValue: { (state) in + XCTAssertTrue(state.isValid == true) + } + + waitForExpectations(timeout: 60) { (error) in + cancelable.cancel() + XCTAssertNil(error, error?.localizedDescription ?? "") + } + } + +} diff --git a/Tests/TrefleAuthTests.swift b/Tests/TrefleAuthTests.swift index df2c333..11dae75 100644 --- a/Tests/TrefleAuthTests.swift +++ b/Tests/TrefleAuthTests.swift @@ -14,7 +14,27 @@ class TrefleSwiftSDKTests: XCTestCase { var config = try? TestConfig.load() override func setUp() { - reset() + + guard let userDefaults = UserDefaults(suiteName: Trefle.userDefaultsSuiteName) else { + XCTFail("Failed to reset!") + return + } + + guard let stateUUID = userDefaults.object(forKey: Trefle.userDefaultsKeychainStateUUID) as? String else { + return + } + + userDefaults.removeObject(forKey: Trefle.userDefaultsKeychainStateUUID) + userDefaults.synchronize() + + try? KeychainPasswordItem(service: Trefle.keychainServiceName, account: stateUUID) + .deleteItem() + + Trefle.shared.jwtState = nil + } + + override class func tearDown() { + Trefle.shared.jwtState = nil } func testConfigure() throws { @@ -51,22 +71,4 @@ class TrefleSwiftSDKTests: XCTestCase { } } - func reset() { - - guard let userDefaults = UserDefaults(suiteName: Trefle.userDefaultsSuiteName) else { - XCTFail("Failed to reset!") - return - } - - guard let stateUUID = userDefaults.object(forKey: Trefle.userDefaultsKeychainStateUUID) as? String else { - return - } - - userDefaults.removeObject(forKey: Trefle.userDefaultsKeychainStateUUID) - userDefaults.synchronize() - - try? KeychainPasswordItem(service: Trefle.keychainServiceName, account: stateUUID) - .deleteItem() - } - } diff --git a/TrefleSwiftSDK.xcodeproj/project.pbxproj b/TrefleSwiftSDK.xcodeproj/project.pbxproj index ef33e86..9ed19c2 100644 --- a/TrefleSwiftSDK.xcodeproj/project.pbxproj +++ b/TrefleSwiftSDK.xcodeproj/project.pbxproj @@ -7,9 +7,21 @@ objects = { /* Begin PBXBuildFile section */ + 181C8794253A409200D8E463 /* TrefleManagers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C8793253A409200D8E463 /* TrefleManagers.swift */; }; + 181C87DB253C5C6100D8E463 /* TrefleAuthPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87DA253C5C6000D8E463 /* TrefleAuthPublisherTests.swift */; }; + 181C87E1253C5CAD00D8E463 /* KingdomsPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87E0253C5CAD00D8E463 /* KingdomsPublisherTests.swift */; }; + 181C87E9253CBC9700D8E463 /* SubkingdomsPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87E8253CBC9700D8E463 /* SubkingdomsPublisherTests.swift */; }; + 181C87EF253CC2DF00D8E463 /* DivisionsPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87EE253CC2DF00D8E463 /* DivisionsPublisherTests.swift */; }; + 181C87F5253CC71200D8E463 /* DivisionClassesPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87F4253CC71200D8E463 /* DivisionClassesPublisherTests.swift */; }; + 181C87FB253CCA0B00D8E463 /* DivisionOrdersPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C87FA253CCA0B00D8E463 /* DivisionOrdersPublisherTests.swift */; }; + 181C8801253CCDA600D8E463 /* FamilyPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C8800253CCDA600D8E463 /* FamilyPublisherTests.swift */; }; + 181C8807253CD25E00D8E463 /* GenusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C8806253CD25E00D8E463 /* GenusPublisherTests.swift */; }; + 181C880D253CD4EE00D8E463 /* PlantsPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C880C253CD4EE00D8E463 /* PlantsPublisherTests.swift */; }; + 181C8813253CD6CF00D8E463 /* SpeciesPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C8812253CD6CF00D8E463 /* SpeciesPublisherTests.swift */; }; + 181C8819253CD82B00D8E463 /* DistributionZonesPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C8818253CD82B00D8E463 /* DistributionZonesPublisherTests.swift */; }; 181CAAC025323C89009A252C /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAABF25323C89009A252C /* Config.swift */; }; 181CAAC425323CDC009A252C /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAAC325323CDC009A252C /* URLExtensions.swift */; }; - 181CAAD12533234A009A252C /* ClaimTokenOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAAD02533234A009A252C /* ClaimTokenOperation.swift */; }; + 181CAAD12533234A009A252C /* JWTStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAAD02533234A009A252C /* JWTStateOperation.swift */; }; 181CAAD5253329F5009A252C /* ListOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAAD4253329F5009A252C /* ListOperation.swift */; }; 181CAADB25338DC6009A252C /* ItemOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CAADA25338DC6009A252C /* ItemOperation.swift */; }; 183B16932529E281007C7A72 /* PlantFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183B16922529E281007C7A72 /* PlantFilter.swift */; }; @@ -61,7 +73,7 @@ 18D11800252B9D210004DC72 /* DivisionOrdersManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D117FF252B9D210004DC72 /* DivisionOrdersManager.swift */; }; 18D11804252B9E1F0004DC72 /* DivisionOrdersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11803252B9E1F0004DC72 /* DivisionOrdersTests.swift */; }; 18D11808252B9FD20004DC72 /* FamiliesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11807252B9FD20004DC72 /* FamiliesManager.swift */; }; - 18D1180C252BA1A90004DC72 /* FamiliesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D1180B252BA1A90004DC72 /* FamiliesTests.swift */; }; + 18D1180C252BA1A90004DC72 /* FamilyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D1180B252BA1A90004DC72 /* FamilyTests.swift */; }; 18D11815252BA3EB0004DC72 /* FamilyFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11814252BA3EB0004DC72 /* FamilyFilter.swift */; }; 18D11819252BA3F50004DC72 /* FamilySortOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11818252BA3F50004DC72 /* FamilySortOrder.swift */; }; 18D1182D252BACD80004DC72 /* GenusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D1182C252BACD80004DC72 /* GenusTests.swift */; }; @@ -75,6 +87,9 @@ 18D1185F252C6ED90004DC72 /* SpeciesRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D1185E252C6ED90004DC72 /* SpeciesRange.swift */; }; 18D11863252C77810004DC72 /* DistributionZonesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11862252C77810004DC72 /* DistributionZonesManager.swift */; }; 18D11869252C78DA0004DC72 /* DistributionZonesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D11868252C78DA0004DC72 /* DistributionZonesTests.swift */; }; + 18D7FBC82535941C000F7C00 /* ResponseListPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D7FBC72535941C000F7C00 /* ResponseListPublisher.swift */; }; + 18D7FBCC2535F8B8000F7C00 /* JWTStatePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D7FBCB2535F8B8000F7C00 /* JWTStatePublisher.swift */; }; + 18D7FBD22535F9DB000F7C00 /* ResponseItemPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D7FBD12535F9DB000F7C00 /* ResponseItemPublisher.swift */; }; 18E787E7245623B80064F78D /* Plant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E787E6245623B80064F78D /* Plant.swift */; }; 18E787EA2456278F0064F78D /* ImageRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E787E92456278F0064F78D /* ImageRef.swift */; }; 18E787F224562BAF0064F78D /* Kingdom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E787F124562BAF0064F78D /* Kingdom.swift */; }; @@ -111,9 +126,21 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 181C8793253A409200D8E463 /* TrefleManagers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrefleManagers.swift; sourceTree = ""; }; + 181C87DA253C5C6000D8E463 /* TrefleAuthPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrefleAuthPublisherTests.swift; sourceTree = ""; }; + 181C87E0253C5CAD00D8E463 /* KingdomsPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KingdomsPublisherTests.swift; sourceTree = ""; }; + 181C87E8253CBC9700D8E463 /* SubkingdomsPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubkingdomsPublisherTests.swift; sourceTree = ""; }; + 181C87EE253CC2DF00D8E463 /* DivisionsPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DivisionsPublisherTests.swift; sourceTree = ""; }; + 181C87F4253CC71200D8E463 /* DivisionClassesPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DivisionClassesPublisherTests.swift; sourceTree = ""; }; + 181C87FA253CCA0B00D8E463 /* DivisionOrdersPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DivisionOrdersPublisherTests.swift; sourceTree = ""; }; + 181C8800253CCDA600D8E463 /* FamilyPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FamilyPublisherTests.swift; sourceTree = ""; }; + 181C8806253CD25E00D8E463 /* GenusPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenusPublisherTests.swift; sourceTree = ""; }; + 181C880C253CD4EE00D8E463 /* PlantsPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlantsPublisherTests.swift; sourceTree = ""; }; + 181C8812253CD6CF00D8E463 /* SpeciesPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeciesPublisherTests.swift; sourceTree = ""; }; + 181C8818253CD82B00D8E463 /* DistributionZonesPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistributionZonesPublisherTests.swift; sourceTree = ""; }; 181CAABF25323C89009A252C /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 181CAAC325323CDC009A252C /* URLExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = ""; }; - 181CAAD02533234A009A252C /* ClaimTokenOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaimTokenOperation.swift; sourceTree = ""; }; + 181CAAD02533234A009A252C /* JWTStateOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTStateOperation.swift; sourceTree = ""; }; 181CAAD4253329F5009A252C /* ListOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOperation.swift; sourceTree = ""; }; 181CAADA25338DC6009A252C /* ItemOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOperation.swift; sourceTree = ""; }; 183B16922529E281007C7A72 /* PlantFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlantFilter.swift; sourceTree = ""; }; @@ -171,7 +198,7 @@ 18D117FF252B9D210004DC72 /* DivisionOrdersManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DivisionOrdersManager.swift; sourceTree = ""; }; 18D11803252B9E1F0004DC72 /* DivisionOrdersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DivisionOrdersTests.swift; sourceTree = ""; }; 18D11807252B9FD20004DC72 /* FamiliesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FamiliesManager.swift; sourceTree = ""; }; - 18D1180B252BA1A90004DC72 /* FamiliesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FamiliesTests.swift; sourceTree = ""; }; + 18D1180B252BA1A90004DC72 /* FamilyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FamilyTests.swift; sourceTree = ""; }; 18D11814252BA3EB0004DC72 /* FamilyFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FamilyFilter.swift; sourceTree = ""; }; 18D11818252BA3F50004DC72 /* FamilySortOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FamilySortOrder.swift; sourceTree = ""; }; 18D1182C252BACD80004DC72 /* GenusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenusTests.swift; sourceTree = ""; }; @@ -185,6 +212,9 @@ 18D1185E252C6ED90004DC72 /* SpeciesRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeciesRange.swift; sourceTree = ""; }; 18D11862252C77810004DC72 /* DistributionZonesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionZonesManager.swift; sourceTree = ""; }; 18D11868252C78DA0004DC72 /* DistributionZonesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionZonesTests.swift; sourceTree = ""; }; + 18D7FBC72535941C000F7C00 /* ResponseListPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseListPublisher.swift; sourceTree = ""; }; + 18D7FBCB2535F8B8000F7C00 /* JWTStatePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTStatePublisher.swift; sourceTree = ""; }; + 18D7FBD12535F9DB000F7C00 /* ResponseItemPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseItemPublisher.swift; sourceTree = ""; }; 18E787E6245623B80064F78D /* Plant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plant.swift; sourceTree = ""; }; 18E787E92456278F0064F78D /* ImageRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRef.swift; sourceTree = ""; }; 18E787F124562BAF0064F78D /* Kingdom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Kingdom.swift; sourceTree = ""; }; @@ -232,7 +262,7 @@ 181CAACF2533232E009A252C /* Authentication */ = { isa = PBXGroup; children = ( - 181CAAD02533234A009A252C /* ClaimTokenOperation.swift */, + 181CAAD02533234A009A252C /* JWTStateOperation.swift */, ); path = Authentication; sourceTree = ""; @@ -263,6 +293,7 @@ 186105BE244F14BF008A863B /* Trefle.swift */, 181CAABF25323C89009A252C /* Config.swift */, 186105C3244F1B92008A863B /* Authentication.swift */, + 181C8793253A409200D8E463 /* TrefleManagers.swift */, 183B16A6252A5877007C7A72 /* KingdomsManager.swift */, 183B16B0252A5D56007C7A72 /* SubkingdomsManager.swift */, 183B16C1252A6006007C7A72 /* DivisionsManager.swift */, @@ -370,6 +401,7 @@ 186105BC244F140C008A863B /* Manager */, 186105BD244F1416008A863B /* Models */, 18DF92632454D94B0034A058 /* Operations */, + 18D7FBC62535941C000F7C00 /* Publishers */, 186105C0244F1AD1008A863B /* Helpers */, 186105CA244F8C1E008A863B /* Extensions */, 18748374244F0E0F00120F73 /* Info.plist */, @@ -382,16 +414,27 @@ children = ( 186105B7244F1332008A863B /* Test Config */, 1874837E244F0E0F00120F73 /* TrefleAuthTests.swift */, + 181C87DA253C5C6000D8E463 /* TrefleAuthPublisherTests.swift */, 183B16AA252A5B1B007C7A72 /* KingdomsTests.swift */, + 181C87E0253C5CAD00D8E463 /* KingdomsPublisherTests.swift */, 183B16B4252A5E8C007C7A72 /* SubkingdomsTests.swift */, + 181C87E8253CBC9700D8E463 /* SubkingdomsPublisherTests.swift */, 183B16C5252A610D007C7A72 /* DivisionsTests.swift */, + 181C87EE253CC2DF00D8E463 /* DivisionsPublisherTests.swift */, 18D117F9252B976B0004DC72 /* DivisionClassesTests.swift */, + 181C87F4253CC71200D8E463 /* DivisionClassesPublisherTests.swift */, 18D11803252B9E1F0004DC72 /* DivisionOrdersTests.swift */, - 18D1180B252BA1A90004DC72 /* FamiliesTests.swift */, + 181C87FA253CCA0B00D8E463 /* DivisionOrdersPublisherTests.swift */, + 18D1180B252BA1A90004DC72 /* FamilyTests.swift */, + 181C8800253CCDA600D8E463 /* FamilyPublisherTests.swift */, 18D1182C252BACD80004DC72 /* GenusTests.swift */, + 181C8806253CD25E00D8E463 /* GenusPublisherTests.swift */, 186105D42450D01A008A863B /* PlantsTests.swift */, + 181C880C253CD4EE00D8E463 /* PlantsPublisherTests.swift */, 18D11849252C6D410004DC72 /* SpeciesTests.swift */, + 181C8812253CD6CF00D8E463 /* SpeciesPublisherTests.swift */, 18D11868252C78DA0004DC72 /* DistributionZonesTests.swift */, + 181C8818253CD82B00D8E463 /* DistributionZonesPublisherTests.swift */, 18748380244F0E0F00120F73 /* Info.plist */, ); path = Tests; @@ -446,12 +489,30 @@ path = Species; sourceTree = ""; }; + 18D7FBC62535941C000F7C00 /* Publishers */ = { + isa = PBXGroup; + children = ( + 18D7FBD52535FA75000F7C00 /* Authentication */, + 18D7FBC72535941C000F7C00 /* ResponseListPublisher.swift */, + 18D7FBD12535F9DB000F7C00 /* ResponseItemPublisher.swift */, + ); + path = Publishers; + sourceTree = ""; + }; + 18D7FBD52535FA75000F7C00 /* Authentication */ = { + isa = PBXGroup; + children = ( + 18D7FBCB2535F8B8000F7C00 /* JWTStatePublisher.swift */, + ); + path = Authentication; + sourceTree = ""; + }; 18DF92632454D94B0034A058 /* Operations */ = { isa = PBXGroup; children = ( + 181CAACF2533232E009A252C /* Authentication */, 181CAAD4253329F5009A252C /* ListOperation.swift */, 181CAADA25338DC6009A252C /* ItemOperation.swift */, - 181CAACF2533232E009A252C /* Authentication */, ); path = Operations; sourceTree = ""; @@ -681,6 +742,7 @@ 18D11838252BAF110004DC72 /* GenusSortOrder.swift in Sources */, 181CAAD5253329F5009A252C /* ListOperation.swift in Sources */, 186105D024507687008A863B /* PlantsManager.swift in Sources */, + 18D7FBCC2535F8B8000F7C00 /* JWTStatePublisher.swift in Sources */, 18E787F224562BAF0064F78D /* Kingdom.swift in Sources */, 18C3D78E2527A9F3006B6413 /* SpeciesRef.swift in Sources */, 186105CE244F8C1E008A863B /* URLRequestExtensions.swift in Sources */, @@ -690,7 +752,7 @@ 186105C2244F1AD1008A863B /* KeychainPasswordItem.swift in Sources */, 18E7881224563AA80064F78D /* GenusRef.swift in Sources */, 183B1697252A4D3E007C7A72 /* PlantExclude.swift in Sources */, - 181CAAD12533234A009A252C /* ClaimTokenOperation.swift in Sources */, + 181CAAD12533234A009A252C /* JWTStateOperation.swift in Sources */, 18D11834252BAF080004DC72 /* GenusFilter.swift in Sources */, 188CA1442528933000C26F55 /* Status.swift in Sources */, 18C3D78A252674FE006B6413 /* Zone.swift in Sources */, @@ -709,7 +771,9 @@ 18C3D77E2526636F006B6413 /* ImageCollection.swift in Sources */, 18D1185B252C6ED00004DC72 /* SpeciesSortOrder.swift in Sources */, 1893393C252C80280099AADF /* GenusManager.swift in Sources */, + 18D7FBD22535F9DB000F7C00 /* ResponseItemPublisher.swift in Sources */, 183B16C2252A6006007C7A72 /* DivisionsManager.swift in Sources */, + 181C8794253A409200D8E463 /* TrefleManagers.swift in Sources */, 186105D324507C37008A863B /* PlantRef.swift in Sources */, 183B16932529E281007C7A72 /* PlantFilter.swift in Sources */, 18C3D78625267496006B6413 /* Distribution.swift in Sources */, @@ -720,6 +784,7 @@ 181CAADB25338DC6009A252C /* ItemOperation.swift in Sources */, 181CAAC425323CDC009A252C /* URLExtensions.swift in Sources */, 18D11842252C6BBC0004DC72 /* SpeciesManager.swift in Sources */, + 18D7FBC82535941C000F7C00 /* ResponseListPublisher.swift in Sources */, 183B169F252A54F2007C7A72 /* PlantRange.swift in Sources */, 188CA1662528942600C26F55 /* Texture.swift in Sources */, 188CA14E2528935600C26F55 /* Duration.swift in Sources */, @@ -748,18 +813,29 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 181C87DB253C5C6100D8E463 /* TrefleAuthPublisherTests.swift in Sources */, + 181C87EF253CC2DF00D8E463 /* DivisionsPublisherTests.swift in Sources */, 183B16B5252A5E8C007C7A72 /* SubkingdomsTests.swift in Sources */, + 181C8807253CD25E00D8E463 /* GenusPublisherTests.swift in Sources */, + 181C8801253CCDA600D8E463 /* FamilyPublisherTests.swift in Sources */, 18D117FA252B976B0004DC72 /* DivisionClassesTests.swift in Sources */, 186105D52450D01A008A863B /* PlantsTests.swift in Sources */, - 18D1180C252BA1A90004DC72 /* FamiliesTests.swift in Sources */, + 18D1180C252BA1A90004DC72 /* FamilyTests.swift in Sources */, 18D11804252B9E1F0004DC72 /* DivisionOrdersTests.swift in Sources */, 183B16AB252A5B1C007C7A72 /* KingdomsTests.swift in Sources */, + 181C8819253CD82B00D8E463 /* DistributionZonesPublisherTests.swift in Sources */, 1874837F244F0E0F00120F73 /* TrefleAuthTests.swift in Sources */, 18D1182D252BACD80004DC72 /* GenusTests.swift in Sources */, + 181C87E1253C5CAD00D8E463 /* KingdomsPublisherTests.swift in Sources */, + 181C87F5253CC71200D8E463 /* DivisionClassesPublisherTests.swift in Sources */, + 181C87FB253CCA0B00D8E463 /* DivisionOrdersPublisherTests.swift in Sources */, 183B16C6252A610D007C7A72 /* DivisionsTests.swift in Sources */, 18D11869252C78DA0004DC72 /* DistributionZonesTests.swift in Sources */, + 181C880D253CD4EE00D8E463 /* PlantsPublisherTests.swift in Sources */, + 181C87E9253CBC9700D8E463 /* SubkingdomsPublisherTests.swift in Sources */, 186105BA244F1332008A863B /* TestConfig.swift in Sources */, 18D1184A252C6D410004DC72 /* SpeciesTests.swift in Sources */, + 181C8813253CD6CF00D8E463 /* SpeciesPublisherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -902,7 +978,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = M5BBZ59SV2; DYLIB_COMPATIBILITY_VERSION = 1; @@ -916,7 +992,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 0.0.3; + MARKETING_VERSION = 0.0.4; PRODUCT_BUNDLE_IDENTIFIER = com.PigonaHill.TrefleSwiftSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -932,7 +1008,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = M5BBZ59SV2; DYLIB_COMPATIBILITY_VERSION = 1; @@ -946,7 +1022,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 0.0.3; + MARKETING_VERSION = 0.0.4; PRODUCT_BUNDLE_IDENTIFIER = com.PigonaHill.TrefleSwiftSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES;