Files
ghostgram/submodules/Postbox/Tests/MessageHistoryTableMediaReferenceTests.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

136 lines
6.2 KiB
Swift

import Foundation
import XCTest
@testable import Postbox
/// A media shared by two or more messages lives in one `Direct` row whose count is
/// the number of messages referencing it. Every write path that touches a message
/// must leave that count equal to the number of live referencing messages, or the
/// row (and for secret chats, its key) is never freed.
final class MessageHistoryTableMediaReferenceTests: XCTestCase {
private var fixture: MessageHistoryTableFixture!
private let peer: Int64 = 100
private let sharedMediaId = MediaId(namespace: 0, id: 555)
override func setUp() {
super.setUp()
self.fixture = MessageHistoryTableFixture(name: "MessageHistoryTableMediaReferenceTests")
}
override func tearDown() {
self.fixture.close()
self.fixture = nil
super.tearDown()
}
// MARK: - Helpers
private func message(_ id: Int32, text: String = "", media: [Media]) -> StoreMessage {
return MessageHistoryTableFixture.storeMessage(id: MessageHistoryTableFixture.messageId(peer: self.peer, id: id), timestamp: 1000 + id, text: text, media: media)
}
private func shared() -> FixtureMedia {
return FixtureMedia(id: self.sharedMediaId, label: "sticker")
}
/// Two messages referencing the same media: the row is `Direct` with count 2.
private func addTwoMessagesSharingTheMedia() {
self.fixture.addMessages([self.message(1, media: [self.shared()]), self.message(2, media: [self.shared()])])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 2)
}
// MARK: - Tests
func testReAddingAnUnchangedMessageKeepsTheSharedReferenceCount() {
self.addTwoMessagesSharingTheMedia()
// An overlapping history fetch delivers message 1 again, byte-for-byte the same.
self.fixture.addMessages([self.message(1, media: [self.shared()])])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 2)
}
func testEditingAMessageInPlaceKeepsTheSharedReferenceCount() {
self.addTwoMessagesSharingTheMedia()
// An edit changes the text but keeps the same media and the same index.
self.fixture.updateMessage(MessageHistoryTableFixture.messageId(peer: self.peer, id: 1), message: self.message(1, text: "edited", media: [self.shared()]))
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 2)
}
func testSharedMediaRowIsFreedOnceEveryReferencingMessageIsGone() {
self.addTwoMessagesSharingTheMedia()
self.fixture.addMessages([self.message(1, media: [self.shared()])])
self.fixture.updateMessage(MessageHistoryTableFixture.messageId(peer: self.peer, id: 2), message: self.message(2, text: "edited", media: [self.shared()]))
self.fixture.removeMessages([MessageHistoryTableFixture.messageId(peer: self.peer, id: 1), MessageHistoryTableFixture.messageId(peer: self.peer, id: 2)])
XCTAssertNil(self.fixture.mediaEntry(self.sharedMediaId), "no message references the media any more, so its row must be gone")
}
func testChangingHowOftenAMessageReferencesAMediaKeepsTheCountExact() {
self.addTwoMessagesSharingTheMedia()
// The same media id twice in one message counts twice, so the multiplicity
// matters even though the set of ids is unchanged.
self.fixture.addMessages([self.message(1, media: [self.shared(), self.shared()])])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 3)
self.fixture.addMessages([self.message(1, media: [self.shared()])])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 2)
self.fixture.addMessages([self.message(1, media: [self.shared(), self.shared()])])
self.fixture.removeMessages([MessageHistoryTableFixture.messageId(peer: self.peer, id: 1)])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 1, "message 2 still references the media")
}
// MARK: - Guards: the paths that already worked must keep working
func testChangingAMessagesTimestampKeepsTheSharedReferenceCount() {
self.addTwoMessagesSharingTheMedia()
// A sent message gets its server timestamp: same media, different index.
self.fixture.updateMessageTimestamp(MessageHistoryTableFixture.messageId(peer: self.peer, id: 1), timestamp: 5000)
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 2)
self.fixture.removeMessages([MessageHistoryTableFixture.messageId(peer: self.peer, id: 1)])
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 1)
}
func testReplacingAMessagesMediaReleasesTheOldReference() {
self.addTwoMessagesSharingTheMedia()
let other = FixtureMedia(id: MediaId(namespace: 0, id: 777), label: "photo")
self.fixture.updateMessage(MessageHistoryTableFixture.messageId(peer: self.peer, id: 1), message: self.message(1, media: [other]))
XCTAssertEqual(self.fixture.referenceCount(self.sharedMediaId), 1)
XCTAssertNotNil(self.fixture.mediaEntry(other.id!))
}
func testReAddingAMessageWithEmbeddedMediaKeepsItEmbedded() {
let only = FixtureMedia(id: MediaId(namespace: 0, id: 888), label: "photo")
self.fixture.addMessages([self.message(3, media: [only])])
self.fixture.addMessages([self.message(3, media: [only])])
guard case .MessageReference(let index)? = self.fixture.mediaEntry(only.id!) else {
return XCTFail("a media referenced by one message stays embedded in it")
}
XCTAssertEqual(index.id, MessageHistoryTableFixture.messageId(peer: self.peer, id: 3))
self.fixture.removeMessages([MessageHistoryTableFixture.messageId(peer: self.peer, id: 3)])
XCTAssertNil(self.fixture.mediaEntry(only.id!))
}
func testSecondMessageSharingAnEmbeddedMediaPromotesItToACountedRow() {
let only = FixtureMedia(id: MediaId(namespace: 0, id: 999), label: "gif")
self.fixture.addMessages([self.message(4, media: [only])])
self.fixture.addMessages([self.message(5, media: [only])])
XCTAssertEqual(self.fixture.referenceCount(only.id!), 2)
self.fixture.removeMessages([MessageHistoryTableFixture.messageId(peer: self.peer, id: 4)])
XCTAssertEqual(self.fixture.referenceCount(only.id!), 1)
}
}