From e54ee3e424985a1156da0bb4cd8e1dfe41a30255 Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 08:38:36 +0700 Subject: [PATCH 1/6] Move OAuthRouter, OAuthConfig, TokenConfig etc to separate files --- TrashCanKit.xcodeproj/project.pbxproj | 16 ++ .../xcschemes/TrashCanKit.xcscheme | 3 +- TrashCanKit/NSURL+URLParameters.swift | 18 ++ TrashCanKit/OAuthConfiguration.swift | 88 +++++++++ TrashCanKit/OAuthRouter.swift | 61 ++++++ TrashCanKit/TokenConfiguration.swift | 13 ++ TrashCanKit/TrashCanKit.swift | 174 ------------------ 7 files changed, 198 insertions(+), 175 deletions(-) create mode 100644 TrashCanKit/NSURL+URLParameters.swift create mode 100644 TrashCanKit/OAuthConfiguration.swift create mode 100644 TrashCanKit/OAuthRouter.swift create mode 100644 TrashCanKit/TokenConfiguration.swift diff --git a/TrashCanKit.xcodeproj/project.pbxproj b/TrashCanKit.xcodeproj/project.pbxproj index 08640f7..f8c7072 100644 --- a/TrashCanKit.xcodeproj/project.pbxproj +++ b/TrashCanKit.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 231B684E1BE99852007AE2A6 /* OAuthConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B684D1BE99852007AE2A6 /* OAuthConfiguration.swift */; }; + 231B68501BE99886007AE2A6 /* NSURL+URLParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B684F1BE99886007AE2A6 /* NSURL+URLParameters.swift */; }; + 231B68521BE9989C007AE2A6 /* TokenConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68511BE9989C007AE2A6 /* TokenConfiguration.swift */; }; + 231B68541BE998C7007AE2A6 /* OAuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */; }; 236364741BE22F1000AA7104 /* TrashCanKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 236364731BE22F1000AA7104 /* TrashCanKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2363647B1BE22F1000AA7104 /* TrashCanKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 236364701BE22F1000AA7104 /* TrashCanKit.framework */; }; 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */; }; @@ -51,6 +55,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 231B684D1BE99852007AE2A6 /* OAuthConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthConfiguration.swift; sourceTree = ""; }; + 231B684F1BE99886007AE2A6 /* NSURL+URLParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURL+URLParameters.swift"; sourceTree = ""; }; + 231B68511BE9989C007AE2A6 /* TokenConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenConfiguration.swift; sourceTree = ""; }; + 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthRouter.swift; sourceTree = ""; }; 236364701BE22F1000AA7104 /* TrashCanKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TrashCanKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 236364731BE22F1000AA7104 /* TrashCanKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrashCanKit.h; sourceTree = ""; }; 236364751BE22F1000AA7104 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -115,6 +123,10 @@ children = ( 236364731BE22F1000AA7104 /* TrashCanKit.h */, 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */, + 231B684F1BE99886007AE2A6 /* NSURL+URLParameters.swift */, + 231B684D1BE99852007AE2A6 /* OAuthConfiguration.swift */, + 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */, + 231B68511BE9989C007AE2A6 /* TokenConfiguration.swift */, 2363648B1BE22FCC00AA7104 /* Repositories.swift */, 2363648C1BE22FCC00AA7104 /* Token.swift */, 2363648D1BE22FCC00AA7104 /* User.swift */, @@ -266,9 +278,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 231B68521BE9989C007AE2A6 /* TokenConfiguration.swift in Sources */, + 231B684E1BE99852007AE2A6 /* OAuthConfiguration.swift in Sources */, 236364911BE22FCC00AA7104 /* User.swift in Sources */, + 231B68501BE99886007AE2A6 /* NSURL+URLParameters.swift in Sources */, 236364901BE22FCC00AA7104 /* Token.swift in Sources */, 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */, + 231B68541BE998C7007AE2A6 /* OAuthRouter.swift in Sources */, 2363648F1BE22FCC00AA7104 /* Repositories.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TrashCanKit.xcodeproj/xcshareddata/xcschemes/TrashCanKit.xcscheme b/TrashCanKit.xcodeproj/xcshareddata/xcschemes/TrashCanKit.xcscheme index 5ac50d2..9ad9194 100644 --- a/TrashCanKit.xcodeproj/xcshareddata/xcschemes/TrashCanKit.xcscheme +++ b/TrashCanKit.xcodeproj/xcshareddata/xcschemes/TrashCanKit.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/TrashCanKit/NSURL+URLParameters.swift b/TrashCanKit/NSURL+URLParameters.swift new file mode 100644 index 0000000..88780d5 --- /dev/null +++ b/TrashCanKit/NSURL+URLParameters.swift @@ -0,0 +1,18 @@ +import Foundation + +internal extension NSURL { + func URLParameters() -> [String: String] { + let stringParams = absoluteString.componentsSeparatedByString("?").last + let params = stringParams?.componentsSeparatedByString("&") + var returnParams: [String: String] = [:] + if let params = params { + for param in params { + let keyValue = param.componentsSeparatedByString("=") + if let key = keyValue.first, value = keyValue.last { + returnParams[key] = value + } + } + } + return returnParams + } +} diff --git a/TrashCanKit/OAuthConfiguration.swift b/TrashCanKit/OAuthConfiguration.swift new file mode 100644 index 0000000..4d2c3a3 --- /dev/null +++ b/TrashCanKit/OAuthConfiguration.swift @@ -0,0 +1,88 @@ +import Foundation +import RequestKit + +public struct OAuthConfiguration: Configuration { + public var apiEndpoint: String + public var accessToken: String? + public let token: String + public let secret: String + public let scopes: [String] + public let webEndpoint: String + + public init(_ url: String = bitbucketBaseURL, webURL: String = bitbucketWebURL, + token: String, secret: String, scopes: [String]) { + apiEndpoint = url + webEndpoint = webURL + self.token = token + self.secret = secret + self.scopes = [] + } + + public func authenticate() -> NSURL? { + return OAuthRouter.Authorize(self).URLRequest?.URL + } + + private func basicAuthenticationString() -> String { + let clientIDSecretString = [token, secret].joinWithSeparator(":") + let clientIDSecretData = clientIDSecretString.dataUsingEncoding(NSUTF8StringEncoding) + let base64 = clientIDSecretData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) + return "Basic \(base64 ?? "")" + } + + public func basicAuthSession() -> NSURLSession { + let config = NSURLSessionConfiguration.defaultSessionConfiguration() + config.HTTPAdditionalHeaders = ["Authorization" : basicAuthenticationString()] + return NSURLSession(configuration: config) + } + + public func authorize(code: String, completion: (config: TokenConfiguration) -> Void) { + let request = OAuthRouter.AccessToken(self, code).URLRequest + if let request = request { + let task = basicAuthSession().dataTaskWithRequest(request) { data, response, err in + if let response = response as? NSHTTPURLResponse { + if response.statusCode != 200 { + return + } else { + if let config = self.configFromData(data) { + completion(config: config) + } + } + } + } + task.resume() + } + } + + private func configFromData(data: NSData?) -> TokenConfiguration? { + guard let data = data else { return nil } + do { + guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String: AnyObject] else { return nil } + let accessToken = json["access_token"] as? String + let refreshToken = json["refresh_token"] as? String + if let accessToken = accessToken, refreshToken = refreshToken { + let config = TokenConfiguration(accessToken, refreshToken: refreshToken) + return config + } + } catch { + return nil + } + return nil + } + + public func handleOpenURL(url: NSURL, completion: (config: TokenConfiguration) -> Void) { + let params = url.URLParameters() + if let code = params["code"] { + authorize(code) { config in + completion(config: config) + } + } + } + + public func accessTokenFromResponse(response: String) -> String? { + let accessTokenParam = response.componentsSeparatedByString("&").first + if let accessTokenParam = accessTokenParam { + return accessTokenParam.componentsSeparatedByString("=").last + } + return nil + } +} diff --git a/TrashCanKit/OAuthRouter.swift b/TrashCanKit/OAuthRouter.swift new file mode 100644 index 0000000..79e632f --- /dev/null +++ b/TrashCanKit/OAuthRouter.swift @@ -0,0 +1,61 @@ +import RequestKit + +public enum OAuthRouter: Router { + case Authorize(OAuthConfiguration) + case AccessToken(OAuthConfiguration, String) + + public var configuration: Configuration { + switch self { + case .Authorize(let config): return config + case .AccessToken(let config, _): return config + } + } + + public var method: HTTPMethod { + switch self { + case .Authorize: + return .GET + case .AccessToken: + return .POST + } + } + + public var encoding: HTTPEncoding { + switch self { + case .Authorize: + return .URL + case .AccessToken: + return .FORM + } + } + + public var path: String { + switch self { + case .Authorize: + return "site/oauth2/authorize" + case .AccessToken: + return "site/oauth2/access_token" + } + } + + public var params: [String: String] { + switch self { + case .Authorize(let config): + return ["client_id": config.token, "response_type": "code"] + case .AccessToken(_, let code): + return ["code": code, "grant_type": "authorization_code"] + } + } + + public var URLRequest: NSURLRequest? { + switch self { + case .Authorize(let config): + let URLString = config.webEndpoint.stringByAppendingURLPath(path) + return request(URLString, parameters: params) + case .AccessToken(let config, _): + let URLString = config.webEndpoint.stringByAppendingURLPath(path) + return request(URLString, parameters: params) + } + } +} + diff --git a/TrashCanKit/TokenConfiguration.swift b/TrashCanKit/TokenConfiguration.swift new file mode 100644 index 0000000..ceef33c --- /dev/null +++ b/TrashCanKit/TokenConfiguration.swift @@ -0,0 +1,13 @@ +import RequestKit + +public struct TokenConfiguration: Configuration { + public var apiEndpoint: String + public var accessToken: String? + public var refreshToken: String? + + public init(_ token: String? = nil, refreshToken: String? = nil, url: String = bitbucketBaseURL) { + apiEndpoint = url + accessToken = token + self.refreshToken = refreshToken + } +} diff --git a/TrashCanKit/TrashCanKit.swift b/TrashCanKit/TrashCanKit.swift index 4dd41fa..2e49886 100644 --- a/TrashCanKit/TrashCanKit.swift +++ b/TrashCanKit/TrashCanKit.swift @@ -5,18 +5,6 @@ let bitbucketBaseURL = "https://bitbucket.org/api/2.0" let bitbucketWebURL = "https://bitbucket.org" let BitbucketErrorDomain = "https://bitbucket.org" -public struct TokenConfiguration: Configuration { - public var apiEndpoint: String - public var accessToken: String? - public var refreshToken: String? - - public init(_ token: String? = nil, refreshToken: String? = nil, url: String = bitbucketBaseURL) { - apiEndpoint = url - accessToken = token - self.refreshToken = refreshToken - } -} - public struct TrashCanKit { public let configuration: TokenConfiguration @@ -24,165 +12,3 @@ public struct TrashCanKit { configuration = config } } - -public struct OAuthConfiguration: Configuration { - public var apiEndpoint: String - public var accessToken: String? - public let token: String - public let secret: String - public let scopes: [String] - public let webEndpoint: String - - public init(_ url: String = bitbucketBaseURL, webURL: String = bitbucketWebURL, - token: String, secret: String, scopes: [String]) { - apiEndpoint = url - webEndpoint = webURL - self.token = token - self.secret = secret - self.scopes = [] - } - - public func authenticate() -> NSURL? { - return OAuthRouter.Authorize(self).URLRequest?.URL - } - - private func basicAuthenticationString() -> String { - let clientIDSecretString = [token, secret].joinWithSeparator(":") - let clientIDSecretData = clientIDSecretString.dataUsingEncoding(NSUTF8StringEncoding) - let base64 = clientIDSecretData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) - return "Basic \(base64 ?? "")" - } - - public func basicAuthSession() -> NSURLSession { - let config = NSURLSessionConfiguration.defaultSessionConfiguration() - config.HTTPAdditionalHeaders = ["Authorization" : basicAuthenticationString()] - return NSURLSession(configuration: config) - } - - public func authorize(code: String, completion: (config: TokenConfiguration) -> Void) { - let request = OAuthRouter.AccessToken(self, code).URLRequest - if let request = request { - let task = basicAuthSession().dataTaskWithRequest(request) { data, response, err in - if let response = response as? NSHTTPURLResponse { - if response.statusCode != 200 { - return - } else { - if let config = self.configFromData(data) { - completion(config: config) - } - } - } - } - task.resume() - } - } - - private func configFromData(data: NSData?) -> TokenConfiguration? { - guard let data = data else { return nil } - do { - guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String: AnyObject] else { return nil } - let accessToken = json["access_token"] as? String - let refreshToken = json["refresh_token"] as? String - if let accessToken = accessToken, refreshToken = refreshToken { - let config = TokenConfiguration(accessToken, refreshToken: refreshToken) - return config - } - } catch { - return nil - } - return nil - } - - public func handleOpenURL(url: NSURL, completion: (config: TokenConfiguration) -> Void) { - let params = url.URLParameters() - if let code = params["code"] { - authorize(code) { config in - completion(config: config) - } - } - } - - public func accessTokenFromResponse(response: String) -> String? { - let accessTokenParam = response.componentsSeparatedByString("&").first - if let accessTokenParam = accessTokenParam { - return accessTokenParam.componentsSeparatedByString("=").last - } - return nil - } -} - -private extension NSURL { - func URLParameters() -> [String: String] { - let stringParams = absoluteString.componentsSeparatedByString("?").last - let params = stringParams?.componentsSeparatedByString("&") - var returnParams: [String: String] = [:] - if let params = params { - for param in params { - let keyValue = param.componentsSeparatedByString("=") - if let key = keyValue.first, value = keyValue.last { - returnParams[key] = value - } - } - } - return returnParams - } -} - -public enum OAuthRouter: Router { - case Authorize(OAuthConfiguration) - case AccessToken(OAuthConfiguration, String) - - public var configuration: Configuration { - switch self { - case .Authorize(let config): return config - case .AccessToken(let config, _): return config - } - } - - public var method: HTTPMethod { - switch self { - case .Authorize: - return .GET - case .AccessToken: - return .POST - } - } - - public var encoding: HTTPEncoding { - switch self { - case .Authorize: - return .URL - case .AccessToken: - return .FORM - } - } - - public var path: String { - switch self { - case .Authorize: - return "site/oauth2/authorize" - case .AccessToken: - return "site/oauth2/access_token" - } - } - - public var params: [String: String] { - switch self { - case .Authorize(let config): - return ["client_id": config.token, "response_type": "code"] - case .AccessToken(_, let code): - return ["code": code, "grant_type": "authorization_code"] - } - } - - public var URLRequest: NSURLRequest? { - switch self { - case .Authorize(let config): - let URLString = config.webEndpoint.stringByAppendingURLPath(path) - return request(URLString, parameters: params) - case .AccessToken(let config, _): - let URLString = config.webEndpoint.stringByAppendingURLPath(path) - return request(URLString, parameters: params) - } - } -} From 961f21467185ef4878a17f600a4d33b547c21a7c Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 09:23:18 +0700 Subject: [PATCH 2/6] Add ConfigurationTests --- TrashCanKit.xcodeproj/project.pbxproj | 8 +++ TrashCanKitTests/ConfigurationTests.swift | 85 +++++++++++++++++++++++ TrashCanKitTests/TestHelper.swift | 13 ++++ TrashCanKitTests/authorize.json | 6 ++ 4 files changed, 112 insertions(+) create mode 100644 TrashCanKitTests/ConfigurationTests.swift create mode 100644 TrashCanKitTests/authorize.json diff --git a/TrashCanKit.xcodeproj/project.pbxproj b/TrashCanKit.xcodeproj/project.pbxproj index f8c7072..d2b343a 100644 --- a/TrashCanKit.xcodeproj/project.pbxproj +++ b/TrashCanKit.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 231B68501BE99886007AE2A6 /* NSURL+URLParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B684F1BE99886007AE2A6 /* NSURL+URLParameters.swift */; }; 231B68521BE9989C007AE2A6 /* TokenConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68511BE9989C007AE2A6 /* TokenConfiguration.swift */; }; 231B68541BE998C7007AE2A6 /* OAuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */; }; + 231B68561BE9997C007AE2A6 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */; }; + 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B68571BE9A13C007AE2A6 /* authorize.json */; }; 236364741BE22F1000AA7104 /* TrashCanKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 236364731BE22F1000AA7104 /* TrashCanKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2363647B1BE22F1000AA7104 /* TrashCanKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 236364701BE22F1000AA7104 /* TrashCanKit.framework */; }; 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */; }; @@ -59,6 +61,8 @@ 231B684F1BE99886007AE2A6 /* NSURL+URLParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURL+URLParameters.swift"; sourceTree = ""; }; 231B68511BE9989C007AE2A6 /* TokenConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenConfiguration.swift; sourceTree = ""; }; 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthRouter.swift; sourceTree = ""; }; + 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; + 231B68571BE9A13C007AE2A6 /* authorize.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = authorize.json; sourceTree = ""; }; 236364701BE22F1000AA7104 /* TrashCanKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TrashCanKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 236364731BE22F1000AA7104 /* TrashCanKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrashCanKit.h; sourceTree = ""; }; 236364751BE22F1000AA7104 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -141,6 +145,7 @@ 236364931BE2301200AA7104 /* TestHelper.swift */, 236364921BE2301200AA7104 /* RepositoriesTests.swift */, 236364951BE2301200AA7104 /* UserTests.swift */, + 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */, 236364A11BE2313200AA7104 /* Fixtures */, 236364811BE22F1000AA7104 /* Info.plist */, ); @@ -153,6 +158,7 @@ 236364A21BE2313B00AA7104 /* Email.json */, 236364A31BE2313B00AA7104 /* Repository.json */, 236364A41BE2313B00AA7104 /* User.json */, + 231B68571BE9A13C007AE2A6 /* authorize.json */, ); name = Fixtures; sourceTree = ""; @@ -265,6 +271,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */, 236364A51BE2313B00AA7104 /* Email.json in Resources */, 236364A71BE2313B00AA7104 /* User.json in Resources */, 236364A61BE2313B00AA7104 /* Repository.json in Resources */, @@ -295,6 +302,7 @@ files = ( 236364991BE2301200AA7104 /* UserTests.swift in Sources */, 236364961BE2301200AA7104 /* RepositoriesTests.swift in Sources */, + 231B68561BE9997C007AE2A6 /* ConfigurationTests.swift in Sources */, 236364971BE2301200AA7104 /* TestHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TrashCanKitTests/ConfigurationTests.swift b/TrashCanKitTests/ConfigurationTests.swift new file mode 100644 index 0000000..f25bab4 --- /dev/null +++ b/TrashCanKitTests/ConfigurationTests.swift @@ -0,0 +1,85 @@ +import XCTest +import Nocilla +import TrashCanKit + +class ConfigurationTests: XCTestCase { + private let enterpriseURL = "https://enterprise.mybitbucketserver.com" + + override func setUp() { + super.setUp() + LSNocilla.sharedInstance().start() + } + + override func tearDown() { + super.tearDown() + LSNocilla.sharedInstance().clearStubs() + LSNocilla.sharedInstance().stop() + } + + func testTokenConfiguration() { + let subject = TokenConfiguration("12345") + XCTAssertEqual(subject.accessToken, "12345") + XCTAssertEqual(subject.apiEndpoint, "https://bitbucket.org/api/2.0") + } + + func testEnterpriseTokenConfiguration() { + let subject = TokenConfiguration("12345", url: enterpriseURL) + XCTAssertEqual(subject.accessToken!, "12345") + XCTAssertEqual(subject.apiEndpoint, enterpriseURL) + } + + func testOAuthConfiguration() { + let subject = OAuthConfiguration(token: "12345", secret: "6789", scopes: []) + XCTAssertEqual(subject.token, "12345") + XCTAssertEqual(subject.secret, "6789") + XCTAssertEqual(subject.apiEndpoint, "https://bitbucket.org/api/2.0") + } + + func testOAuthTokenConfiguration() { + let subject = OAuthConfiguration(enterpriseURL, token: "12345", secret: "6789", scopes: []) + XCTAssertEqual(subject.token, "12345") + XCTAssertEqual(subject.secret, "6789") + XCTAssertEqual(subject.apiEndpoint, enterpriseURL) + } + + func testAuthorizeURLRequest() { + let config = OAuthConfiguration(token: "12345", secret: "6789", scopes: []) + let request = OAuthRouter.Authorize(config).URLRequest + let expected = NSURL(string: "https://bitbucket.org/site/oauth2/authorize?client_id=12345&response_type=code") + XCTAssertEqual(request?.URL, expected) + } + + func testAccessTokenURLRequest() { + let config = OAuthConfiguration(token: "12345", secret: "6789", scopes: []) + let request = OAuthRouter.AccessToken(config, "dhfjgh23493").URLRequest + let expected = NSURL(string: "https://bitbucket.org/site/oauth2/access_token") + let expectedBody = "code=dhfjgh23493&grant_type=authorization_code" + XCTAssertEqual(request?.URL, expected) + let string = NSString(data: request!.HTTPBody!, encoding: NSUTF8StringEncoding)! + XCTAssertEqual(string as String, expectedBody) + } + + func testAccessTokenFromResponse() { + let config = OAuthConfiguration(token: "12345", secret: "6789", scopes: []) + let response = "access_token=017ec60f4a182&token_type=bearer" + let expectation = "017ec60f4a182" + XCTAssertEqual(config.accessTokenFromResponse(response)!, expectation) + } + + func testHandleOpenURL() { + let config = OAuthConfiguration(token: "12345", secret: "6789", scopes: []) + let json = TestHelper.loadJSONString("authorize") + let headers = ["Authorization": "Basic MTIzNDU6Njc4OQ==", "Content-Type": "application/x-www-form-urlencoded" ] + stubRequest("POST", "https://bitbucket.org/site/oauth2/access_token").withHeaders(headers).andReturn(200).withBody(json) + let expectation = expectationWithDescription("access_token") + let url = NSURL(string: "urlscheme://authorize?code=dhfjgh23493")! + config.handleOpenURL(url) { token in + XCTAssertEqual(token.refreshToken, "14567") + XCTAssertEqual(token.accessToken, "017ec60f4a182") + expectation.fulfill() + } + waitForExpectationsWithTimeout(1, handler: { error in + XCTAssertNil(error, "\(error)") + }) + } +} diff --git a/TrashCanKitTests/TestHelper.swift b/TrashCanKitTests/TestHelper.swift index 8d3d746..a43054d 100644 --- a/TrashCanKitTests/TestHelper.swift +++ b/TrashCanKitTests/TestHelper.swift @@ -14,4 +14,17 @@ class TestHelper { return Dictionary() } + + static func loadJSONString(name: String) -> String { + let bundle = NSBundle(forClass: self) + let path = bundle.pathForResource(name, ofType: "json") + if let path = path, data = NSData(contentsOfFile: path) { + let jsonString = String(data: data, encoding: NSUTF8StringEncoding) + if let json = jsonString { + return json + } + } + + return "" + } } diff --git a/TrashCanKitTests/authorize.json b/TrashCanKitTests/authorize.json new file mode 100644 index 0000000..8159678 --- /dev/null +++ b/TrashCanKitTests/authorize.json @@ -0,0 +1,6 @@ +{ + "access_token": "017ec60f4a182", + "scope": "read%3Aorg%2Crepo", + "token_type": "bearer", + "refresh_token": "14567" +} \ No newline at end of file From 19cf32015627d2ece085350c01d314f7b064444d Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 09:46:56 +0700 Subject: [PATCH 3/6] Add tests for refreshing token --- TrashCanKit.xcodeproj/project.pbxproj | 8 +++ TrashCanKit/TrashCanKit.swift | 2 +- TrashCanKitTests/TokenTests.swift | 63 +++++++++++++++++++++++ TrashCanKitTests/refresh_token_error.json | 3 ++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 TrashCanKitTests/TokenTests.swift create mode 100644 TrashCanKitTests/refresh_token_error.json diff --git a/TrashCanKit.xcodeproj/project.pbxproj b/TrashCanKit.xcodeproj/project.pbxproj index d2b343a..3bb8ae3 100644 --- a/TrashCanKit.xcodeproj/project.pbxproj +++ b/TrashCanKit.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 231B68541BE998C7007AE2A6 /* OAuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */; }; 231B68561BE9997C007AE2A6 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */; }; 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B68571BE9A13C007AE2A6 /* authorize.json */; }; + 231B685A1BE9A3DD007AE2A6 /* TokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */; }; + 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */; }; 236364741BE22F1000AA7104 /* TrashCanKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 236364731BE22F1000AA7104 /* TrashCanKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2363647B1BE22F1000AA7104 /* TrashCanKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 236364701BE22F1000AA7104 /* TrashCanKit.framework */; }; 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */; }; @@ -63,6 +65,8 @@ 231B68531BE998C7007AE2A6 /* OAuthRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthRouter.swift; sourceTree = ""; }; 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; 231B68571BE9A13C007AE2A6 /* authorize.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = authorize.json; sourceTree = ""; }; + 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenTests.swift; sourceTree = ""; }; + 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = refresh_token_error.json; sourceTree = ""; }; 236364701BE22F1000AA7104 /* TrashCanKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TrashCanKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 236364731BE22F1000AA7104 /* TrashCanKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrashCanKit.h; sourceTree = ""; }; 236364751BE22F1000AA7104 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -146,6 +150,7 @@ 236364921BE2301200AA7104 /* RepositoriesTests.swift */, 236364951BE2301200AA7104 /* UserTests.swift */, 231B68551BE9997C007AE2A6 /* ConfigurationTests.swift */, + 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */, 236364A11BE2313200AA7104 /* Fixtures */, 236364811BE22F1000AA7104 /* Info.plist */, ); @@ -159,6 +164,7 @@ 236364A31BE2313B00AA7104 /* Repository.json */, 236364A41BE2313B00AA7104 /* User.json */, 231B68571BE9A13C007AE2A6 /* authorize.json */, + 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */, ); name = Fixtures; sourceTree = ""; @@ -272,6 +278,7 @@ buildActionMask = 2147483647; files = ( 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */, + 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */, 236364A51BE2313B00AA7104 /* Email.json in Resources */, 236364A71BE2313B00AA7104 /* User.json in Resources */, 236364A61BE2313B00AA7104 /* Repository.json in Resources */, @@ -301,6 +308,7 @@ buildActionMask = 2147483647; files = ( 236364991BE2301200AA7104 /* UserTests.swift in Sources */, + 231B685A1BE9A3DD007AE2A6 /* TokenTests.swift in Sources */, 236364961BE2301200AA7104 /* RepositoriesTests.swift in Sources */, 231B68561BE9997C007AE2A6 /* ConfigurationTests.swift in Sources */, 236364971BE2301200AA7104 /* TestHelper.swift in Sources */, diff --git a/TrashCanKit/TrashCanKit.swift b/TrashCanKit/TrashCanKit.swift index 2e49886..63bedfe 100644 --- a/TrashCanKit/TrashCanKit.swift +++ b/TrashCanKit/TrashCanKit.swift @@ -3,7 +3,7 @@ import RequestKit let bitbucketBaseURL = "https://bitbucket.org/api/2.0" let bitbucketWebURL = "https://bitbucket.org" -let BitbucketErrorDomain = "https://bitbucket.org" +let BitbucketErrorDomain = "com.nerdishbynature.bitbucket.error" public struct TrashCanKit { public let configuration: TokenConfiguration diff --git a/TrashCanKitTests/TokenTests.swift b/TrashCanKitTests/TokenTests.swift new file mode 100644 index 0000000..be1fddd --- /dev/null +++ b/TrashCanKitTests/TokenTests.swift @@ -0,0 +1,63 @@ +import XCTest +import Nocilla +import TrashCanKit + +class TokenTests: XCTestCase { + override func setUp() { + super.setUp() + LSNocilla.sharedInstance().start() + } + + override func tearDown() { + super.tearDown() + LSNocilla.sharedInstance().clearStubs() + LSNocilla.sharedInstance().stop() + } + + func testRefreshToken() { + let oauthConfig = OAuthConfiguration(token: "12345", secret: "67890", scopes: []) + let tokenConfig = TokenConfiguration("09876", refreshToken: "54321") + let kit = TrashCanKit(tokenConfig) + let expectation = expectationWithDescription("refreshToken") + stubRequest("POST", "https://bitbucket.org/site/oauth2/access_token").withHeaders([ "Authorization": "Basic MTIzNDU6Njc4OTA=", "Content-Type": "application/x-www-form-urlencoded" ]).withBody("grant_type=refresh_token&refresh_token=54321").andReturn(200).withBody(TestHelper.loadJSONString("authorize")) + kit.refreshToken(oauthConfig, refreshToken: tokenConfig.refreshToken!) { response in + switch response { + case .Success(let newToken): + XCTAssertEqual(newToken, "017ec60f4a182") + expectation.fulfill() + case .Failure: + XCTAssertFalse(true) + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } + + func testFailGettingRefreshToken() { + let oauthConfig = OAuthConfiguration(token: "12345", secret: "67890", scopes: []) + let tokenConfig = TokenConfiguration("09876", refreshToken: "54321") + let kit = TrashCanKit(tokenConfig) + let expectation = expectationWithDescription("refreshToken") + stubRequest("POST", "https://bitbucket.org/site/oauth2/access_token").withHeaders([ "Authorization": "Basic MTIzNDU6Njc4OTA=", "Content-Type": "application/x-www-form-urlencoded" ]).withBody("grant_type=refresh_token&refresh_token=54321").andReturn(401).withBody(TestHelper.loadJSONString("refresh_token_error")) + kit.refreshToken(oauthConfig, refreshToken: tokenConfig.refreshToken!) { response in + switch response { + case .Success: + XCTAssertFalse(true) + expectation.fulfill() + case .Failure(let error as NSError): + XCTAssertEqual(error.domain, "com.nerdishbynature.bitbucket.error") + XCTAssertEqual(error.code, 401) + XCTAssertEqual(error.localizedDescription, "Oh Oh, another error.") + expectation.fulfill() + case .Failure: + XCTAssertFalse(true) + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } +} diff --git a/TrashCanKitTests/refresh_token_error.json b/TrashCanKitTests/refresh_token_error.json new file mode 100644 index 0000000..963e072 --- /dev/null +++ b/TrashCanKitTests/refresh_token_error.json @@ -0,0 +1,3 @@ +{ + "error_description": "Oh Oh, another error." +} \ No newline at end of file From aae25f79f5001a451936fa0b8e930b7eeccba29d Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 10:01:47 +0700 Subject: [PATCH 4/6] Add Repositories Tests --- TrashCanKit.xcodeproj/project.pbxproj | 4 ++ TrashCanKitTests/Fixtures/Repositories.json | 71 +++++++++++++++++++++ TrashCanKitTests/RepositoriesTests.swift | 58 +++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 TrashCanKitTests/Fixtures/Repositories.json diff --git a/TrashCanKit.xcodeproj/project.pbxproj b/TrashCanKit.xcodeproj/project.pbxproj index 3bb8ae3..f9a08a4 100644 --- a/TrashCanKit.xcodeproj/project.pbxproj +++ b/TrashCanKit.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B68571BE9A13C007AE2A6 /* authorize.json */; }; 231B685A1BE9A3DD007AE2A6 /* TokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */; }; 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */; }; + 231B685F1BE9AAC6007AE2A6 /* Repositories.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B685D1BE9AA66007AE2A6 /* Repositories.json */; }; 236364741BE22F1000AA7104 /* TrashCanKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 236364731BE22F1000AA7104 /* TrashCanKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2363647B1BE22F1000AA7104 /* TrashCanKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 236364701BE22F1000AA7104 /* TrashCanKit.framework */; }; 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */; }; @@ -67,6 +68,7 @@ 231B68571BE9A13C007AE2A6 /* authorize.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = authorize.json; sourceTree = ""; }; 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenTests.swift; sourceTree = ""; }; 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = refresh_token_error.json; sourceTree = ""; }; + 231B685D1BE9AA66007AE2A6 /* Repositories.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Repositories.json; path = Fixtures/Repositories.json; sourceTree = ""; }; 236364701BE22F1000AA7104 /* TrashCanKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TrashCanKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 236364731BE22F1000AA7104 /* TrashCanKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrashCanKit.h; sourceTree = ""; }; 236364751BE22F1000AA7104 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -165,6 +167,7 @@ 236364A41BE2313B00AA7104 /* User.json */, 231B68571BE9A13C007AE2A6 /* authorize.json */, 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */, + 231B685D1BE9AA66007AE2A6 /* Repositories.json */, ); name = Fixtures; sourceTree = ""; @@ -277,6 +280,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 231B685F1BE9AAC6007AE2A6 /* Repositories.json in Resources */, 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */, 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */, 236364A51BE2313B00AA7104 /* Email.json in Resources */, diff --git a/TrashCanKitTests/Fixtures/Repositories.json b/TrashCanKitTests/Fixtures/Repositories.json new file mode 100644 index 0000000..d5a5690 --- /dev/null +++ b/TrashCanKitTests/Fixtures/Repositories.json @@ -0,0 +1,71 @@ +{ + "page": 1, + "pagelen": 10, + "size": 1, + "values": [ + { + "created_on": "2014-08-13T19:05:22.148829+00:00", + "description": "This repo ", + "fork_policy": "allow_forks", + "full_name": "dans9190/new-repository", + "has_issues": false, + "has_wiki": false, + "is_private": false, + "language": "", + "links": { + "avatar": { + "href": "https://d3oaxc4q5k2d6q.cloudfront.net/m/57a3af3749c5/img/language-avatars/default_16.png" + }, + "clone": [ + { + "href": "https://bitbucket.org/dans9190/new-repository.git", + "name": "https" + }, + { + "href": "ssh://git@bitbucket.org/dans9190/new-repository.git", + "name": "ssh" + } + ], + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/dans9190/new-repository/commits" + }, + "forks": { + "href": "https://api.bitbucket.org/2.0/repositories/dans9190/new-repository/forks" + }, + "html": { + "href": "https://bitbucket.org/dans9190/new-repository" + }, + "pullrequests": { + "href": "https://api.bitbucket.org/2.0/repositories/dans9190/new-repository/pullrequests" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/dans9190/new-repository" + }, + "watchers": { + "href": "https://api.bitbucket.org/2.0/repositories/dans9190/new-repository/watchers" + } + }, + "name": "New repository", + "owner": { + "display_name": "Daniel Stevens", + "links": { + "avatar": { + "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2014/Jan/29/dans9190-avatar-2647033047-1_avatar.png" + }, + "html": { + "href": "https://bitbucket.org/dans9190" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/dans9190" + } + }, + "username": "dans9190", + "uuid": "{1cd06601-cd0e-4fce-be03-e9ac226978b7}" + }, + "scm": "git", + "size": 33348, + "updated_on": "2014-08-13T19:05:22.168083+00:00", + "uuid": "{9bdd6b69-3dc4-406b-a8dd-ff5119aefc61}" + } + ] +} diff --git a/TrashCanKitTests/RepositoriesTests.swift b/TrashCanKitTests/RepositoriesTests.swift index 42309a8..7cb6f3a 100644 --- a/TrashCanKitTests/RepositoriesTests.swift +++ b/TrashCanKitTests/RepositoriesTests.swift @@ -1,7 +1,19 @@ import XCTest +import Nocilla import TrashCanKit class RepositoriesTests: XCTestCase { + override func setUp() { + super.setUp() + LSNocilla.sharedInstance().start() + } + + override func tearDown() { + super.tearDown() + LSNocilla.sharedInstance().clearStubs() + LSNocilla.sharedInstance().stop() + } + func testConstructFromJSON() { let repository = BitbucketRepository(json: TestHelper.loadJSON("Repository")) XCTAssertEqual(repository.id, "{cb706a3e-1e13-41fb-ac9d-e53e8adc2bd7}") @@ -15,4 +27,50 @@ class RepositoriesTests: XCTestCase { XCTAssertEqual(repository.cloneURL, "https://bitbucket.org/pietbrauer/octokit.swift.git") XCTAssertEqual(repository.size, 156382) } + + func testFailToConstructFromJSON() { + let repository = BitbucketRepository(json: [:]) + XCTAssertEqual(repository.id, "-1") + XCTAssertEqual(repository.isPrivate, false) + XCTAssertEqual(repository.size, 0) + } + + func testGetRepositories() { + let tokenConfig = TokenConfiguration("123456", refreshToken: "7890") + stubRequest("GET", "https://bitbucket.org/api/2.0/repositories/bitbucketCat?access_token=123456").andReturn(200).withBody(TestHelper.loadJSONString("Repositories")) + let expectation = expectationWithDescription("get_repos") + TrashCanKit(tokenConfig).repositories("bitbucketCat") { response in + switch response { + case .Success(let repos): + XCTAssertEqual(repos.count, 1) + expectation.fulfill() + case .Failure: + XCTAssertFalse(true) + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } + + func testFailToGetRepositories() { + let tokenConfig = TokenConfiguration("123456", refreshToken: "7890") + stubRequest("GET", "https://bitbucket.org/api/2.0/repositories/bitbucketCat?access_token=123456").andReturn(401).withBody(TestHelper.loadJSONString("refresh_token_error")) + let expectation = expectationWithDescription("get_repos") + TrashCanKit(tokenConfig).repositories("bitbucketCat") { response in + switch response { + case .Success: + XCTAssertTrue(false) + expectation.fulfill() + case .Failure(let error): + XCTAssertEqual((error as NSError).code, 401) + XCTAssertEqual((error as NSError).domain, "com.octokit.swift") + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } } From 380ef3f77a2f58360128ac62bf3ce7635873ca47 Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 10:07:00 +0700 Subject: [PATCH 5/6] Add test for getting me and my emails --- TrashCanKit.xcodeproj/project.pbxproj | 8 +++++ TrashCanKitTests/Fixtures/Emails.json | 18 ++++++++++ TrashCanKitTests/Me.json | 29 ++++++++++++++++ TrashCanKitTests/UserTests.swift | 50 +++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 TrashCanKitTests/Fixtures/Emails.json create mode 100644 TrashCanKitTests/Me.json diff --git a/TrashCanKit.xcodeproj/project.pbxproj b/TrashCanKit.xcodeproj/project.pbxproj index f9a08a4..9db6cf9 100644 --- a/TrashCanKit.xcodeproj/project.pbxproj +++ b/TrashCanKit.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 231B685A1BE9A3DD007AE2A6 /* TokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */; }; 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */; }; 231B685F1BE9AAC6007AE2A6 /* Repositories.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B685D1BE9AA66007AE2A6 /* Repositories.json */; }; + 231B68611BE9AD30007AE2A6 /* Me.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B68601BE9AD30007AE2A6 /* Me.json */; }; + 231B68631BE9AD8D007AE2A6 /* Emails.json in Resources */ = {isa = PBXBuildFile; fileRef = 231B68621BE9AD8D007AE2A6 /* Emails.json */; }; 236364741BE22F1000AA7104 /* TrashCanKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 236364731BE22F1000AA7104 /* TrashCanKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2363647B1BE22F1000AA7104 /* TrashCanKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 236364701BE22F1000AA7104 /* TrashCanKit.framework */; }; 2363648E1BE22FCC00AA7104 /* TrashCanKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363648A1BE22FCC00AA7104 /* TrashCanKit.swift */; }; @@ -69,6 +71,8 @@ 231B68591BE9A3DD007AE2A6 /* TokenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenTests.swift; sourceTree = ""; }; 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = refresh_token_error.json; sourceTree = ""; }; 231B685D1BE9AA66007AE2A6 /* Repositories.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Repositories.json; path = Fixtures/Repositories.json; sourceTree = ""; }; + 231B68601BE9AD30007AE2A6 /* Me.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Me.json; sourceTree = ""; }; + 231B68621BE9AD8D007AE2A6 /* Emails.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Emails.json; path = Fixtures/Emails.json; sourceTree = ""; }; 236364701BE22F1000AA7104 /* TrashCanKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TrashCanKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 236364731BE22F1000AA7104 /* TrashCanKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TrashCanKit.h; sourceTree = ""; }; 236364751BE22F1000AA7104 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -168,6 +172,8 @@ 231B68571BE9A13C007AE2A6 /* authorize.json */, 231B685B1BE9A699007AE2A6 /* refresh_token_error.json */, 231B685D1BE9AA66007AE2A6 /* Repositories.json */, + 231B68601BE9AD30007AE2A6 /* Me.json */, + 231B68621BE9AD8D007AE2A6 /* Emails.json */, ); name = Fixtures; sourceTree = ""; @@ -280,11 +286,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 231B68611BE9AD30007AE2A6 /* Me.json in Resources */, 231B685F1BE9AAC6007AE2A6 /* Repositories.json in Resources */, 231B68581BE9A13C007AE2A6 /* authorize.json in Resources */, 231B685C1BE9A699007AE2A6 /* refresh_token_error.json in Resources */, 236364A51BE2313B00AA7104 /* Email.json in Resources */, 236364A71BE2313B00AA7104 /* User.json in Resources */, + 231B68631BE9AD8D007AE2A6 /* Emails.json in Resources */, 236364A61BE2313B00AA7104 /* Repository.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TrashCanKitTests/Fixtures/Emails.json b/TrashCanKitTests/Fixtures/Emails.json new file mode 100644 index 0000000..e4957d0 --- /dev/null +++ b/TrashCanKitTests/Fixtures/Emails.json @@ -0,0 +1,18 @@ +{ + "page": 1, + "pagelen": 10, + "size": 1, + "values": [ + { + "email": "tutorials@bitbucket.org", + "is_confirmed": true, + "is_primary": true, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/user/emails/tutorials@bitbucket.org" + } + }, + "type": "email" + } + ] +} \ No newline at end of file diff --git a/TrashCanKitTests/Me.json b/TrashCanKitTests/Me.json new file mode 100644 index 0000000..b13089c --- /dev/null +++ b/TrashCanKitTests/Me.json @@ -0,0 +1,29 @@ +{ + "created_on": "2011-12-20T16:34:07.132459+00:00", + "display_name": "tutorials account", + "links": { + "avatar": { + "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png" + }, + "followers": { + "href": "https://api.bitbucket.org/2.0/users/tutorials/followers" + }, + "following": { + "href": "https://api.bitbucket.org/2.0/users/tutorials/following" + }, + "html": { + "href": "https://bitbucket.org/tutorials" + }, + "repositories": { + "href": "https://api.bitbucket.org/2.0/repositories/tutorials" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/tutorials" + } + }, + "location": "Santa Monica, CA", + "type": "user", + "username": "tutorials", + "uuid": "{c788b2da-b7a2-404c-9e26-d3f077557007}", + "website": "https://tutorials.bitbucket.org/" +} \ No newline at end of file diff --git a/TrashCanKitTests/UserTests.swift b/TrashCanKitTests/UserTests.swift index 9ffa26f..2b1c662 100644 --- a/TrashCanKitTests/UserTests.swift +++ b/TrashCanKitTests/UserTests.swift @@ -1,7 +1,19 @@ import XCTest +import Nocilla import TrashCanKit class UserTests: XCTestCase { + override func setUp() { + super.setUp() + LSNocilla.sharedInstance().start() + } + + override func tearDown() { + super.tearDown() + LSNocilla.sharedInstance().clearStubs() + LSNocilla.sharedInstance().stop() + } + func testConstructFromJSON() { let subject = User(TestHelper.loadJSON("User")) XCTAssertEqual(subject.id, "{e9f0168c-cdf8-404a-95bb-3943dd2a65b6}") @@ -16,4 +28,42 @@ class UserTests: XCTestCase { XCTAssertEqual(subject.email, "me@supercooldomain.io") XCTAssertEqual(subject.type, "email") } + + func testMe() { + let tokenConfig = TokenConfiguration("123456", refreshToken: "7890") + stubRequest("GET", "https://bitbucket.org/api/2.0/user?access_token=123456").andReturn(200).withBody(TestHelper.loadJSONString("Me")) + let expectation = expectationWithDescription("get_me") + TrashCanKit(tokenConfig).me() { response in + switch response { + case .Success(let user): + XCTAssertEqual(user.name, "tutorials account") + expectation.fulfill() + case .Failure: + XCTAssertFalse(true) + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } + + func testMyEmail() { + let tokenConfig = TokenConfiguration("123456", refreshToken: "7890") + stubRequest("GET", "https://bitbucket.org/api/2.0/user/emails?access_token=123456").andReturn(200).withBody(TestHelper.loadJSONString("Emails")) + let expectation = expectationWithDescription("get_me") + TrashCanKit(tokenConfig).emails() { response in + switch response { + case .Success(let emails): + XCTAssertEqual(emails.first?.email, "tutorials@bitbucket.org") + expectation.fulfill() + case .Failure: + XCTAssertFalse(true) + expectation.fulfill() + } + } + waitForExpectationsWithTimeout(1) { error in + XCTAssertNil(error) + } + } } From f85ad3da85b8b9ecc960d2a7ac7e9f21795e363b Mon Sep 17 00:00:00 2001 From: Piet Brauer Date: Wed, 4 Nov 2015 11:35:36 +0700 Subject: [PATCH 6/6] Add test for failing constructing email --- TrashCanKit/Repositories.swift | 7 ------- TrashCanKit/TrashCanKit.swift | 6 ++++++ TrashCanKit/User.swift | 8 -------- TrashCanKitTests/UserTests.swift | 8 ++++++++ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/TrashCanKit/Repositories.swift b/TrashCanKit/Repositories.swift index bde9dc2..bf1d139 100644 --- a/TrashCanKit/Repositories.swift +++ b/TrashCanKit/Repositories.swift @@ -95,11 +95,4 @@ public enum RepositoryRouter: Router { return "/repositories/\(userName)" } } - - public var URLRequest: NSURLRequest? { - switch self { - case .ReadRepositories(_, _): - return request() - } - } } diff --git a/TrashCanKit/TrashCanKit.swift b/TrashCanKit/TrashCanKit.swift index 63bedfe..5c49720 100644 --- a/TrashCanKit/TrashCanKit.swift +++ b/TrashCanKit/TrashCanKit.swift @@ -12,3 +12,9 @@ public struct TrashCanKit { configuration = config } } + +internal extension Router { + internal var URLRequest: NSURLRequest? { + return request() + } +} diff --git a/TrashCanKit/User.swift b/TrashCanKit/User.swift index 93c4184..e8b25b1 100644 --- a/TrashCanKit/User.swift +++ b/TrashCanKit/User.swift @@ -71,13 +71,11 @@ public extension TrashCanKit { public enum UserRouter: Router { case ReadAuthenticatedUser(Configuration) - case ReadUser(String, Configuration) case ReadEmails(Configuration) public var configuration: Configuration { switch self { case .ReadAuthenticatedUser(let config): return config - case .ReadUser(_, let config): return config case .ReadEmails(let config): return config } } @@ -94,8 +92,6 @@ public enum UserRouter: Router { switch self { case .ReadAuthenticatedUser: return "user" - case .ReadUser(let username, _): - return "users/\(username)" case .ReadEmails: return "user/emails" } @@ -104,8 +100,4 @@ public enum UserRouter: Router { public var params: [String: String] { return [:] } - - public var URLRequest: NSURLRequest? { - return request() - } } diff --git a/TrashCanKitTests/UserTests.swift b/TrashCanKitTests/UserTests.swift index 2b1c662..1ef4eff 100644 --- a/TrashCanKitTests/UserTests.swift +++ b/TrashCanKitTests/UserTests.swift @@ -29,6 +29,14 @@ class UserTests: XCTestCase { XCTAssertEqual(subject.type, "email") } + func testConstructEmailFromEmptyJSON() { + let subject = Email(json: [:]) + XCTAssertEqual(subject.isPrimary, false) + XCTAssertEqual(subject.isConfirmed, false) + XCTAssertEqual(subject.email, nil) + XCTAssertEqual(subject.type, nil) + } + func testMe() { let tokenConfig = TokenConfiguration("123456", refreshToken: "7890") stubRequest("GET", "https://bitbucket.org/api/2.0/user?access_token=123456").andReturn(200).withBody(TestHelper.loadJSONString("Me"))