field_ios

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

commit 7f679fa8e0d4c43ed6828f718cd1cd01a2821e68
parent 6fc190f6e7bcbeb5b58a86fed6b31dd0f18225a8
Author: triesap <tyson@radroots.org>
Date:   Tue,  8 Sep 2026 17:51:44 +0000

tera: capture current product equivalence

- exercise both calendar variants through the installed ffi
- preserve saved and queued fields across runtime reopen
- register the native test in the generated xcode project
- verify five-flow publication, donor round trips and native api checks

Diffstat:
MRadroots.xcodeproj/project.pbxproj | 4++++
ARadrootsTests/RadrootsCalendarFFICompatibilityTests.swift | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mproject.yml | 1+
3 files changed, 101 insertions(+), 0 deletions(-)

diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 3BCAFE2B1A96BBE5D741D8FC /* RadrootsSupportingStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41B94CEAFD958CE92D6B9265 /* RadrootsSupportingStoreTests.swift */; }; 3FA27C59441DFE8F1B276642 /* RadrootsClockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF79F087C35C4B0CFD5CAA68 /* RadrootsClockTests.swift */; }; 51FE0A95B71A5D4EDCEB0748 /* RadrootsStateMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26D512B6CBD1A9BE4780682 /* RadrootsStateMigrationTests.swift */; }; + 5372708DA5127DFB175A3828 /* RadrootsCalendarFFICompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969358BEB12ED1EDAAF4F28B /* RadrootsCalendarFFICompatibilityTests.swift */; }; 53C82BC2E86FCB582E8A8B32 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD3E502CA8ADE621038AEB47 /* Localizable.strings */; }; 60B853393C8DF4FDF2C01CAF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 81E991EAE0A0EAB4853747C6 /* PrivacyInfo.xcprivacy */; }; 75C044403AF08F7230DE9485 /* RadrootsRootShellUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF484F988B3D1D3D81880388 /* RadrootsRootShellUITests.swift */; }; @@ -62,6 +63,7 @@ 81E991EAE0A0EAB4853747C6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 8E51B7E9599732A711D60E1B /* RadrootsMediaStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadrootsMediaStoreTests.swift; sourceTree = "<group>"; }; 93AA285819DD1269C3EAD80A /* Radroots.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Radroots.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 969358BEB12ED1EDAAF4F28B /* RadrootsCalendarFFICompatibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadrootsCalendarFFICompatibilityTests.swift; sourceTree = "<group>"; }; 9F54FC4930051FC4611B37D3 /* ios_app */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ios_app; path = .; sourceTree = SOURCE_ROOT; }; B8F89E4B3A775E88F661B891 /* RadrootsRootShellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadrootsRootShellTests.swift; sourceTree = "<group>"; }; C26D512B6CBD1A9BE4780682 /* RadrootsStateMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadrootsStateMigrationTests.swift; sourceTree = "<group>"; }; @@ -99,6 +101,7 @@ isa = PBXGroup; children = ( 42C296DF1E35100FE59C04D3 /* RadrootsAddStoreTests.swift */, + 969358BEB12ED1EDAAF4F28B /* RadrootsCalendarFFICompatibilityTests.swift */, CF79F087C35C4B0CFD5CAA68 /* RadrootsClockTests.swift */, D95CB1F4A964D87D2256DA9C /* RadrootsLifecycleTests.swift */, 8E51B7E9599732A711D60E1B /* RadrootsMediaStoreTests.swift */, @@ -362,6 +365,7 @@ buildActionMask = 2147483647; files = ( 27B7FB991398E2BE7034C72E /* RadrootsAddStoreTests.swift in Sources */, + 5372708DA5127DFB175A3828 /* RadrootsCalendarFFICompatibilityTests.swift in Sources */, 3FA27C59441DFE8F1B276642 /* RadrootsClockTests.swift in Sources */, CD88BB6222151E0B8242F84A /* RadrootsLifecycleTests.swift in Sources */, AEEF096F40BCBC6D66A3BF32 /* RadrootsMediaStoreTests.swift in Sources */, diff --git a/RadrootsTests/RadrootsCalendarFFICompatibilityTests.swift b/RadrootsTests/RadrootsCalendarFFICompatibilityTests.swift @@ -0,0 +1,96 @@ +import Foundation +import RadrootsKitBindings +import XCTest + +final class RadrootsCalendarFFICompatibilityTests: XCTestCase { + private let publicKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + + func testBothCalendarVariantsReopenThroughInstalledFFI() async throws { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("tera-calendar-ffi-\(UUID().uuidString.lowercased())") + try FileManager.default.createDirectory( + at: root.appendingPathComponent("radroots/users/\(publicKey)"), + withIntermediateDirectories: true + ) + defer { try? FileManager.default.removeItem(at: root) } + let first = try await runtime(root) + var queued = [FfiDraftStatusRecord]() + for (index, timing) in [FfiEventTimingKind.allDay, .timed].enumerated() { + let input = calendar(timing) + let saved = try await first.phase1SaveDraft( + draftId: String(repeating: index == 0 ? "01" : "02", count: 16), + input: input, + authoredAtUnixS: 1_900_000_000, + expectedRevision: nil, + persistedAtUnixMs: 1_900_000_000_000 + ) + let form = try XCTUnwrap(saved.form) + XCTAssertEqual(form.commandType, .createEvent) + XCTAssertEqual(form.eventTiming, timing) + XCTAssertEqual(form.eventStartDate, input.eventStartDate) + XCTAssertEqual(form.eventEndDate, input.eventEndDate) + XCTAssertEqual(form.eventStartUnixS, input.eventStartUnixS) + XCTAssertEqual(form.eventEndUnixS, input.eventEndUnixS) + XCTAssertEqual(form.eventTimezone, input.eventTimezone) + let status = try await first.phase1QueueDraft( + draftId: saved.draftId, + expectedRevision: saved.revision, + policy: FfiQueuePolicyRecord( + schemaVersion: 1, + relayUrls: ["wss://relay.example"], + satisfaction: .allAccepted, + deliveryDeadlineUnixMs: 2_000_000_000_000, + cancellation: .localCooperative + ), + queuedAtUnixMs: 1_900_000_000_001 + ) + XCTAssertEqual(status.state, .queued) + XCTAssertNotNil(status.operationId) + XCTAssertEqual(status.form, saved.form) + queued.append(status) + } + _ = try await first.shutdown() + let reopened = try await runtime(root) + for expected in queued { + let restored = try await reopened.phase1DraftStatus(draftId: expected.draftId) + XCTAssertEqual(restored, expected) + } + _ = try await reopened.shutdown() + } + + private func runtime(_ root: URL) async throws -> RadrootsRuntime { + try await RadrootsRuntime( + applicationSupportDirectory: root.path, + publicKeyHex: publicKey, + sourceGenerationHex: String(repeating: "04", count: 32), + sourceGenerationCreatedAtUnixMs: 1_800_000_000_000, + protectedData: .available + ) + } + + private func calendar(_ timing: FfiEventTimingKind) -> FfiAddDraftInput { + let allDay = timing == .allDay + return FfiAddDraftInput( + schemaVersion: 1, + commandType: .createEvent, + content: "Synthetic calendar boundary fixture", + identifier: allDay ? "all-day-fixture" : "timed-fixture", + title: "Synthetic calendar event", + summary: nil, + location: "Fixture location", + eventTiming: timing, + eventStartDate: allDay ? "2030-03-18" : nil, + eventEndDate: allDay ? "2030-03-19" : nil, + eventStartUnixS: allDay ? nil : 1_900_003_600, + eventEndUnixS: allDay ? nil : 1_900_007_200, + eventTimezone: allDay ? nil : "Etc/UTC", + priceAmount: nil, + currency: nil, + unit: nil, + quantity: nil, + foodPublishedAtUnixS: nil, + foodStatus: nil, + media: [] + ) + } +} diff --git a/project.yml b/project.yml @@ -54,6 +54,7 @@ targets: - path: RadrootsTests/RadrootsSupportingStoreTests.swift - path: RadrootsTests/RadrootsLifecycleTests.swift - path: RadrootsTests/RadrootsClockTests.swift + - path: RadrootsTests/RadrootsCalendarFFICompatibilityTests.swift - path: RadrootsTests/RadrootsRemoteQualificationTests.swift - path: RadrootsTests/RadrootsUserMessagesTests.swift settings: