Files
Luma/Tests/ArchiveStoreTests.swift
T
wt cbb820a86e
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
migrate persistence from JSON to SwiftData
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.
2026-08-18 22:58:18 +07:00

121 lines
4.6 KiB
Swift

import SwiftData
import XCTest
@testable import Luma
@MainActor
final class ArchiveStoreTests: XCTestCase {
func testStoreRoundTrip() async throws {
let jid = "test-\(UUID().uuidString)@example.org"
let store = try ArchiveStore(accountJID: jid)
defer { try? store.erase() }
let conversation = Conversation(jid: "bob@example.org", displayName: "Bob")
let message = ChatMessage(
conversationID: conversation.jid,
senderJID: jid,
body: "Encrypted hello",
timestamp: Date(timeIntervalSince1970: 1_700_000_000),
direction: .outgoing,
delivery: .sent,
security: .omemo
)
store.context.insert(conversation)
store.context.insert(message)
try store.save(
locallyDeletedMessageIDs: ["deleted-message-id"],
rosterContactJIDs: [conversation.jid],
lastSuccessfulMAMSync: Date(timeIntervalSince1970: 1_700_000_000),
lastSuccessfulMAMCursor: "mam-cursor-42",
mamCheckpoints: [:]
)
let loaded = store.load()
XCTAssertEqual(loaded.conversations.map(\.jid), [conversation.jid])
XCTAssertEqual(loaded.messages.map(\.clientID), [message.clientID])
XCTAssertEqual(loaded.locallyDeletedMessageIDs, Set(["deleted-message-id"]))
XCTAssertEqual(loaded.rosterContactJIDs, Set([conversation.jid]))
XCTAssertEqual(
loaded.lastSuccessfulMAMSync,
Date(timeIntervalSince1970: 1_700_000_000)
)
XCTAssertEqual(loaded.lastSuccessfulMAMCursor, "mam-cursor-42")
}
func testLegacyImportMapsConversationAndMessage() throws {
let jid = "test-\(UUID().uuidString)@example.org"
let legacy = LegacyArchiveImporter.Snapshot(
schemaVersion: 1,
conversations: [
LegacyArchiveImporter.LegacyConversation(
id: "bob@example.org",
jid: "bob@example.org",
displayName: "Bob",
lastMessage: "",
lastActivity: .distantPast,
unreadCount: 0,
isOnline: false,
isPinned: false,
encryptionPreference: .inheritGlobal,
kind: .direct,
groupNickname: nil,
isGroupJoined: false,
shouldAutojoin: false,
occupantCount: 0,
invitedBy: nil
)
],
messages: [
LegacyArchiveImporter.LegacyMessage(
id: "msg-1",
conversationID: "bob@example.org",
senderJID: jid,
body: "Hello",
timestamp: Date(timeIntervalSince1970: 1_700_000_000),
direction: .outgoing,
delivery: .sent,
security: .omemo,
kind: .text,
remoteAttachmentURL: nil,
localFilename: nil,
mimeType: nil,
duration: nil,
byteCount: nil,
encryptionFingerprint: nil,
editedAt: nil,
replyToID: nil,
replyToJID: nil,
replyPreview: nil,
forwardedFrom: nil,
retractedAt: nil,
originID: nil,
stanzaID: nil,
senderDisplayName: nil,
isGroupMessage: false,
callHistory: nil,
reactions: []
)
],
locallyDeletedMessageIDs: [],
rosterContactJIDs: [],
lastSuccessfulMAMSync: nil,
lastSuccessfulMAMCursor: nil,
mamCheckpoints: [:]
)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .millisecondsSince1970
let data = try encoder.encode(legacy)
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("legacy-\(UUID().uuidString).json")
defer { try? FileManager.default.removeItem(at: url) }
try data.write(to: url)
let imported = try LegacyArchiveImporter.importIfNeeded(from: url)
XCTAssertNotNil(imported)
XCTAssertEqual(imported?.conversations.map(\.jid), ["bob@example.org"])
XCTAssertEqual(imported?.messages.map(\.clientID), ["msg-1"])
XCTAssertEqual(imported?.messages.first?.conversation?.jid, "bob@example.org")
}
}