This commit is contained in:
144
tests/clipboredtests/ClipboardCloudSyncServiceTests.swift
Normal file
144
tests/clipboredtests/ClipboardCloudSyncServiceTests.swift
Normal file
@@ -0,0 +1,144 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class ClipboardCloudSyncServiceTests: XCTestCase {
|
||||
private var tempRoot: URL!
|
||||
private var defaultsSuites: [String] = []
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
tempRoot = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clipbored-cloud-sync-tests", isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: tempRoot, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
for suite in defaultsSuites {
|
||||
UserDefaults(suiteName: suite)?.removePersistentDomain(forName: suite)
|
||||
}
|
||||
if let tempRoot {
|
||||
try? FileManager.default.removeItem(at: tempRoot)
|
||||
}
|
||||
defaultsSuites = []
|
||||
tempRoot = nil
|
||||
try super.tearDownWithError()
|
||||
}
|
||||
|
||||
func testUnavailableContainerReportsStatusAndThrows() {
|
||||
let service = ClipboardCloudSyncService(containerProvider: { nil })
|
||||
|
||||
let status = service.status()
|
||||
|
||||
XCTAssertFalse(status.isAvailable)
|
||||
XCTAssertNil(status.archiveURL)
|
||||
XCTAssertTrue(status.message.contains("iCloud Sync is unavailable"))
|
||||
XCTAssertThrowsError(try service.syncArchiveURL()) { error in
|
||||
XCTAssertEqual(error as? ClipboardCloudSyncError, .unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
func testPushWritesArchiveToPrivateDocumentsFolder() throws {
|
||||
let environment = try makeStoreEnvironment(named: "source")
|
||||
environment.store.upsert(makeItem("cloud note", created: Date(timeIntervalSince1970: 10)))
|
||||
environment.store.flushPersistenceForTesting()
|
||||
|
||||
let cloudRoot = tempRoot.appendingPathComponent("cloud", isDirectory: true)
|
||||
let service = ClipboardCloudSyncService(containerProvider: { cloudRoot })
|
||||
|
||||
let summary = try service.push(store: environment.store)
|
||||
let archiveURL = try service.syncArchiveURL()
|
||||
let status = service.status()
|
||||
|
||||
XCTAssertEqual(summary.itemCount, 1)
|
||||
XCTAssertEqual(
|
||||
archiveURL,
|
||||
cloudRoot
|
||||
.appendingPathComponent("Documents", isDirectory: true)
|
||||
.appendingPathComponent(AppConfiguration.appName, isDirectory: true)
|
||||
.appendingPathComponent(ClipboardCloudSyncService.archiveFileName)
|
||||
)
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: archiveURL.path))
|
||||
XCTAssertEqual(try posixPermissions(archiveURL), 0o600)
|
||||
XCTAssertTrue(status.isAvailable)
|
||||
XCTAssertEqual(status.archiveURL, archiveURL)
|
||||
XCTAssertNotNil(status.lastModifiedAt)
|
||||
}
|
||||
|
||||
func testPullImportsExistingCloudArchiveIntoAnotherStore() throws {
|
||||
let source = try makeStoreEnvironment(named: "source")
|
||||
let sourceItem = makeItem("shared through icloud", created: Date(timeIntervalSince1970: 20))
|
||||
source.store.upsert(sourceItem)
|
||||
source.store.flushPersistenceForTesting()
|
||||
|
||||
let cloudRoot = tempRoot.appendingPathComponent("cloud", isDirectory: true)
|
||||
let service = ClipboardCloudSyncService(containerProvider: { cloudRoot })
|
||||
try service.push(store: source.store)
|
||||
|
||||
let destination = try makeStoreEnvironment(named: "destination")
|
||||
let summary = try service.pull(store: destination.store)
|
||||
destination.store.flushPersistenceForTesting()
|
||||
|
||||
XCTAssertEqual(summary.itemCount, 1)
|
||||
XCTAssertEqual(destination.store.items.count, 1)
|
||||
XCTAssertEqual(destination.store.items.first?.id, sourceItem.id)
|
||||
XCTAssertEqual(destination.store.items.first?.payload, "shared through icloud")
|
||||
}
|
||||
|
||||
func testPullWithoutRemoteArchiveThrowsNoRemoteArchive() throws {
|
||||
let destination = try makeStoreEnvironment(named: "destination")
|
||||
let cloudRoot = tempRoot.appendingPathComponent("empty-cloud", isDirectory: true)
|
||||
let service = ClipboardCloudSyncService(containerProvider: { cloudRoot })
|
||||
|
||||
XCTAssertThrowsError(try service.pull(store: destination.store)) { error in
|
||||
guard case ClipboardCloudSyncError.noRemoteArchive(let url) = error else {
|
||||
return XCTFail("Expected noRemoteArchive, got \(error)")
|
||||
}
|
||||
XCTAssertEqual(url.lastPathComponent, ClipboardCloudSyncService.archiveFileName)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeStoreEnvironment(named name: String) throws -> (settings: SettingsModel, store: ClipboardStore) {
|
||||
let suiteName = "com.clipbored.cloudsync.\(name).\(UUID().uuidString)"
|
||||
defaultsSuites.append(suiteName)
|
||||
let defaults = try XCTUnwrap(UserDefaults(suiteName: suiteName))
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
|
||||
let settings = SettingsModel(defaults: defaults)
|
||||
settings.maxHistoryItems = 50
|
||||
settings.historyRetention = .forever
|
||||
let baseURL = tempRoot.appendingPathComponent(name, isDirectory: true)
|
||||
let encryptionService = ClipboardEncryptionService(keyProvider: { nil })
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: encryptionService)
|
||||
let store = ClipboardStore(
|
||||
settings: settings,
|
||||
cacheService: cacheService,
|
||||
baseURL: baseURL,
|
||||
encryptionService: encryptionService
|
||||
)
|
||||
return (settings, store)
|
||||
}
|
||||
|
||||
private func makeItem(_ payload: String, created: Date) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .text,
|
||||
displayText: payload,
|
||||
payload: payload,
|
||||
payloadHash: String(payload.hashValue),
|
||||
createdAt: created,
|
||||
lastUsedAt: created,
|
||||
useCount: 1,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: false
|
||||
)
|
||||
}
|
||||
|
||||
private func posixPermissions(_ url: URL) throws -> Int {
|
||||
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||
return try XCTUnwrap(attributes[.posixPermissions] as? Int)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user