From e930a0f8ac6df424247672ba032d4656768a16a7 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 11 Dec 2025 14:07:18 -0500 Subject: [PATCH] Do not include release notes in chat exports. --- .../chat_item_simple_updates_05.binproto | Bin 566 -> 579 bytes .../chat_item_simple_updates_06.binproto | Bin 579 -> 557 bytes .../chat_item_simple_updates_07.binproto | Bin 557 -> 579 bytes .../chat_item_simple_updates_08.binproto | Bin 579 -> 579 bytes .../chat_item_simple_updates_09.binproto | Bin 579 -> 579 bytes .../chat_item_simple_updates_10.binproto | Bin 579 -> 579 bytes .../chat_item_simple_updates_11.binproto | Bin 579 -> 557 bytes .../chat_item_simple_updates_12.binproto | Bin 557 -> 557 bytes .../chat_item_simple_updates_13.binproto | Bin 557 -> 557 bytes .../chat_item_simple_updates_14.binproto | Bin 557 -> 557 bytes .../chat_item_simple_updates_15.binproto | Bin 557 -> 0 bytes .../chat_release_notes_00.binproto | Bin 588 -> 0 bytes .../chat_release_notes_01.binproto | Bin 701 -> 0 bytes .../chat_release_notes_02.binproto | Bin 694 -> 0 bytes .../chat_release_notes_03.binproto | Bin 650 -> 0 bytes .../chat_release_notes_04.binproto | Bin 645 -> 0 bytes .../chat_release_notes_05.binproto | Bin 729 -> 0 bytes .../backup/v2/ArchiveImportExportTests.kt | 5 - .../database/ThreadTableArchiveExtensions.kt | 42 +--- .../v2/exporters/ChatItemArchiveExporter.kt | 4 +- .../securesms/database/AttachmentTable.kt | 5 + .../helpers/SignalDatabaseMigrations.kt | 6 +- .../migration/V298_DoNotBackupReleaseNotes.kt | 52 +++++ .../V298_DoNotBackupReleaseNotesTest.kt | 198 ++++++++++++++++++ 24 files changed, 266 insertions(+), 46 deletions(-) delete mode 100644 app/src/androidTest/assets/backupTests/chat_item_simple_updates_15.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_00.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_01.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_02.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_03.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_04.binproto delete mode 100644 app/src/androidTest/assets/backupTests/chat_release_notes_05.binproto create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotes.kt create mode 100644 app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotesTest.kt diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_05.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_05.binproto index e1cd798eaad3e6ed02bf5f57a14ff0c02263ed80..4199f41a8d20ff79e3418114337a9fa2a93050ad 100644 GIT binary patch delta 52 ucmdnSa+qaV&P)qV26mZNE}^_ED8W_hz&ph delta 39 scmX@ivW;bfIFmew6dMPN08;>?sFE;#gfdgBYq01yWPU;qFB delta 31 ncmZ3>a+qa<4wE>$sFE-TlK_jvvDL4)t}_i{sAAz_;$Q~=gjfgE diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_07.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_07.binproto index 629a0edd2be1a4b65e2904d1f2aeef6ab6387c12..2df0e83a9a1d280bd632351981c9912113804bfa 100644 GIT binary patch delta 31 ncmZ3>a+qa<4wE>isFE-TlK_jv@zt-lt}_i{sAAz_;@|`Tgq8={ delta 9 QcmX@ivX*6o4ih5>01yWPU;qFB diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_08.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_08.binproto index e89c066472296fab70383c4a6142eb36e230f4fe..a28639a41e9c7d788a774aff99cacf57380efa98 100644 GIT binary patch delta 31 ncmX@ia+qa<4wE>SsFE-TlK_jviPf*St}_i{sAAz_;@|=Rhh_)z delta 31 ncmX@ia+qa<4wE>isFE-TlK_jviPf*St}_i{sAAz_;@|`ThfW9Z diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_09.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_09.binproto index d4768b50471b4d5a1b645da45e59cfbcdb492299..544296ee475f57aa2d884c5be2cd349662411cdd 100644 GIT binary patch delta 31 ncmX@ia+qa<4wE>ysFE-TlK_jv$SsFE-TlK_jv$KsFE-TlK_jvsnxHyt}_i{sAAz_;@|-Qhq4Fw delta 31 ncmX@ia+qa<4wE>ysFE-TlK_jvsnxHyt}_i{sAAz_;@}1VhnffW diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_11.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_11.binproto index 4775fa305f392e7299c3a1fb153e3f6fd75d82ac..445e694ff4d097b1abefb719ef580405314a3a6a 100644 GIT binary patch delta 9 QcmX@ivX*6o4ih6U01ylUWdHyG delta 31 ncmZ3>a+qa<4wE>KsFE-TlK_jv>D8~dt}_i{sAAz_;@|-Qg%$_n diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_12.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_12.binproto index c2073d0d216df572d2f4c5bca7c91df18f19ae06..e4d64cc13c53cb860996f9d3e1c4bb8b4ddd64f2 100644 GIT binary patch delta 9 QcmZ3>vX*6o4ih6E01rn3Pyhe` delta 9 QcmZ3>vX*6o4ih6U01rk2PXGV_ diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_13.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_13.binproto index 60a7bc5413d40e7920d7b45f9e003b4a9ee63690..83562eae909cf26ec2d5af08c7d024ac0269a1cb 100644 GIT binary patch delta 9 QcmZ3>vX*6o4ih6k01rq4Q2+n{ delta 9 QcmZ3>vX*6o4ih6E01rn3Pyhe` diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_14.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_14.binproto index 7a5c84b23f47fb212d7254925d1d81c08e24d7f0..2817d7b29b29d796589f95020167555015be52c7 100644 GIT binary patch delta 9 QcmZ3>vX*6o4ilpQ01rt5QUCw| delta 9 QcmZ3>vX*6o4ih6k01rq4Q2+n{ diff --git a/app/src/androidTest/assets/backupTests/chat_item_simple_updates_15.binproto b/app/src/androidTest/assets/backupTests/chat_item_simple_updates_15.binproto deleted file mode 100644 index aa2f102aa063ffdd77e80779af390cbf7235a12a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 557 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 GgAD*oCF$4z diff --git a/app/src/androidTest/assets/backupTests/chat_release_notes_03.binproto b/app/src/androidTest/assets/backupTests/chat_release_notes_03.binproto deleted file mode 100644 index a8d8d59a2bcdfa46d23d589bf72313aacbb8340e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zf|)~#je|viDS+`gqtX*b5L4pz;@4Z(nK}t^Y?^y&>1soPZ_j7HSzrosa?~M4AucYC z#JrNs{DQ<>sm+XBa~PF46H8Mv^YwEJj2&(Ydt74&eT z`rrI$YI8)UB?euvo`z3F99IUR0|G|6dVt>?vNe4^f3bjwB1utZn zcyz^5xqB-l&aeElgjtEl)2pyDINK-C-8HC0gTdfh$B!qU4Wks)ANGYF>MPWrWqbE< z%agR9eWK3V=S2JT{Uefhsy{)4RFN>&>FxZC}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zf|)~#je|viDS+`2qtbmw5L4pL;@4Z(nL6=tY@T~+>1soP@6TtySzsD(V;J$kAtr~Do%@U_vcOh=jtE&rJuK$w#+tgn%U6+RZ%5jun&8G%>Q`aD2Sno Jg^P)U4FG_--FW~2 diff --git a/app/src/androidTest/assets/backupTests/chat_release_notes_05.binproto b/app/src/androidTest/assets/backupTests/chat_release_notes_05.binproto deleted file mode 100644 index 0e5dc43b0645bd52daba53f865c1f2bf9bbecdaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 729 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3C}+#osG&J zqAx{nsQlHNEF_S4kZ}>iRVVSzL#z@vJgYuPuqd%O0Zq|jaRVA-rOBo6W_QMM6Jsg8 z#l~lsyPxcF4Ng@`jL`N!7xm!Y3g2_7l0wX(!7dVvUWq_2GfFTjFlsOwFj`EIabiq# zVoY*kO!i<5V2of)V9a1FV60$lU}WrIWSYgu&cP_kz`*by#9{)n{)1V}q745T{_kL9 zJjBSs!7RWm!KA=+gOT9@BjXE3Mh+o14n{2|4sJFf77ivO1{onK4rV1WE&-r9C_suy zf|)~#je|viDS&YslhS4;5L4pr;@4Z(nK}t^Y?*s%>1soPAJ1pMSzrosa@0g7Armg6 zf};G)JcXjvwEUvn%#_5uVuk#o#FET>g`$l7yyVhih0NT7)S{Hk)DnfV{G8I<(qgF_ zj9dp9mAEo<6Vp@mvkFqvtvLN$!`+JHc~YX5BJP3&dq9Ma*XUf6aauAhjTYmu;K!J_$x8 qfg8b}^tWGeFX?)+-qulumqk=b7#wPSKjwctZxqB(#lpqJ!3F?Dxcf8! diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt index e144cb3581..d61388b94a 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ArchiveImportExportTests.kt @@ -66,11 +66,6 @@ class ArchiveImportExportTests { runTests { it.matches(Regex("^chat_%d%d.binproto$")) } } -// @Test - fun chatReleaseNotes() { - runTests { it.startsWith("chat_release_notes_") } - } - // @Test fun chatFolders() { runTests { it.startsWith("chat_folder_") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt index e901fa5ef1..c5f21a3533 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt @@ -5,19 +5,17 @@ package org.thoughtcrime.securesms.backup.v2.database -import org.signal.core.util.SqlUtil -import org.signal.core.util.forEach -import org.signal.core.util.requireInt -import org.signal.core.util.requireLong -import org.signal.core.util.select import org.thoughtcrime.securesms.backup.v2.ExportState import org.thoughtcrime.securesms.backup.v2.exporters.ChatArchiveExporter -import org.thoughtcrime.securesms.database.MessageTable import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.ThreadTable fun ThreadTable.getThreadsForBackup(db: SignalDatabase, exportState: ExportState, includeImageWallpapers: Boolean): ChatArchiveExporter { + val notReleaseNoteClause = exportState.releaseNoteRecipientId?.let { + "AND ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} != $it" + } ?: "" + //language=sql val query = """ SELECT @@ -37,39 +35,9 @@ fun ThreadTable.getThreadsForBackup(db: SignalDatabase, exportState: ExportState LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} WHERE ${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE} NOT IN (${RecipientTable.RecipientType.DISTRIBUTION_LIST.id}, ${RecipientTable.RecipientType.CALL_LINK.id}) + $notReleaseNoteClause """ val cursor = readableDatabase.query(query) return ChatArchiveExporter(cursor, db, exportState, includeImageWallpapers) } - -fun ThreadTable.getThreadGroupStatus(messageIds: Collection): Map { - if (messageIds.isEmpty()) { - return emptyMap() - } - - val out: MutableMap = mutableMapOf() - - val query = SqlUtil.buildFastCollectionQuery("${MessageTable.TABLE_NAME}.${MessageTable.ID}", messageIds) - readableDatabase - .select( - "${MessageTable.TABLE_NAME}.${MessageTable.ID}", - "${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE}" - ) - .from( - """ - ${MessageTable.TABLE_NAME} - INNER JOIN ${ThreadTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ${ThreadTable.TABLE_NAME}.${ThreadTable.ID} - INNER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} - """ - ) - .where(query.where, query.whereArgs) - .run() - .forEach { cursor -> - val messageId = cursor.requireLong(MessageTable.ID) - val type = cursor.requireInt(RecipientTable.TYPE) - out[messageId] = type != RecipientTable.RecipientType.INDIVIDUAL.id - } - - return out -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index b8cafc25cf..fc1d0a827e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -182,7 +182,7 @@ class ChatItemArchiveExporter( val builder = record.toBasicChatItemBuilder(selfRecipientId, extraData.groupReceiptsById[id], exportState, backupStartTime) transformTimer.emit("basic") - if (builder == null) { + if (builder == null || builder.authorId == exportState.releaseNoteRecipientId) { continue } @@ -1639,7 +1639,7 @@ private fun ChatItem.validateChatItem(exportState: ExportState, selfRecipientId: return null } - if (this.incoming != null && this.authorId != exportState.releaseNoteRecipientId && exportState.recipientIdToAci[this.authorId] == null && exportState.recipientIdToE164[this.authorId] == null) { + if (this.incoming != null && exportState.recipientIdToAci[this.authorId] == null && exportState.recipientIdToE164[this.authorId] == null) { Log.w(TAG, ExportSkips.incomingMessageAuthorDoesNotHaveAciOrE164(this.dateSent)) return null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index 8bc36d7902..e1103321c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -3168,6 +3168,10 @@ class AttachmentTable( } private fun buildAttachmentsThatNeedUploadQuery(transferStateFilter: String = "$ARCHIVE_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})"): String { + val notReleaseChannelClause = SignalStore.releaseChannel.releaseChannelRecipientId?.let { + "(${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} != ${it.toLong()}) AND" + } ?: "" + return """ $transferStateFilter AND $DATA_FILE NOT NULL AND @@ -3176,6 +3180,7 @@ class AttachmentTable( $TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND (${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND (${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 OR ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} > ${ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds}) AND + $notReleaseChannelClause $CONTENT_TYPE != '${MediaUtil.LONG_TEXT}' AND ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} = 0 """ diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 6a579073bb..f1f00edcc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -151,6 +151,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V294_RemoveLastReso import org.thoughtcrime.securesms.database.helpers.migration.V295_AddLastRestoreKeyTypeTableIfMissingMigration import org.thoughtcrime.securesms.database.helpers.migration.V296_RemovePollVoteConstraint import org.thoughtcrime.securesms.database.helpers.migration.V297_AddPinnedMessageColumns +import org.thoughtcrime.securesms.database.helpers.migration.V298_DoNotBackupReleaseNotes import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase /** @@ -308,10 +309,11 @@ object SignalDatabaseMigrations { 294 to V294_RemoveLastResortKeyTupleColumnConstraintMigration, 295 to V295_AddLastRestoreKeyTypeTableIfMissingMigration, 296 to V296_RemovePollVoteConstraint, - 297 to V297_AddPinnedMessageColumns + 297 to V297_AddPinnedMessageColumns, + 298 to V298_DoNotBackupReleaseNotes ) - const val DATABASE_VERSION = 297 + const val DATABASE_VERSION = 298 @JvmStatic fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotes.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotes.kt new file mode 100644 index 0000000000..f33213c219 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotes.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import org.signal.core.util.SqlUtil +import org.signal.core.util.logging.Log +import org.signal.core.util.requireLong +import org.thoughtcrime.securesms.database.KeyValueDatabase +import org.thoughtcrime.securesms.database.SQLiteDatabase + +object V298_DoNotBackupReleaseNotes : SignalDatabaseMigration { + + private val TAG = Log.tag(V298_DoNotBackupReleaseNotes::class.java) + + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + val releaseNoteRecipientId = getReleaseNoteRecipientId(context) ?: return + migrateWithRecipientId(db, releaseNoteRecipientId) + } + + fun migrateWithRecipientId(db: SQLiteDatabase, releaseNoteRecipientId: Long) { + db.execSQL( + """ + UPDATE attachment + SET archive_transfer_state = 0 + WHERE message_id IN ( + SELECT _id FROM message WHERE from_recipient_id = $releaseNoteRecipientId + ) + """ + ) + } + + private fun getReleaseNoteRecipientId(context: Application): Long? { + return if (KeyValueDatabase.exists(context)) { + val keyValueDatabase = KeyValueDatabase.getInstance(context).readableDatabase + keyValueDatabase.query("key_value", arrayOf("value"), "key = ?", SqlUtil.buildArgs("releasechannel.recipient_id"), null, null, null).use { cursor -> + if (cursor.moveToFirst()) { + cursor.requireLong("value") + } else { + Log.w(TAG, "Release note channel recipient ID not found in KV database!") + null + } + } + } else { + Log.w(TAG, "Pre-KV database, not doing anything.") + null + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotesTest.kt new file mode 100644 index 0000000000..08ed410908 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V298_DoNotBackupReleaseNotesTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import android.content.ContentValues +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.thoughtcrime.securesms.database.AttachmentTable +import org.thoughtcrime.securesms.testutil.SignalDatabaseMigrationRule + +@Suppress("ClassName") +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class V298_DoNotBackupReleaseNotesTest { + + @get:Rule val signalDatabaseRule = SignalDatabaseMigrationRule(297) + + private val releaseNoteRecipientId = 100L + private val otherRecipientId = 200L + + @Test + fun `migrate - attachments from release note recipient - clears archive transfer state`() { + val messageId = insertMessage(fromRecipientId = releaseNoteRecipientId) + val attachmentId = insertAttachment( + messageId = messageId, + archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value, + archiveCdn = 2 + ) + + runMigration() + + assertArchiveState(attachmentId, expectedState = AttachmentTable.ArchiveTransferState.NONE.value) + } + + @Test + fun `migrate - attachments from other recipient - no change`() { + val messageId = insertMessage(fromRecipientId = otherRecipientId) + val attachmentId = insertAttachment( + messageId = messageId, + archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value, + archiveCdn = 2 + ) + + runMigration() + + assertArchiveState(attachmentId, expectedState = AttachmentTable.ArchiveTransferState.FINISHED.value) + } + + @Test + fun `migrate - multiple attachments from release note recipient - all cleared`() { + val messageId1 = insertMessage(fromRecipientId = releaseNoteRecipientId) + val messageId2 = insertMessage(fromRecipientId = releaseNoteRecipientId) + + val attachmentId1 = insertAttachment( + messageId = messageId1, + archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value, + archiveCdn = 2 + ) + val attachmentId2 = insertAttachment( + messageId = messageId2, + archiveTransferState = AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS.value, + archiveCdn = 3 + ) + + runMigration() + + assertArchiveState(attachmentId1, expectedState = AttachmentTable.ArchiveTransferState.NONE.value) + assertArchiveState(attachmentId2, expectedState = AttachmentTable.ArchiveTransferState.NONE.value) + } + + @Test + fun `migrate - mixed recipients - only release note attachments cleared`() { + val releaseNoteMessageId = insertMessage(fromRecipientId = releaseNoteRecipientId) + val otherMessageId = insertMessage(fromRecipientId = otherRecipientId) + + val releaseNoteAttachmentId = insertAttachment( + messageId = releaseNoteMessageId, + archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value, + archiveCdn = 2 + ) + val otherAttachmentId = insertAttachment( + messageId = otherMessageId, + archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value, + archiveCdn = 2 + ) + + runMigration() + + assertArchiveState(releaseNoteAttachmentId, expectedState = AttachmentTable.ArchiveTransferState.NONE.value) + assertArchiveState(otherAttachmentId, expectedState = AttachmentTable.ArchiveTransferState.FINISHED.value) + } + + private fun runMigration() { + V298_DoNotBackupReleaseNotes.migrateWithRecipientId( + db = signalDatabaseRule.database, + releaseNoteRecipientId = releaseNoteRecipientId + ) + } + + private val insertedRecipients = mutableSetOf() + private val insertedThreads = mutableMapOf() + private var dateSentCounter = System.currentTimeMillis() + + private fun insertRecipient(recipientId: Long): Long { + if (recipientId in insertedRecipients) { + return recipientId + } + + val db = signalDatabaseRule.database + + val values = ContentValues().apply { + put("_id", recipientId) + put("type", 0) + } + + db.insert("recipient", null, values) + insertedRecipients.add(recipientId) + return recipientId + } + + private fun insertThread(recipientId: Long): Long { + insertedThreads[recipientId]?.let { return it } + + val db = signalDatabaseRule.database + + val values = ContentValues().apply { + put("recipient_id", recipientId) + put("date", System.currentTimeMillis()) + } + + val threadId = db.insert("thread", null, values) + insertedThreads[recipientId] = threadId + return threadId + } + + private fun insertMessage(fromRecipientId: Long): Long { + val db = signalDatabaseRule.database + + insertRecipient(fromRecipientId) + val threadId = insertThread(fromRecipientId) + dateSentCounter++ + + val values = ContentValues().apply { + put("date_sent", dateSentCounter) + put("date_received", System.currentTimeMillis()) + put("thread_id", threadId) + put("from_recipient_id", fromRecipientId) + put("to_recipient_id", fromRecipientId) + put("type", 0) + } + + return db.insert("message", null, values) + } + + private fun insertAttachment(messageId: Long, archiveTransferState: Int, archiveCdn: Int?): Long { + val db = signalDatabaseRule.database + + val values = ContentValues().apply { + put("message_id", messageId) + put("data_file", "/fake/path/attachment.jpg") + put("data_random", "/fake/path/attachment.jpg".toByteArray()) + put("transfer_state", 0) + put("archive_transfer_state", archiveTransferState) + if (archiveCdn != null) { + put("archive_cdn", archiveCdn) + } + } + + return db.insert("attachment", null, values) + } + + private fun assertArchiveState(attachmentId: Long, expectedState: Int) { + val db = signalDatabaseRule.database + val cursor = db.query( + "attachment", + arrayOf("archive_transfer_state"), + "_id = ?", + arrayOf(attachmentId.toString()), + null, + null, + null + ) + + cursor.use { + assertThat(it.moveToFirst()).isEqualTo(true) + assertThat(it.getInt(it.getColumnIndexOrThrow("archive_transfer_state"))).isEqualTo(expectedState) + } + } +}