Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Click2Minimize/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct Click2MinimizeApp: App {
}

class AppDelegate: NSObject, NSApplicationDelegate {
var urlSession: URLSession = .shared
var eventTap: CFMachPort?
var mainWindow: NSWindow?
var cancellables = Set<AnyCancellable>()
Expand Down Expand Up @@ -380,7 +381,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {

func checkForUpdates() {
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 = self.urlSession.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("Error fetching updates: \(error?.localizedDescription ?? "Unknown error")")
return
Expand Down Expand Up @@ -426,9 +427,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}

private func fetchLatestDMG(releaseInfo: Release) {
func fetchLatestDMG(releaseInfo: Release) {
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 = self.urlSession.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("Error fetching release info: \(error?.localizedDescription ?? "Unknown error")")
return
Expand All @@ -444,6 +445,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
break
}
}
} else {
print("Error: Failed to parse JSON or missing 'assets' array.")
}
}
task.resume()
Expand All @@ -452,7 +455,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private func downloadDMG(from urlString: String) {
guard let url = URL(string: urlString) else { return }

let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
let task = self.urlSession.downloadTask(with: url) { localURL, response, error in
guard let localURL = localURL, error == nil else {
print("Error downloading DMG: \(error?.localizedDescription ?? "Unknown error")")
// Open the browser link for manual upgrade
Expand Down
161 changes: 161 additions & 0 deletions Click2MinimizeTests/AppDelegateTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import XCTest
@testable import Click2Minimize

class MockURLProtocol: URLProtocol {
static var mockData: Data?
static var mockResponse: URLResponse?
static var mockError: Error?
static var requestURLs: [URL] = []

static func reset() {
mockData = nil
mockResponse = nil
mockError = nil
requestURLs.removeAll()
}

override class func canInit(with request: URLRequest) -> Bool {
return true
}

override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}

override func startLoading() {
if let url = request.url {
MockURLProtocol.requestURLs.append(url)
}

if let error = MockURLProtocol.mockError {
self.client?.urlProtocol(self, didFailWithError: error)
} else {
if let response = MockURLProtocol.mockResponse {
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
}
if let data = MockURLProtocol.mockData {
self.client?.urlProtocol(self, didLoad: data)
}
}
self.client?.urlProtocolDidFinishLoading(self)
}

override func stopLoading() {}
}

class AppDelegateTests: XCTestCase {
var appDelegate: AppDelegate!

override func setUp() {
super.setUp()
appDelegate = AppDelegate()

// Setup mock URLSession
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let mockSession = URLSession(configuration: configuration)

appDelegate.urlSession = mockSession
MockURLProtocol.reset()
}

override func tearDown() {
appDelegate = nil
MockURLProtocol.reset()
super.tearDown()
}

func testFetchLatestDMG_InvalidJSON() {
let invalidJSONString = "{ invalid_json: "
MockURLProtocol.mockData = invalidJSONString.data(using: .utf8)

let releaseInfo = AppDelegate.Release(tag_name: "1.0.0")

let expectation = XCTestExpectation(description: "Wait for fetch task to complete")
appDelegate.fetchLatestDMG(releaseInfo: releaseInfo)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// Verify only 1 request was made (to the releases API), and no DMG download was triggered.
XCTAssertEqual(MockURLProtocol.requestURLs.count, 1, "Expected exactly 1 request for the release API.")
if let firstURL = MockURLProtocol.requestURLs.first {
XCTAssertTrue(firstURL.absoluteString.contains("releases/latest"), "Expected API URL.")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
}

func testFetchLatestDMG_ValidJSON_MissingAssets() {
let validJSONString = "{\"not_assets\": []}"
MockURLProtocol.mockData = validJSONString.data(using: .utf8)

let releaseInfo = AppDelegate.Release(tag_name: "1.0.0")

let expectation = XCTestExpectation(description: "Wait for fetch task to complete")
appDelegate.fetchLatestDMG(releaseInfo: releaseInfo)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// Verify only 1 request was made (to the releases API), and no DMG download was triggered.
XCTAssertEqual(MockURLProtocol.requestURLs.count, 1, "Expected exactly 1 request for the release API.")
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
}

func testFetchLatestDMG_ValidJSON_NoDMG() {
let validJSONString = """
{
"assets": [
{
"browser_download_url": "https://example.com/app.zip",
"name": "app.zip"
}
]
}
"""
MockURLProtocol.mockData = validJSONString.data(using: .utf8)

let releaseInfo = AppDelegate.Release(tag_name: "1.0.0")

let expectation = XCTestExpectation(description: "Wait for fetch task to complete")
appDelegate.fetchLatestDMG(releaseInfo: releaseInfo)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// Verify only 1 request was made (to the releases API), and no DMG download was triggered.
XCTAssertEqual(MockURLProtocol.requestURLs.count, 1, "Expected exactly 1 request for the release API.")
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
}

func testFetchLatestDMG_HappyPath() {
let validJSONString = """
{
"assets": [
{
"browser_download_url": "https://example.com/app.dmg",
"name": "app.dmg"
}
]
}
"""
MockURLProtocol.mockData = validJSONString.data(using: .utf8)

let releaseInfo = AppDelegate.Release(tag_name: "1.0.0")

let expectation = XCTestExpectation(description: "Wait for fetch task to complete")
appDelegate.fetchLatestDMG(releaseInfo: releaseInfo)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// Verify 2 requests were made (API fetch + DMG download)
XCTAssertEqual(MockURLProtocol.requestURLs.count, 2, "Expected 2 requests: one for API, one for DMG download.")

if MockURLProtocol.requestURLs.count == 2 {
XCTAssertTrue(MockURLProtocol.requestURLs[0].absoluteString.contains("releases/latest"), "First request should be the API.")
XCTAssertTrue(MockURLProtocol.requestURLs[1].absoluteString.contains("app.dmg"), "Second request should be the DMG download.")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
}
}