Skip to content

Commit

Permalink
Bugfix FXIOS-10811 [Wallpaper] [Crash] Fix async/await in networking …
Browse files Browse the repository at this point in the history
…module (#23596)

* they call me pistol pete

* I'm a cowman, not a cowboy, dad
  • Loading branch information
adudenamedruby authored Dec 5, 2024
1 parent c0acb41 commit 52186d6
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 32 deletions.
3 changes: 3 additions & 0 deletions BrowserKit/Sources/Common/Logger/LoggerCategory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ public enum LoggerCategory: String {

/// Password Generator
case passwordGenerator

/// Related to wallpaper functionality, like fetching metadata, images, etc
case wallpaper
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,47 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Foundation
import Shared

class WallpaperNetworkingModule: WallpaperNetworking {
private var urlSession: URLSessionProtocol
private var logger: Logger

init(with urlSession: URLSessionProtocol = URLSession.sharedMPTCP) {
init(
with urlSession: URLSessionProtocol = URLSession.sharedMPTCP,
logger: Logger = DefaultLogger.shared
) {
self.urlSession = urlSession
self.logger = logger
}

/// A basic async/await wrapper
func data(from url: URL) async throws -> (Data, URLResponse) {
return try await withCheckedThrowingContinuation { continuation in
urlSession.dataTaskWith(url) { data, response, error in
if let error = error {
continuation.resume(throwing: error)
return
}

guard let response = validatedHTTPResponse(response, statusCode: 200..<300) else {
continuation.resume(throwing: URLError(.badServerResponse))
return
}

guard let data = data,
!data.isEmpty
else {
continuation.resume(throwing: WallpaperServiceError.dataUnavailable)
return
}

continuation.resume(returning: (data, response))
}.resume()
do {
logger.log(
"Attempting to fetch wallpaper data",
level: .debug,
category: .wallpaper
)
let (data, response) = try await urlSession.data(from: url)

guard let response = validatedHTTPResponse(
response,
statusCode: 200..<300
) else { throw URLError(.badServerResponse) }

guard !data.isEmpty else { throw WallpaperServiceError.dataUnavailable }

logger.log(
"Wallpaper data fetched successfully",
level: .debug,
category: .wallpaper
)

return (data, response)
} catch {
throw error
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Foundation
protocol URLSessionProtocol {
typealias DataTaskResult = (Data?, URLResponse?, Error?) -> Void

func data(from url: URL) async throws -> (Data, URLResponse)

func dataTaskWith(_ url: URL,
completionHandler: @escaping DataTaskResult
) -> URLSessionDataTaskProtocol
Expand All @@ -18,6 +20,10 @@ protocol URLSessionProtocol {
}

extension URLSession: URLSessionProtocol {
public func data(from url: URL) async throws -> (Data, URLResponse) {
try await data(from: url, delegate: nil)
}

func dataTaskWith(
request: URLRequest,
completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class WallpaperURLSessionMock: URLSessionProtocol {
self.error = error
}

func data(from url: URL) async throws -> (Data, URLResponse) {
if let error = error {
throw error
}

return (data ?? Data(), response ?? URLResponse())
}

func dataTaskWith(_ url: URL,
completionHandler completion: @escaping DataTaskResult
) -> URLSessionDataTaskProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,41 @@ import XCTest
class WallpaperNetworkingModuleTests: XCTestCase, WallpaperTestDataProvider {
let url = URL(string: "my.testurl.com")!

func testResumeWasCalled() async {
let dataTask = WallpaperURLSessionDataTaskMock()
let session = WallpaperURLSessionMock(with: nil, response: nil, and: nil)
session.dataTask = dataTask
func testAsyncDataCall() async {
let expectedData = "Test data".data(using: .utf8)!
let expectedResponse = HTTPURLResponse(url: URL(string: "https://example.com")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil)!

let session = WallpaperURLSessionMock(
with: expectedData,
response: expectedResponse,
and: nil
)
let subject = WallpaperNetworkingModule(with: session)

_ = try? await subject.data(from: url)
XCTAssertTrue(dataTask.resumeWasCalled)
do {
let (responseData, response) = try await subject.data(from: url)
XCTAssertEqual(responseData, expectedData)
XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200)
} catch {
XCTFail("This test should not throw an error, but it did.")
}
}

func testServerReturnsError() async {
let expectedError = URLError(.cannotConnectToHost)
let session = WallpaperURLSessionMock(with: nil,
response: nil,
and: URLError(.cannotConnectToHost))
and: expectedError)
let subject = WallpaperNetworkingModule(with: session)

do {
_ = try await subject.data(from: url)
XCTFail("This test should throw an error, but it did not.")
} catch {
XCTAssertEqual(error as? URLError,
URLError(.cannotConnectToHost))
XCTAssertEqual(error as? URLError, expectedError)
}
}

Expand Down

0 comments on commit 52186d6

Please sign in to comment.