apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

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 }