From 5f3accfce11159409bf0b262788f11b29c668c1d Mon Sep 17 00:00:00 2001 From: hatimhtm <106043141+hatimhtm@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:49:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Add=20missing=20error=20path=20t?= =?UTF-8?q?est=20for=20JSON=20parsing=20in=20fetchLatestDMG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- Click2Minimize/AppDelegate.swift | 16 +++- Click2MinimizeTests/AppDelegateTests.swift | 87 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 Click2MinimizeTests/AppDelegateTests.swift diff --git a/Click2Minimize/AppDelegate.swift b/Click2Minimize/AppDelegate.swift index 55a4109..47e5dab 100644 --- a/Click2Minimize/AppDelegate.swift +++ b/Click2Minimize/AppDelegate.swift @@ -37,6 +37,8 @@ struct Click2MinimizeApp: App { } class AppDelegate: NSObject, NSApplicationDelegate { + var didOpenBrowserForManualUpgrade = false + var urlOpener: (URL) -> Void = { NSWorkspace.shared.open($0) } var eventTap: CFMachPort? var mainWindow: NSWindow? var cancellables = Set() @@ -426,14 +428,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - private func fetchLatestDMG(releaseInfo: Release) { + func fetchLatestDMG(releaseInfo: Release, session: URLSession = .shared) { let url = URL(string: "https://api.github.com/repos/hatimhtm/Click2Minimize/releases/latest")! - let task = URLSession.shared.dataTask(with: url) { data, response, error in + let task = session.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { print("Error fetching release info: \(error?.localizedDescription ?? "Unknown error")") return } + var foundDMG = false if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let assets = json["assets"] as? [[String: Any]] { for asset in assets { @@ -441,10 +444,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { let name = asset["name"] as? String, name.hasSuffix(".dmg") { self.downloadDMG(from: downloadURL) + foundDMG = true break } } } + if !foundDMG { + self.openBrowserForManualUpgrade() + } } task.resume() } @@ -509,9 +516,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { task.resume() } - private func openBrowserForManualUpgrade() { + func openBrowserForManualUpgrade() { + self.didOpenBrowserForManualUpgrade = true if let url = URL(string: "https://github.com/hatimhtm/Click2Minimize/releases") { - NSWorkspace.shared.open(url) + urlOpener(url) } } diff --git a/Click2MinimizeTests/AppDelegateTests.swift b/Click2MinimizeTests/AppDelegateTests.swift new file mode 100644 index 0000000..42d1c09 --- /dev/null +++ b/Click2MinimizeTests/AppDelegateTests.swift @@ -0,0 +1,87 @@ +import XCTest +@testable import Click2Minimize + +class MockURLProtocol: URLProtocol { + static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data?))? + + override class func canInit(with request: URLRequest) -> Bool { + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + guard let handler = MockURLProtocol.requestHandler else { + fatalError("Handler is unavailable.") + } + + do { + let (response, data) = try handler(request) + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + if let data = data { + client?.urlProtocol(self, didLoad: data) + } + client?.urlProtocolDidFinishLoading(self) + } catch { + client?.urlProtocol(self, didFailWithError: error) + } + } + + override func stopLoading() { + } +} + +final class AppDelegateTests: XCTestCase { + + var appDelegate: AppDelegate! + var session: URLSession! + + override func setUpWithError() throws { + try super.setUpWithError() + appDelegate = AppDelegate() + + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [MockURLProtocol.self] + session = URLSession(configuration: configuration) + } + + override func tearDownWithError() throws { + appDelegate = nil + session = nil + MockURLProtocol.requestHandler = nil + try super.tearDownWithError() + } + + func testFetchLatestDMG_JSONParsingError() throws { + // Arrange + let expectation = self.expectation(description: "Fetch Latest DMG") + + // Mock invalid JSON response (missing "assets" key) + let jsonString = """ + { + "tag_name": "v1.2.0" + } + """ + let responseData = jsonString.data(using: .utf8)! + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + return (response, responseData) + } + + // Override urlOpener to fulfill expectation and avoid actual opening + appDelegate.urlOpener = { url in + XCTAssertEqual(url.absoluteString, "https://github.com/hatimhtm/Click2Minimize/releases") + expectation.fulfill() + } + + // Act + appDelegate.fetchLatestDMG(releaseInfo: AppDelegate.Release(tag_name: "v1.2.0"), session: session) + + // Assert + waitForExpectations(timeout: 2.0) + XCTAssertTrue(appDelegate.didOpenBrowserForManualUpgrade) + } +}