TeraVisibilityTests.swift (5644B)
1 import Foundation 2 @testable import TeraApp 3 import XCTest 4 5 @MainActor 6 final class TeraVisibilityTests: XCTestCase { 7 func testChangeClearsEveryVisibleStoreAndRejectsLateSearchAndMedia() async throws { 8 let backend = try TeraScopeBackend() 9 let client = try await TeraScopeFixtures.client(backend) 10 let stores = TeraProductStores(runtimeClient: client) 11 stores.configure(snapshot: TeraScopeFixtures.snapshot()) 12 let context = try XCTUnwrap(stores.today.selectedContext) 13 await stores.today.reload(refreshProjection: false) 14 let card = try XCTUnwrap(stores.today.cards.first) 15 stores.search.updateQuery("old") 16 await stores.search.search() 17 await stores.me.reload() 18 XCTAssertFalse(stores.search.results.isEmpty) 19 XCTAssertNotNil(stores.me.snapshot) 20 let search = await backend.pause(.search) 21 let searching = Task { await stores.search.search() } 22 await search.entered.wait() 23 let media = await backend.pause(.media) 24 let reference = TeraScopeFixtures.reference() 25 stores.media.load(media: reference, context: context) 26 await media.entered.wait() 27 let change = ResourceTestPause() 28 await backend.visibilityStorage.hold(change) 29 let oldScope = stores.today.scopeGeneration 30 let changing = Task { await stores.visibility.change(author: card.authorPublicKey, to: .blocked) } 31 await change.entered.wait() 32 XCTAssertTrue(stores.today.cards.isEmpty) 33 XCTAssertNil(stores.today.currentCard(id: card.id)) 34 XCTAssertNotEqual(stores.today.scopeGeneration, oldScope) 35 XCTAssertTrue(stores.search.results.isEmpty) 36 XCTAssertNil(stores.me.snapshot) 37 await backend.setPage(TeraTodayPage(asOfUnixSeconds: 1, items: [], nextCursor: nil, calendar: TeraScopeFixtures.viewerCalendar(asOf: 1))) 38 await change.resume.open() 39 await changing.value 40 await search.resume.open() 41 await searching.value 42 await media.resume.open() 43 XCTAssertTrue(stores.search.results.isEmpty) 44 XCTAssertTrue(stores.today.cards.isEmpty) 45 XCTAssertNotEqual(stores.media.state(for: reference, context: context), try .ready(TeraScopeFixtures.artifact("a"))) 46 XCTAssertEqual(stores.visibility.policy?.entries.first?.visibility, .blocked) 47 let counts = await backend.counts 48 XCTAssertNil(counts[.refresh], "Visibility changes only re-read local data") 49 XCTAssertEqual(counts[.media], 1) 50 stores.stop() 51 _ = try await client.stop() 52 } 53 54 func testUnconfirmedChangeRetainsClearedPresentation() async throws { 55 let backend = try TeraScopeBackend() 56 let client = try await TeraScopeFixtures.client(backend) 57 let stores = TeraProductStores(runtimeClient: client) 58 stores.configure(snapshot: TeraScopeFixtures.snapshot()) 59 await stores.today.reload(refreshProjection: false) 60 XCTAssertFalse(stores.today.cards.isEmpty) 61 await backend.visibilityStorage.fail() 62 await stores.visibility.change(author: String(repeating: "a", count: 64), to: .muted) 63 XCTAssertTrue(stores.today.cards.isEmpty) 64 XCTAssertNil(stores.visibility.policy) 65 XCTAssertNotNil(stores.visibility.message) 66 XCTAssertFalse(stores.visibility.isWorking) 67 stores.stop() 68 _ = try await client.stop() 69 } 70 71 func testRealGeneratedPolicyPersistsAndExplicitRestoreSurvivesRestart() async throws { 72 let fixture = try OfflineMediaFixture() 73 defer { fixture.remove() } 74 let client = TeraRuntimeClient.production() 75 let signer = ComposerForbiddenSigner() 76 let configuration = fixture.configuration(signer) 77 let snapshot = try await client.start(configuration: configuration) 78 let author = snapshot.identity.publicKeyHex 79 let blocked = try await client.setAuthorVisibility(author: author, visibility: .blocked) 80 XCTAssertEqual(blocked.entries, [TeraAuthorVisibilityEntry(author: author, visibility: .blocked)]) 81 XCTAssertFalse(String(reflecting: blocked).contains(author)) 82 XCTAssertFalse(String(reflecting: blocked.entries).contains(author)) 83 _ = try await client.stop() 84 _ = try await client.start(configuration: configuration) 85 let loaded = try await client.authorVisibility() 86 XCTAssertEqual(loaded, blocked) 87 let muted = try await client.setAuthorVisibility(author: author, visibility: .muted) 88 XCTAssertEqual(muted.revision, blocked.revision + 1) 89 let restored = try await client.setAuthorVisibility(author: author, visibility: .visible) 90 XCTAssertTrue(restored.entries.isEmpty) 91 XCTAssertEqual(restored.revision, muted.revision + 1) 92 let signs = await signer.requests 93 XCTAssertEqual(signs, 0) 94 _ = try await client.stop() 95 } 96 } 97 98 actor VisibilityTestStorage { 99 private var policy = TeraAuthorVisibilityPolicy(revision: 0, entries: []) 100 private var pause: ResourceTestPause? 101 private var failed = false 102 func hold(_ value: ResourceTestPause) { 103 pause = value 104 } 105 106 func fail() { 107 failed = true 108 } 109 110 func read() -> TeraAuthorVisibilityPolicy { 111 policy 112 } 113 114 func change(author: String, to visibility: TeraAuthorVisibility) async throws -> TeraAuthorVisibilityPolicy { 115 await pause?.wait() 116 if failed { 117 throw TeraComposerAcknowledgment.unconfirmed 118 } 119 policy = TeraAuthorVisibilityPolicy(revision: policy.revision + 1, entries: visibility == .visible ? [] : [.init(author: author, visibility: visibility)]) 120 return policy 121 } 122 } 123 124 extension TeraScopeBackend { 125 func authorVisibility() async -> TeraAuthorVisibilityPolicy { 126 await visibilityStorage.read() 127 } 128 129 func setAuthorVisibility(author: String, visibility: TeraAuthorVisibility) async throws -> TeraAuthorVisibilityPolicy { 130 try await visibilityStorage.change(author: author, to: visibility) 131 } 132 }