[GH-ISSUE #337] Tests with OAuthSwift throwing retain error every once in a while #210

Closed
opened 2026-03-03 16:46:42 +03:00 by kerem · 5 comments
Owner

Originally created by @jalopezmo on GitHub (Feb 16, 2017).
Original GitHub issue: https://github.com/OAuthSwift/OAuthSwift/issues/337

Description:

OAuth Provider (Twitter, Github, ..):

OAuth Version:

  • Version 1
  • Version 2

OS (Please fill the version) :

  • iOS :
  • OSX :
  • TVOS :
  • WatchOS :

Installation method:

  • Carthage
  • CocoaPods
  • Manually

Library version:

  • head
  • v1.0.0
  • v0.6
  • other: (Please fill in the version you are using.)

Xcode version:

  • 8.0 (Swift 3.0)

  • 8.0 (Swift 2.3)

  • 7.3.1

  • other: (Please fill in the version you are using.)

  • objective c

I had to install the framework manually because I had to edit some things in order to comply with server requirements. But I have been having some issues. Specifically, what has been happening is that in most of my tests, the OAuth process is throwing a retain error. It is important to clarify that this doesn't happen when running the app, just when the tests run.

This is the class that does the OAuth process.

class OAuthTokenGenerator {
    
    static let sharedInstance = OAuthTokenGenerator()
    
    var oauthSwift: OAuth1Swift?
    
    func generateToken(responseHandler: @escaping(_ result: ResultResult<OAuthSwiftCredential, OAuthSwiftError>.R) -> Void) {
        
        if let requestTokenURL = OAuthEndpoints.requestToken.generateEndpointURL,
           let authorizeURL = OAuthEndpoints.authorize.generateEndpointURL,
           var accessTokenURL = OAuthEndpoints.accessToken.generateEndpointURL {
            
            if User.sharedInstance.status == .loggedIn {
                accessTokenURL = URL(string: accessTokenURL.absoluteString + "?\(OAuth.URLParameters.verifier.fieldName)=" + User.sharedInstance.authorizationToken)!
            }
            
            oauthSwift = OAuth1Swift(
                consumerKey: OAuth.Credentials.key.value,
                consumerSecret: OAuth.Credentials.secret.value,
                requestTokenUrl: requestTokenURL.absoluteString,
                authorizeUrl: authorizeURL.absoluteString,
                accessTokenUrl: accessTokenURL.absoluteString
            )
            
            if let oauth = oauthSwift {
            
                SessionManager.default.adapter = oauth.requestAdapter
                
                oauth.allowMissingOAuthVerifier = true
                oauth.authorizeURLHandler = self
                
                oauth.authorize(withCallbackURL: URL(string: "OauthTest://oauth-callback/test")!, success: {
                    credential, response, parameters in
                    
                    responseHandler(.success(credential))
                    
                }, failure: {
                    error in
                    
                    responseHandler(.failure(error))
                })
            }
            else {
                responseHandler(.failure(.configurationError(message: "Couldn't initialize")))
            }
        }
        else {
            responseHandler(.failure(.configurationError(message: "Configuration error")))
        }
    }
}
extension OAuthTokenGenerator: OAuthSwiftURLHandlerType {
    func handle(_ url: URL) {
        let authorizeURL = buildAuthorizationURL(base: url.absoluteString)
        
        if let oauthSwift = oauthSwift {
            _ = oauthSwift.client.get(authorizeURL, success: {
                _ in
                
                OAuthSwift.handle(url: url)
                
            }, failure: {
                _ in
            })
        }
    }
    
    func buildAuthorizationURL(base: String) -> String {
        
        let user = User.sharedInstance
        
        if user.status == .loggedOut {
            return base + "&\(OAuth.URLParameters.userID.fieldName)=0"
        }
        
        var additionalData = "&\(OAuth.URLParameters.userID.fieldName)=" + String(user.id)
        additionalData +=  "&\(OAuth.URLParameters.email.fieldName)=" + user.email
        additionalData += "&\(OAuth.URLParameters.verifier.fieldName)=" + user.authorizationToken
        
        return base + additionalData.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
    }
}

And this is a failing test:

func testTokenGenerationForGuest() {
        let serviceExpectation = expectation(description: "Oauth token generator for guest function")
        
        OAuthTokenGenerator.sharedInstance.generateToken(responseHandler: {
            result in
            
            switch result {
            case let .success(credentials):
                XCTAssert(true, credentials.oauthToken)
                
            case let .failure(error):
                XCTFail(error.description)
            }
            
            serviceExpectation.fulfill()
        })
        
        self.waitForExpectations(timeout: 300, handler: {
            error in
            
            if let error = error {
                XCTFail("Exceeded timeout: \(error)")
            }
        })
    }
Originally created by @jalopezmo on GitHub (Feb 16, 2017). Original GitHub issue: https://github.com/OAuthSwift/OAuthSwift/issues/337 ### Description: ### OAuth Provider (Twitter, Github, ..): ### OAuth Version: - [x] Version 1 - [ ] Version 2 ### OS (Please fill the version) : - [x] iOS : - [ ] OSX : - [ ] TVOS : - [ ] WatchOS : ### Installation method: - [ ] Carthage - [ ] CocoaPods - [x] Manually ### Library version: - [x] head - [ ] v1.0.0 - [ ] v0.6 - [ ] other: (Please fill in the version you are using.) ### Xcode version: - [x] 8.0 (Swift 3.0) - [ ] 8.0 (Swift 2.3) - [ ] 7.3.1 - [ ] other: (Please fill in the version you are using.) - [ ] objective c I had to install the framework manually because I had to edit some things in order to comply with server requirements. But I have been having some issues. Specifically, what has been happening is that in most of my tests, the OAuth process is throwing a retain error. It is important to clarify that this doesn't happen when running the app, just when the tests run. This is the class that does the OAuth process. ```swift class OAuthTokenGenerator { static let sharedInstance = OAuthTokenGenerator() var oauthSwift: OAuth1Swift? func generateToken(responseHandler: @escaping(_ result: ResultResult<OAuthSwiftCredential, OAuthSwiftError>.R) -> Void) { if let requestTokenURL = OAuthEndpoints.requestToken.generateEndpointURL, let authorizeURL = OAuthEndpoints.authorize.generateEndpointURL, var accessTokenURL = OAuthEndpoints.accessToken.generateEndpointURL { if User.sharedInstance.status == .loggedIn { accessTokenURL = URL(string: accessTokenURL.absoluteString + "?\(OAuth.URLParameters.verifier.fieldName)=" + User.sharedInstance.authorizationToken)! } oauthSwift = OAuth1Swift( consumerKey: OAuth.Credentials.key.value, consumerSecret: OAuth.Credentials.secret.value, requestTokenUrl: requestTokenURL.absoluteString, authorizeUrl: authorizeURL.absoluteString, accessTokenUrl: accessTokenURL.absoluteString ) if let oauth = oauthSwift { SessionManager.default.adapter = oauth.requestAdapter oauth.allowMissingOAuthVerifier = true oauth.authorizeURLHandler = self oauth.authorize(withCallbackURL: URL(string: "OauthTest://oauth-callback/test")!, success: { credential, response, parameters in responseHandler(.success(credential)) }, failure: { error in responseHandler(.failure(error)) }) } else { responseHandler(.failure(.configurationError(message: "Couldn't initialize"))) } } else { responseHandler(.failure(.configurationError(message: "Configuration error"))) } } } extension OAuthTokenGenerator: OAuthSwiftURLHandlerType { func handle(_ url: URL) { let authorizeURL = buildAuthorizationURL(base: url.absoluteString) if let oauthSwift = oauthSwift { _ = oauthSwift.client.get(authorizeURL, success: { _ in OAuthSwift.handle(url: url) }, failure: { _ in }) } } func buildAuthorizationURL(base: String) -> String { let user = User.sharedInstance if user.status == .loggedOut { return base + "&\(OAuth.URLParameters.userID.fieldName)=0" } var additionalData = "&\(OAuth.URLParameters.userID.fieldName)=" + String(user.id) additionalData += "&\(OAuth.URLParameters.email.fieldName)=" + user.email additionalData += "&\(OAuth.URLParameters.verifier.fieldName)=" + user.authorizationToken return base + additionalData.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! } } ``` And this is a failing test: ```swift func testTokenGenerationForGuest() { let serviceExpectation = expectation(description: "Oauth token generator for guest function") OAuthTokenGenerator.sharedInstance.generateToken(responseHandler: { result in switch result { case let .success(credentials): XCTAssert(true, credentials.oauthToken) case let .failure(error): XCTFail(error.description) } serviceExpectation.fulfill() }) self.waitForExpectations(timeout: 300, handler: { error in if let error = error { XCTFail("Exceeded timeout: \(error)") } }) } ```
kerem 2026-03-03 16:46:42 +03:00
  • closed this issue
  • added the
    invalid
    label
Author
Owner

@phimage commented on GitHub (Feb 17, 2017):

"I had to install the framework manually because I had to edit some things in order to comply with server requirements"
-> you can do it also with cocoapod using dev pod

pod 'OAuthSwift', :path => 'absolute/or/relative/path/OAuthSwift/OAuthSwift.podspec'

you can have also your fork on github -> so I can see the difference


To show me a bug, it's a very very good choice to make an unit test, but you could also commit it, so I have just to download your fork and test it
Here there is too much specific code that I cannot test (User, OAuth.URLParameters,...)

then your oauth workflow is a little different and maybe that's the point
I try to convert your code with a test server, and comment some part, but there is no issue for me

import Foundation
import XCTest
@testable import OAuthSwift

class ATests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
        try? OAuthTokenGenerator.sharedInstance.server.start()
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
        OAuthTokenGenerator.sharedInstance.server.stop()
    }
    
    
    func testTokenGenerationForGuest() {
        let serviceExpectation = expectation(description: "Oauth token generator for guest function")
      
        OAuthTokenGenerator.sharedInstance.generateToken(responseHandler: {
            result in
            
            switch result {
            case let .success(credentials):
                XCTAssert(true, credentials.oauthToken)
                
            case let .failure(error):
                XCTFail(error.description)
            }
            
            serviceExpectation.fulfill()
        })
        
        self.waitForExpectations(timeout: 300, handler: {
            error in
            
            if let error = error {
                XCTFail("Exceeded timeout: \(error)")
            }
        })
    }

}

enum Result<T,E> {
    case success(T)
    case failure(E)
}

class OAuthTokenGenerator {
    
    static let server = TestServer()
    var server: TestServer { return OAuth1SwiftTests.server }
    static let sharedInstance = OAuthTokenGenerator()
    
    var oauthSwift: OAuth1Swift?
    
    func generateToken(responseHandler: @escaping(_ result: Result<OAuthSwiftCredential, OAuthSwiftError>) -> Void) {

            
            oauthSwift = OAuth1Swift(
                consumerKey: server.valid_key,
                consumerSecret: server.valid_secret,
                requestTokenUrl: server.requestTokenURL,
                authorizeUrl: server.authorizeURL,
                accessTokenUrl: server.accessTokenURL
            )
            
            if let oauth = oauthSwift {
                
                
                oauth.allowMissingOAuthVerifier = true
                oauth.authorizeURLHandler = self
                
                oauth.authorize(withCallbackURL: URL(string: "OauthTest://oauth-callback/test")!, success: {
                    credential, response, parameters in
                    
                    responseHandler(.success(credential))
                    
                }, failure: {
                    error in
                    
                    responseHandler(.failure(error))
                })
            }
            else {
                responseHandler(.failure(.configurationError(message: "Couldn't initialize")))
            }

    }
}
extension OAuthTokenGenerator: OAuthSwiftURLHandlerType {
    func handle(_ url: URL) {
        /*let authorizeURL = buildAuthorizationURL(base: url.absoluteString)
        
        if let oauthSwift = oauthSwift {
            _ = oauthSwift.client.get(authorizeURL, success: {
                _ in*/
                
                OAuthSwift.handle(url: url)
                
           /* }, failure: {
                _ in
            })
        }*/
    }
    
  /*func buildAuthorizationURL(base: String) -> String {
        
        
        if user.status == .loggedOut {
            return base + "&\(OAuth.URLParameters.userID.fieldName)=0"
        }
        
        var additionalData = "&\(OAuth.URLParameters.userID.fieldName)=3"
        additionalData +=  "&\(OAuth.URLParameters.email.fieldName)=aze"
        additionalData += "&\(OAuth.URLParameters.verifier.fieldName)=azeae"
        
        return base + additionalData.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
    }*/
}
<!-- gh-comment-id:280684735 --> @phimage commented on GitHub (Feb 17, 2017): "I had to install the framework manually because I had to edit some things in order to comply with server requirements" -> you can do it also with cocoapod using dev pod ``` pod 'OAuthSwift', :path => 'absolute/or/relative/path/OAuthSwift/OAuthSwift.podspec' ``` you can have also your fork on github -> so I can see the difference --- To show me a bug, it's a very very good choice to make an unit test, but you could also commit it, so I have just to download your fork and test it Here there is too much specific code that I cannot test (User, OAuth.URLParameters,...) then your oauth workflow is a little different and maybe that's the point I try to convert your code with a test server, and comment some part, but there is no issue for me ```swift import Foundation import XCTest @testable import OAuthSwift class ATests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. try? OAuthTokenGenerator.sharedInstance.server.start() } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() OAuthTokenGenerator.sharedInstance.server.stop() } func testTokenGenerationForGuest() { let serviceExpectation = expectation(description: "Oauth token generator for guest function") OAuthTokenGenerator.sharedInstance.generateToken(responseHandler: { result in switch result { case let .success(credentials): XCTAssert(true, credentials.oauthToken) case let .failure(error): XCTFail(error.description) } serviceExpectation.fulfill() }) self.waitForExpectations(timeout: 300, handler: { error in if let error = error { XCTFail("Exceeded timeout: \(error)") } }) } } enum Result<T,E> { case success(T) case failure(E) } class OAuthTokenGenerator { static let server = TestServer() var server: TestServer { return OAuth1SwiftTests.server } static let sharedInstance = OAuthTokenGenerator() var oauthSwift: OAuth1Swift? func generateToken(responseHandler: @escaping(_ result: Result<OAuthSwiftCredential, OAuthSwiftError>) -> Void) { oauthSwift = OAuth1Swift( consumerKey: server.valid_key, consumerSecret: server.valid_secret, requestTokenUrl: server.requestTokenURL, authorizeUrl: server.authorizeURL, accessTokenUrl: server.accessTokenURL ) if let oauth = oauthSwift { oauth.allowMissingOAuthVerifier = true oauth.authorizeURLHandler = self oauth.authorize(withCallbackURL: URL(string: "OauthTest://oauth-callback/test")!, success: { credential, response, parameters in responseHandler(.success(credential)) }, failure: { error in responseHandler(.failure(error)) }) } else { responseHandler(.failure(.configurationError(message: "Couldn't initialize"))) } } } extension OAuthTokenGenerator: OAuthSwiftURLHandlerType { func handle(_ url: URL) { /*let authorizeURL = buildAuthorizationURL(base: url.absoluteString) if let oauthSwift = oauthSwift { _ = oauthSwift.client.get(authorizeURL, success: { _ in*/ OAuthSwift.handle(url: url) /* }, failure: { _ in }) }*/ } /*func buildAuthorizationURL(base: String) -> String { if user.status == .loggedOut { return base + "&\(OAuth.URLParameters.userID.fieldName)=0" } var additionalData = "&\(OAuth.URLParameters.userID.fieldName)=3" additionalData += "&\(OAuth.URLParameters.email.fieldName)=aze" additionalData += "&\(OAuth.URLParameters.verifier.fieldName)=azeae" return base + additionalData.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! }*/ } ```
Author
Owner

@jalopezmo commented on GitHub (Feb 17, 2017):

Hi, thank you for your answer.

I think I may have found the source of the bug, so you know. It seems that since the app and the tests are running at the same time, somehow the app is clearing the oauth instance the test was using. Causing the retain error, which is actually that the object was released while waiting for the local notification to arrive.

I have found a temporal fix, which is to comment the line that starts the OAuth flow in the app when running my tests.

<!-- gh-comment-id:280693251 --> @jalopezmo commented on GitHub (Feb 17, 2017): Hi, thank you for your answer. I think I may have found the source of the bug, so you know. It seems that since the app and the tests are running at the same time, somehow the app is clearing the oauth instance the test was using. Causing the retain error, which is actually that the object was released while waiting for the local notification to arrive. I have found a temporal fix, which is to comment the line that starts the OAuth flow in the app when running my tests.
Author
Owner

@jalopezmo commented on GitHub (Feb 17, 2017):

Oh, and by the way, the only modification I had to do to your code was being able to change the timestamp you are using to sign the request.

<!-- gh-comment-id:280693566 --> @jalopezmo commented on GitHub (Feb 17, 2017): Oh, and by the way, the only modification I had to do to your code was being able to change the timestamp you are using to sign the request.
Author
Owner

@phimage commented on GitHub (Feb 17, 2017):

ok, so this is a true retain error, the object has been released

<!-- gh-comment-id:280697322 --> @phimage commented on GitHub (Feb 17, 2017): ok, so this is a true retain error, the object has been released
Author
Owner

@jalopezmo commented on GitHub (Feb 17, 2017):

Yes, thank you for your help.

<!-- gh-comment-id:280705444 --> @jalopezmo commented on GitHub (Feb 17, 2017): Yes, thank you for your help.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/OAuthSwift#210
No description provided.