Models: - Convert Conversation and ChatMessage to SwiftData @Model classes with a one-to-many relationship and cascade delete. - Rename ChatMessage.id to clientID and drop Conversation.id in favor of jid (the string id would clash with PersistentIdentifier). - Keep MessageReaction as a Codable value stored in an array attribute. Persistence: - Add ArchiveStore, a per-account ModelContainer/ModelContext that replaces the JSON ChatArchive with load/save/erase. - Add ArchiveMetadataRecord for the durable MAM checkpoints, cursor, locally deleted message IDs and roster contact JIDs. - Add LegacyArchiveImporter to import the old JSON snapshot once and delete the file afterwards, preserving existing history. AppModel: - Replace ChatArchive with ArchiveStore and persist via context.save(); insert new models and delete the replaced object on upsert merges so SwiftData never keeps an orphaned duplicate. Tests: - Replace ChatArchiveTests with ArchiveStoreTests (store round-trip and legacy import) and drop the now-obsolete Codable round-trip assertions.
86 lines
3.1 KiB
Swift
86 lines
3.1 KiB
Swift
import Foundation
|
|
import XCTest
|
|
@testable import Luma
|
|
|
|
final class MessageInteractionTests: XCTestCase {
|
|
func testReplyMetadataIsStored() {
|
|
let message = ChatMessage(
|
|
conversationID: "bob@example.org",
|
|
senderJID: "alice@example.org",
|
|
body: "Ответ",
|
|
direction: .outgoing,
|
|
delivery: .sent,
|
|
security: .omemo,
|
|
replyToID: "original-id",
|
|
replyToJID: "bob@example.org",
|
|
replyPreview: "Исходный текст"
|
|
)
|
|
|
|
XCTAssertEqual(message.replyToID, "original-id")
|
|
XCTAssertEqual(message.replyToJID, "bob@example.org")
|
|
XCTAssertEqual(message.replyPreview, "Исходный текст")
|
|
}
|
|
|
|
func testRetractedMessageCannotBeEditedRepliedOrForwarded() {
|
|
let message = ChatMessage(
|
|
conversationID: "bob@example.org",
|
|
senderJID: "alice@example.org",
|
|
body: "Удалено",
|
|
direction: .outgoing,
|
|
delivery: .delivered,
|
|
security: .omemo,
|
|
retractedAt: Date()
|
|
)
|
|
|
|
XCTAssertFalse(message.canBeEdited)
|
|
XCTAssertFalse(message.canBeRepliedTo)
|
|
XCTAssertFalse(message.canBeForwarded)
|
|
XCTAssertFalse(message.canBeRetracted)
|
|
XCTAssertEqual(message.previewText, "🚫 Сообщение удалено")
|
|
}
|
|
|
|
func testGroupReplyRequiresRoomAssignedStanzaID() {
|
|
var message = ChatMessage(
|
|
conversationID: "team@conference.example.org",
|
|
senderJID: "team@conference.example.org/alice",
|
|
body: "Сообщение группы",
|
|
direction: .incoming,
|
|
delivery: .delivered,
|
|
security: .omemo,
|
|
senderDisplayName: "Alice",
|
|
isGroupMessage: true
|
|
)
|
|
|
|
XCTAssertFalse(message.canBeRepliedTo)
|
|
message.stanzaID = "room-stable-id"
|
|
XCTAssertTrue(message.canBeRepliedTo)
|
|
XCTAssertEqual(message.replyIdentifier, "room-stable-id")
|
|
XCTAssertEqual(message.security, .omemo)
|
|
XCTAssertFalse(message.canBeEdited)
|
|
XCTAssertFalse(message.canBeRetracted)
|
|
}
|
|
|
|
func testCompletedVideoCallHistoryShowsMetadata() {
|
|
let metadata = CallHistoryMetadata(isVideo: true, outcome: .completed)
|
|
let message = ChatMessage(
|
|
id: "call-alice-123",
|
|
conversationID: "alice@example.org",
|
|
senderJID: "me@example.org",
|
|
body: "Видеозвонок",
|
|
direction: .outgoing,
|
|
delivery: .sent,
|
|
security: .plaintext,
|
|
kind: .system,
|
|
duration: 65,
|
|
callHistory: metadata
|
|
)
|
|
|
|
XCTAssertEqual(message.callHistory, metadata)
|
|
XCTAssertEqual(message.callTitle, "Исходящий видеозвонок")
|
|
XCTAssertEqual(message.callSubtitle, "Длительность 1:05")
|
|
XCTAssertEqual(message.previewText, "🎥 Исходящий видеозвонок")
|
|
XCTAssertFalse(message.canBeRepliedTo)
|
|
XCTAssertFalse(message.canBeForwarded)
|
|
}
|
|
}
|