Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/test/Storage/InfoMessageGroupUpdateMigratorTest.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import GRDB
import LibSignalClient
import XCTest

@testable import Signal
@testable import SignalServiceKit

class InfoMessageGroupUpdateMigratorTest: SignalBaseTest {

    func testGroupUpdateMigration() async {
        let localIdentifiers: LocalIdentifiers = .forUnitTests
        let localAci = localIdentifiers.aci

        let db = InMemoryDB()
        let modelReadCaches: () -> ModelReadCaches = {
            return ModelReadCaches(factory: ModelReadCacheFactory(appReadiness: AppReadinessMock()))
        }
        let tsAccountManager: () -> TSAccountManager = {
            let mgr = MockTSAccountManager()
            mgr.localIdentifiersMock = { localIdentifiers }
            return mgr
        }

        await db.awaitableWrite { tx in
            insertInfoMessage(infoMessageUserInfo: nil, tx: tx)
            insertInfoMessage(
                infoMessageUserInfo: [
                    .legacyGroupUpdateItems: TSInfoMessage.LegacyPersistableGroupUpdateItemsWrapper(
                        [.inviteRemoved(invitee: ServiceIdUppercaseString(wrappedValue: Pni.randomForTesting()), wasLocalUser: true)],
                    ),
                    .legacyUpdaterKnownToBeLocalUser: false,
                    .groupUpdateSourceLegacyAddress: SignalServiceAddress.isolatedRandomForTesting(),
                ],
                tx: tx,
            )
            insertInfoMessage(infoMessageUserInfo: nil, tx: tx)
            insertInfoMessage(
                infoMessageUserInfo: [.newGroupModel: TSGroupModelV2.forMigrationTest(name: "grüp", localAci: localAci)],
                tx: tx,
            )
            insertInfoMessage(infoMessageUserInfo: nil, tx: tx)
            insertInfoMessage(
                infoMessageUserInfo: [
                    .oldGroupModel: TSGroupModelV2.forMigrationTest(name: "grop", localAci: localAci),
                    .oldDisappearingMessageToken: DisappearingMessageToken(isEnabled: true, durationSeconds: 10),
                    .newGroupModel: TSGroupModelV2.forMigrationTest(name: "grüp", localAci: localAci),
                    .newDisappearingMessageToken: DisappearingMessageToken(isEnabled: true, durationSeconds: 12),
                    .groupUpdateSourceLegacyAddress: SignalServiceAddress.isolatedRandomForTesting(),
                ],
                tx: tx,
            )
            insertInfoMessage(infoMessageUserInfo: nil, tx: tx)
        }

        let migrator = InfoMessageGroupUpdateMigrator(
            db: db,
            modelReadCaches: modelReadCaches,
            tsAccountManager: tsAccountManager,
        )
        try! await migrator.run()

        db.read { tx in
            let infoMessageUserInfoBlobs: [Data] = try! Row.fetchAll(
                tx.database,
                sql: """
                    SELECT infoMessageUserInfo FROM model_TSInteraction
                """,
            ).compactMap { $0["infoMessageUserInfo"] }

            XCTAssertEqual(infoMessageUserInfoBlobs.count, 3)

            for blob in infoMessageUserInfoBlobs {
                let keys = Set(decode(blob).keys)
                XCTAssertEqual(keys, [.groupUpdateItems])
            }
        }
    }

    private func insertInfoMessage(
        infoMessageUserInfo: [InfoMessageUserInfoKey: Any]?,
        tx: DBWriteTransaction,
    ) {
        try! tx.database.execute(
            sql: """
                INSERT INTO model_TSInteraction
                (recordType, uniqueId, receivedAtTimestamp, timestamp, uniqueThreadId, infoMessageUserInfo)
                VALUES (?, ?, 0, 0, ?, ?)
            """,
            arguments: [SDSRecordType.infoMessage.rawValue, UUID().uuidString, UUID().uuidString, infoMessageUserInfo.map { encode($0) }],
        )
    }

    private func encode(_ infoMessageUserInfo: [InfoMessageUserInfoKey: Any]) -> Data {
        return try! NSKeyedArchiver.archivedData(
            withRootObject: infoMessageUserInfo,
            requiringSecureCoding: true,
        )
    }

    private func decode(_ data: Data) -> [InfoMessageUserInfoKey: AnyObject] {
        return try! SDSDeserialization.unarchivedInfoDictionary(from: data)
    }
}

// MARK: -

private extension TSGroupModelV2 {
    static func forMigrationTest(
        name: String,
        localAci: Aci,
    ) -> TSGroupModelV2 {
        return TSGroupModelV2(
            groupId: Data(repeating: 8, count: 32),
            name: name,
            descriptionText: nil,
            avatarDataState: .missing,
            groupMembership: GroupMembership(membersForTest: [SignalServiceAddress.isolatedForTesting(serviceId: localAci)]),
            groupAccess: .defaultForV2,
            revision: 3,
            secretParamsData: Data(repeating: 9, count: 10),
            avatarUrlPath: nil,
            inviteLinkPassword: nil,
            isAnnouncementsOnly: false,
            isJoinRequestPlaceholder: false,
            wasJustMigrated: false,
            didJustAddSelfViaGroupLink: false,
            addedByAddress: nil,
            isTerminated: false,
        )
    }
}