RadrootsAppleMobileStoreTests.swift (4899B)
1 import Foundation 2 @testable import RadrootsKit 3 import Testing 4 5 private let publicKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" 6 7 @Test func mobileStorePreparesTheExactIdentityScopedDirectory() throws { 8 let fixture = try MobileStoreFixture() 9 defer { fixture.remove() } 10 11 let configuration = try RadrootsAppleMobileStore.prepare( 12 roots: fixture.roots, 13 publicKeyHex: publicKey, 14 protectedDataAvailability: .available 15 ) 16 17 let expectedOwner = fixture.roots.dataRoot 18 .appendingPathComponent("radroots", isDirectory: true) 19 .appendingPathComponent("users", isDirectory: true) 20 .appendingPathComponent(publicKey, isDirectory: true) 21 .standardizedFileURL 22 #expect(configuration.applicationSupportDirectory == fixture.roots.dataRoot) 23 #expect(configuration.ownerDirectory == expectedOwner) 24 #expect(configuration.protectedDataAvailability == .available) 25 26 // swiftlint:disable trailing_comma 27 let values = try expectedOwner.resourceValues(forKeys: [ 28 .isDirectoryKey, 29 .isSymbolicLinkKey, 30 .isExcludedFromBackupKey, 31 ]) 32 // swiftlint:enable trailing_comma 33 #expect(values.isDirectory == true) 34 #expect(values.isSymbolicLink != true) 35 #expect(values.isExcludedFromBackup == true) 36 } 37 38 @Test func mobileStoreDoesNotTouchDiskWhileProtectedDataIsUnavailable() throws { 39 let fixture = try MobileStoreFixture() 40 defer { fixture.remove() } 41 42 #expect(throws: RadrootsAppleMobileStoreError.protectedDataUnavailable) { 43 _ = try RadrootsAppleMobileStore.prepare( 44 roots: fixture.roots, 45 publicKeyHex: publicKey, 46 protectedDataAvailability: .unavailable 47 ) 48 } 49 #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path)) 50 } 51 52 @Test func mobileStoreRejectsNonCanonicalPublicKeysBeforeTouchingDisk() throws { 53 let fixture = try MobileStoreFixture() 54 defer { fixture.remove() } 55 56 // swiftlint:disable trailing_comma 57 for invalidKey in [ 58 "", "79BE" + String(publicKey.dropFirst(4)), String(repeating: "g", count: 64), 59 ] { 60 #expect(throws: RadrootsAppleMobileStoreError.invalidPublicKey) { 61 _ = try RadrootsAppleMobileStore.prepare( 62 roots: fixture.roots, 63 publicKeyHex: invalidKey, 64 protectedDataAvailability: .available 65 ) 66 } 67 } 68 // swiftlint:enable trailing_comma 69 #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path)) 70 } 71 72 @Test func mobileStoreRejectsAFileInTheDirectoryChain() throws { 73 let fixture = try MobileStoreFixture() 74 defer { fixture.remove() } 75 try FileManager.default.createDirectory( 76 at: fixture.roots.dataRoot, 77 withIntermediateDirectories: true 78 ) 79 let productPath = fixture.roots.dataRoot.appendingPathComponent("radroots") 80 try Data("not a directory".utf8).write(to: productPath) 81 82 #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) { 83 _ = try RadrootsAppleMobileStore.prepare( 84 roots: fixture.roots, 85 publicKeyHex: publicKey, 86 protectedDataAvailability: .available 87 ) 88 } 89 } 90 91 @Test func mobileStoreRejectsASymlinkInTheDirectoryChain() throws { 92 let fixture = try MobileStoreFixture() 93 defer { fixture.remove() } 94 try FileManager.default.createDirectory( 95 at: fixture.roots.dataRoot, 96 withIntermediateDirectories: true 97 ) 98 let target = fixture.root.appendingPathComponent("target", isDirectory: true) 99 try FileManager.default.createDirectory(at: target, withIntermediateDirectories: false) 100 try FileManager.default.createSymbolicLink( 101 at: fixture.roots.dataRoot.appendingPathComponent("radroots"), 102 withDestinationURL: target 103 ) 104 105 #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) { 106 _ = try RadrootsAppleMobileStore.prepare( 107 roots: fixture.roots, 108 publicKeyHex: publicKey, 109 protectedDataAvailability: .available 110 ) 111 } 112 } 113 114 private struct MobileStoreFixture { 115 let root: URL 116 let roots: RadrootsAppleFileRoots 117 118 init() throws { 119 root = FileManager.default.temporaryDirectory 120 .appendingPathComponent("radroots-mobile-store-\(UUID().uuidString)", isDirectory: true) 121 try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false) 122 roots = try RadrootsAppleFileRoots( 123 appIdentifier: "org.radroots.tests", 124 dataRoot: root.appendingPathComponent("data", isDirectory: true), 125 cacheRoot: root.appendingPathComponent("cache", isDirectory: true), 126 temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true) 127 ) 128 } 129 130 func remove() { 131 try? FileManager.default.removeItem(at: root) 132 } 133 }