field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraMediaOwnershipFFITests.swift (8108B)


      1 import CryptoKit
      2 import Darwin
      3 import Foundation
      4 @testable import TeraApp
      5 import TeraKitBindings
      6 import XCTest
      7 
      8 final class TeraMediaOwnershipFFITests: XCTestCase {
      9   func testCallerCloseBeforeGeneratedConversionPreservesAdmittedBytes() async throws {
     10     let fixture = try MediaOwnershipFixture()
     11     defer { fixture.remove() }
     12     let runtime = try await fixture.runtime()
     13     let opened = try TeraMediaFileFixture.open([fixture.media], bytes: fixture.bytes)
     14     opened.close()
     15     let saved = try await fixture.save(runtime, handle: XCTUnwrap(opened.handles.first))
     16     XCTAssertEqual(saved.form?.media.first?.sha256, fixture.media.sha256)
     17     _ = try await runtime.shutdown()
     18   }
     19 
     20   func testReusingCallerDescriptorBeforeGeneratedConversionCannotSubstituteBytes() async throws {
     21     let fixture = try MediaOwnershipFixture()
     22     defer { fixture.remove() }
     23     let runtime = try await fixture.runtime()
     24     let original = try fixture.original()
     25     defer { try? original.close() }
     26     let handle = try TeraPreparedMediaHandle(media: fixture.media, fileDescriptor: UInt64(original.fileDescriptor))
     27     let replacementURL = fixture.root.appendingPathComponent("replacement")
     28     try Data(repeating: 0, count: fixture.bytes.count).write(to: replacementURL)
     29     let replacement = try FileHandle(forReadingFrom: replacementURL)
     30     defer { try? replacement.close() }
     31     // Both descriptor slots belong exclusively to this fixture. The original
     32     // FileHandle remains the sole closer of the atomically replaced slot.
     33     XCTAssertEqual(dup2(replacement.fileDescriptor, original.fileDescriptor), original.fileDescriptor)
     34     XCTAssertEqual(try original.readToEnd(), Data(repeating: 0, count: fixture.bytes.count))
     35     let saved = try await fixture.save(runtime, handle: handle)
     36     XCTAssertEqual(saved.form?.media.first?.sha256, fixture.media.sha256)
     37     _ = try await runtime.shutdown()
     38   }
     39 
     40   func testCancelledBoundedCallerCanCloseWhileLateForeignWorkRetainsItsFile() async throws {
     41     try await assertLateWork(timeout: false)
     42   }
     43 
     44   func testTimedOutBoundedCallerCanCloseWhileLateForeignWorkRetainsItsFile() async throws {
     45     try await assertLateWork(timeout: true)
     46   }
     47 
     48   func testAdmissionRejectsInvalidAndNonregularDescriptorsSynchronously() throws {
     49     let fixture = try MediaOwnershipFixture()
     50     defer { fixture.remove() }
     51     XCTAssertThrowsError(try TeraPreparedMediaHandle(media: fixture.media, fileDescriptor: .max))
     52     let directory = Darwin.open(fixture.root.path, O_RDONLY)
     53     XCTAssertGreaterThanOrEqual(directory, 0)
     54     guard directory >= 0 else { return }
     55     defer { Darwin.close(directory) }
     56     XCTAssertThrowsError(try TeraPreparedMediaHandle(media: fixture.media, fileDescriptor: UInt64(directory)))
     57   }
     58 
     59   func testPreparedMediaSchemaIsVersionedWithoutChangingDraftSchema() async throws {
     60     let fixture = try MediaOwnershipFixture()
     61     defer { fixture.remove() }
     62     let runtime = try await fixture.runtime()
     63     let opened = try TeraMediaFileFixture.open([fixture.media], bytes: fixture.bytes)
     64     let handle = try XCTUnwrap(opened.handles.first)
     65     var input = fixture.input(handle)
     66     XCTAssertEqual(input.schemaVersion, 1)
     67     XCTAssertEqual(input.media[0].schemaVersion, 2)
     68     input.media[0].schemaVersion = 1
     69     do {
     70       _ = try await runtime.phase1SaveDraft(
     71         draftId: String(repeating: "31", count: 16), input: input,
     72         authoredAtUnixS: 1_800_000_000, expectedRevision: nil, persistedAtUnixMs: 1_800_000_000_000
     73       )
     74       XCTFail("The borrowed media schema must be rejected")
     75     } catch let TeraAppError.Failure(report) {
     76       XCTAssertEqual(report.code, "unsupported_schema_version")
     77     }
     78     _ = try await runtime.shutdown()
     79   }
     80 
     81   private func assertLateWork(timeout: Bool) async throws {
     82     let fixture = try MediaOwnershipFixture()
     83     defer { fixture.remove() }
     84     let runtime = try await fixture.runtime()
     85     let original = try fixture.original()
     86     let handle = try TeraPreparedMediaHandle(media: fixture.media, fileDescriptor: UInt64(original.fileDescriptor))
     87     let opened = TeraOpenedMedia(handles: [handle], files: [original])
     88     let pause = ResourceTestPause()
     89     let task = TeraRuntimeBoundedTask<String>(deadlineNanoseconds: timeout ? 1 : .max) {
     90       await pause.wait()
     91       // Model independently executing foreign work: caller cancellation does
     92       // not establish that the operation stopped or that its resource is free.
     93       return await Task.detached { () -> Result<String, TeraRuntimeFailure> in
     94         do {
     95           let saved = try await fixture.save(runtime, handle: handle)
     96           return .success(saved.form?.media.first?.sha256 ?? "missing")
     97         } catch {
     98           return .failure(TeraRuntimeFailure.local(operation: "fixture", code: "fixture.media.failed", safeMessage: "Fixture failed."))
     99         }
    100       }.value
    101     }
    102     await pause.entered.wait()
    103     if !timeout {
    104       task.cancel()
    105     }
    106     switch await task.value() {
    107     case .timedOut: XCTAssertTrue(timeout)
    108     case .cancelled: XCTAssertFalse(timeout)
    109     case .completed: XCTFail("The foreign work is still paused")
    110     }
    111     await TeraOpenedMediaCloseFixture.closeConcurrently(opened)
    112     XCTAssertNil(task.settlement())
    113     await pause.resume.open()
    114     let late = await task.settle()
    115     XCTAssertEqual(try late.get(), fixture.media.sha256)
    116     _ = try await runtime.shutdown()
    117   }
    118 }
    119 
    120 struct MediaOwnershipFixture: Sendable {
    121   let root: URL
    122   let bytes = Data([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 2, 0, 0, 0, 2])
    123   private let publicKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
    124 
    125   init() throws {
    126     root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
    127     try FileManager.default.createDirectory(at: root.appendingPathComponent("radroots/users/\(publicKey)"), withIntermediateDirectories: true)
    128     try bytes.write(to: root.appendingPathComponent("original.png"))
    129   }
    130 
    131   var media: TeraPreparedMedia {
    132     let hash = SHA256.hash(data: bytes).map { String(format: "%02x", $0) }.joined()
    133     return TeraPreparedMedia(opaqueReference: "media:\(hash)", remoteURL: nil, sha256: hash,
    134                              mediaType: "image/png", byteSize: UInt64(bytes.count), width: 2, height: 2,
    135                              alt: "Synthetic ownership fixture", preparedAtUnixSeconds: 1_800_000_000)
    136   }
    137 
    138   func original() throws -> FileHandle {
    139     try FileHandle(forReadingFrom: root.appendingPathComponent("original.png"))
    140   }
    141 
    142   func remove() {
    143     try? FileManager.default.removeItem(at: root)
    144   }
    145 
    146   func runtime() async throws -> TeraRuntime {
    147     let runtime = try await TeraRuntime(applicationSupportDirectory: root.path, publicKeyHex: publicKey,
    148                                         sourceGenerationHex: String(repeating: "04", count: 32),
    149                                         sourceGenerationCreatedAtUnixMs: 1_800_000_000_000, protectedData: .available)
    150     try await runtime.configureBlossom(hostKind: .simulator, endpointAuthority: .loopbackDevelopment,
    151                                        primaryOrigin: "http://127.0.0.1:3000", fallbackOrigins: [])
    152     return runtime
    153   }
    154 
    155   func save(_ runtime: TeraRuntime, handle: TeraPreparedMediaHandle) async throws -> FfiDraftStatusRecord {
    156     try await runtime.phase1SaveDraft(draftId: String(repeating: "31", count: 16), input: input(handle),
    157                                       authoredAtUnixS: 1_800_000_000, expectedRevision: nil, persistedAtUnixMs: 1_800_000_000_000)
    158   }
    159 
    160   func input(_ handle: TeraPreparedMediaHandle) -> FfiAddDraftInput {
    161     FfiAddDraftInput(schemaVersion: 1, commandType: .createPhotoUpdate, content: "Synthetic ownership fixture",
    162                      identifier: nil, title: nil, summary: nil, location: nil, eventTiming: nil,
    163                      eventStartDate: nil, eventEndDate: nil, eventStartUnixS: nil, eventEndUnixS: nil,
    164                      eventTimezone: nil, priceAmount: nil, currency: nil, unit: nil, quantity: nil,
    165                      foodPublishedAtUnixS: nil, foodStatus: nil, media: [handle.generatedValue])
    166   }
    167 }