diff --git a/AutoCat2.xcodeproj/project.pbxproj b/AutoCat2.xcodeproj/project.pbxproj index a6c7d72..33c7abb 100644 --- a/AutoCat2.xcodeproj/project.pbxproj +++ b/AutoCat2.xcodeproj/project.pbxproj @@ -43,7 +43,6 @@ 7A49F4C627D4061B00AEAAE0 /* AutoCat2UITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F4C527D4061B00AEAAE0 /* AutoCat2UITestsLaunchTests.swift */; }; 7A49F4DB27D4064500AEAAE0 /* AutoCatCore.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F4DA27D4064500AEAAE0 /* AutoCatCore.docc */; }; 7A49F4E127D4064500AEAAE0 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */; }; - 7A49F4E827D4064500AEAAE0 /* AutoCatCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F4E727D4064500AEAAE0 /* AutoCatCoreTests.swift */; }; 7A49F4E927D4064500AEAAE0 /* AutoCatCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A49F4D927D4064500AEAAE0 /* AutoCatCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7A49F4EC27D4064500AEAAE0 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */; }; 7A49F4ED27D4064500AEAAE0 /* AutoCatCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -61,6 +60,13 @@ 7A49F51127D406CB00AEAAE0 /* PlateNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50A27D406CB00AEAAE0 /* PlateNumber.swift */; }; 7A49F51227D406CB00AEAAE0 /* VName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50B27D406CB00AEAAE0 /* VName.swift */; }; 7A49F51527D40C6100AEAAE0 /* AutoCat2.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */; }; + 7A558AB027FA3CCF001A18EE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AA527FA3CCF001A18EE /* SettingsTests.swift */; }; + 7A558AB127FA3CCF001A18EE /* ApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AA627FA3CCF001A18EE /* ApiTests.swift */; }; + 7A558AB227FA3CCF001A18EE /* LoginMethodMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AA927FA3CCF001A18EE /* LoginMethodMock.swift */; }; + 7A558AB327FA3CCF001A18EE /* ApiMethodMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AAA27FA3CCF001A18EE /* ApiMethodMock.swift */; }; + 7A558AB427FA3CCF001A18EE /* login_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A558AAC27FA3CCF001A18EE /* login_success.json */; }; + 7A558AB527FA3CCF001A18EE /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AAE27FA3CCF001A18EE /* MockURLProtocol.swift */; }; + 7A558AB627FA3CCF001A18EE /* ApiMethodMockProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AAF27FA3CCF001A18EE /* ApiMethodMockProtocol.swift */; }; 7A9F2AC327E71531006492A9 /* ACTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F2AC227E71531006492A9 /* ACTabBarController.swift */; }; 7AE32D6427F05F89004EF6E0 /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D6327F05F89004EF6E0 /* VehicleCell.swift */; }; 7AE32D6627F063A1004EF6E0 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D6527F063A1004EF6E0 /* UIEdgeInsets.swift */; }; @@ -169,7 +175,6 @@ 7A49F4D927D4064500AEAAE0 /* AutoCatCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoCatCore.h; sourceTree = ""; }; 7A49F4DA27D4064500AEAAE0 /* AutoCatCore.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = AutoCatCore.docc; sourceTree = ""; }; 7A49F4E027D4064500AEAAE0 /* AutoCatCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 7A49F4E727D4064500AEAAE0 /* AutoCatCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCatCoreTests.swift; sourceTree = ""; }; 7A49F4F627D406B200AEAAE0 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 7A49F4F727D406B200AEAAE0 /* ApiError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = ""; }; 7A49F4F827D406B200AEAAE0 /* Api.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api.swift; sourceTree = ""; }; @@ -184,6 +189,13 @@ 7A49F50A27D406CB00AEAAE0 /* PlateNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlateNumber.swift; sourceTree = ""; }; 7A49F50B27D406CB00AEAAE0 /* VName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VName.swift; sourceTree = ""; }; 7A49F51427D40C6100AEAAE0 /* Shared.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Shared.xcdatamodel; sourceTree = ""; }; + 7A558AA527FA3CCF001A18EE /* SettingsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; + 7A558AA627FA3CCF001A18EE /* ApiTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiTests.swift; sourceTree = ""; }; + 7A558AA927FA3CCF001A18EE /* LoginMethodMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginMethodMock.swift; sourceTree = ""; }; + 7A558AAA27FA3CCF001A18EE /* ApiMethodMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiMethodMock.swift; sourceTree = ""; }; + 7A558AAC27FA3CCF001A18EE /* login_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = login_success.json; sourceTree = ""; }; + 7A558AAE27FA3CCF001A18EE /* MockURLProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = ""; }; + 7A558AAF27FA3CCF001A18EE /* ApiMethodMockProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiMethodMockProtocol.swift; sourceTree = ""; }; 7A9F2AC227E71531006492A9 /* ACTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarController.swift; sourceTree = ""; }; 7AE32D6327F05F89004EF6E0 /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = ""; }; 7AE32D6527F063A1004EF6E0 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = ""; }; @@ -399,7 +411,9 @@ 7A49F4E627D4064500AEAAE0 /* AutoCatCoreTests */ = { isa = PBXGroup; children = ( - 7A49F4E727D4064500AEAAE0 /* AutoCatCoreTests.swift */, + 7A558AA727FA3CCF001A18EE /* Api */, + 7A558AA627FA3CCF001A18EE /* ApiTests.swift */, + 7A558AA527FA3CCF001A18EE /* SettingsTests.swift */, ); path = AutoCatCoreTests; sourceTree = ""; @@ -444,6 +458,42 @@ path = Models; sourceTree = ""; }; + 7A558AA727FA3CCF001A18EE /* Api */ = { + isa = PBXGroup; + children = ( + 7A558AA827FA3CCF001A18EE /* Mocks */, + 7A558AAB27FA3CCF001A18EE /* Responses */, + 7A558AAD27FA3CCF001A18EE /* Lib */, + ); + path = Api; + sourceTree = ""; + }; + 7A558AA827FA3CCF001A18EE /* Mocks */ = { + isa = PBXGroup; + children = ( + 7A558AA927FA3CCF001A18EE /* LoginMethodMock.swift */, + 7A558AAA27FA3CCF001A18EE /* ApiMethodMock.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + 7A558AAB27FA3CCF001A18EE /* Responses */ = { + isa = PBXGroup; + children = ( + 7A558AAC27FA3CCF001A18EE /* login_success.json */, + ); + path = Responses; + sourceTree = ""; + }; + 7A558AAD27FA3CCF001A18EE /* Lib */ = { + isa = PBXGroup; + children = ( + 7A558AAE27FA3CCF001A18EE /* MockURLProtocol.swift */, + 7A558AAF27FA3CCF001A18EE /* ApiMethodMockProtocol.swift */, + ); + path = Lib; + sourceTree = ""; + }; 7AE32D6227F05F5D004EF6E0 /* Cells */ = { isa = PBXGroup; children = ( @@ -699,6 +749,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7A558AB427FA3CCF001A18EE /* login_success.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -791,7 +842,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7A49F4E827D4064500AEAAE0 /* AutoCatCoreTests.swift in Sources */, + 7A558AB227FA3CCF001A18EE /* LoginMethodMock.swift in Sources */, + 7A558AB027FA3CCF001A18EE /* SettingsTests.swift in Sources */, + 7A558AB127FA3CCF001A18EE /* ApiTests.swift in Sources */, + 7A558AB627FA3CCF001A18EE /* ApiMethodMockProtocol.swift in Sources */, + 7A558AB527FA3CCF001A18EE /* MockURLProtocol.swift in Sources */, + 7A558AB327FA3CCF001A18EE /* ApiMethodMock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d07623f..6a81f52 100644 --- a/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,43 +1,41 @@ { - "object": { - "pins": [ - { - "package": "DifferenceKit", - "repositoryURL": "https://github.com/ra1028/DifferenceKit", - "state": { - "branch": null, - "revision": "62745d7780deef4a023a792a1f8f763ec7bf9705", - "version": "1.2.0" - } - }, - { - "package": "PKHUD", - "repositoryURL": "https://github.com/pkluz/PKHUD.git", - "state": { - "branch": null, - "revision": "8fd26f23057c6bebd6695524b1c3e05e93aba571", - "version": "5.4.0" - } - }, - { - "package": "SwiftDate", - "repositoryURL": "https://github.com/malcommac/SwiftDate", - "state": { - "branch": null, - "revision": "6190d0cefff3013e77ed567e6b074f324e5c5bf5", - "version": "6.3.1" - } - }, - { - "package": "SwiftEntryKit", - "repositoryURL": "https://github.com/huri000/SwiftEntryKit", - "state": { - "branch": null, - "revision": "5ad36cccf0c4b9fea32f4e9b17a8e38f07563ef0", - "version": "2.0.0" - } + "pins" : [ + { + "identity" : "differencekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ra1028/DifferenceKit", + "state" : { + "revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705", + "version" : "1.2.0" } - ] - }, - "version": 1 + }, + { + "identity" : "pkhud", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pkluz/PKHUD.git", + "state" : { + "revision" : "8fd26f23057c6bebd6695524b1c3e05e93aba571", + "version" : "5.4.0" + } + }, + { + "identity" : "swiftdate", + "kind" : "remoteSourceControl", + "location" : "https://github.com/malcommac/SwiftDate", + "state" : { + "revision" : "6190d0cefff3013e77ed567e6b074f324e5c5bf5", + "version" : "6.3.1" + } + }, + { + "identity" : "swiftentrykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/huri000/SwiftEntryKit", + "state" : { + "revision" : "5ad36cccf0c4b9fea32f4e9b17a8e38f07563ef0", + "version" : "2.0.0" + } + } + ], + "version" : 2 } diff --git a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist index 8545f97..b1921f4 100644 --- a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,33 +12,33 @@ AutoCat2.xcscheme_^#shared#^_ orderHint - 0 + 1 AutoCatCore.xcscheme_^#shared#^_ orderHint - 2 + 0 DifferenceKit (Playground) 1.xcscheme isShown orderHint - 6 + 3 DifferenceKit (Playground) 2.xcscheme isShown orderHint - 7 + 4 DifferenceKit (Playground).xcscheme isShown orderHint - 1 + 2 SwiftDate (Playground) 1.xcscheme @@ -62,5 +62,13 @@ 3 + SuppressBuildableAutocreation + + 7A49F4D627D4064500AEAAE0 + + primary + + + diff --git a/AutoCatCore/Models/Vehicle.swift b/AutoCatCore/Models/Vehicle.swift index 15c88c4..eec2126 100644 --- a/AutoCatCore/Models/Vehicle.swift +++ b/AutoCatCore/Models/Vehicle.swift @@ -21,12 +21,12 @@ public struct Vehicle: Decodable { public let isJapanese: Bool? public let addedBy: String? public let engine: VEngine? - public let photos: [VPhoto] - public let ownershipPeriods: [VOwnershipPeriod] - public let events: [VEvent] - public let osagoContracts: [VOsago] - public let ads: [VAd] - public let notes: [VNote] + public let photos: [VPhoto]? + public let ownershipPeriods: [VOwnershipPeriod]? + public let events: [VEvent]? + public let osagoContracts: [VOsago]? + public let ads: [VAd]? + public let notes: [VNote]? public let debugInfo: DebugInfo? } @@ -75,12 +75,25 @@ extension CDVehicle { self.addedBy = vehicle.addedBy self.synchronized = true self.engine = CDVEngine(model: vehicle.engine, context: context) - self.photos = NSSet(array: vehicle.photos.map { CDVPhoto(model: $0, context: context) }) - self.ownershipPeriods = NSSet(array: vehicle.ownershipPeriods.map { CDVOwnershipPeriod(model: $0, context: context) }) - self.events = NSSet(array: vehicle.events.map { CDVEvent(model: $0, context: context) }) - self.osagoContracts = NSSet(array: vehicle.osagoContracts.map { CDVOsago(model: $0, context: context) }) - self.ads = NSSet(array: vehicle.ads.map { CDVAd(model: $0, context: context) }) - self.notes = NSSet(array: vehicle.notes.map { CDVNote(model: $0, context: context) }) + + let photos = vehicle.photos?.map { CDVPhoto(model: $0, context: context) } ?? [] + self.photos = NSSet(array: photos) + + let ownershipPeriods = vehicle.ownershipPeriods?.map { CDVOwnershipPeriod(model: $0, context: context) } ?? [] + self.ownershipPeriods = NSSet(array: ownershipPeriods) + + let events = vehicle.events?.map { CDVEvent(model: $0, context: context) } ?? [] + self.events = NSSet(array: events) + + let osagoContracts = vehicle.osagoContracts?.map { CDVOsago(model: $0, context: context) } ?? [] + self.osagoContracts = NSSet(array: osagoContracts) + + let ads = vehicle.ads?.map { CDVAd(model: $0, context: context) } ?? [] + self.ads = NSSet(array: ads) + + let notes = vehicle.notes?.map { CDVNote(model: $0, context: context) } ?? [] + self.notes = NSSet(array: notes) + self.debugInfo = CDDebugInfo(model: vehicle.debugInfo, context: context) } diff --git a/AutoCatCore/Utils/Api.swift b/AutoCatCore/Utils/Api.swift index 4288f41..3042f79 100644 --- a/AutoCatCore/Utils/Api.swift +++ b/AutoCatCore/Utils/Api.swift @@ -74,14 +74,14 @@ public class Api: ApiProtocol { } - // let str = String(data: data, encoding: .utf8) - // print("================================") - // if let string = str?.replacingOccurrences(of: "\\\"", with: "\"") - // .replacingOccurrences(of: "\\'", with: "'") - // .replacingOccurrences(of: "\\n", with: "") { - // print(string) - // } - // print("================================") +// let str = String(data: data, encoding: .utf8) +// print("================================") +// if let string = str?.replacingOccurrences(of: "\\\"", with: "\"") +// .replacingOccurrences(of: "\\'", with: "'") +// .replacingOccurrences(of: "\\n", with: "") { +// print(string) +// } +// print("================================") do { let resp = try JSONDecoder().decode(Response.self, from: data) diff --git a/AutoCatCore/Utils/ApiError.swift b/AutoCatCore/Utils/ApiError.swift index 8de2cb9..974d0aa 100644 --- a/AutoCatCore/Utils/ApiError.swift +++ b/AutoCatCore/Utils/ApiError.swift @@ -1,6 +1,6 @@ import Foundation -public enum ApiError: LocalizedError { +public enum ApiError: LocalizedError, Equatable { case generic case message(String) @@ -28,4 +28,13 @@ public enum ApiError: LocalizedError { self = .generic } } + + public func code() -> Int? { + switch self { + case .invalidLoginOrPassword: + return 0 + default: + return nil + } + } } diff --git a/AutoCatCoreTests/Api/Lib/ApiMethodMockProtocol.swift b/AutoCatCoreTests/Api/Lib/ApiMethodMockProtocol.swift new file mode 100644 index 0000000..bf45947 --- /dev/null +++ b/AutoCatCoreTests/Api/Lib/ApiMethodMockProtocol.swift @@ -0,0 +1,7 @@ +import Foundation + +protocol ApiMethodMockProtocol { + var path: String { get } + var httpMethod: String { get } + func response(headers: [String: String], params: [String: Any]) -> (status: Int, data: Data?) +} diff --git a/AutoCatCoreTests/Api/Lib/MockURLProtocol.swift b/AutoCatCoreTests/Api/Lib/MockURLProtocol.swift new file mode 100644 index 0000000..b60320c --- /dev/null +++ b/AutoCatCoreTests/Api/Lib/MockURLProtocol.swift @@ -0,0 +1,96 @@ +import Foundation + +extension URL { + public var queryParameters: [String: String]? { + guard + let components = URLComponents(url: self, resolvingAgainstBaseURL: true), + let queryItems = components.queryItems else { return nil } + return queryItems.reduce(into: [String: String]()) { (result, item) in + result[item.name] = item.value + } + } +} + +extension URLRequest { + func bodySteamAsJSON() -> [String: Any]? { + guard let bodyStream = self.httpBodyStream else { return nil } + + bodyStream.open() + + // Will read 16 chars per iteration. Can use bigger buffer if needed + let bufferSize: Int = 16 + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + var dat = Data() + + while bodyStream.hasBytesAvailable { + let readDat = bodyStream.read(buffer, maxLength: bufferSize) + dat.append(buffer, count: readDat) + } + + buffer.deallocate() + bodyStream.close() + + return try? JSONSerialization.jsonObject(with: dat, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any] + } +} + +class MockURLProtocol: URLProtocol { + + static var baseUrl: String = "" + static var apiMethodMocks: [ApiMethodMockProtocol] = [] + + override class func canInit(with request: URLRequest) -> Bool { + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + guard let requestUrl = request.url else { return } + + let methodMock = MockURLProtocol.apiMethodMocks.first { + return request.url?.absoluteString == MockURLProtocol.baseUrl + $0.path + && request.httpMethod == $0.httpMethod + } + + guard let methodMock = methodMock else { + if let response = HTTPURLResponse(url: requestUrl, statusCode: 404, httpVersion: "HTTP/2", headerFields: [:]) { + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocolDidFinishLoading(self) + } + + return + } + + // Assuming we use url parameters in GET requests and JSON-encoded body in everything else + var params: [String: Any] = [:] + if request.httpBodyStream != nil { + if let bodyDict = request.bodySteamAsJSON() { + params = bodyDict + } + } else { + if let urlParams = requestUrl.queryParameters { + params = urlParams + } + } + + let result = methodMock.response(headers: request.allHTTPHeaderFields ?? [:], params: params) + guard let response = HTTPURLResponse(url: requestUrl, statusCode: result.status, httpVersion: "HTTP/2", headerFields: [:]) else { return } + + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + + if let data = result.data { + client?.urlProtocol(self, didLoad: data) + } + + client?.urlProtocolDidFinishLoading(self) + + //client?.urlProtocol(self, didFailWithError: error) + } + + override func stopLoading() { + + } +} diff --git a/AutoCatCoreTests/Api/Mocks/ApiMethodMock.swift b/AutoCatCoreTests/Api/Mocks/ApiMethodMock.swift new file mode 100644 index 0000000..f6d3016 --- /dev/null +++ b/AutoCatCoreTests/Api/Mocks/ApiMethodMock.swift @@ -0,0 +1,39 @@ +import Foundation +import AutoCatCore + +open class ApiMethodMock: ApiMethodMockProtocol { + + private(set) var path: String + private(set) var httpMethod: String + + init(httpMethod: String, path: String) { + self.httpMethod = httpMethod + self.path = path + } + + func readData(from path: String) -> Data? { + guard let url = Bundle(for: type(of: self)).url(forResource: path, withExtension: "json") else { return nil } + return try? Data(contentsOf: url) + } + + func error(message: String, code: Int? = nil) -> Data? { + var errorData: [String: AnyEncodable] = [ + "success": false, + "error": AnyEncodable(message) + ] + + if let code = code { + errorData["errorCode"] = AnyEncodable(code) + } + + return try? JSONEncoder().encode(errorData) + } + + func notFoundResponse() -> (status: Int, data: Data?) { + return (status: 404, data: self.error(message: "Not found")) + } + + open func response(headers: [String : String], params: [String : Any]) -> (status: Int, data: Data?) { + return self.notFoundResponse() + } +} diff --git a/AutoCatCoreTests/Api/Mocks/LoginMethodMock.swift b/AutoCatCoreTests/Api/Mocks/LoginMethodMock.swift new file mode 100644 index 0000000..0f00711 --- /dev/null +++ b/AutoCatCoreTests/Api/Mocks/LoginMethodMock.swift @@ -0,0 +1,25 @@ +import Foundation +import AutoCatCore + +class LoginMethodMock: ApiMethodMock { + private var login: String + private var password: String + + init(httpMethod: String, path: String, login: String, password: String) { + self.login = login + self.password = password + super.init(httpMethod: httpMethod, path: path) + } + + override func response(headers: [String : String], params: [String : Any]) -> (status: Int, data: Data?) { + guard let login = params["email"] as? String, let password = params["password"] as? String else { + return (status: 400, data: self.error(message: "Invalid parameters")) + } + + if login != self.login || password != self.password { + return (status: 200, data: self.error(message: "Incorrect login or password", code: ApiError.invalidLoginOrPassword.code())) + } + + return (status: 200, data: readData(from: "login_success")) + } +} diff --git a/AutoCatCoreTests/Api/Responses/login_success.json b/AutoCatCoreTests/Api/Responses/login_success.json new file mode 100644 index 0000000..60c13de --- /dev/null +++ b/AutoCatCoreTests/Api/Responses/login_success.json @@ -0,0 +1,8 @@ +{ + "success": true, + "data": { + "_id": "832c1bd4-5caa-4c9d-b24c-4c000cd8a793", + "email": "selim@fastmail.fm", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InNlbGltQGZhc3RtYWlsLmZtIiwiaWF0IjoxNjI2MjgwNDgxLCJleHAiOjE2NTc4MTY0ODF9.eU6wpacgCSnhM4EiyMY2lUptsfSfHz9guuvOsAw4X90" + } +} diff --git a/AutoCatCoreTests/ApiTests.swift b/AutoCatCoreTests/ApiTests.swift new file mode 100644 index 0000000..be3185b --- /dev/null +++ b/AutoCatCoreTests/ApiTests.swift @@ -0,0 +1,45 @@ +import XCTest +import AutoCatCore + +class ApiTests: XCTestCase { + + private var api: ApiProtocol! + + private let testLogin = "test@gmail.com" + private let testPassword = "12345" + + override func setUpWithError() throws { + MockURLProtocol.baseUrl = Constants.baseUrl + MockURLProtocol.apiMethodMocks = [ + LoginMethodMock(httpMethod: "POST", path: "user/login", login: self.testLogin, password: self.testPassword) + ] + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.protocolClasses = [MockURLProtocol.self] + let session = URLSession(configuration: sessionConfig) + self.api = Api(session: session) + } + + override func tearDownWithError() throws { + MockURLProtocol.baseUrl = "" + MockURLProtocol.apiMethodMocks = [] + } + + func testLoginSuccess() async throws { + let user = try await self.api.login(email: self.testLogin, password: self.testPassword) + XCTAssertTrue(!user.token.isEmpty) + } + + func testLoginInvalidParams() async throws { + do { + _ = try await self.api.login(email: "", password: "") + } catch let error as ApiError { + XCTAssertTrue(error == .invalidLoginOrPassword) + return + } catch { + XCTFail("Wrong exception type") + } + + XCTFail("Exception expected") + } +} diff --git a/AutoCatCoreTests/AutoCatCoreTests.swift b/AutoCatCoreTests/AutoCatCoreTests.swift deleted file mode 100644 index 95ed638..0000000 --- a/AutoCatCoreTests/AutoCatCoreTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AutoCatCoreTests.swift -// AutoCatCoreTests -// -// Created by Selim Mustafaev on 05.03.2022. -// - -import XCTest -@testable import AutoCatCore - -class AutoCatCoreTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/AutoCatCoreTests/SettingsTests.swift b/AutoCatCoreTests/SettingsTests.swift new file mode 100644 index 0000000..d7c9d3b --- /dev/null +++ b/AutoCatCoreTests/SettingsTests.swift @@ -0,0 +1,79 @@ +import XCTest +import AutoCatCore + +enum TestError: LocalizedError { + + case createDefaultsFailed + + public var errorDescription: String? { + switch self { + case .createDefaultsFailed: + return "Failed to create UserDefaults" + } + } +} + +class SettingsTests: XCTestCase { + + private var settings: SettingsProtocol! + + override func setUpWithError() throws { + guard let userDefaults = UserDefaults(suiteName: #file) else { + throw TestError.createDefaultsFailed + } + userDefaults.removePersistentDomain(forName: #file) + self.settings = Settings(defaults: userDefaults) + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testAlternativeOrder() { + XCTAssert(self.settings.recognizeAlternativeOrder == false, "recognizeAlternativeOrder: wrong default value") + self.settings.recognizeAlternativeOrder = true + XCTAssert(self.settings.recognizeAlternativeOrder == true, "recognizeAlternativeOrder: failed to change value") + } + + func testShortenedNumbers() { + XCTAssert(self.settings.recognizeShortenedNumbers == false, "recognizeShortenedNumbers: wrong default value") + self.settings.recognizeShortenedNumbers = true + XCTAssert(self.settings.recognizeShortenedNumbers == true, "recognizeShortenedNumbers: failed to change value") + } + + func testDefaultRegion() { + XCTAssert(self.settings.defaultRegion == "161", "defaultRegion: wrong default value") + self.settings.defaultRegion = "761" + XCTAssert(self.settings.defaultRegion == "761", "defaultRegion: failed to change value") + } + + func testRecordBeep() { + XCTAssert(self.settings.recordBeep == false, "recordBeep: wrong default value") + self.settings.recordBeep = true + XCTAssert(self.settings.recordBeep == true, "recordBeep: failed to change value") + } + + func testDebugInfo() { + XCTAssert(self.settings.showDebugInfo == false, "showDebugInfo: wrong default value") + self.settings.showDebugInfo = true + XCTAssert(self.settings.showDebugInfo == true, "showDebugInfo: failed to change value") + } + + func testDefaultUser() { + XCTAssert(self.settings.user.email.isEmpty, "Default user email is not empty") + XCTAssert(self.settings.user.token.isEmpty, "Default user token is not empty") + XCTAssert(self.settings.user.firebaseIdToken == nil, "Default user firebase ID token is not nil") + XCTAssert(self.settings.user.firebaseRefreshToken == nil, "Default user firebase refresh token is not nil") + } + + func testSaveUser() { + self.settings.user.token = "TestToken" + XCTAssert(self.settings.user.token == "TestToken", "Failed to save user token to settings") + + self.settings.user.firebaseIdToken = "TestFirebaseToken" + XCTAssert(self.settings.user.firebaseIdToken == "TestFirebaseToken", "Failed to save user firebaseIdToken to settings") + + self.settings.user.firebaseRefreshToken = "TestResreshToken" + XCTAssert(self.settings.user.firebaseRefreshToken == "TestResreshToken", "Failed to save user firebaseRefreshToken to settings") + } +}