field_ios

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

TeraSupportingStoreTests.swift (8079B)


      1 @testable import TeraApp
      2 import XCTest
      3 
      4 final class TeraSupportingStoreTests: XCTestCase {
      5     @MainActor
      6     func testSearchUsesCurrentContextDeduplicatesAndClearsEmptyQueries() async throws {
      7         let backend = SupportingBackend()
      8         let client = try await Self.startedClient(backend)
      9         let store = TeraSearchStore(
     10           runtimeClient: client,
     11           clock: .fixed(unixSeconds: 1_800_000_000)
     12         )
     13         store.configure(context: Self.context(id: "farm"))
     14         store.updateQuery(" carrots ")
     15 
     16         await store.search()
     17 
     18         XCTAssertEqual(store.state, .loaded)
     19         XCTAssertEqual(store.results.map(\.id), ["card", "profile"])
     20         let request = await backend.lastSearchRequest()
     21         XCTAssertEqual(request?.contextID, "farm")
     22         XCTAssertEqual(request?.query, "carrots")
     23         store.updateQuery("   ")
     24         XCTAssertEqual(store.state, .idle)
     25         XCTAssertTrue(store.results.isEmpty)
     26         _ = try await client.stop()
     27     }
     28 
     29     @MainActor
     30     func testSearchContextChangeFencesLateResults() async throws {
     31         let backend = SupportingBackend(searchDelayNanoseconds: 40_000_000)
     32         let client = try await Self.startedClient(backend)
     33         let store = TeraSearchStore(
     34           runtimeClient: client,
     35           clock: .fixed(unixSeconds: 1_800_000_000)
     36         )
     37         store.configure(context: Self.context(id: "first"))
     38         store.updateQuery("carrots")
     39 
     40         let search = Task { await store.search() }
     41         try await Task.sleep(nanoseconds: 2_000_000)
     42         store.configure(context: Self.context(id: "second"))
     43         await search.value
     44 
     45         XCTAssertTrue(store.results.isEmpty)
     46         XCTAssertEqual(store.state, .idle)
     47         _ = try await client.stop()
     48     }
     49 
     50     @MainActor
     51     func testMePreservesAdoptedProfileFieldsAndCurrentCards() async throws {
     52         let backend = SupportingBackend()
     53         let client = try await Self.startedClient(backend)
     54         let store = TeraMeStore(
     55           runtimeClient: client,
     56           clock: .fixed(unixSeconds: 1_800_000_000)
     57         )
     58         store.configure(context: Self.context(id: "farm"))
     59 
     60         await store.start()
     61 
     62         let snapshot = try XCTUnwrap(store.snapshot)
     63         XCTAssertEqual(snapshot.profile?.displayName, "Moss Farm")
     64         XCTAssertEqual(snapshot.profile?.about, "Local roots")
     65         XCTAssertEqual(snapshot.profile?.nip05, "moss@example.com")
     66         XCTAssertEqual(snapshot.profile?.website, "https://moss.example")
     67         XCTAssertEqual(snapshot.profile?.lightningAddress, "moss@example.com")
     68         XCTAssertEqual(snapshot.cards.map(\.id), ["card"])
     69         store.stop()
     70         _ = try await client.stop()
     71     }
     72 
     73     func testVisualIdentityIsStableAndKeyBound() {
     74         let first = TeraStableVisualIdentity(publicKeyHex: String(repeating: "a", count: 64))
     75         let repeated = TeraStableVisualIdentity(publicKeyHex: String(repeating: "a", count: 64))
     76         let second = TeraStableVisualIdentity(publicKeyHex: String(repeating: "b", count: 64))
     77 
     78         XCTAssertEqual(first, repeated)
     79         XCTAssertNotEqual(first.digestHex, second.digestHex)
     80         XCTAssertTrue((0 ..< 12).contains(first.paletteIndex))
     81     }
     82 
     83     @MainActor
     84     func testSettingsRoundTripUsesTypedRuntimeAndReportsReconfigurationEffects() async throws {
     85         let backend = SupportingBackend()
     86         let client = try await Self.startedClient(backend)
     87         let store = TeraSettingsStore(runtimeClient: client)
     88 
     89         await store.load(profile: nil)
     90         XCTAssertEqual(store.settings?.identity.identities.count, 1)
     91         store.networkEnvironment = .physicalDevice
     92         store.relays = [
     93           TeraRelayPreference(url: "wss://radroots.org/", access: .readWrite),
     94           TeraRelayPreference(url: "wss://read.example/", access: .readOnly),
     95         ]
     96         store.blossomPrimaryOrigin = "https://blossom.radroots.org"
     97         store.allowCellularUploads = false
     98         store.mediaCacheMegabytes = 512
     99         store.mediaCacheArtifacts = 2000
    100 
    101         let restartRequired = await store.saveSettings()
    102 
    103         XCTAssertTrue(restartRequired)
    104         XCTAssertEqual(store.settings?.revision, 2)
    105         XCTAssertEqual(store.settings?.relays.map(\.access), [.readWrite, .readOnly])
    106         XCTAssertEqual(store.settings?.mediaCacheBytes, 512 * 1_048_576)
    107         XCTAssertEqual(store.settings?.mediaCacheArtifacts, 2000)
    108         XCTAssertEqual(store.message, "Settings saved; required changes: runtime restart, outbox requeue, media cache refresh.")
    109         XCTAssertNil(store.failureCode)
    110         _ = try await client.stop()
    111     }
    112 
    113     @MainActor
    114     func testInvalidSettingsFailClosedWithoutReplacingLastAcceptedState() async throws {
    115         let backend = SupportingBackend()
    116         let client = try await Self.startedClient(backend)
    117         let store = TeraSettingsStore(runtimeClient: client)
    118         await store.load(profile: nil)
    119         store.relays = [TeraRelayPreference(url: "https://not-a-relay.example", access: .readWrite)]
    120 
    121         let restartRequired = await store.saveSettings()
    122 
    123         XCTAssertFalse(restartRequired)
    124         XCTAssertEqual(store.failureCode, "invalid_relay_endpoint")
    125         XCTAssertEqual(store.settings?.revision, 1)
    126         let revision = await backend.settingsRevision()
    127         XCTAssertEqual(revision, 1)
    128         _ = try await client.stop()
    129     }
    130 
    131     @MainActor
    132     func testProfileEditingUsesDurableTypedStatus() async throws {
    133         let backend = SupportingBackend()
    134         let client = try await Self.startedClient(backend)
    135         let store = TeraSettingsStore(runtimeClient: client)
    136         store.profileName = "moss"
    137         store.profileDisplayName = "Moss Farm"
    138         store.profileAbout = "Local roots"
    139         store.profileNip05 = "moss@example.com"
    140 
    141         await store.saveProfile()
    142         XCTAssertEqual(store.profileStatus?.state, .queued)
    143         await store.advanceProfile()
    144         XCTAssertEqual(store.profileStatus?.state, .complete)
    145         XCTAssertEqual(store.profileStatus?.settlement?.deliverySatisfied, 1)
    146         _ = try await client.stop()
    147     }
    148 
    149     static func startedClient(_ backend: SupportingBackend) async throws -> TeraRuntimeClient {
    150         let client = TeraRuntimeClient { _ in
    151             await TeraRuntimeBackendStart(backend: backend, snapshot: backend.snapshotValue())
    152         }
    153         _ = try await client.start(configuration: configuration())
    154         return client
    155     }
    156 
    157     private static func configuration() -> TeraRuntimeLaunchConfiguration {
    158         TeraRuntimeLaunchConfiguration(
    159           applicationSupportDirectory: "/tmp/radroots-supporting-tests",
    160           publicKeyHex: String(repeating: "a", count: 64),
    161           sourceGenerationHex: String(repeating: "c", count: 64),
    162           sourceGenerationCreatedAtUnixMilliseconds: 1,
    163           protectedData: .available,
    164           networkProfile: .simulator,
    165           writableRelays: ["ws://127.0.0.1:7447"],
    166           blossom: TeraBlossomEndpointConfiguration(
    167             hostKind: .simulator,
    168             endpointAuthority: .loopbackDevelopment,
    169             primaryOrigin: "http://127.0.0.1:3000",
    170             fallbackOrigins: []
    171           ),
    172           app: TeraRuntimeAppMetadata(
    173             bundleIdentifier: "org.radroots.supporting-tests",
    174             version: "0.1.0-alpha",
    175             buildNumber: "1",
    176             buildSHA: nil
    177           ),
    178           signerGeneration: "supporting-tests",
    179           signer: SupportingSigner(),
    180           adoptBootstrapSettings: false
    181         )
    182     }
    183 
    184     private static func context(id: String) -> TeraLocalNetwork {
    185         TeraLocalNetwork(
    186           schemaVersion: 1,
    187           id: id,
    188           label: "Farm",
    189           relayURLs: ["ws://127.0.0.1:7447"],
    190           locality: "Metchosin",
    191           followedAuthors: [],
    192           generation: 1
    193         )
    194     }
    195 }
    196 
    197 private struct SupportingSigner: TeraRuntimeSigner {
    198     func availability() -> TeraRuntimeSignerAvailability {
    199         .ready
    200     }
    201 
    202     func sign(_: TeraRuntimeSigningRequest) -> TeraRuntimeSigningOutcome {
    203         .failed
    204     }
    205 }