import AppKit import Foundation import Testing @testable import Haven private final class ManagerURLProtocol: URLProtocol, @unchecked Sendable { override class func canInit(with request: URLRequest) -> Bool { true } override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } override func startLoading() { let path = request.url!.path let statusCode = path.contains("fail-http") ? 500 : 200 let data: Data if path.contains("image.png") { data = onePixelPNG } else if path.contains("invalid.png") { data = Data("not an image".utf8) } else { data = Data("wallpaper".utf8) } let response = HTTPURLResponse( url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: nil)! client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } override func stopLoading() {} } private func managerSession() -> URLSession { let configuration = URLSessionConfiguration.ephemeral configuration.protocolClasses = [ManagerURLProtocol.self] return URLSession(configuration: configuration) } private let onePixelPNG = Data( base64Encoded: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" )! @Suite("Favorites persistence", .serialized) struct FavoritesManagerTests { private let defaultsKey = "favoriteWallpapers" @Test("Toggling adds and removes a favorite") func toggleFavorite() { UserDefaults.standard.removeObject(forKey: defaultsKey) defer { UserDefaults.standard.removeObject(forKey: defaultsKey) } let manager = FavoritesManager() let wallpaper = makeWallpaper() manager.toggleFavorite(wallpaper) #expect(manager.favorites == [wallpaper]) #expect(manager.isFavorite(wallpaper)) manager.toggleFavorite(wallpaper) #expect(manager.favorites.isEmpty) #expect(!manager.isFavorite(wallpaper)) } @Test("Favorites survive manager recreation") func favoritesPersist() { UserDefaults.standard.removeObject(forKey: defaultsKey) defer { UserDefaults.standard.removeObject(forKey: defaultsKey) } let wallpaper = makeWallpaper() FavoritesManager().toggleFavorite(wallpaper) let reloaded = FavoritesManager() #expect(reloaded.favorites == [wallpaper]) } } @Suite("History persistence", .serialized) struct HistoryManagerTests { private let defaultsKey = "wallpaperHistory" @Test("History keeps newest items first and promotes duplicates") func orderingAndPromotion() { UserDefaults.standard.removeObject(forKey: defaultsKey) defer { UserDefaults.standard.removeObject(forKey: defaultsKey) } let manager = HistoryManager() let first = makeWallpaper(id: "first") let second = makeWallpaper(id: "second") manager.addToHistory(first) manager.addToHistory(second) manager.addToHistory(first) #expect(manager.history.map(\.id) == ["first", "second"]) } @Test("History is capped at fifty entries") func historyLimit() { UserDefaults.standard.removeObject(forKey: defaultsKey) defer { UserDefaults.standard.removeObject(forKey: defaultsKey) } let manager = HistoryManager() for index in 0..<55 { manager.addToHistory(makeWallpaper(id: "item-\(index)")) } #expect(manager.history.count == 50) #expect(manager.history.first?.id == "item-54") #expect(manager.history.last?.id == "item-5") } @Test("History survives recreation and clear removes persisted data") func persistenceAndClear() { UserDefaults.standard.removeObject(forKey: defaultsKey) defer { UserDefaults.standard.removeObject(forKey: defaultsKey) } HistoryManager().addToHistory(makeWallpaper(id: "persisted")) let reloaded = HistoryManager() #expect(reloaded.history.map(\.id) == ["persisted"]) reloaded.clearHistory() #expect(HistoryManager().history.isEmpty) } } @Suite("Image cache") struct ImageCacheTests { @Test("Inserted images are returned for the same URL") func insertAndRetrieve() { let url = URL(string: "https://example.com/\(UUID().uuidString).png")! let image = NSImage(size: NSSize(width: 10, height: 20)) ImageCache.shared.insertImage(image, for: url) #expect(ImageCache.shared.image(for: url) === image) } @Test("Different URLs do not collide") func keysDoNotCollide() { let base = UUID().uuidString let firstURL = URL(string: "https://example.com/\(base)-first.png")! let secondURL = URL(string: "https://example.com/\(base)-second.png")! ImageCache.shared.insertImage(NSImage(size: NSSize(width: 1, height: 1)), for: firstURL) #expect(ImageCache.shared.image(for: secondURL) == nil) } @Test("Fetch decodes and caches successful image responses") func fetchAndCache() async throws { let cache = ImageCache(session: managerSession()) let url = URL(string: "https://example.com/image.png")! let first = try await cache.fetchImage(url: url) let second = try await cache.fetchImage(url: url) #expect(first === second) } @Test("Fetch rejects HTTP errors and invalid image bytes") func fetchFailures() async { let cache = ImageCache(session: managerSession()) let httpError = URL(string: "https://example.com/fail-http.png")! let invalid = URL(string: "https://example.com/invalid.png")! await #expect(throws: WallhavenError.self) { try await cache.fetchImage(url: httpError) } await #expect(throws: WallhavenError.self) { try await cache.fetchImage(url: invalid) } } } @Suite("Wallpaper file management", .serialized) @MainActor struct WallpaperManagerTests { @Test("Download validates HTTP status and writes response data") func download() async throws { let manager = WallpaperManager(session: managerSession()) let url = URL(string: "https://example.com/\(UUID().uuidString).jpg")! let downloaded = try await manager.download(from: url) defer { try? FileManager.default.removeItem(at: downloaded) } #expect(try Data(contentsOf: downloaded) == Data("wallpaper".utf8)) let errorURL = URL(string: "https://example.com/fail-http.jpg")! await #expect(throws: WallhavenError.self) { try await manager.download(from: errorURL) } } @Test("Desktop setter receives explicit and default screens") @MainActor func desktopSetter() throws { guard let screen = NSScreen.screens.first else { return } var received: [NSScreen] = [] let manager = WallpaperManager(desktopSetter: { _, target in received.append(target) }) let url = URL(fileURLWithPath: "/tmp/test-wallpaper.jpg") try manager.setDesktopImage(from: url, screen: screen) try manager.setDesktopImage(from: url) #expect(received.count == 2) #expect(received[0] === screen) } @Test("Default desktop adapter reports invalid local files") func defaultDesktopAdapter() { guard let screen = NSScreen.screens.first else { return } let manager = WallpaperManager() #expect(throws: Error.self) { try manager.setDesktopImage( from: URL(fileURLWithPath: "/definitely-missing-haven-wallpaper.jpg"), screen: screen) } } @Test("Set wallpaper composes download and desktop update") @MainActor func setWallpaper() async throws { guard let screen = NSScreen.screens.first else { return } var receivedURL: URL? let manager = WallpaperManager( session: managerSession(), desktopSetter: { url, _ in receivedURL = url }) let remote = URL(string: "https://example.com/\(UUID().uuidString).jpg")! try await manager.setWallpaper(from: remote, screen: screen) defer { if let receivedURL { try? FileManager.default.removeItem(at: receivedURL) } } #expect(receivedURL?.lastPathComponent == remote.lastPathComponent) } @Test("Saving creates the destination folder and copies the file") func saveCreatesFolderAndCopies() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let source = root.appendingPathComponent("source/wallpaper.jpg") let destination = root.appendingPathComponent("nested/destination") try FileManager.default.createDirectory( at: source.deletingLastPathComponent(), withIntermediateDirectories: true) try Data("first".utf8).write(to: source) try WallpaperManager.shared.saveToFolder(from: source, to: destination) let copied = try Data(contentsOf: destination.appendingPathComponent("wallpaper.jpg")) #expect(copied == Data("first".utf8)) } @Test("Saving replaces an existing file with fresh data") func saveReplacesExistingFile() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let source = root.appendingPathComponent("source/wallpaper.jpg") let destination = root.appendingPathComponent("destination") try FileManager.default.createDirectory( at: source.deletingLastPathComponent(), withIntermediateDirectories: true) try FileManager.default.createDirectory(at: destination, withIntermediateDirectories: true) try Data("new".utf8).write(to: source) try Data("old".utf8).write(to: destination.appendingPathComponent("wallpaper.jpg")) try WallpaperManager.shared.saveToFolder(from: source, to: destination) let copied = try Data(contentsOf: destination.appendingPathComponent("wallpaper.jpg")) #expect(copied == Data("new".utf8)) } } @Suite("Autostart persistence", .serialized) @MainActor struct AutostartManagerTests { @Test("Enable writes a launch agent and disable removes it") func enableAndDisable() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let manager = AutostartManager( libraryDirectory: root, executablePath: { "/Applications/Haven.app/Contents/MacOS/Haven" }) #expect(!manager.isEnabled) manager.isEnabled = true #expect(manager.isEnabled) manager.isEnabled = true let plist = root.appendingPathComponent("LaunchAgents/com.june.Haven.plist") let contents = try PropertyListSerialization.propertyList( from: Data(contentsOf: plist), format: nil) as? [String: Any] #expect(contents?["Label"] as? String == "com.june.Haven") #expect( contents?["ProgramArguments"] as? [String] == ["/Applications/Haven.app/Contents/MacOS/Haven"]) #expect(contents?["RunAtLoad"] as? Bool == true) manager.isEnabled = false #expect(!manager.isEnabled) manager.isEnabled = false } @Test("Missing executable leaves autostart disabled") func missingExecutable() { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let manager = AutostartManager(libraryDirectory: root, executablePath: { nil }) manager.isEnabled = true #expect(!manager.isEnabled) } @Test("Default executable provider writes only inside the injected library") func defaultExecutableProvider() { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let manager = AutostartManager(libraryDirectory: root) manager.isEnabled = true #expect(manager.isEnabled) manager.isEnabled = false } @Test("Unwritable launch-agent roots do not enable autostart") func writeFailure() throws { let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: file) } try Data("not a directory".utf8).write(to: file) let manager = AutostartManager(libraryDirectory: file, executablePath: { "/tmp/Haven" }) manager.isEnabled = true #expect(!manager.isEnabled) } @Test("Removal failures leave the launch agent enabled") func removalFailure() { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } let manager = AutostartManager( libraryDirectory: root, executablePath: { "/tmp/Haven" }, removePlist: { _ in throw CocoaError(.fileWriteNoPermission) }) manager.isEnabled = true manager.isEnabled = false #expect(manager.isEnabled) } } @Suite("Local wallpaper library", .serialized) @MainActor struct LocalWallpapersManagerTests { private func waitForScan(_ manager: LocalWallpapersManager) async { for _ in 0..<100 where manager.isLoading { try? await Task.sleep(nanoseconds: 10_000_000) } } @Test("Scan filters files, reads metadata, and deletes wallpapers") func scanAndDelete() async throws { let defaults = UserDefaults.standard let oldPath = defaults.string(forKey: "saveFolderPath") defaults.removeObject(forKey: "saveFolderPath") defer { if let oldPath { defaults.set(oldPath, forKey: "saveFolderPath") } else { defaults.removeObject(forKey: "saveFolderPath") } } let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: root) } try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) try onePixelPNG.write(to: root.appendingPathComponent("wallpaper.png")) try Data("broken".utf8).write(to: root.appendingPathComponent("broken.png")) try Data("ignored".utf8).write(to: root.appendingPathComponent("notes.txt")) let manager = LocalWallpapersManager() manager.scanFolder(path: root.path) manager.scanFolder(path: root.path) await waitForScan(manager) let wallpaper = try #require(manager.wallpapers.first) #expect(manager.wallpapers.count == 1) #expect(wallpaper.thumbnailURL == wallpaper.url) #expect(wallpaper.filename == "wallpaper.png") #expect(wallpaper.resolution == "1x1") #expect(LocalWallpapersManager.wallpaper(at: root.appendingPathComponent("broken.png")) == nil) #expect(LocalWallpapersManager.wallpaper(at: root.appendingPathComponent("missing.png")) == nil) manager.deleteWallpaper(wallpaper) #expect(manager.wallpapers.isEmpty) manager.deleteWallpaper(wallpaper) #expect(manager.errorMessage?.hasPrefix("Failed to delete") == true) } @Test("Invalid scan roots surface an error") func scanError() async throws { let defaults = UserDefaults.standard defaults.removeObject(forKey: "saveFolderPath") let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: file) } try Data("file".utf8).write(to: file) let manager = LocalWallpapersManager() manager.scanFolder(path: file.path) await waitForScan(manager) #expect(manager.errorMessage?.hasPrefix("Failed to scan folder") == true) #expect(!manager.isLoading) } @Test("Monitoring configured folders observes filesystem changes and stops") func monitoring() async throws { let defaults = UserDefaults.standard let oldPath = defaults.string(forKey: "saveFolderPath") let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) defaults.set(root.path, forKey: "saveFolderPath") defer { try? FileManager.default.removeItem(at: root) if let oldPath { defaults.set(oldPath, forKey: "saveFolderPath") } else { defaults.removeObject(forKey: "saveFolderPath") } } let manager = LocalWallpapersManager() await waitForScan(manager) try onePixelPNG.write(to: root.appendingPathComponent("observed.png")) try? await Task.sleep(nanoseconds: 100_000_000) await waitForScan(manager) #expect(manager.wallpapers.contains(where: { $0.filename == "observed.png" })) manager.stopMonitoring() try? await Task.sleep(nanoseconds: 500_000_000) let invalidMonitor = FolderMonitor(folderPath: root.appendingPathComponent("missing").path) {} invalidMonitor.start() invalidMonitor.closeFileDescriptor() let descriptor = open("/dev/null", O_RDONLY) let descriptorMonitor = FolderMonitor( folderPath: "/dev/null", fileDescriptor: descriptor, callback: {}) descriptorMonitor.closeFileDescriptor() #expect(fcntl(descriptor, F_GETFD) == -1) } }