Files
ghostgram/submodules/Postbox/Tests/MediaBoxFileContextV2MovedFileTests.swift
ichmagmaus111andClaude Opus 5.5 0622057a4f Ghostgram: open-source release
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:57:09 +01:00

196 lines
8.1 KiB
Swift

import Foundation
import XCTest
import SwiftSignalKit
import RangeSet
@testable import Postbox
/// Some fetchers deliver a whole file at once (`.moveLocalFile`, `.moveTempFile`,
/// `.copyLocalItem`) instead of streaming data parts. After such a result the context is
/// complete, and every kind of request must observe that: range requests complete,
/// range status completes, status is Local, and the leftover partial files are gone.
final class MediaBoxFileContextV2MovedFileTests: XCTestCase {
private var queue: Queue!
private var basePath: String!
private var manager: MediaBoxFileManager!
private var context: MediaBoxFileContextV2Impl!
private var fullPath: String { return self.basePath + "/resource" }
private var partialPath: String { return self.basePath + "/resource_partial" }
private var metaPath: String { return self.basePath + "/resource_partial.meta" }
private static let tempBoxInitialized: Bool = {
TempBox.initializeShared(basePath: NSTemporaryDirectory() + "MediaBoxFileContextV2MovedFileTests-TempBox", processType: "tests", launchSpecificId: Int64(Date().timeIntervalSince1970 * 1000))
return true
}()
override func setUp() {
super.setUp()
XCTAssertTrue(MediaBoxFileContextV2MovedFileTests.tempBoxInitialized)
self.queue = Queue(name: "MediaBoxFileContextV2MovedFileTests")
self.basePath = NSTemporaryDirectory() + "MediaBoxFileContextV2MovedFileTests-" + UUID().uuidString
try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true)
self.manager = MediaBoxFileManager(queue: self.queue)
self.queue.sync {
self.context = MediaBoxFileContextV2Impl(
queue: self.queue,
manager: self.manager,
storageBox: nil,
resourceId: "resource".data(using: .utf8)!,
path: self.fullPath,
partialPath: self.partialPath,
metaPath: self.metaPath
)
}
XCTAssertNotNil(self.context)
}
override func tearDown() {
self.queue.sync {
self.context = nil
}
self.manager = nil
let _ = try? FileManager.default.removeItem(atPath: self.basePath)
super.tearDown()
}
// MARK: - Helpers
private typealias Fetch = (Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>
private func fetchDelivering(_ result: MediaResourceDataFetchResult) -> Fetch {
return { _ in
return Signal { subscriber in
subscriber.putNext(result)
subscriber.putCompletion()
return EmptyDisposable
}
}
}
private func writeSourceFile(named name: String, payload: Data) -> String {
let path = self.basePath + "/" + name
XCTAssertTrue(FileManager.default.createFile(atPath: path, contents: payload))
return path
}
private enum Outcome {
case completed
case failed(MediaResourceDataFetchError)
case hung
}
/// Issues a ranged fetch and returns once the completion or error fired.
private func fetch(range: Range<Int64>, with fetch: @escaping Fetch) -> Outcome {
let finished = self.expectation(description: "fetch finished")
var outcome: Outcome = .hung
self.queue.sync {
let _ = self.context.fetched(range: range, priority: .default, fetch: fetch, error: { error in
outcome = .failed(error)
finished.fulfill()
}, completed: {
outcome = .completed
finished.fulfill()
})
}
self.wait(for: [finished], timeout: 5.0)
self.queue.sync {}
return outcome
}
// MARK: - Tests
func testRangeRequestCompletesAfterAMovedLocalFile() {
let payload = Data(repeating: 0x11, count: 20)
let source = self.writeSourceFile(named: "download.tmp", payload: payload)
let outcome = self.fetch(range: 0 ..< 20, with: self.fetchDelivering(.moveLocalFile(path: source)))
guard case .completed = outcome else {
return XCTFail("expected completion, got \(outcome)")
}
XCTAssertEqual(try? Data(contentsOf: URL(fileURLWithPath: self.fullPath)), payload)
}
func testRangeRequestCompletesAfterAMovedTempFile() {
let payload = Data(repeating: 0x22, count: 20)
let tempFile = TempBox.shared.tempFile(fileName: "download.bin")
XCTAssertTrue(FileManager.default.createFile(atPath: tempFile.path, contents: payload))
let outcome = self.fetch(range: 0 ..< 20, with: self.fetchDelivering(.moveTempFile(file: tempFile)))
guard case .completed = outcome else {
return XCTFail("expected completion, got \(outcome)")
}
XCTAssertEqual(try? Data(contentsOf: URL(fileURLWithPath: self.fullPath)), payload)
}
func testRangeStatusCompletesAndStatusIsLocalAfterAMovedFile() {
let payload = Data(repeating: 0x33, count: 20)
let source = self.writeSourceFile(named: "download.tmp", payload: payload)
var rangeStatuses: [RangeSet<Int64>] = []
let rangeStatusCompleted = self.expectation(description: "range status completed")
var statuses: [MediaResourceStatus] = []
self.queue.sync {
let _ = self.context.rangeStatus(next: { ranges in
rangeStatuses.append(ranges)
}, completed: {
rangeStatusCompleted.fulfill()
})
let _ = self.context.status(next: { status in
statuses.append(status)
}, completed: {}, size: 20)
}
let _ = self.fetch(range: 0 ..< 20, with: self.fetchDelivering(.moveLocalFile(path: source)))
self.wait(for: [rangeStatusCompleted], timeout: 5.0)
XCTAssertEqual(rangeStatuses.last, RangeSet(0 ..< 20))
XCTAssertEqual(statuses.last, .Local)
}
func testPartialAndMetaFilesAreRemovedAfterAMovedFile() {
let payload = Data(repeating: 0x44, count: 20)
let source = self.writeSourceFile(named: "download.tmp", payload: payload)
let _ = self.fetch(range: 0 ..< 20, with: self.fetchDelivering(.moveLocalFile(path: source)))
XCTAssertTrue(FileManager.default.fileExists(atPath: self.fullPath))
XCTAssertFalse(FileManager.default.fileExists(atPath: self.partialPath), "partial file must not linger next to the complete file")
XCTAssertFalse(FileManager.default.fileExists(atPath: self.metaPath), "meta file must not linger next to the complete file")
}
func testTrailingSizeUpdateAfterAMoveDoesNotRecreateTheMetaFile() {
let payload = Data(repeating: 0x55, count: 20)
let source = self.writeSourceFile(named: "download.tmp", payload: payload)
// Some fetchers report the size after handing over the file; that trailing
// event must not write a map describing a partial file that no longer exists.
let outcome = self.fetch(range: 0 ..< 20, with: { _ in
return Signal { subscriber in
subscriber.putNext(.moveLocalFile(path: source))
subscriber.putNext(.resourceSizeUpdated(20))
subscriber.putCompletion()
return EmptyDisposable
}
})
guard case .completed = outcome else {
return XCTFail("expected completion, got \(outcome)")
}
XCTAssertTrue(FileManager.default.fileExists(atPath: self.fullPath))
XCTAssertFalse(FileManager.default.fileExists(atPath: self.metaPath), "meta file must not be recreated after the move")
}
func testFailedMoveReportsAnErrorInsteadOfHanging() {
let missingSource = self.basePath + "/does-not-exist.tmp"
let outcome = self.fetch(range: 0 ..< 20, with: self.fetchDelivering(.moveLocalFile(path: missingSource)))
guard case .failed = outcome else {
return XCTFail("expected an error, got \(outcome)")
}
XCTAssertFalse(FileManager.default.fileExists(atPath: self.fullPath))
}
}