From 6610c3edbd2272a0ad767a880803def676e0e9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Pinto=20Castillo?= Date: Sun, 18 Mar 2018 09:10:12 -0700 Subject: [PATCH 1/8] Fix so CI only builds (#29) * Adds empty credentials file so tests compile * Creates EmptyTests target The empty tests are now run on the iOS scheme. Let's keep it like this until we have mocked objects for the tests. The purpose of doing this is to make sure our CI passes the builds. --- .gitignore | 2 +- Tesla-API.xcodeproj/project.pbxproj | 121 +++++++++++++++++- .../xcschemes/TeslaAPI_iOS.xcscheme | 12 +- TeslaAPITestsEmpty/Info.plist | 22 ++++ TeslaAPITestsEmpty/TeslaAPITestsEmpty.swift | 27 ++++ Tests/Credentials.swift | 17 +++ 6 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 TeslaAPITestsEmpty/Info.plist create mode 100644 TeslaAPITestsEmpty/TeslaAPITestsEmpty.swift create mode 100644 Tests/Credentials.swift diff --git a/.gitignore b/.gitignore index 052522f..dd74d9d 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,4 @@ fastlane/screenshots fastlane/test_output fastlane/README.md -Tests/Credentials.swift \ No newline at end of file +Tests/Credentials.swift diff --git a/Tesla-API.xcodeproj/project.pbxproj b/Tesla-API.xcodeproj/project.pbxproj index 5176192..0e340b0 100644 --- a/Tesla-API.xcodeproj/project.pbxproj +++ b/Tesla-API.xcodeproj/project.pbxproj @@ -74,6 +74,8 @@ FC99DFDA1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FC99DFDB1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FCD161971F1E79F0003FE381 /* Tesla-API.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD161901F1E793F003FE381 /* Tesla-API.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FCE31678205E3284004EF4B0 /* TeslaAPITestsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */; }; + FCE3167A205E3284004EF4B0 /* TeslaAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */; }; FCE825FC1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; FCE825FD1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; FCE825FE1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; @@ -114,6 +116,13 @@ remoteGlobalIDString = FC48F96B1F1E7E4800D2400B; remoteInfo = TeslaAPI_tvOS; }; + FCE3167B205E3284004EF4B0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = FC6195CD1F1C074300144FD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = FCD1615C1F1E7844003FE381; + remoteInfo = TeslaAPI_iOS; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -143,6 +152,9 @@ FCD161921F1E793F003FE381 /* WebRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRequest.swift; sourceTree = ""; }; FCD161941F1E79C3003FE381 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCD161951F1E79C3003FE381 /* TeslaAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeslaAPITests.swift; sourceTree = ""; }; + FCE31675205E3284004EF4B0 /* TeslaAPITestsEmpty.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TeslaAPITestsEmpty.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeslaAPITestsEmpty.swift; sourceTree = ""; }; + FCE31679205E3284004EF4B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeStateRequest.swift; sourceTree = ""; }; FCE826001F8C075C00B72C08 /* ChargeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeState.swift; sourceTree = ""; }; FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeStateRequestTests.swift; sourceTree = ""; }; @@ -203,6 +215,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FCE31672205E3284004EF4B0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FCE3167A205E3284004EF4B0 /* TeslaAPI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -223,6 +243,7 @@ children = ( FCD1618C1F1E793F003FE381 /* Source */, FCD161931F1E79C3003FE381 /* Tests */, + FCE31676205E3284004EF4B0 /* TeslaAPITestsEmpty */, FC6195D71F1C074300144FD8 /* Products */, ); sourceTree = ""; @@ -237,6 +258,7 @@ FC48F9791F1E7E5800D2400B /* TeslaAPI.framework */, FC48F9881F1E7F1500D2400B /* TeslaAPI_macOS_Tests.xctest */, FC48F9971F1E7F2500D2400B /* TeslaAPI_tvOS_Tests.xctest */, + FCE31675205E3284004EF4B0 /* TeslaAPITestsEmpty.xctest */, ); name = Products; sourceTree = ""; @@ -299,6 +321,15 @@ path = Tests; sourceTree = ""; }; + FCE31676205E3284004EF4B0 /* TeslaAPITestsEmpty */ = { + isa = PBXGroup; + children = ( + FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */, + FCE31679205E3284004EF4B0 /* Info.plist */, + ); + path = TeslaAPITestsEmpty; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -463,13 +494,31 @@ productReference = FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */; productType = "com.apple.product-type.framework"; }; + FCE31674205E3284004EF4B0 /* TeslaAPITestsEmpty */ = { + isa = PBXNativeTarget; + buildConfigurationList = FCE3167F205E3284004EF4B0 /* Build configuration list for PBXNativeTarget "TeslaAPITestsEmpty" */; + buildPhases = ( + FCE31671205E3284004EF4B0 /* Sources */, + FCE31672205E3284004EF4B0 /* Frameworks */, + FCE31673205E3284004EF4B0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FCE3167C205E3284004EF4B0 /* PBXTargetDependency */, + ); + name = TeslaAPITestsEmpty; + productName = TeslaAPITestsEmpty; + productReference = FCE31675205E3284004EF4B0 /* TeslaAPITestsEmpty.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ FC6195CD1F1C074300144FD8 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0830; + LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 0910; ORGANIZATIONNAME = JagCesar; TargetAttributes = { @@ -503,6 +552,10 @@ LastSwiftMigration = 0910; ProvisioningStyle = Manual; }; + FCE31674205E3284004EF4B0 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = FC6195D01F1C074300144FD8 /* Build configuration list for PBXProject "Tesla-API" */; @@ -524,6 +577,7 @@ FC48F9171F1E7B6200D2400B /* TeslaAPI_iOS_Tests */, FC48F9871F1E7F1500D2400B /* TeslaAPI_macOS_Tests */, FC48F9961F1E7F2500D2400B /* TeslaAPI_tvOS_Tests */, + FCE31674205E3284004EF4B0 /* TeslaAPITestsEmpty */, ); }; /* End PBXProject section */ @@ -578,6 +632,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FCE31673205E3284004EF4B0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -706,6 +767,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FCE31671205E3284004EF4B0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FCE31678205E3284004EF4B0 /* TeslaAPITestsEmpty.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -724,6 +793,11 @@ target = FC48F96B1F1E7E4800D2400B /* TeslaAPI_tvOS */; targetProxy = FC48F99D1F1E7F2500D2400B /* PBXContainerItemProxy */; }; + FCE3167C205E3284004EF4B0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = FCD1615C1F1E7844003FE381 /* TeslaAPI_iOS */; + targetProxy = FCE3167B205E3284004EF4B0 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -1098,6 +1172,42 @@ }; name = Release; }; + FCE3167D205E3284004EF4B0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TeslaAPITestsEmpty/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = se.jagcesar.TeslaAPITestsEmpty; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FCE3167E205E3284004EF4B0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TeslaAPITestsEmpty/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = se.jagcesar.TeslaAPITestsEmpty; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1173,6 +1283,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FCE3167F205E3284004EF4B0 /* Build configuration list for PBXNativeTarget "TeslaAPITestsEmpty" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FCE3167D205E3284004EF4B0 /* Debug */, + FCE3167E205E3284004EF4B0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = FC6195CD1F1C074300144FD8 /* Project object */; diff --git a/Tesla-API.xcodeproj/xcshareddata/xcschemes/TeslaAPI_iOS.xcscheme b/Tesla-API.xcodeproj/xcshareddata/xcschemes/TeslaAPI_iOS.xcscheme index 1b31b68..d97b4f0 100644 --- a/Tesla-API.xcodeproj/xcshareddata/xcschemes/TeslaAPI_iOS.xcscheme +++ b/Tesla-API.xcodeproj/xcshareddata/xcschemes/TeslaAPI_iOS.xcscheme @@ -30,7 +30,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + skipped = "YES"> + + + + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/TeslaAPITestsEmpty/TeslaAPITestsEmpty.swift b/TeslaAPITestsEmpty/TeslaAPITestsEmpty.swift new file mode 100644 index 0000000..bf81469 --- /dev/null +++ b/TeslaAPITestsEmpty/TeslaAPITestsEmpty.swift @@ -0,0 +1,27 @@ +// +// TeslaAPITestsEmpty.swift +// TeslaAPITestsEmpty +// +// Created by JagCesar on 2018-03-17. +// Copyright © 2018 JagCesar. All rights reserved. +// + +import XCTest + +class TeslaAPITestsEmpty: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } +} diff --git a/Tests/Credentials.swift b/Tests/Credentials.swift new file mode 100644 index 0000000..11005e4 --- /dev/null +++ b/Tests/Credentials.swift @@ -0,0 +1,17 @@ +import XCTest +@testable import TeslaAPI + +extension TeslaAPITests { + func username() -> String { + return "" + } + func password() -> String { + return "" + } + func accessToken() -> String { + return "" + } + func vehicleIdentifier() -> String { + return "" + } +} From 1d1cf9fc3eaddc217507a1ead00ce6768b9ea059 Mon Sep 17 00:00:00 2001 From: Simon Westerlund Date: Wed, 21 Mar 2018 21:43:54 +0100 Subject: [PATCH 2/8] Tidy up the requests and the base WebRequest --- Source/Requests/AuthenticateRequest.swift | 36 ++++++++----------- Source/Requests/ChargeStateRequest.swift | 14 +++----- Source/Requests/ListVehiclesRequest.swift | 16 ++++----- Source/Requests/LockRequest.swift | 14 +++----- .../MobileEnabledForVehicleRequest.swift | 14 +++----- Source/Requests/OpenChargePortRequest.swift | 14 +++----- Source/WebRequest.swift | 33 ++++++++--------- 7 files changed, 55 insertions(+), 86 deletions(-) diff --git a/Source/Requests/AuthenticateRequest.swift b/Source/Requests/AuthenticateRequest.swift index 3757d41..a7f8bc6 100644 --- a/Source/Requests/AuthenticateRequest.swift +++ b/Source/Requests/AuthenticateRequest.swift @@ -16,35 +16,29 @@ public struct AuthenticateRequest: RequestProtocol { } public func execute(completion: @escaping (Result) -> Void) { - let params: [String: AnyObject] = [ - "email": username as NSString, - "password": password as NSString, - "grant_type": grantType as NSString, - "client_id": clientIdentifier as NSString, - "client_secret": clientSecret as NSString - ] + let params = [ + "email": username, + "password": password, + "grant_type": grantType, + "client_id": clientIdentifier, + "client_secret": clientSecret + ] WebRequest.request( path: path, method: method, params: params) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else { - guard let responseDictionary = response as? [String: AnyObject] else { - DispatchQueue.main.async { + } else { + guard let responseDictionary = response as? [String: AnyObject] else { completion(Result.failure(APIError())) + return } - return - } - do { - let result = try Result.success(Token(dictionary: responseDictionary)) - DispatchQueue.main.async { + do { + let result = try Result.success(Token(dictionary: responseDictionary)) completion(result) - } - } catch let error { - DispatchQueue.main.async { + } catch { completion(Result.failure(error)) } } diff --git a/Source/Requests/ChargeStateRequest.swift b/Source/Requests/ChargeStateRequest.swift index e54d724..6f75979 100644 --- a/Source/Requests/ChargeStateRequest.swift +++ b/Source/Requests/ChargeStateRequest.swift @@ -19,17 +19,13 @@ public struct ChargeStateRequest: RequestProtocol { path: path, method: method, accessToken: accessToken) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else if let response = response as? [String: [String: Any]], - let dictionary = response["response"] { - DispatchQueue.main.async { + } else if let response = response as? [String: [String: Any]], + let dictionary = response["response"] { completion(Result.success(ChargeState(dictionary: dictionary))) - } - } else { - DispatchQueue.main.async { + } else { completion(Result.failure(APIError())) } } diff --git a/Source/Requests/ListVehiclesRequest.swift b/Source/Requests/ListVehiclesRequest.swift index 74cebe6..7ea5ea0 100644 --- a/Source/Requests/ListVehiclesRequest.swift +++ b/Source/Requests/ListVehiclesRequest.swift @@ -15,19 +15,15 @@ public struct ListVehiclesRequest: RequestProtocol { path: path, method: method, accessToken: accessToken) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else { - guard let responseArray = response?["response"] as? [[String: AnyObject]] else { - DispatchQueue.main.async { + } else { + guard let responseArray = response?["response"] as? [[String: AnyObject]] else { completion(Result.failure(APIError())) + return } - return - } - let vehicles = responseArray.flatMap { return Vehicle(dictionary: $0) } - DispatchQueue.main.async { + let vehicles = responseArray.flatMap { return Vehicle(dictionary: $0) } completion(Result.success(vehicles)) } } diff --git a/Source/Requests/LockRequest.swift b/Source/Requests/LockRequest.swift index 1c44f24..0d78541 100644 --- a/Source/Requests/LockRequest.swift +++ b/Source/Requests/LockRequest.swift @@ -30,17 +30,13 @@ public struct LockRequest: RequestProtocol { path: path, method: method, accessToken: accessToken) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else if let response = response as? [String: [String: Any]], - let resultBool = response["response"]?["result"] as? Bool { - DispatchQueue.main.async { + } else if let response = response as? [String: [String: Any]], + let resultBool = response["response"]?["result"] as? Bool { completion(Result.success(resultBool)) - } - } else { - DispatchQueue.main.async { + } else { completion(Result.failure(APIError())) } } diff --git a/Source/Requests/MobileEnabledForVehicleRequest.swift b/Source/Requests/MobileEnabledForVehicleRequest.swift index 9c986b0..142c43f 100644 --- a/Source/Requests/MobileEnabledForVehicleRequest.swift +++ b/Source/Requests/MobileEnabledForVehicleRequest.swift @@ -19,17 +19,13 @@ struct MobileEnabledForVehicleRequest: RequestProtocol { path: path, method: method, accessToken: accessToken) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else if let response = response as? [String: Bool], - let responseBool = response["response"] { - DispatchQueue.main.async { + } else if let response = response as? [String: Bool], + let responseBool = response["response"] { completion(Result.success(responseBool)) - } - } else { - DispatchQueue.main.async { + } else { completion(Result.failure(APIError())) } } diff --git a/Source/Requests/OpenChargePortRequest.swift b/Source/Requests/OpenChargePortRequest.swift index b2804d1..51c3a8a 100644 --- a/Source/Requests/OpenChargePortRequest.swift +++ b/Source/Requests/OpenChargePortRequest.swift @@ -19,17 +19,13 @@ public struct OpenChargePortRequest: RequestProtocol { path: path, method: method, accessToken: accessToken) { response, error in - if let error = error { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let error = error { completion(Result.failure(error)) - } - } else if let response = response as? [String: [String: Any]], - let resultBool = response["response"]?["result"] as? Bool { - DispatchQueue.main.async { + } else if let response = response as? [String: [String: Any]], + let resultBool = response["response"]?["result"] as? Bool { completion(Result.success(resultBool)) - } - } else { - DispatchQueue.main.async { + } else { completion(Result.failure(APIError())) } } diff --git a/Source/WebRequest.swift b/Source/WebRequest.swift index 7d4e2e0..5edf459 100644 --- a/Source/WebRequest.swift +++ b/Source/WebRequest.swift @@ -1,19 +1,15 @@ import Foundation -class WebRequest { +final class WebRequest { enum RequestMethod: String { case get = "GET" case post = "POST" case put = "PUT" } - private init() { - - } - static private func clientURLRequest( path: String, - params: [String: AnyObject]? = nil, + params: [String: String] = [:], accessToken: String? = nil) -> URLRequest { var components = URLComponents() @@ -22,17 +18,15 @@ class WebRequest { components.path = path var request = URLRequest(url: components.url!) - if let params = params { - var paramString = "" - for (key, value) in params { - let escapedKey = (key as NSString).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - let escapedValue = (value as! NSString).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - paramString += "\(escapedKey)=\(escapedValue)&" - } - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - request.httpBody = paramString.data(using: String.Encoding.utf8) - } + let bodyString = params.flatMap { args -> String? in + guard let escapedKey = args.key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } + guard let escapedValue = args.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } + return escapedKey + "=" + escapedValue + }.joined(separator: "&") + + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = bodyString.data(using: .utf8) if let accessToken = accessToken { request.addValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization") @@ -44,7 +38,8 @@ class WebRequest { static public func request( path: String, method: RequestMethod, - params: [String: AnyObject]? = nil, accessToken: String? = nil, + params: [String: String] = [:], + accessToken: String? = nil, completion: @escaping (_ response: AnyObject?, _ error: Error?) -> Void) { dataTask( request: clientURLRequest( @@ -61,9 +56,9 @@ class WebRequest { completion: @escaping (_ response: AnyObject?, _ error: Error?) -> Void) { var request = request request.httpMethod = method.rawValue - let session = URLSession(configuration: URLSessionConfiguration.default) + let session = URLSession(configuration: .default) - let task = session.dataTask(with: request as URLRequest) { data, response, error -> Void in + let task = session.dataTask(with: request) { data, response, error -> Void in if let error = error { completion(nil, error) return From 5371962671c673c168c58e6ec9ab26c64fd28690 Mon Sep 17 00:00:00 2001 From: Simon Westerlund Date: Wed, 21 Mar 2018 21:51:38 +0100 Subject: [PATCH 3/8] Shorten really long line --- Source/WebRequest.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/WebRequest.swift b/Source/WebRequest.swift index 5edf459..9e43e41 100644 --- a/Source/WebRequest.swift +++ b/Source/WebRequest.swift @@ -20,8 +20,8 @@ final class WebRequest { var request = URLRequest(url: components.url!) let bodyString = params.flatMap { args -> String? in - guard let escapedKey = args.key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } - guard let escapedValue = args.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } + guard let escapedKey = args.key.percentageEncoded else { return nil } + guard let escapedValue = args.value.percentageEncoded else { return nil } return escapedKey + "=" + escapedValue }.joined(separator: "&") @@ -73,3 +73,9 @@ final class WebRequest { task.resume() } } + +private extension String { + var percentageEncoded: String? { + return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) + } +} From d6e6a681df36e319560afb8b789c8ce8886a10ef Mon Sep 17 00:00:00 2001 From: Simon Westerlund Date: Wed, 21 Mar 2018 21:51:58 +0100 Subject: [PATCH 4/8] Remove unused parameter in callback --- Source/WebRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/WebRequest.swift b/Source/WebRequest.swift index 9e43e41..ee90a50 100644 --- a/Source/WebRequest.swift +++ b/Source/WebRequest.swift @@ -58,7 +58,7 @@ final class WebRequest { request.httpMethod = method.rawValue let session = URLSession(configuration: .default) - let task = session.dataTask(with: request) { data, response, error -> Void in + let task = session.dataTask(with: request) { data, _, error -> Void in if let error = error { completion(nil, error) return From 145e69f2f1a56e785ae0834940d016527312d21a Mon Sep 17 00:00:00 2001 From: JagCesar Date: Wed, 28 Mar 2018 13:13:35 +0200 Subject: [PATCH 5/8] Updates properties on ChargeState Updates eu_vehicle and motorized_charge_port, both of these properties are now optional. --- Source/Models/ChargeState.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Models/ChargeState.swift b/Source/Models/ChargeState.swift index 21674c2..e297c68 100644 --- a/Source/Models/ChargeState.swift +++ b/Source/Models/ChargeState.swift @@ -31,7 +31,7 @@ public class ChargeState { public let chargerVoltage: Int public let chargingState: ChargingState public let estBatteryRange: Double - public let euVehicle: Bool + public let euVehicle: Bool? public let fastChargerPresent: Bool public let fastChargerType: String? public let idealBatteryRange: Double @@ -39,7 +39,7 @@ public class ChargeState { public let managedChargingStartTime: Date? public let managedChargingUserCanceled: Bool public let maxRangeChargeCounter: Int - public let motorizedChargePort: Bool + public let motorizedChargePort: Bool? public let notEnoughPowerToHeat: Bool? public let scheduledChargingPending: Bool public let scheduledChargingStartTime: Date? @@ -74,7 +74,7 @@ public class ChargeState { chargerVoltage = dictionary["charger_voltage"] as! Int chargingState = ChargingState(rawValue: dictionary["charging_state"] as! String)! estBatteryRange = dictionary["est_battery_range"] as! Double - euVehicle = dictionary["eu_vehicle"] as! Bool + euVehicle = dictionary["eu_vehicle"] as? Bool fastChargerPresent = dictionary["fast_charger_present"] as! Bool fastChargerType = dictionary["fast_charger_type"] as? String idealBatteryRange = dictionary["ideal_battery_range"] as! Double @@ -86,7 +86,7 @@ public class ChargeState { } managedChargingUserCanceled = dictionary["managed_charging_user_canceled"] as! Bool maxRangeChargeCounter = dictionary["max_range_charge_counter"] as! Int - motorizedChargePort = dictionary["motorized_charge_port"] as! Bool + motorizedChargePort = dictionary["motorized_charge_port"] as? Bool notEnoughPowerToHeat = dictionary["not_enough_power_to_heat"] as? Bool scheduledChargingPending = dictionary["scheduled_charging_pending"] as! Bool if let timestamp = dictionary["scheduled_charging_start_time"] as? Double { From d70a185e8903db66a5bc28bf689436c612626717 Mon Sep 17 00:00:00 2001 From: JagCesar Date: Sun, 15 Jul 2018 17:54:06 +0200 Subject: [PATCH 6/8] Add climate state request --- Source/Models/ClimateState.swift | 23 ++++++++++++ Source/Requests/ClimateStateRequest.swift | 35 +++++++++++++++++++ Tesla-API.xcodeproj/project.pbxproj | 32 +++++++++++++++-- .../ClimateStateRequestTests.swift | 19 ++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 Source/Models/ClimateState.swift create mode 100644 Source/Requests/ClimateStateRequest.swift create mode 100644 Tests/Request Tests/ClimateStateRequestTests.swift diff --git a/Source/Models/ClimateState.swift b/Source/Models/ClimateState.swift new file mode 100644 index 0000000..5392057 --- /dev/null +++ b/Source/Models/ClimateState.swift @@ -0,0 +1,23 @@ +import Foundation + +public struct ClimateState { + let insideTemp: Double + let outsideTemp: Double + let driverTempSetting: Double + let passengerTempSetting: Double + let isAutoConditioningOn: Bool + let isFrontDefrosterOn: Bool + let isRearDefrosterOn: Bool + let fanStatus: Int? + + init(dict: [String: Any]) { + insideTemp = dict["inside_temp"] as! Double + outsideTemp = dict["outside_temp"] as! Double + driverTempSetting = dict["driver_temp_setting"] as! Double + passengerTempSetting = dict["passenger_temp_setting"] as! Double + isAutoConditioningOn = dict["is_auto_conditioning_on"] as! Bool + isFrontDefrosterOn = dict["is_front_defroster_on"] as! Bool + isRearDefrosterOn = dict["is_rear_defroster_on"] as! Bool + fanStatus = dict["fan_status"] as? Int + } +} diff --git a/Source/Requests/ClimateStateRequest.swift b/Source/Requests/ClimateStateRequest.swift new file mode 100644 index 0000000..412a636 --- /dev/null +++ b/Source/Requests/ClimateStateRequest.swift @@ -0,0 +1,35 @@ +import Foundation + +public struct ClimateStateRequest: RequestProtocol { + typealias CompletionType = ClimateState + var path: String { + return "/api/1/vehicles/\(vehicleIdentifier)/data_request/climate_state" + } + let method = WebRequest.RequestMethod.get + let accessToken: String + let vehicleIdentifier: String + + public init(accessToken: String, vehicleIdentifier: String) { + self.accessToken = accessToken + self.vehicleIdentifier = vehicleIdentifier + } + + public func execute(completion: @escaping (Result) -> Void) { + WebRequest.request( + path: path, + method: method, + accessToken: accessToken) { response, error in + if let error = error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } + return + } + let responseDict = response as! [String: [String: Any]] + let dataDict = responseDict["response"]! + DispatchQueue.main.async { + completion(Result.success(ClimateState(dict: dataDict))) + } + } + } +} diff --git a/Tesla-API.xcodeproj/project.pbxproj b/Tesla-API.xcodeproj/project.pbxproj index 0e340b0..db6b488 100644 --- a/Tesla-API.xcodeproj/project.pbxproj +++ b/Tesla-API.xcodeproj/project.pbxproj @@ -73,6 +73,17 @@ FC99DFD91F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FC99DFDA1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FC99DFDB1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; + FCB9328220FB913700D19874 /* ClimateStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328120FB913700D19874 /* ClimateStateRequest.swift */; }; + FCB9328320FB913700D19874 /* ClimateStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328120FB913700D19874 /* ClimateStateRequest.swift */; }; + FCB9328420FB913700D19874 /* ClimateStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328120FB913700D19874 /* ClimateStateRequest.swift */; }; + FCB9328520FB913700D19874 /* ClimateStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328120FB913700D19874 /* ClimateStateRequest.swift */; }; + FCB9328720FB917F00D19874 /* ClimateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328620FB917F00D19874 /* ClimateState.swift */; }; + FCB9328820FB917F00D19874 /* ClimateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328620FB917F00D19874 /* ClimateState.swift */; }; + FCB9328920FB917F00D19874 /* ClimateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328620FB917F00D19874 /* ClimateState.swift */; }; + FCB9328A20FB917F00D19874 /* ClimateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328620FB917F00D19874 /* ClimateState.swift */; }; + FCB9328C20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; + FCB9328D20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; + FCB9328E20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; FCD161971F1E79F0003FE381 /* Tesla-API.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD161901F1E793F003FE381 /* Tesla-API.h */; settings = {ATTRIBUTES = (Public, ); }; }; FCE31678205E3284004EF4B0 /* TeslaAPITestsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */; }; FCE3167A205E3284004EF4B0 /* TeslaAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */; }; @@ -144,6 +155,9 @@ FC80D5011F2CE6FD0013F6DF /* ListVehiclesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListVehiclesRequest.swift; sourceTree = ""; }; FC99DFD21F1FB41400013FE9 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; FC99DFD71F1FBBE100013FE9 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; + FCB9328120FB913700D19874 /* ClimateStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateStateRequest.swift; sourceTree = ""; }; + FCB9328620FB917F00D19874 /* ClimateState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateState.swift; sourceTree = ""; }; + FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateStateRequestTests.swift; sourceTree = ""; }; FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TeslaAPI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FCD1618D1F1E793F003FE381 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCD1618F1F1E793F003FE381 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; @@ -229,11 +243,12 @@ FC27C97D1F4995070064E1AE /* Request Tests */ = { isa = PBXGroup; children = ( + FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */, + FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */, FC27C97E1F4995240064E1AE /* ListVehiclesRequestTests.swift */, FC27C97F1F4995240064E1AE /* LockRequestTests.swift */, FC27C9801F4995240064E1AE /* MobileEnabledForRequestTests.swift */, FC27C9811F4995240064E1AE /* OpenChargePortRequestTests.swift */, - FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */, ); path = "Request Tests"; sourceTree = ""; @@ -275,11 +290,12 @@ isa = PBXGroup; children = ( FCE946461F2CF10E004913FC /* AuthenticateRequest.swift */, + FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */, + FCB9328120FB913700D19874 /* ClimateStateRequest.swift */, FC80D5011F2CE6FD0013F6DF /* ListVehiclesRequest.swift */, FC75EC521F2E4AD7007ED697 /* LockRequest.swift */, FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */, FC75EC571F2E613A007ED697 /* OpenChargePortRequest.swift */, - FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */, ); path = Requests; sourceTree = ""; @@ -302,6 +318,7 @@ isa = PBXGroup; children = ( FCE826001F8C075C00B72C08 /* ChargeState.swift */, + FCB9328620FB917F00D19874 /* ClimateState.swift */, FC99DFD71F1FBBE100013FE9 /* Error.swift */, FC99DFD21F1FB41400013FE9 /* Token.swift */, FCD1618F1F1E793F003FE381 /* Vehicle.swift */, @@ -647,6 +664,7 @@ buildActionMask = 2147483647; files = ( FC27C98B1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCB9328C20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9821F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A1291FA32CE900BA5C3F /* Credentials.swift in Sources */, FCE826061F8C092C00B72C08 /* ChargeStateRequestTests.swift in Sources */, @@ -664,8 +682,10 @@ FC48F9AE1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC48F9AA1F1E7FBA00D2400B /* WebRequest.swift in Sources */, FC48F9A61F1E7FB600D2400B /* TeslaAPI.swift in Sources */, + FCB9328820FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FD1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4C1F2E068E007ED697 /* RequestProtocol.swift in Sources */, + FCB9328320FB913700D19874 /* ClimateStateRequest.swift in Sources */, FC75EC541F2E4F2F007ED697 /* LockRequest.swift in Sources */, FCE946481F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B80981F498A2E00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, @@ -684,8 +704,10 @@ FC48F9AD1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC48F9A91F1E7FBA00D2400B /* WebRequest.swift in Sources */, FC48F9A51F1E7FB500D2400B /* TeslaAPI.swift in Sources */, + FCB9328920FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FE1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4A1F2E068C007ED697 /* RequestProtocol.swift in Sources */, + FCB9328420FB913700D19874 /* ClimateStateRequest.swift in Sources */, FC75EC551F2E4F30007ED697 /* LockRequest.swift in Sources */, FCE946491F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B80991F498A2F00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, @@ -704,8 +726,10 @@ FC48F9AC1F1E7FCD00D2400B /* Vehicle.swift in Sources */, FC48F9A81F1E7FBA00D2400B /* WebRequest.swift in Sources */, FC48F9A41F1E7FB400D2400B /* TeslaAPI.swift in Sources */, + FCB9328A20FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FF1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC491F2E068C007ED697 /* RequestProtocol.swift in Sources */, + FCB9328520FB913700D19874 /* ClimateStateRequest.swift in Sources */, FC75EC561F2E4F30007ED697 /* LockRequest.swift in Sources */, FCE9464A1F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B809A1F498A3000D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, @@ -722,6 +746,7 @@ buildActionMask = 2147483647; files = ( FC27C98C1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCB9328D20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9831F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12A1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E61FA12BB3009BF1C4 /* ChargeStateRequestTests.swift in Sources */, @@ -737,6 +762,7 @@ buildActionMask = 2147483647; files = ( FC27C98D1F4995250064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCB9328E20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9841F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12B1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E71FA12BB4009BF1C4 /* ChargeStateRequestTests.swift in Sources */, @@ -754,8 +780,10 @@ FC48F9AF1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC99DFD31F1FB41400013FE9 /* Token.swift in Sources */, FC48F9AB1F1E7FBB00D2400B /* WebRequest.swift in Sources */, + FCB9328720FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FC1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4B1F2E068D007ED697 /* RequestProtocol.swift in Sources */, + FCB9328220FB913700D19874 /* ClimateStateRequest.swift in Sources */, FCE946471F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC75EC531F2E4AD7007ED697 /* LockRequest.swift in Sources */, FC1B80971F49883500D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, diff --git a/Tests/Request Tests/ClimateStateRequestTests.swift b/Tests/Request Tests/ClimateStateRequestTests.swift new file mode 100644 index 0000000..838e0aa --- /dev/null +++ b/Tests/Request Tests/ClimateStateRequestTests.swift @@ -0,0 +1,19 @@ +import XCTest +@testable import TeslaAPI + +extension TeslaAPITests { + func testClimateState() { + let waitExpectation = expectation(description: "Climate state") + ClimateStateRequest( + accessToken: accessToken(), + vehicleIdentifier: vehicleIdentifier()).execute { result in + switch result { + case .success(_): + waitExpectation.fulfill() + case .failure(_): + XCTFail() + } + } + waitForExpectations(timeout: 30, handler: nil) + } +} From c6b73ad5ca065bbfccf993292e95a1e02c647d8e Mon Sep 17 00:00:00 2001 From: JagCesar Date: Sun, 15 Jul 2018 19:14:56 +0200 Subject: [PATCH 7/8] Add SetTemperaturesRequest --- Source/Requests/SetTemperaturesRequest.swift | 43 +++++++++++++++++++ Tesla-API.xcodeproj/project.pbxproj | 18 ++++++++ .../SetTemperaturesRequestTests.swift | 27 ++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 Source/Requests/SetTemperaturesRequest.swift create mode 100644 Tests/Request Tests/SetTemperaturesRequestTests.swift diff --git a/Source/Requests/SetTemperaturesRequest.swift b/Source/Requests/SetTemperaturesRequest.swift new file mode 100644 index 0000000..e1c78cb --- /dev/null +++ b/Source/Requests/SetTemperaturesRequest.swift @@ -0,0 +1,43 @@ +import Foundation + +public struct SetTemperaturesRequest: RequestProtocol { + typealias CompletionType = Bool + var path: String { + return "/api/1/vehicles/\(vehicleIdentifier)/command/set_temps" + } + let method = WebRequest.RequestMethod.post + let accessToken: String + let vehicleIdentifier: String + let driverTemp: Double + let passengerTemp: Double + + public init(accessToken: String, vehicleIdentifier: String, driverTemp: Double, passengerTemp: Double) { + self.accessToken = accessToken + self.vehicleIdentifier = vehicleIdentifier + self.driverTemp = driverTemp + self.passengerTemp = passengerTemp + } + + public func execute(completion: @escaping (Result) -> Void) { + WebRequest.request( + path: path, + method: method, + params: ["driver_temp": "\(driverTemp)", "passenger_temp": "\(passengerTemp)"], + accessToken: accessToken) { response, error in + if let error = error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } + } else if let response = response as? [String: [String: Any]], + let resultBool = response["response"]?["result"] as? Bool { + DispatchQueue.main.async { + completion(Result.success(resultBool)) + } + } else { + DispatchQueue.main.async { + completion(Result.failure(APIError())) + } + } + } + } +} diff --git a/Tesla-API.xcodeproj/project.pbxproj b/Tesla-API.xcodeproj/project.pbxproj index db6b488..6ea9e10 100644 --- a/Tesla-API.xcodeproj/project.pbxproj +++ b/Tesla-API.xcodeproj/project.pbxproj @@ -84,6 +84,13 @@ FCB9328C20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; FCB9328D20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; FCB9328E20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */; }; + FCB9329020FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */; }; + FCB9329120FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */; }; + FCB9329220FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */; }; + FCB9329320FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */; }; + FCB9329520FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; + FCB9329620FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; + FCB9329720FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; FCD161971F1E79F0003FE381 /* Tesla-API.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD161901F1E793F003FE381 /* Tesla-API.h */; settings = {ATTRIBUTES = (Public, ); }; }; FCE31678205E3284004EF4B0 /* TeslaAPITestsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */; }; FCE3167A205E3284004EF4B0 /* TeslaAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */; }; @@ -158,6 +165,8 @@ FCB9328120FB913700D19874 /* ClimateStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateStateRequest.swift; sourceTree = ""; }; FCB9328620FB917F00D19874 /* ClimateState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateState.swift; sourceTree = ""; }; FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateStateRequestTests.swift; sourceTree = ""; }; + FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetTemperaturesRequest.swift; sourceTree = ""; }; + FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetTemperaturesRequestTests.swift; sourceTree = ""; }; FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TeslaAPI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FCD1618D1F1E793F003FE381 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCD1618F1F1E793F003FE381 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; @@ -249,6 +258,7 @@ FC27C97F1F4995240064E1AE /* LockRequestTests.swift */, FC27C9801F4995240064E1AE /* MobileEnabledForRequestTests.swift */, FC27C9811F4995240064E1AE /* OpenChargePortRequestTests.swift */, + FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */, ); path = "Request Tests"; sourceTree = ""; @@ -296,6 +306,7 @@ FC75EC521F2E4AD7007ED697 /* LockRequest.swift */, FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */, FC75EC571F2E613A007ED697 /* OpenChargePortRequest.swift */, + FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */, ); path = Requests; sourceTree = ""; @@ -668,6 +679,7 @@ FC27C9821F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A1291FA32CE900BA5C3F /* Credentials.swift in Sources */, FCE826061F8C092C00B72C08 /* ChargeStateRequestTests.swift in Sources */, + FCB9329520FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */, FC27C97A1F4993E10064E1AE /* ModelMocks.swift in Sources */, FC27C9851F4995240064E1AE /* LockRequestTests.swift in Sources */, FC27C9881F4995240064E1AE /* MobileEnabledForRequestTests.swift in Sources */, @@ -692,6 +704,7 @@ FCE826021F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5031F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFD91F1FBBE100013FE9 /* Error.swift in Sources */, + FCB9329120FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */, FC75EC591F2E6219007ED697 /* OpenChargePortRequest.swift in Sources */, FC99DFD61F1FB65A00013FE9 /* Token.swift in Sources */, ); @@ -714,6 +727,7 @@ FCE826031F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5041F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFDA1F1FBBE100013FE9 /* Error.swift in Sources */, + FCB9329220FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */, FC75EC5A1F2E621A007ED697 /* OpenChargePortRequest.swift in Sources */, FC99DFD51F1FB65A00013FE9 /* Token.swift in Sources */, ); @@ -736,6 +750,7 @@ FCE826041F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5051F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFDB1F1FBBE100013FE9 /* Error.swift in Sources */, + FCB9329320FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */, FC75EC5B1F2E621A007ED697 /* OpenChargePortRequest.swift in Sources */, FC99DFD41F1FB65A00013FE9 /* Token.swift in Sources */, ); @@ -750,6 +765,7 @@ FC27C9831F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12A1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E61FA12BB3009BF1C4 /* ChargeStateRequestTests.swift in Sources */, + FCB9329620FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */, FC27C97B1F4993E10064E1AE /* ModelMocks.swift in Sources */, FC27C9861F4995240064E1AE /* LockRequestTests.swift in Sources */, FC27C9891F4995240064E1AE /* MobileEnabledForRequestTests.swift in Sources */, @@ -766,6 +782,7 @@ FC27C9841F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12B1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E71FA12BB4009BF1C4 /* ChargeStateRequestTests.swift in Sources */, + FCB9329720FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */, FC27C97C1F4993E10064E1AE /* ModelMocks.swift in Sources */, FC27C9871F4995240064E1AE /* LockRequestTests.swift in Sources */, FC27C98A1F4995240064E1AE /* MobileEnabledForRequestTests.swift in Sources */, @@ -790,6 +807,7 @@ FCE826011F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5021F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFD81F1FBBE100013FE9 /* Error.swift in Sources */, + FCB9329020FBADE700D19874 /* SetTemperaturesRequest.swift in Sources */, FC75EC581F2E613A007ED697 /* OpenChargePortRequest.swift in Sources */, FC48F9A71F1E7FB600D2400B /* TeslaAPI.swift in Sources */, ); diff --git a/Tests/Request Tests/SetTemperaturesRequestTests.swift b/Tests/Request Tests/SetTemperaturesRequestTests.swift new file mode 100644 index 0000000..0013067 --- /dev/null +++ b/Tests/Request Tests/SetTemperaturesRequestTests.swift @@ -0,0 +1,27 @@ +import XCTest +@testable import TeslaAPI + +extension TeslaAPITests { + func testSetTemperatures() { + let waitExpectation = expectation(description: "Set temperatures") + + SetTemperaturesRequest( + accessToken: accessToken(), + vehicleIdentifier: vehicleIdentifier(), + driverTemp: 21, + passengerTemp: 21).execute { result in + XCTAssert(Thread.isMainThread) + switch result { + case .success(let result): + if result == true { + waitExpectation.fulfill() + } else { + XCTFail() + } + case .failure(_): + XCTFail() + } + } + waitForExpectations(timeout: 30, handler: nil) + } +} From 5cee5f23c7cb013fb13d3f7faed2e09bc4a08746 Mon Sep 17 00:00:00 2001 From: JagCesar Date: Sun, 15 Jul 2018 19:25:46 +0200 Subject: [PATCH 8/8] Add HVAC requests --- Source/Requests/HVACRequests.swift | 49 +++++++++++++++++++++ Tesla-API.xcodeproj/project.pbxproj | 18 ++++++++ Tests/Request Tests/HVACRequestsTests.swift | 48 ++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 Source/Requests/HVACRequests.swift create mode 100644 Tests/Request Tests/HVACRequestsTests.swift diff --git a/Source/Requests/HVACRequests.swift b/Source/Requests/HVACRequests.swift new file mode 100644 index 0000000..c0bdc79 --- /dev/null +++ b/Source/Requests/HVACRequests.swift @@ -0,0 +1,49 @@ +import Foundation + +public struct HVACRequest: RequestProtocol { + public enum HVACState { + case on + case off + } + typealias CompletionType = Bool + var path: String { + switch state { + case .on: + return "/api/1/vehicles/\(vehicleIdentifier)/command/auto_conditioning_start" + case .off: + return "/api/1/vehicles/\(vehicleIdentifier)/command/auto_conditioning_stop" + } + } + let method = WebRequest.RequestMethod.post + let accessToken: String + let vehicleIdentifier: String + let state: HVACState + + public init(accessToken: String, vehicleIdentifier: String, state: HVACState) { + self.accessToken = accessToken + self.vehicleIdentifier = vehicleIdentifier + self.state = state + } + + public func execute(completion: @escaping (Result) -> Void) { + WebRequest.request( + path: path, + method: method, + accessToken: accessToken) { response, error in + if let error = error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } + } else if let response = response as? [String: [String: Any]], + let resultBool = response["response"]?["result"] as? Bool { + DispatchQueue.main.async { + completion(Result.success(resultBool)) + } + } else { + DispatchQueue.main.async { + completion(Result.failure(APIError())) + } + } + } + } +} diff --git a/Tesla-API.xcodeproj/project.pbxproj b/Tesla-API.xcodeproj/project.pbxproj index 6ea9e10..f394ae6 100644 --- a/Tesla-API.xcodeproj/project.pbxproj +++ b/Tesla-API.xcodeproj/project.pbxproj @@ -91,6 +91,13 @@ FCB9329520FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; FCB9329620FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; FCB9329720FBB50B00D19874 /* SetTemperaturesRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */; }; + FCB9329920FBB91100D19874 /* HVACRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329820FBB91100D19874 /* HVACRequests.swift */; }; + FCB9329A20FBB91100D19874 /* HVACRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329820FBB91100D19874 /* HVACRequests.swift */; }; + FCB9329B20FBB91100D19874 /* HVACRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329820FBB91100D19874 /* HVACRequests.swift */; }; + FCB9329C20FBB91100D19874 /* HVACRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329820FBB91100D19874 /* HVACRequests.swift */; }; + FCB9329E20FBB97500D19874 /* HVACRequestsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329D20FBB97500D19874 /* HVACRequestsTests.swift */; }; + FCB9329F20FBB97500D19874 /* HVACRequestsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329D20FBB97500D19874 /* HVACRequestsTests.swift */; }; + FCB932A020FBB97500D19874 /* HVACRequestsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB9329D20FBB97500D19874 /* HVACRequestsTests.swift */; }; FCD161971F1E79F0003FE381 /* Tesla-API.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD161901F1E793F003FE381 /* Tesla-API.h */; settings = {ATTRIBUTES = (Public, ); }; }; FCE31678205E3284004EF4B0 /* TeslaAPITestsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE31677205E3284004EF4B0 /* TeslaAPITestsEmpty.swift */; }; FCE3167A205E3284004EF4B0 /* TeslaAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */; }; @@ -167,6 +174,8 @@ FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClimateStateRequestTests.swift; sourceTree = ""; }; FCB9328F20FBADE700D19874 /* SetTemperaturesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetTemperaturesRequest.swift; sourceTree = ""; }; FCB9329420FBB50B00D19874 /* SetTemperaturesRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetTemperaturesRequestTests.swift; sourceTree = ""; }; + FCB9329820FBB91100D19874 /* HVACRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HVACRequests.swift; sourceTree = ""; }; + FCB9329D20FBB97500D19874 /* HVACRequestsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HVACRequestsTests.swift; sourceTree = ""; }; FCD1615D1F1E7844003FE381 /* TeslaAPI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TeslaAPI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FCD1618D1F1E793F003FE381 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCD1618F1F1E793F003FE381 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; @@ -254,6 +263,7 @@ children = ( FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */, FCB9328B20FB9B3A00D19874 /* ClimateStateRequestTests.swift */, + FCB9329D20FBB97500D19874 /* HVACRequestsTests.swift */, FC27C97E1F4995240064E1AE /* ListVehiclesRequestTests.swift */, FC27C97F1F4995240064E1AE /* LockRequestTests.swift */, FC27C9801F4995240064E1AE /* MobileEnabledForRequestTests.swift */, @@ -302,6 +312,7 @@ FCE946461F2CF10E004913FC /* AuthenticateRequest.swift */, FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */, FCB9328120FB913700D19874 /* ClimateStateRequest.swift */, + FCB9329820FBB91100D19874 /* HVACRequests.swift */, FC80D5011F2CE6FD0013F6DF /* ListVehiclesRequest.swift */, FC75EC521F2E4AD7007ED697 /* LockRequest.swift */, FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */, @@ -674,6 +685,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FCB9329E20FBB97500D19874 /* HVACRequestsTests.swift in Sources */, FC27C98B1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, FCB9328C20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9821F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, @@ -693,6 +705,7 @@ files = ( FC48F9AE1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC48F9AA1F1E7FBA00D2400B /* WebRequest.swift in Sources */, + FCB9329A20FBB91100D19874 /* HVACRequests.swift in Sources */, FC48F9A61F1E7FB600D2400B /* TeslaAPI.swift in Sources */, FCB9328820FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FD1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, @@ -716,6 +729,7 @@ files = ( FC48F9AD1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC48F9A91F1E7FBA00D2400B /* WebRequest.swift in Sources */, + FCB9329B20FBB91100D19874 /* HVACRequests.swift in Sources */, FC48F9A51F1E7FB500D2400B /* TeslaAPI.swift in Sources */, FCB9328920FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FE1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, @@ -739,6 +753,7 @@ files = ( FC48F9AC1F1E7FCD00D2400B /* Vehicle.swift in Sources */, FC48F9A81F1E7FBA00D2400B /* WebRequest.swift in Sources */, + FCB9329C20FBB91100D19874 /* HVACRequests.swift in Sources */, FC48F9A41F1E7FB400D2400B /* TeslaAPI.swift in Sources */, FCB9328A20FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FF1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, @@ -760,6 +775,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FCB9329F20FBB97500D19874 /* HVACRequestsTests.swift in Sources */, FC27C98C1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, FCB9328D20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9831F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, @@ -777,6 +793,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FCB932A020FBB97500D19874 /* HVACRequestsTests.swift in Sources */, FC27C98D1F4995250064E1AE /* OpenChargePortRequestTests.swift in Sources */, FCB9328E20FB9B3A00D19874 /* ClimateStateRequestTests.swift in Sources */, FC27C9841F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, @@ -796,6 +813,7 @@ files = ( FC48F9AF1F1E7FCE00D2400B /* Vehicle.swift in Sources */, FC99DFD31F1FB41400013FE9 /* Token.swift in Sources */, + FCB9329920FBB91100D19874 /* HVACRequests.swift in Sources */, FC48F9AB1F1E7FBB00D2400B /* WebRequest.swift in Sources */, FCB9328720FB917F00D19874 /* ClimateState.swift in Sources */, FCE825FC1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, diff --git a/Tests/Request Tests/HVACRequestsTests.swift b/Tests/Request Tests/HVACRequestsTests.swift new file mode 100644 index 0000000..bce2f5f --- /dev/null +++ b/Tests/Request Tests/HVACRequestsTests.swift @@ -0,0 +1,48 @@ +import XCTest +@testable import TeslaAPI + +extension TeslaAPITests { + func testEnableHvac() { + let waitExpectation = expectation(description: "Enable HVAC") + + HVACRequest( + accessToken: accessToken(), + vehicleIdentifier: vehicleIdentifier(), + state: .on).execute { result in + XCTAssert(Thread.isMainThread) + switch result { + case .success(let result): + if result { + waitExpectation.fulfill() + } else { + XCTFail() + } + case .failure(_): + XCTFail() + } + } + waitForExpectations(timeout: 30, handler: nil) + } + + func testDisableHvac() { + let waitExpectation = expectation(description: "Disable HVAC") + + HVACRequest( + accessToken: accessToken(), + vehicleIdentifier: vehicleIdentifier(), + state: .off).execute { result in + XCTAssert(Thread.isMainThread) + switch result { + case .success(let result): + if result { + waitExpectation.fulfill() + } else { + XCTFail() + } + case .failure(_): + XCTFail() + } + } + waitForExpectations(timeout: 30, handler: nil) + } +}