mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 05:05:19 +00:00
Persist private media delivery receipts
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
// `ChatPrivateConversationCoordinatorContextTests` exemplars.
|
||||
//
|
||||
// Real file/codec work remains covered by `ChatMediaPreparationTests`. These
|
||||
// tests inject a paused voice-note preparer to exercise cancellation ownership
|
||||
// tests inject paused media preparers to exercise cancellation ownership
|
||||
// across the detached-preparation/MainActor boundary deterministically.
|
||||
//
|
||||
|
||||
@@ -216,6 +216,65 @@ private final class PausedVoiceNotePreparer: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private final class PausedImagePreparer: @unchecked Sendable {
|
||||
private let condition = NSCondition()
|
||||
private var started = false
|
||||
private var released = false
|
||||
private var finished = false
|
||||
let outputURL: URL
|
||||
|
||||
init(outputURL: URL) {
|
||||
self.outputURL = outputURL
|
||||
}
|
||||
|
||||
func prepare(_ sourceURL: URL) throws -> ChatPreparedImage {
|
||||
condition.lock()
|
||||
started = true
|
||||
condition.broadcast()
|
||||
while !released {
|
||||
condition.wait()
|
||||
}
|
||||
condition.unlock()
|
||||
|
||||
let content = Data([0xFF, 0xD8, 0xFF, 0xD9])
|
||||
try content.write(to: outputURL, options: .atomic)
|
||||
let prepared = ChatPreparedImage(
|
||||
outputURL: outputURL,
|
||||
packet: BitchatFilePacket(
|
||||
fileName: outputURL.lastPathComponent,
|
||||
fileSize: UInt64(content.count),
|
||||
mimeType: "image/jpeg",
|
||||
content: content
|
||||
)
|
||||
)
|
||||
|
||||
condition.lock()
|
||||
finished = true
|
||||
condition.broadcast()
|
||||
condition.unlock()
|
||||
return prepared
|
||||
}
|
||||
|
||||
var hasStarted: Bool {
|
||||
condition.lock()
|
||||
defer { condition.unlock() }
|
||||
return started
|
||||
}
|
||||
|
||||
var hasFinished: Bool {
|
||||
condition.lock()
|
||||
defer { condition.unlock() }
|
||||
return finished
|
||||
}
|
||||
|
||||
func release() {
|
||||
condition.lock()
|
||||
released = true
|
||||
condition.broadcast()
|
||||
condition.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Coordinator Tests Against Mock Context
|
||||
|
||||
/// Exercises `ChatMediaTransferCoordinator` against
|
||||
@@ -409,6 +468,54 @@ struct ChatMediaTransferCoordinatorContextTests {
|
||||
coordinator.cleanupLocalFile(forMessage: message)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func panicDuringImagePreparationDeletesStaleOutputWithoutSideEffects() async throws {
|
||||
let context = MockChatMediaTransferContext()
|
||||
let peerID = PeerID(str: "99aabbccddeeff00")
|
||||
context.selectedPrivateChatPeer = peerID
|
||||
let sourceURL = try makeCoordinatorTestImageURL()
|
||||
let outputURL = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(
|
||||
"panic-stale-image-\(UUID().uuidString).jpg"
|
||||
)
|
||||
let preparer = PausedImagePreparer(outputURL: outputURL)
|
||||
let coordinator = ChatMediaTransferCoordinator(
|
||||
context: context,
|
||||
prepareImagePacket: { url in try preparer.prepare(url) }
|
||||
)
|
||||
defer {
|
||||
preparer.release()
|
||||
try? FileManager.default.removeItem(at: sourceURL)
|
||||
try? FileManager.default.removeItem(at: outputURL)
|
||||
}
|
||||
|
||||
coordinator.sendImage(from: sourceURL)
|
||||
#expect(await TestHelpers.waitUntil(
|
||||
{ preparer.hasStarted },
|
||||
timeout: TestConstants.longTimeout
|
||||
))
|
||||
|
||||
coordinator.resetForPanic()
|
||||
preparer.release()
|
||||
|
||||
#expect(await TestHelpers.waitUntil(
|
||||
{ preparer.hasFinished },
|
||||
timeout: TestConstants.longTimeout
|
||||
))
|
||||
#expect(await TestHelpers.waitUntil(
|
||||
{ !FileManager.default.fileExists(atPath: outputURL.path) },
|
||||
timeout: TestConstants.longTimeout
|
||||
))
|
||||
#expect(context.privateChats[peerID]?.isEmpty != false)
|
||||
#expect(context.appendedPublicMessages.isEmpty)
|
||||
#expect(context.privateFileSends.isEmpty)
|
||||
#expect(context.broadcastFileSends.isEmpty)
|
||||
#expect(context.systemMessages.isEmpty)
|
||||
#expect(context.deliveryStatusUpdates.isEmpty)
|
||||
#expect(coordinator.transferIdToMessageIDs.isEmpty)
|
||||
#expect(coordinator.messageIDToTransferId.isEmpty)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func cancelVoiceNoteDuringDetachedPreparationCannotSendOrRestoreMapping() async throws {
|
||||
let context = MockChatMediaTransferContext()
|
||||
|
||||
Reference in New Issue
Block a user