migrate persistence from JSON to SwiftData
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

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.
This commit is contained in:
wt committed 2026-08-18 22:58:18 +07:00
1 parent 0317277d46
commit cbb820a86e
30 files changed
+726 -665

No files matched your search

+11 -46
View File
@@ -3,7 +3,7 @@ import XCTest
@testable import Luma
final class MessageInteractionTests: XCTestCase {
func testReplyMetadataRoundTrips() throws {
func testReplyMetadataIsStored() {
let message = ChatMessage(
conversationID: "bob@example.org",
senderJID: "alice@example.org",
@@ -16,12 +16,9 @@ final class MessageInteractionTests: XCTestCase {
replyPreview: "Исходный текст"
)
let data = try JSONEncoder().encode(message)
let decoded = try JSONDecoder().decode(ChatMessage.self, from: data)
XCTAssertEqual(decoded.replyToID, "original-id")
XCTAssertEqual(decoded.replyToJID, "bob@example.org")
XCTAssertEqual(decoded.replyPreview, "Исходный текст")
XCTAssertEqual(message.replyToID, "original-id")
XCTAssertEqual(message.replyToJID, "bob@example.org")
XCTAssertEqual(message.replyPreview, "Исходный текст")
}
func testRetractedMessageCannotBeEditedRepliedOrForwarded() {
@@ -42,35 +39,6 @@ final class MessageInteractionTests: XCTestCase {
XCTAssertEqual(message.previewText, "🚫 Сообщение удалено")
}
func testLegacyMessageWithoutInteractionFieldsStillDecodes() throws {
let original = ChatMessage(
conversationID: "bob@example.org",
senderJID: "alice@example.org",
body: "Старый архив",
direction: .incoming,
delivery: .delivered,
security: .omemo
)
let encoded = try JSONEncoder().encode(original)
var object = try XCTUnwrap(JSONSerialization.jsonObject(with: encoded) as? [String: Any])
for key in [
"replyToID", "replyToJID", "replyPreview", "forwardedFrom", "retractedAt",
"stanzaID", "senderDisplayName", "isGroupMessage", "callHistory"
] {
object.removeValue(forKey: key)
}
let decoded = try JSONDecoder().decode(
ChatMessage.self,
from: JSONSerialization.data(withJSONObject: object)
)
XCTAssertNil(decoded.replyToID)
XCTAssertNil(decoded.retractedAt)
XCTAssertEqual(decoded.body, "Старый архив")
XCTAssertFalse(decoded.isGroupMessage)
}
func testGroupReplyRequiresRoomAssignedStanzaID() {
var message = ChatMessage(
conversationID: "team@conference.example.org",
@@ -92,7 +60,7 @@ final class MessageInteractionTests: XCTestCase {
XCTAssertFalse(message.canBeRetracted)
}
func testCompletedVideoCallHistoryRoundTripsWithDuration() throws {
func testCompletedVideoCallHistoryShowsMetadata() {
let metadata = CallHistoryMetadata(isVideo: true, outcome: .completed)
let message = ChatMessage(
id: "call-alice-123",
@@ -107,14 +75,11 @@ final class MessageInteractionTests: XCTestCase {
callHistory: metadata
)
let data = try JSONEncoder().encode(message)
let decoded = try JSONDecoder().decode(ChatMessage.self, from: data)
XCTAssertEqual(decoded.callHistory, metadata)
XCTAssertEqual(decoded.callTitle, "Исходящий видеозвонок")
XCTAssertEqual(decoded.callSubtitle, "Длительность 1:05")
XCTAssertEqual(decoded.previewText, "🎥 Исходящий видеозвонок")
XCTAssertFalse(decoded.canBeRepliedTo)
XCTAssertFalse(decoded.canBeForwarded)
XCTAssertEqual(message.callHistory, metadata)
XCTAssertEqual(message.callTitle, "Исходящий видеозвонок")
XCTAssertEqual(message.callSubtitle, "Длительность 1:05")
XCTAssertEqual(message.previewText, "🎥 Исходящий видеозвонок")
XCTAssertFalse(message.canBeRepliedTo)
XCTAssertFalse(message.canBeForwarded)
}
}