Created
March 20, 2023 16:00
-
-
Save spicyjack/75fefd4c551607f62ae4ddd4dfdc9f6c to your computer and use it in GitHub Desktop.
URL shortening tests using URLSession with both closures and async/await
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// BitlyURLShortenerTests.swift | |
// | |
// Test URL shortening code | |
// | |
// | |
import XCTVapor | |
@testable import App | |
class BitlyURLShortenerTests: XCTestCase { | |
private var testBitly: BitlyURLShortener! | |
private var spyURLSession: SpyURLSession! | |
override func setUpWithError() throws { | |
guard let bitlyAPIToken = Environment.get("BITLY_API_TOKEN") else { | |
throw ConfigError.missingBitlyAPIToken(msg: "ERROR: missing bit.ly API token in environment") | |
} | |
guard let bitlyGroupGUID = Environment.get("BITLY_GROUP_GUID") else { | |
throw ConfigError.missingBitlyGroupGUID(msg: "ERROR: missing bit.ly group GUID in environment") | |
} | |
spyURLSession = SpyURLSession() | |
testBitly = BitlyURLShortener(session: spyURLSession, | |
token: bitlyAPIToken, | |
groupGUID: bitlyGroupGUID) | |
} | |
func test_shorten_successfulBitlyResponse() throws { | |
let testURL = URL(string: "http://example.com/foo")! | |
var testBitlyResult: BitlyShortenResponse! | |
var testHTTPStatus: HTTPURLResponse! | |
// var testHTTPError: Error! | |
let expectation = XCTestExpectation(description: "URLShorten response") | |
testBitly.shorten(testURL, backhalf: nil, completion: { result in | |
switch result { | |
case .success((let resultData, let resultHTTPStatus)): | |
let decoder = JSONDecoder() | |
testHTTPStatus = resultHTTPStatus | |
testBitlyResult = try? decoder.decode(BitlyShortenResponse.self, from: resultData) | |
// dump("Result data: \(String(describing: testBitlyResult))") | |
// dump("Result HTTP status: \(resultHTTPStatus)") | |
case .failure(let resultError): | |
// testHTTPError = resultError | |
XCTFail("Failed to shorten URL: \(resultError)") | |
} | |
expectation.fulfill() | |
}) | |
let fakeRequest = fakeShortenURLRequest(testURL) | |
spyURLSession.dataTaskArgsCompletionHandler.first?( | |
fakeShortenURLResponse(fakeRequest), | |
successHTTPURLResponse(to: fakeRequest), | |
nil | |
) | |
wait(for: [expectation], timeout: 0.1) | |
XCTAssertEqual(1, spyURLSession.dataTaskCallCount) | |
XCTAssertTrue(testBitlyResult.link.starts(with: "https://bit.ly")) | |
XCTAssertTrue(testBitlyResult.id.starts(with: "bit.ly")) | |
XCTAssertEqual(testURL.absoluteString, testBitlyResult.long_url) | |
XCTAssertEqual(200, testHTTPStatus.statusCode) | |
} | |
func test_shorten_successfulBitlyAsyncResponse() async throws { | |
let testURL = URL(string: "http://example.com/foo")! | |
var testBitlyResult: BitlyShortenResponse! | |
var testHTTPStatus: HTTPURLResponse! | |
// var testHTTPError: Error! | |
let fakeRequest = fakeShortenURLRequest(testURL) | |
spyURLSession.asyncDataResponse = fakeShortenURLResponse(fakeRequest) | |
spyURLSession.asyncURLResponse = successHTTPURLResponse(to: fakeRequest) | |
let result = try await testBitly.shorten(testURL, backhalf: nil) | |
switch result { | |
case .success((let resultData, let resultHTTPStatus)): | |
let decoder = JSONDecoder() | |
testHTTPStatus = resultHTTPStatus | |
testBitlyResult = try? decoder.decode(BitlyShortenResponse.self, from: resultData) | |
case .failure(let resultError): | |
XCTFail("Failed to shorten URL: \(resultError)") | |
} | |
XCTAssertEqual(1, spyURLSession.dataTaskCallCount) | |
XCTAssertTrue(testBitlyResult.link.starts(with: "https://bit.ly")) | |
XCTAssertTrue(testBitlyResult.id.starts(with: "bit.ly")) | |
XCTAssertEqual(testURL.absoluteString, testBitlyResult.long_url) | |
XCTAssertEqual(200, testHTTPStatus.statusCode) | |
} | |
// MARK: - Testing Helpers | |
private func generateISO8601Date(date: Date = Date()) -> String { | |
let formatter = ISO8601DateFormatter() | |
return formatter.string(from: date) | |
} | |
private func generateCurrentUNIXEpoch(date: Date = Date()) -> String { | |
return "\(Int(Date().timeIntervalSince1970))" | |
} | |
private func generateFakeBitlyID() -> String { | |
// use a common log date value | |
let dateStr = generateCurrentUNIXEpoch() | |
return "bit.ly/\(dateStr)" | |
} | |
func successHTTPURLResponse(to request: URLRequest) -> HTTPURLResponse { | |
return HTTPURLResponse(url: request.url ?? anyURL(), | |
statusCode: 200, | |
httpVersion: "HTTP/1.1", | |
headerFields: [:])! | |
} | |
func anyURL() -> URL { | |
return URL(string: "http://example.com")! | |
} | |
private func fakeShortenURLRequest(_ url: URL) -> URLRequest { | |
let bitlyURL = URL(string: "https://api-ssl.bitly.com/v4/shorten")! | |
var request = URLRequest(url: bitlyURL, | |
cachePolicy: .reloadIgnoringLocalCacheData) | |
request.httpMethod = "POST" | |
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
request.setValue("Bearer fakebitlyapitken", forHTTPHeaderField: "Authorization") | |
let encoder = JSONEncoder() | |
let reqData = BitlyShortenRequest(long_url: url.absoluteString, | |
domain: "bit.ly", | |
group_guid: "fakebitlyguid") | |
let encodedJSON = try? encoder.encode(reqData) | |
request.httpBody = encodedJSON | |
return request | |
} | |
private func fakeShortenURLResponse(_ request: URLRequest) -> Data { | |
let decoder = JSONDecoder() | |
let bitlyRequest = try? decoder.decode(BitlyShortenRequest.self, | |
from: request.httpBody!) | |
let encoder = JSONEncoder() | |
let fakeBitlyID = generateFakeBitlyID() | |
let reqData = BitlyShortenResponse( | |
created_at: generateISO8601Date(), | |
id: fakeBitlyID, | |
link: "https://" + fakeBitlyID, | |
long_url: bitlyRequest?.long_url ?? "", | |
archived: false, | |
custom_bitlinks: [], | |
tags: [], | |
deeplinks: [] | |
) | |
let encodedJSON = try? encoder.encode(reqData) | |
return encodedJSON ?? Data("{}".utf8) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment