WIP
This commit is contained in:
319
tests/clipboredtests/ClipboardCacheServiceTests.swift
Normal file
319
tests/clipboredtests/ClipboardCacheServiceTests.swift
Normal file
@@ -0,0 +1,319 @@
|
||||
import AppKit
|
||||
import CryptoKit
|
||||
import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class ClipboardCacheServiceTests: XCTestCase {
|
||||
private var tempURLs: [URL] = []
|
||||
|
||||
override func tearDown() {
|
||||
tempURLs.forEach { try? FileManager.default.removeItem(at: $0) }
|
||||
tempURLs.removeAll()
|
||||
try? FileManager.default.removeItem(at: temporaryPreviewRoot())
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testPurgeRemovesImageCacheFilesOverByteLimit() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: noOpEncryptionService())
|
||||
let first = try XCTUnwrap(cacheService.cacheImage(makeImage(color: .systemRed), id: UUID()))
|
||||
let second = try XCTUnwrap(cacheService.cacheImage(makeImage(color: .systemBlue), id: UUID()))
|
||||
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: first.full))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: first.thumb))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: second.full))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: second.thumb))
|
||||
|
||||
cacheService.purgeIfNeeded(maxBytes: 1)
|
||||
cacheService.flushForTesting()
|
||||
|
||||
let remaining = try imageCacheFileURLs(in: baseURL)
|
||||
XCTAssertTrue(remaining.isEmpty)
|
||||
}
|
||||
|
||||
func testClearCacheRemovesOnlyImageCacheFiles() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: noOpEncryptionService())
|
||||
let image = try XCTUnwrap(cacheService.cacheImage(makeImage(color: .systemGreen), id: UUID()))
|
||||
let pdfPath = try XCTUnwrap(cacheService.cachePDF(Data("%PDF-1.4\n%%EOF".utf8), id: UUID()))
|
||||
let pdfItem = pdfItem(path: pdfPath)
|
||||
let previewURL = try XCTUnwrap(cacheService.temporaryReadableURL(for: pdfItem))
|
||||
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: image.full))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: image.thumb))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: pdfPath))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: previewURL.path))
|
||||
|
||||
cacheService.clearCache()
|
||||
cacheService.flushForTesting()
|
||||
|
||||
XCTAssertTrue(try imageCacheFileURLs(in: baseURL).isEmpty)
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: pdfPath))
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: previewURL.path))
|
||||
}
|
||||
|
||||
func testImageCacheFilesAreEncryptedAndLoadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let image = makeImage(color: .systemPurple)
|
||||
|
||||
let paths = try XCTUnwrap(cacheService.cacheImage(image, id: UUID()))
|
||||
let rawFull = try Data(contentsOf: URL(fileURLWithPath: paths.full))
|
||||
let rawThumb = try Data(contentsOf: URL(fileURLWithPath: paths.thumb))
|
||||
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawFull))
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawThumb))
|
||||
XCTAssertNil(NSImage(data: rawFull))
|
||||
XCTAssertNotNil(cacheService.image(for: paths.full))
|
||||
XCTAssertNotNil(cacheService.image(for: paths.thumb))
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: paths.full)), 0o600)
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: paths.thumb)), 0o600)
|
||||
}
|
||||
|
||||
func testPreviewThumbnailUsesExistingFilePreview() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: noOpEncryptionService())
|
||||
let imageURL = baseURL.appendingPathComponent("Copied Image.png")
|
||||
let imageData = try XCTUnwrap(makeImage(color: .systemOrange).pngData())
|
||||
try imageData.write(to: imageURL)
|
||||
|
||||
let item = fileItem(path: imageURL.path)
|
||||
|
||||
XCTAssertNotNil(cacheService.previewThumbnail(for: item))
|
||||
}
|
||||
|
||||
func testPreviewThumbnailUsesManagedPDFPreviewFallback() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let pdfData = try makePDFData()
|
||||
let path = try XCTUnwrap(cacheService.cachePDF(pdfData, id: UUID()))
|
||||
|
||||
XCTAssertNotNil(cacheService.previewThumbnail(for: pdfItem(path: path)))
|
||||
}
|
||||
|
||||
func testPDFCacheFilesAreEncryptedAndReadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let pdfData = Data("%PDF-1.4\nclipbored\n%%EOF".utf8)
|
||||
|
||||
let path = try XCTUnwrap(cacheService.cachePDF(pdfData, id: UUID()))
|
||||
let rawPDF = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawPDF))
|
||||
XCTAssertNotEqual(rawPDF, pdfData)
|
||||
XCTAssertEqual(cacheService.data(for: path), pdfData)
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: path)), 0o600)
|
||||
}
|
||||
|
||||
func testAudioCacheFilesAreEncryptedAndReadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let audioData = Data([0, 1, 2, 3, 5, 8, 13])
|
||||
|
||||
let path = try XCTUnwrap(cacheService.cacheAudio(audioData, id: UUID()))
|
||||
let rawAudio = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawAudio))
|
||||
XCTAssertNotEqual(rawAudio, audioData)
|
||||
XCTAssertEqual(cacheService.data(for: path), audioData)
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: path)), 0o600)
|
||||
}
|
||||
|
||||
func testRichTextCacheFilesAreEncryptedAndReadable() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let rtfData = Data("{\\rtf1\\ansi ClipBored}".utf8)
|
||||
|
||||
let path = try XCTUnwrap(cacheService.cacheRichText(rtfData, id: UUID()))
|
||||
let rawRTF = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(rawRTF))
|
||||
XCTAssertNotEqual(rawRTF, rtfData)
|
||||
XCTAssertEqual(cacheService.data(for: path), rtfData)
|
||||
XCTAssertEqual(try posixPermissions(URL(fileURLWithPath: path)), 0o600)
|
||||
}
|
||||
|
||||
func testLegacyManagedSidecarIsEncryptedAfterRead() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let attachmentDirectory = baseURL.appendingPathComponent("attachments", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: attachmentDirectory, withIntermediateDirectories: true)
|
||||
let url = attachmentDirectory.appendingPathComponent("\(UUID().uuidString).pdf")
|
||||
let pdfData = Data("%PDF-1.4\nlegacy\n%%EOF".utf8)
|
||||
try pdfData.write(to: url)
|
||||
|
||||
XCTAssertEqual(cacheService.data(for: url.path), pdfData)
|
||||
|
||||
let migrated = try Data(contentsOf: url)
|
||||
XCTAssertTrue(ClipboardEncryptionService.isProtected(migrated))
|
||||
XCTAssertFalse(String(decoding: migrated, as: UTF8.self).contains("legacy"))
|
||||
}
|
||||
|
||||
func testTemporaryReadableURLWritesPrivateCopyAndCleanupRemovesIt() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let pdfData = Data("%PDF-1.4\ntemporary preview\n%%EOF".utf8)
|
||||
let path = try XCTUnwrap(cacheService.cachePDF(pdfData, id: UUID()))
|
||||
|
||||
let previewURL = try XCTUnwrap(cacheService.temporaryReadableURL(for: pdfItem(path: path)))
|
||||
|
||||
XCTAssertEqual(try Data(contentsOf: previewURL), pdfData)
|
||||
XCTAssertEqual(try posixPermissions(previewURL.deletingLastPathComponent()), 0o700)
|
||||
XCTAssertEqual(try posixPermissions(previewURL), 0o600)
|
||||
|
||||
cacheService.clearTemporaryPreviews(wait: true)
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: previewURL.path))
|
||||
}
|
||||
|
||||
func testTemporaryReadableURLWorksForAudio() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let audioData = Data([9, 8, 7, 6])
|
||||
let path = try XCTUnwrap(cacheService.cacheAudio(audioData, id: UUID()))
|
||||
|
||||
let previewURL = try XCTUnwrap(cacheService.temporaryReadableURL(for: audioItem(path: path)))
|
||||
|
||||
XCTAssertEqual(try Data(contentsOf: previewURL), audioData)
|
||||
XCTAssertEqual(previewURL.pathExtension, "sound")
|
||||
XCTAssertEqual(try posixPermissions(previewURL), 0o600)
|
||||
}
|
||||
|
||||
func testTemporaryReadableURLWorksForRichText() throws {
|
||||
let baseURL = try makeTempDirectory()
|
||||
let cacheService = ClipboardCacheService(baseURL: baseURL, encryptionService: fixedEncryptionService())
|
||||
let rtfData = Data("{\\rtf1\\ansi Temporary Rich Text}".utf8)
|
||||
let path = try XCTUnwrap(cacheService.cacheRichText(rtfData, id: UUID()))
|
||||
|
||||
let previewURL = try XCTUnwrap(cacheService.temporaryReadableURL(for: richTextItem(path: path)))
|
||||
|
||||
XCTAssertEqual(try Data(contentsOf: previewURL), rtfData)
|
||||
XCTAssertEqual(previewURL.pathExtension, "rtf")
|
||||
XCTAssertEqual(try posixPermissions(previewURL), 0o600)
|
||||
}
|
||||
|
||||
private func makeTempDirectory() throws -> URL {
|
||||
let url = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clipboredtests", isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
|
||||
tempURLs.append(url)
|
||||
return url
|
||||
}
|
||||
|
||||
private func imageCacheFileURLs(in baseURL: URL) throws -> [URL] {
|
||||
let imageDirectory = baseURL.appendingPathComponent("images", isDirectory: true)
|
||||
return try FileManager.default.contentsOfDirectory(at: imageDirectory, includingPropertiesForKeys: nil)
|
||||
}
|
||||
|
||||
private func pdfItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .pdf,
|
||||
displayText: "PDF",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func fileItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .file,
|
||||
displayText: "File",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func audioItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .audio,
|
||||
displayText: "Audio",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func richTextItem(path: String) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .richText,
|
||||
displayText: "Rich Text",
|
||||
payload: path,
|
||||
payloadHash: "hash",
|
||||
createdAt: Date(),
|
||||
lastUsedAt: Date(),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func temporaryPreviewRoot() -> URL {
|
||||
FileManager.default.temporaryDirectory.appendingPathComponent(AppConfiguration.appName, isDirectory: true)
|
||||
}
|
||||
|
||||
private func makeImage(color: NSColor) -> NSImage {
|
||||
let size = NSSize(width: 24, height: 24)
|
||||
let image = NSImage(size: size)
|
||||
image.lockFocus()
|
||||
color.setFill()
|
||||
NSRect(origin: .zero, size: size).fill()
|
||||
image.unlockFocus()
|
||||
return image
|
||||
}
|
||||
|
||||
private func makePDFData() throws -> Data {
|
||||
let data = NSMutableData()
|
||||
guard let consumer = CGDataConsumer(data: data as CFMutableData) else {
|
||||
throw NSError(domain: "ClipBoredTests", code: 1)
|
||||
}
|
||||
var box = CGRect(x: 0, y: 0, width: 160, height: 120)
|
||||
guard let context = CGContext(consumer: consumer, mediaBox: &box, nil) else {
|
||||
throw NSError(domain: "ClipBoredTests", code: 2)
|
||||
}
|
||||
|
||||
context.beginPDFPage(nil)
|
||||
context.setFillColor(NSColor.systemOrange.cgColor)
|
||||
context.fill(CGRect(x: 0, y: 0, width: 160, height: 120))
|
||||
context.setFillColor(NSColor.systemBlue.cgColor)
|
||||
context.fillEllipse(in: CGRect(x: 45, y: 30, width: 70, height: 60))
|
||||
context.endPDFPage()
|
||||
context.closePDF()
|
||||
return data as Data
|
||||
}
|
||||
|
||||
private func posixPermissions(_ url: URL) throws -> Int {
|
||||
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||
return try XCTUnwrap(attributes[.posixPermissions] as? Int) & 0o777
|
||||
}
|
||||
|
||||
private func noOpEncryptionService() -> ClipboardEncryptionService {
|
||||
ClipboardEncryptionService(keyProvider: { nil })
|
||||
}
|
||||
|
||||
private func fixedEncryptionService(byte: UInt8 = 7) -> ClipboardEncryptionService {
|
||||
let keyData = Data(repeating: byte, count: 32)
|
||||
return ClipboardEncryptionService(keyProvider: { SymmetricKey(data: keyData) })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user