diff --git a/Podfile.lock b/Podfile.lock index 7c902621..d95d56e7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -27,7 +27,7 @@ PODS: - OHHTTPStubs/Swift (6.1.0): - OHHTTPStubs/Default - UIDeviceIdentifier (1.1.4) - - WordPressKit (2.0.0-beta.3): + - WordPressKit (2.0.0-beta.4): - Alamofire (~> 4.7.3) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -70,7 +70,7 @@ SPEC CHECKSUMS: OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab OHHTTPStubs: 1e21c7d2c084b8153fc53d48400d8919d2d432d0 UIDeviceIdentifier: 8f8a24b257a4d978c8d40ad1e7355b944ffbfa8c - WordPressKit: 7978809060775c2301d1c0f83215b34dc4aaba2e + WordPressKit: c08da00b4a479cd52cda11d085e78b99a741e202 WordPressShared: a2fc2db66c210a05d317ae9678b5823dd6a4d708 wpxmlrpc: 6ba55c773cfa27083ae4a2173e69b19f46da98e2 diff --git a/WordPressKit.podspec b/WordPressKit.podspec index db31af7e..1b9275c6 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressKit" - s.version = "2.0.0-beta.3" + s.version = "2.0.0-beta.4" s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API." s.description = <<-DESC diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index d4243f37..613f2167 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 7328420421CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7328420321CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift */; }; 7328420621CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7328420521CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift */; }; 736C971021E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C970F21E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift */; }; + 7397F01A220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7397F019220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift */; }; 73A2F38A21E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */; }; 73A2F38D21E7FC8200388609 /* WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A2F38C21E7FC8200388609 /* WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift */; }; 73A2F38E21E7FD9B00388609 /* site-verticals-prompt.json in Resources */ = {isa = PBXBuildFile; fileRef = 73A2F38B21E7FC2A00388609 /* site-verticals-prompt.json */; }; @@ -508,6 +509,7 @@ 7328420321CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemote+SiteCreation.swift"; sourceTree = ""; }; 7328420521CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemoteTests+SiteCreation.swift"; sourceTree = ""; }; 736C970F21E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVerticalsPromptResponseDecodingTests.swift; sourceTree = ""; }; + 7397F019220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityServiceRemote_ApiVersion1_0.swift; sourceTree = ""; }; 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemote+SiteVerticalsPrompt.swift"; sourceTree = ""; }; 73A2F38B21E7FC2A00388609 /* site-verticals-prompt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-verticals-prompt.json"; sourceTree = ""; }; 73A2F38C21E7FC8200388609 /* WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift"; sourceTree = ""; }; @@ -1237,6 +1239,7 @@ 93BD273A1EE73282002BB00B /* AccountServiceRemoteREST.m */, 7403A2E31EF06ED500DED7DC /* AccountSettingsRemote.swift */, 826016F01F9FA13A00533B6C /* ActivityServiceRemote.swift */, + 7397F019220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift */, 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */, 82FFBF551F460DD400F4573F /* BlogJetpackSettingsServiceRemote.swift */, 74B5F0DB1EF829B800B411E7 /* BlogServiceRemote.h */, @@ -2214,6 +2217,7 @@ 93BD27831EE73944002BB00B /* WordPressRSDParser.swift in Sources */, 7328420421CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift in Sources */, 826016F31F9FA17B00533B6C /* Activity.swift in Sources */, + 7397F01A220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift in Sources */, 7E3E7A4A20E443890075D159 /* Scanner+extensions.swift in Sources */, 742362E31F1025B400BD0A7F /* RemoteMenuLocation.m in Sources */, 82FFBF561F460DD400F4573F /* BlogJetpackSettingsServiceRemote.swift in Sources */, diff --git a/WordPressKit/ActivityServiceRemote.swift b/WordPressKit/ActivityServiceRemote.swift index 88cde629..5b3f9632 100644 --- a/WordPressKit/ActivityServiceRemote.swift +++ b/WordPressKit/ActivityServiceRemote.swift @@ -97,39 +97,8 @@ public class ActivityServiceRemote: ServiceRemoteWordPressComREST { }) } - /// Makes a request to Restore a site to a previous state. - /// - /// - Parameters: - /// - siteID: The target site's ID. - /// - rewindID: The rewindID to restore to. - /// - success: Closure to be executed on success - /// - failure: Closure to be executed on error. - /// - /// - Returns: A restoreID to check the status of the rewind request. - /// - @objc public func restoreSite(_ siteID: Int, - rewindID: String, - success: @escaping (_ restoreID: String) -> Void, - failure: @escaping (Error) -> Void) { - let endpoint = "activity-log/\(siteID)/rewind/to/\(rewindID)" - let path = self.path(forEndpoint: endpoint, withVersion: ._1_0) - - wordPressComRestApi.POST(path, - parameters: nil, - success: { response, _ in - guard let restoreID = response["restore_id"] as? Int else { - failure(ResponseError.decodingFailure) - return - } - success(String(restoreID)) - }, - failure: { error, _ in - failure(error) - }) } -} - private extension ActivityServiceRemote { func mapActivitiesResponse(_ response: AnyObject) throws -> ([Activity], Int) { diff --git a/WordPressKit/ActivityServiceRemote_ApiVersion1_0.swift b/WordPressKit/ActivityServiceRemote_ApiVersion1_0.swift new file mode 100644 index 00000000..272a4b61 --- /dev/null +++ b/WordPressKit/ActivityServiceRemote_ApiVersion1_0.swift @@ -0,0 +1,38 @@ + +@objc public class ActivityServiceRemote_ApiVersion1_0: ServiceRemoteWordPressComREST { + + public enum ResponseError: Error { + case decodingFailure + } + + /// Makes a request to Restore a site to a previous state. + /// + /// - Parameters: + /// - siteID: The target site's ID. + /// - rewindID: The rewindID to restore to. + /// - success: Closure to be executed on success + /// - failure: Closure to be executed on error. + /// + /// - Returns: A restoreID to check the status of the rewind request. + /// + @objc public func restoreSite(_ siteID: Int, + rewindID: String, + success: @escaping (_ restoreID: String) -> Void, + failure: @escaping (Error) -> Void) { + let endpoint = "activity-log/\(siteID)/rewind/to/\(rewindID)" + let path = self.path(forEndpoint: endpoint, withVersion: ._1_0) + + wordPressComRestApi.POST(path, + parameters: nil, + success: { response, _ in + guard let restoreID = response["restore_id"] as? Int else { + failure(ResponseError.decodingFailure) + return + } + success(String(restoreID)) + }, + failure: { error, _ in + failure(error) + }) + } +} diff --git a/WordPressKit/WordPressComRestApi.swift b/WordPressKit/WordPressComRestApi.swift index 74910f9e..3510f2f0 100644 --- a/WordPressKit/WordPressComRestApi.swift +++ b/WordPressKit/WordPressComRestApi.swift @@ -2,6 +2,8 @@ import Foundation import WordPressShared import Alamofire +// MARK: - WordPressComRestApiError + /** Error constants for the WordPress.com REST API @@ -24,10 +26,19 @@ import Alamofire case preconditionFailure } -open class WordPressComRestApi: NSObject { - @objc public static let ErrorKeyErrorCode: String = "WordPressComRestApiErrorCodeKey" - @objc public static let ErrorKeyErrorMessage: String = "WordPressComRestApiErrorMessageKey" - @objc public static let SessionTaskKey: String = "WordPressComRestAPI.sessionTask" +// MARK: - WordPressComRestApi + +open class WordPressComRestApi: NSObject { + + // MARK: Properties + + @objc public static let ErrorKeyErrorCode = "WordPressComRestApiErrorCodeKey" + @objc public static let ErrorKeyErrorMessage = "WordPressComRestApiErrorMessageKey" + + @objc public static let LocaleKeyDefault = "locale" // locale is specified with this for v1 endpoints + @objc public static let LocaleKeyV2 = "_locale" // locale is prefixed with an underscore for v2 + + @objc public static let SessionTaskKey = "WordPressComRestAPI.sessionTask" public typealias RequestEnqueuedBlock = (_ taskID : NSNumber) -> Void public typealias SuccessResponseBlock = (_ responseObject: AnyObject, _ httpResponse: HTTPURLResponse?) -> () @@ -37,23 +48,24 @@ open class WordPressComRestApi: NSObject { @objc public static let defaultBackgroundSessionIdentifier = "org.wordpress.wpcomrestapi" + private let oAuthToken: String? + + private let userAgent: String? + @objc public let backgroundSessionIdentifier: String @objc public let sharedContainerIdentifier: String? - - fileprivate let backgroundUploads: Bool - static let localeKey = "locale" + private let backgroundUploads: Bool - fileprivate let oAuthToken: String? - fileprivate let userAgent: String? + private let localeKey: String /** Configure whether or not the user's preferred language locale should be appended. Defaults to true. */ @objc open var appendsPreferredLanguageLocale = true - fileprivate lazy var sessionManager: Alamofire.SessionManager = { + private lazy var sessionManager: Alamofire.SessionManager = { let sessionConfiguration = URLSessionConfiguration.default let sessionManager = self.makeSessionManager(configuration: sessionConfiguration) return sessionManager @@ -67,7 +79,7 @@ open class WordPressComRestApi: NSObject { return result } - fileprivate lazy var uploadSessionManager: Alamofire.SessionManager = { + private lazy var uploadSessionManager: Alamofire.SessionManager = { if self.backgroundUploads { let sessionConfiguration = URLSessionConfiguration.background(withIdentifier: self.backgroundSessionIdentifier) sessionConfiguration.sharedContainerIdentifier = self.sharedContainerIdentifier @@ -78,7 +90,7 @@ open class WordPressComRestApi: NSObject { return self.sessionManager }() - fileprivate func makeSessionManager(configuration sessionConfiguration: URLSessionConfiguration) -> Alamofire.SessionManager { + private func makeSessionManager(configuration sessionConfiguration: URLSessionConfiguration) -> Alamofire.SessionManager { var additionalHeaders: [String : AnyObject] = [:] if let oAuthToken = self.oAuthToken { additionalHeaders["Authorization"] = "Bearer \(oAuthToken)" as AnyObject? @@ -92,6 +104,8 @@ open class WordPressComRestApi: NSObject { return sessionManager } + + // MARK: WordPressComRestApi @objc convenience public init(oAuthToken: String? = nil, userAgent: String? = nil) { self.init(oAuthToken: oAuthToken, userAgent: userAgent, backgroundUploads: false, backgroundSessionIdentifier: WordPressComRestApi.defaultBackgroundSessionIdentifier) @@ -105,6 +119,7 @@ open class WordPressComRestApi: NSObject { /// - backgroundUploads: If this value is true the API object will use a background session to execute uploads requests when using the `multipartPOST` function. The default value is false. /// - backgroundSessionIdentifier: The session identifier to use for the background session. This must be unique in the system. /// - sharedContainerIdentifier: An optional string used when setting up background sessions for use in an app extension. Default is nil. + /// - localeKey: The key with which to specify locale in the parameters of a request. /// /// - Discussion: When backgroundUploads are activated any request done by the multipartPOST method will use background session. This background session is shared for all multipart /// requests and the identifier used must be unique in the system, Apple recomends to use invert DNS base on your bundle ID. Keep in mind these requests will continue even @@ -114,12 +129,15 @@ open class WordPressComRestApi: NSObject { @objc public init(oAuthToken: String? = nil, userAgent: String? = nil, backgroundUploads: Bool = false, backgroundSessionIdentifier: String = WordPressComRestApi.defaultBackgroundSessionIdentifier, - sharedContainerIdentifier: String? = nil) { + sharedContainerIdentifier: String? = nil, + localeKey: String = WordPressComRestApi.LocaleKeyDefault) { self.oAuthToken = oAuthToken self.userAgent = userAgent self.backgroundUploads = backgroundUploads self.backgroundSessionIdentifier = backgroundSessionIdentifier self.sharedContainerIdentifier = sharedContainerIdentifier + self.localeKey = localeKey + super.init() } @@ -136,7 +154,7 @@ open class WordPressComRestApi: NSObject { uploadSessionManager.session.invalidateAndCancel() } - // MARK: - Network requests + // MARK: Network requests private func request(method: HTTPMethod, urlString: String, @@ -309,10 +327,9 @@ open class WordPressComRestApi: NSObject { /// - Parameters: /// - path: the path for the request, which might include `locale` /// - parameters: the request parameters, which could conceivably include `locale` - /// - localeKey: the locale key to search for (`locale` in v1 endpoints, `_locale` for v2) /// - Returns: a request URL if successful, `nil` otherwise. /// - func buildRequestURLFor(path: String, parameters: [String: AnyObject]? = [:], localeKey: String = WordPressComRestApi.localeKey) -> String? { + func buildRequestURLFor(path: String, parameters: [String: AnyObject]? = [:]) -> String? { let baseURL = URL(string: WordPressComRestApi.apiBaseURLString) @@ -355,6 +372,8 @@ open class WordPressComRestApi: NSObject { } } +// MARK: - FilePart + /// FilePart represents the infomartion needed to encode a file on a multipart form request public final class FilePart: NSObject { @objc let parameterName: String @@ -370,6 +389,8 @@ public final class FilePart: NSObject { } } +// MARK: - Error processing + extension WordPressComRestApi { /// A custom error processor to handle error responses when status codes are betwen 400 and 500 @@ -447,15 +468,25 @@ extension WordPressComRestApi { return errorWithLocalizedMessage } } +// MARK: - Anonymous API support extension WordPressComRestApi { - /// Returns an Api object without an oAuthtoken defined and with the userAgent set for the WordPress App user agent + /// Returns an API object without an OAuth token defined & with the userAgent set for the WordPress App user agent + /// @objc class public func anonymousApi(userAgent: String) -> WordPressComRestApi { return WordPressComRestApi(oAuthToken: nil, userAgent: userAgent) } + + /// Returns an API object without an OAuth token defined & with both the userAgent & localeKey set for the WordPress App user agent + /// + @objc class public func anonymousApi(userAgent: String, localeKey: String) -> WordPressComRestApi { + return WordPressComRestApi(oAuthToken: nil, userAgent: userAgent, localeKey: localeKey) + } } +// MARK: - Progress + @objc extension Progress { @objc var sessionTask: URLSessionTask? { diff --git a/WordPressKitTests/ActivityServiceRemoteTests.swift b/WordPressKitTests/ActivityServiceRemoteTests.swift index 9514aca6..9586a407 100644 --- a/WordPressKitTests/ActivityServiceRemoteTests.swift +++ b/WordPressKitTests/ActivityServiceRemoteTests.swift @@ -28,6 +28,7 @@ class ActivityServiceRemoteTests: RemoteTestCase, RESTTestable { var restoreEndpoint: String { return "activity-log/\(siteID)/rewind/to/\(rewindID)" } var rewindStatusEndpoint: String { return "sites/\(siteID)/rewind" } + var remoteV1: ActivityServiceRemote_ApiVersion1_0! var remote: ActivityServiceRemote! /// MARK: - Overridden Methods @@ -35,7 +36,10 @@ class ActivityServiceRemoteTests: RemoteTestCase, RESTTestable { override func setUp() { super.setUp() - remote = ActivityServiceRemote(wordPressComRestApi: getRestApi()) + remoteV1 = ActivityServiceRemote_ApiVersion1_0(wordPressComRestApi: getRestApi()) + + let v2RestApi = WordPressComRestApi(localeKey: WordPressComRestApi.LocaleKeyV2) + remote = ActivityServiceRemote(wordPressComRestApi: v2RestApi) } override func tearDown() { @@ -142,7 +146,7 @@ class ActivityServiceRemoteTests: RemoteTestCase, RESTTestable { stubRemoteResponse(restoreEndpoint, filename: restoreSuccessMockFilename, contentType: .ApplicationJSON) - remote.restoreSite(siteID, + remoteV1.restoreSite(siteID, rewindID: rewindID, success: { (restoreID) in XCTAssertEqual(restoreID, self.restoreID) diff --git a/WordPressKitTests/WordPressComRestApiTests+Locale.swift b/WordPressKitTests/WordPressComRestApiTests+Locale.swift index 4459982d..9f28c4d0 100644 --- a/WordPressKitTests/WordPressComRestApiTests+Locale.swift +++ b/WordPressKitTests/WordPressComRestApiTests+Locale.swift @@ -37,7 +37,7 @@ extension WordPressComRestApiTests { XCTAssertNotNil(actualQueryItem!) let actualQueryItemKey = actualQueryItem!.name - let expectedQueryItemKey = WordPressComRestApi.localeKey + let expectedQueryItemKey = WordPressComRestApi.LocaleKeyDefault XCTAssertEqual(expectedQueryItemKey, actualQueryItemKey) let actualQueryItemValue = actualQueryItem!.value @@ -79,7 +79,7 @@ extension WordPressComRestApiTests { let actualQueryString = actualURLComponents?.query XCTAssertNotNil(actualQueryString) - let queryStringIncludesLocale = actualQueryString!.contains(WordPressComRestApi.localeKey) + let queryStringIncludesLocale = actualQueryString!.contains(WordPressComRestApi.LocaleKeyDefault) XCTAssertTrue(queryStringIncludesLocale) } @@ -114,7 +114,7 @@ extension WordPressComRestApiTests { XCTAssertNotNil(actualQueryItem!) let actualQueryItemKey = actualQueryItem!.name - let expectedQueryItemKey = WordPressComRestApi.localeKey + let expectedQueryItemKey = WordPressComRestApi.LocaleKeyDefault XCTAssertEqual(expectedQueryItemKey, actualQueryItemKey) let actualQueryItemValue = actualQueryItem!.value @@ -129,7 +129,7 @@ extension WordPressComRestApiTests { let inputPath = "/path/path" let expectedLocaleValue = "foo" let params: [String : AnyObject] = [ - WordPressComRestApi.localeKey: expectedLocaleValue as AnyObject + WordPressComRestApi.LocaleKeyDefault: expectedLocaleValue as AnyObject ] // When @@ -164,4 +164,39 @@ extension WordPressComRestApiTests { let actualQueryItems = actualURLComponents!.queryItems XCTAssertNil(actualQueryItems) } + + func testThatAlternateLocaleKeyIsHonoredWhenSpecified() { + // Given + let path = "/path/path" + let expectedKey = "foo" + + // When + let api = WordPressComRestApi(localeKey: expectedKey) + let localeAppendedPath = api.buildRequestURLFor(path: path) + + // Then + XCTAssertNotNil(localeAppendedPath) + let actualURL = URL(string: localeAppendedPath!, relativeTo: URL(string: WordPressComRestApi.apiBaseURLString)) + XCTAssertNotNil(actualURL) + + let actualURLComponents = URLComponents(url: actualURL!, resolvingAgainstBaseURL: false) + XCTAssertNotNil(actualURLComponents) + + let expectedPath = path + let actualPath = actualURLComponents!.path + XCTAssertEqual(expectedPath, actualPath) + + let actualQueryItems = actualURLComponents!.queryItems + XCTAssertNotNil(actualQueryItems) + + let expectedQueryItemCount = 1 + let actualQueryItemCount = actualQueryItems!.count + XCTAssertEqual(expectedQueryItemCount, actualQueryItemCount) + + let actualQueryItem = actualQueryItems!.first + XCTAssertNotNil(actualQueryItem!) + + let actualQueryItemKey = actualQueryItem!.name + XCTAssertEqual(expectedKey, actualQueryItemKey) + } }