From a47aeebc1bf2da3be326ac8e5ccaefa5c47fed06 Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Wed, 28 Aug 2024 10:10:48 -0700 Subject: [PATCH] Replace individual Backup integration test cases with auto-iteration --- Signal.xcodeproj/project.pbxproj | 100 +--------- SignalServiceKit/Environment/AppSetup.swift | 7 +- .../MessageBackupProtoStreamProvider.swift | 160 +++++++++------ .../MessageBackupManagerImpl.swift | 17 +- .../MessageBackupAccountDataTest.swift | 54 ----- .../MessageBackupContactTest.swift | 87 --------- .../MessageBackupDistributionListTest.swift | 63 ------ ...eBackupExpirationTimerChatUpdateTest.swift | 56 ------ ...geBackupIncomingMessageWithEditsTest.swift | 139 ------------- ...ft => MessageBackupIntegrationTests.swift} | 163 ++++++++++------ ...geBackupLearnedProfileChatUpdateTest.swift | 52 ----- ...geBackupOutgoingMessageWithEditsTest.swift | 109 ----------- ...ageBackupProfileChangeChatUpdateTest.swift | 46 ----- ...ackupSessionSwitchoverChatUpdateTest.swift | 51 ----- .../MessageBackupSimpleChatUpdateTest.swift | 184 ------------------ ...ssageBackupThreadMergeChatUpdateTest.swift | 51 ----- 16 files changed, 225 insertions(+), 1114 deletions(-) delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupAccountDataTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupContactTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupDistributionListTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupExpirationTimerChatUpdateTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupIncomingMessageWithEditsTest.swift rename SignalServiceKit/tests/MessageBackup/{MessageBackupIntegrationTestCase.swift => MessageBackupIntegrationTests.swift} (64%) delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupLearnedProfileChatUpdateTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupOutgoingMessageWithEditsTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupProfileChangeChatUpdateTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupSessionSwitchoverChatUpdateTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupSimpleChatUpdateTest.swift delete mode 100644 SignalServiceKit/tests/MessageBackup/MessageBackupThreadMergeChatUpdateTest.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 69d006f3ce4..3ffa28ad529 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -1755,7 +1755,6 @@ C1CF83D42B9A207800CDC9C4 /* TransformingOutputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CF83D32B9A207800CDC9C4 /* TransformingOutputStream.swift */; }; C1CF83D62B9A20FA00CDC9C4 /* EncryptingStreamTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CF83D52B9A20FA00CDC9C4 /* EncryptingStreamTransform.swift */; }; C1D3814C2C08D3FB00E46008 /* story-distribution-list.binproto in Resources */ = {isa = PBXBuildFile; fileRef = C1D381482C08D34900E46008 /* story-distribution-list.binproto */; }; - C1D3814D2C08D3FB00E46008 /* story-distribution-list.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = C1D381492C08D34900E46008 /* story-distribution-list.jsonproto */; }; C1D9B1532B7E949500D94595 /* SpamReportingUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D9B1522B7E949500D94595 /* SpamReportingUIUtils.swift */; }; C1D9B1552B7FA28200D94595 /* SafetyTipsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D9B1542B7FA28200D94595 /* SafetyTipsViewController.swift */; }; C1DAA7582C13C1E00078AE84 /* ArchivedPayment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAA7572C13C1E00078AE84 /* ArchivedPayment.swift */; }; @@ -1820,9 +1819,6 @@ D91D9C8C2C3F06400009E4F7 /* MessageBackupExpirationTimerChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D91D9C8B2C3F06400009E4F7 /* MessageBackupExpirationTimerChatUpdateArchiver.swift */; }; D91F0B4E2B193A5C0086DB30 /* GroupCallRecordRingUpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9B91D8D2B17E2A600BCB11A /* GroupCallRecordRingUpdateDelegate.swift */; }; D91F0B4F2B193A7A0086DB30 /* GroupCallRecordRingUpdateDelegateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D91F0B4B2B1939B60086DB30 /* GroupCallRecordRingUpdateDelegateTest.swift */; }; - D9247E9E2BFBED2A00DFEF6F /* registered-blocked-contact.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E982BFBED2A00DFEF6F /* registered-blocked-contact.jsonproto */; }; - D9247E9F2BFBED2A00DFEF6F /* unregistered-contact.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E992BFBED2A00DFEF6F /* unregistered-contact.jsonproto */; }; - D9247EA02BFBED2A00DFEF6F /* account-data.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E9A2BFBED2A00DFEF6F /* account-data.jsonproto */; }; D9247EA12BFBED2A00DFEF6F /* account-data.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E9B2BFBED2A00DFEF6F /* account-data.binproto */; }; D9247EA22BFBED2A00DFEF6F /* unregistered-contact.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E9C2BFBED2A00DFEF6F /* unregistered-contact.binproto */; }; D9247EA32BFBED2A00DFEF6F /* registered-blocked-contact.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9247E9D2BFBED2A00DFEF6F /* registered-blocked-contact.binproto */; }; @@ -1894,7 +1890,6 @@ D96234702C0E99DE00DAF6CB /* DeleteForMeMostRecentAddressableMessageCursorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D962346F2C0E99DE00DAF6CB /* DeleteForMeMostRecentAddressableMessageCursorTest.swift */; }; D96269D92C58407400152314 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = D96269D82C58407400152314 /* README.md */; }; D968F71B2C3355AB00AB318B /* simple-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D968F7192C3355AA00AB318B /* simple-chat-update-message.binproto */; }; - D968F71C2C3355AB00AB318B /* simple-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D968F71A2C3355AB00AB318B /* simple-chat-update-message.jsonproto */; }; D968F71E2C34884B00AB318B /* MessageBackupReleaseNotesRecipientArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D968F71D2C34884B00AB318B /* MessageBackupReleaseNotesRecipientArchiver.swift */; }; D96A94A72954E57F004EA434 /* DonateViewController+MonthlyPaypalDonation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D96A94A62954E57F004EA434 /* DonateViewController+MonthlyPaypalDonation.swift */; }; D96BE42E292EF04200E4FE1A /* PaypalButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D96BE42D292EF04200E4FE1A /* PaypalButton.swift */; }; @@ -1927,16 +1922,9 @@ D990CAC92B4CCB97000A99A2 /* MockLocalUsernameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D990CABF2B4CC930000A99A2 /* MockLocalUsernameManager.swift */; }; D990CACA2B4CCB9D000A99A2 /* MockUsernameLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D990CAC12B4CC9CE000A99A2 /* MockUsernameLinkManager.swift */; }; D990CACD2B4DCD83000A99A2 /* ConsumableMockPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93830772A70399C006CDCDE /* ConsumableMockPromise.swift */; }; - D994C7C12C45C836009ECEDA /* expiration-timer-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D994C7BF2C45C836009ECEDA /* expiration-timer-chat-update-message.jsonproto */; }; D994C7C22C45C836009ECEDA /* expiration-timer-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D994C7C02C45C836009ECEDA /* expiration-timer-chat-update-message.binproto */; }; - D994C7C42C45D023009ECEDA /* MessageBackupAccountDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7C32C45D023009ECEDA /* MessageBackupAccountDataTest.swift */; }; - D994C7C92C45D0B9009ECEDA /* MessageBackupContactTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7C52C45D07F009ECEDA /* MessageBackupContactTest.swift */; }; - D994C7CA2C45D0BF009ECEDA /* MessageBackupDistributionListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7C72C45D0B4009ECEDA /* MessageBackupDistributionListTest.swift */; }; - D994C7CE2C45D100009ECEDA /* MessageBackupExpirationTimerChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7CD2C45D100009ECEDA /* MessageBackupExpirationTimerChatUpdateTest.swift */; }; D994C7D12C45D24F009ECEDA /* MessageBackupProfileChangeChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7D02C45D24F009ECEDA /* MessageBackupProfileChangeChatUpdateArchiver.swift */; }; - D994C7D42C45E765009ECEDA /* profile-change-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D994C7D22C45E764009ECEDA /* profile-change-chat-update-message.jsonproto */; }; D994C7D52C45E765009ECEDA /* profile-change-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D994C7D32C45E764009ECEDA /* profile-change-chat-update-message.binproto */; }; - D994C7D72C45E7A0009ECEDA /* MessageBackupProfileChangeChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7D62C45E7A0009ECEDA /* MessageBackupProfileChangeChatUpdateTest.swift */; }; D994C7D92C486E54009ECEDA /* ContextMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7D82C486E54009ECEDA /* ContextMenuButton.swift */; }; D995546E2AF563150001E15C /* ReceiptCredentialRedemptionSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D995546D2AF563150001E15C /* ReceiptCredentialRedemptionSuccess.swift */; }; D995546F2AF5668E0001E15C /* ProfileBadgesSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99554692AF4873E0001E15C /* ProfileBadgesSnapshot.swift */; }; @@ -1981,16 +1969,10 @@ D9B95A9B29E8923B00D7CB95 /* InMemoryDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9B95A9929E8918200D7CB95 /* InMemoryDB.swift */; }; D9B95A9D29E894A600D7CB95 /* ValidatableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9B95A9C29E894A600D7CB95 /* ValidatableModel.swift */; }; D9BFB8BF2C4EE33C00D67881 /* MessageBackupThreadMergeChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BFB8BE2C4EE33C00D67881 /* MessageBackupThreadMergeChatUpdateArchiver.swift */; }; - D9BFB8C22C4EF0C800D67881 /* MessageBackupThreadMergeChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BFB8C02C4EF0B500D67881 /* MessageBackupThreadMergeChatUpdateTest.swift */; }; - D9BFB8C52C4EF32A00D67881 /* thread-merge-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9BFB8C32C4EF32A00D67881 /* thread-merge-chat-update-message.jsonproto */; }; D9BFB8C62C4EF32A00D67881 /* thread-merge-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9BFB8C42C4EF32A00D67881 /* thread-merge-chat-update-message.binproto */; }; D9BFB8C82C4F02C900D67881 /* MessageBackupSessionSwitchoverChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BFB8C72C4F02C900D67881 /* MessageBackupSessionSwitchoverChatUpdateArchiver.swift */; }; D9BFB8CF2C4F052800D67881 /* session-switchover-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9BFB8CD2C4F052800D67881 /* session-switchover-chat-update-message.binproto */; }; - D9BFB8D02C4F052800D67881 /* session-switchover-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9BFB8CE2C4F052800D67881 /* session-switchover-chat-update-message.jsonproto */; }; - D9BFB8D22C4F054A00D67881 /* MessageBackupSessionSwitchoverChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BFB8D12C4F054A00D67881 /* MessageBackupSessionSwitchoverChatUpdateTest.swift */; }; - D9C047022C6FC03600CF0286 /* MessageBackupOutgoingMessageWithEditsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C047012C6FC03600CF0286 /* MessageBackupOutgoingMessageWithEditsTest.swift */; }; D9C047052C6FD8A600CF0286 /* outgoing-message-with-edits.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9C047032C6FD8A600CF0286 /* outgoing-message-with-edits.binproto */; }; - D9C047062C6FD8A600CF0286 /* outgoing-message-with-edits.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9C047042C6FD8A600CF0286 /* outgoing-message-with-edits.jsonproto */; }; D9C0AE652BD7103100FCB05E /* OWSDeviceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C0AE632BD7103100FCB05E /* OWSDeviceStore.swift */; }; D9C0AE662BD7103100FCB05E /* InactiveLinkedDeviceFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C0AE642BD7103100FCB05E /* InactiveLinkedDeviceFinder.swift */; }; D9C0AE672BD7162300FCB05E /* InactiveLinkedDeviceFinderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C0AE612BD7102500FCB05E /* InactiveLinkedDeviceFinderTest.swift */; }; @@ -2013,7 +1995,7 @@ D9C964092BE44D700058F143 /* XCTest+Thenable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964072BE44D510058F143 /* XCTest+Thenable.swift */; }; D9C964102BE451CE0058F143 /* TSMessageStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9640F2BE451CE0058F143 /* TSMessageStorageTest.swift */; }; D9C964142BE45A030058F143 /* SignedPreKeyDeletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964132BE45A030058F143 /* SignedPreKeyDeletionTests.swift */; }; - D9C964172BE56DFB0058F143 /* MessageBackupIntegrationTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTestCase.swift */; }; + D9C964172BE56DFB0058F143 /* MessageBackupIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTests.swift */; }; D9CA5BF729B3F61E00D9AAD1 /* LegacyChangePhoneNumber+ChangeTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA5BF629B3F61E00D9AAD1 /* LegacyChangePhoneNumber+ChangeTokens.swift */; }; D9CA61482C2E2D0000F99EA3 /* MessageBackupAdHocCallArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA61472C2E2D0000F99EA3 /* MessageBackupAdHocCallArchiver.swift */; }; D9CA614B2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA614A2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift */; }; @@ -2040,11 +2022,7 @@ D9DCFDB22A3BDAB000C73C0B /* ExportableQRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9DCFDB12A3BDAB000C73C0B /* ExportableQRCodeGenerator.swift */; }; D9DCFDB42A3BDC7400C73C0B /* BasicDisplayQRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9DCFDB32A3BDC7400C73C0B /* BasicDisplayQRCodeGenerator.swift */; }; D9DD64CC2C58514C007CE0C3 /* learned-profile-chat-update-message.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9DD64CA2C58514C007CE0C3 /* learned-profile-chat-update-message.binproto */; }; - D9DD64CD2C58514C007CE0C3 /* learned-profile-chat-update-message.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9DD64CB2C58514C007CE0C3 /* learned-profile-chat-update-message.jsonproto */; }; - D9DD64CF2C585173007CE0C3 /* MessageBackupLearnedProfileChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9DD64CE2C585173007CE0C3 /* MessageBackupLearnedProfileChatUpdateTest.swift */; }; - D9DD64D52C5ACEC6007CE0C3 /* MessageBackupIncomingMessageWithEditsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9DD64D42C5ACEC6007CE0C3 /* MessageBackupIncomingMessageWithEditsTest.swift */; }; D9DD64D82C5AD38B007CE0C3 /* incoming-message-with-edits.binproto in Resources */ = {isa = PBXBuildFile; fileRef = D9DD64D62C5AD38B007CE0C3 /* incoming-message-with-edits.binproto */; }; - D9DD64D92C5AD38B007CE0C3 /* incoming-message-with-edits.jsonproto in Resources */ = {isa = PBXBuildFile; fileRef = D9DD64D72C5AD38B007CE0C3 /* incoming-message-with-edits.jsonproto */; }; D9E335A42993269100825677 /* MockDBV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66138FAE2982F4C4002E0CFE /* MockDBV2.swift */; }; D9E335A929933B1A00825677 /* Usernames+HashedUsername.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9E335A829933B1A00825677 /* Usernames+HashedUsername.swift */; }; D9E7C8752B9A3FD1005BD3B9 /* CallRecordCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9E7C8742B9A3FD1005BD3B9 /* CallRecordCursor.swift */; }; @@ -2055,7 +2033,6 @@ D9E8EDF32C0FD8C800923E3C /* DeleteForMeInfoSheetCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9E8EDF22C0FD8C800923E3C /* DeleteForMeInfoSheetCoordinator.swift */; }; D9EA2A872C2B609800B367DF /* MessageBackupChatUpdateMessageArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA2A862C2B609800B367DF /* MessageBackupChatUpdateMessageArchiver.swift */; }; D9EA2A892C2B929400B367DF /* MessageBackupSimpleChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EA2A882C2B929400B367DF /* MessageBackupSimpleChatUpdateArchiver.swift */; }; - D9EAB2A32C6AC6E100E26207 /* MessageBackupSimpleChatUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D994C7CB2C45D0D9009ECEDA /* MessageBackupSimpleChatUpdateTest.swift */; }; D9EB221E2A4B636C00C73E1D /* Bitmaps+LineDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EB22192A4B636B00C73E1D /* Bitmaps+LineDrawing.swift */; }; D9EB221F2A4B636C00C73E1D /* Bitmaps+Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EB221A2A4B636B00C73E1D /* Bitmaps+Shapes.swift */; }; D9EB22202A4B636C00C73E1D /* Bitmaps+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EB221B2A4B636B00C73E1D /* Bitmaps+Image.swift */; }; @@ -4842,7 +4819,6 @@ C1CF83D32B9A207800CDC9C4 /* TransformingOutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformingOutputStream.swift; sourceTree = ""; }; C1CF83D52B9A20FA00CDC9C4 /* EncryptingStreamTransform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptingStreamTransform.swift; sourceTree = ""; }; C1D381482C08D34900E46008 /* story-distribution-list.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "story-distribution-list.binproto"; path = "Signal-Message-Backup-Tests/test-cases/story-distribution-list.binproto"; sourceTree = ""; }; - C1D381492C08D34900E46008 /* story-distribution-list.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "story-distribution-list.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/story-distribution-list.jsonproto"; sourceTree = ""; }; C1D5836E2B03DFED00EE8FD9 /* Stripe+IDEAL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stripe+IDEAL.swift"; sourceTree = ""; }; C1D9B1522B7E949500D94595 /* SpamReportingUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpamReportingUIUtils.swift; sourceTree = ""; }; C1D9B1542B7FA28200D94595 /* SafetyTipsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafetyTipsViewController.swift; sourceTree = ""; }; @@ -4907,9 +4883,6 @@ D91D9C8B2C3F06400009E4F7 /* MessageBackupExpirationTimerChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupExpirationTimerChatUpdateArchiver.swift; sourceTree = ""; }; D91F0B4B2B1939B60086DB30 /* GroupCallRecordRingUpdateDelegateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCallRecordRingUpdateDelegateTest.swift; sourceTree = ""; }; D91F7A2C2935A32F00012C64 /* DonationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationMode.swift; sourceTree = ""; }; - D9247E982BFBED2A00DFEF6F /* registered-blocked-contact.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "registered-blocked-contact.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/registered-blocked-contact.jsonproto"; sourceTree = ""; }; - D9247E992BFBED2A00DFEF6F /* unregistered-contact.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "unregistered-contact.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/unregistered-contact.jsonproto"; sourceTree = ""; }; - D9247E9A2BFBED2A00DFEF6F /* account-data.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "account-data.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/account-data.jsonproto"; sourceTree = ""; }; D9247E9B2BFBED2A00DFEF6F /* account-data.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "account-data.binproto"; path = "Signal-Message-Backup-Tests/test-cases/account-data.binproto"; sourceTree = ""; }; D9247E9C2BFBED2A00DFEF6F /* unregistered-contact.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "unregistered-contact.binproto"; path = "Signal-Message-Backup-Tests/test-cases/unregistered-contact.binproto"; sourceTree = ""; }; D9247E9D2BFBED2A00DFEF6F /* registered-blocked-contact.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "registered-blocked-contact.binproto"; path = "Signal-Message-Backup-Tests/test-cases/registered-blocked-contact.binproto"; sourceTree = ""; }; @@ -4987,7 +4960,6 @@ D96269D82C58407400152314 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D9668B34291B088200665298 /* SignalMessagingJobQueues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalMessagingJobQueues.swift; sourceTree = ""; }; D968F7192C3355AA00AB318B /* simple-chat-update-message.binproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "simple-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/simple-chat-update-message.binproto"; sourceTree = ""; }; - D968F71A2C3355AB00AB318B /* simple-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "simple-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/simple-chat-update-message.jsonproto"; sourceTree = ""; }; D968F71D2C34884B00AB318B /* MessageBackupReleaseNotesRecipientArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupReleaseNotesRecipientArchiver.swift; sourceTree = ""; }; D96A94A62954E57F004EA434 /* DonateViewController+MonthlyPaypalDonation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DonateViewController+MonthlyPaypalDonation.swift"; sourceTree = ""; }; D96A94A82955270D004EA434 /* Stripe+Subscriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stripe+Subscriptions.swift"; sourceTree = ""; }; @@ -5022,17 +4994,9 @@ D990CABD2B4CC8FD000A99A2 /* MockUsernameApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUsernameApiClient.swift; sourceTree = ""; }; D990CABF2B4CC930000A99A2 /* MockLocalUsernameManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocalUsernameManager.swift; sourceTree = ""; }; D990CAC12B4CC9CE000A99A2 /* MockUsernameLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUsernameLinkManager.swift; sourceTree = ""; }; - D994C7BF2C45C836009ECEDA /* expiration-timer-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "expiration-timer-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/expiration-timer-chat-update-message.jsonproto"; sourceTree = ""; }; D994C7C02C45C836009ECEDA /* expiration-timer-chat-update-message.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "expiration-timer-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/expiration-timer-chat-update-message.binproto"; sourceTree = ""; }; - D994C7C32C45D023009ECEDA /* MessageBackupAccountDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupAccountDataTest.swift; sourceTree = ""; }; - D994C7C52C45D07F009ECEDA /* MessageBackupContactTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupContactTest.swift; sourceTree = ""; }; - D994C7C72C45D0B4009ECEDA /* MessageBackupDistributionListTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupDistributionListTest.swift; sourceTree = ""; }; - D994C7CB2C45D0D9009ECEDA /* MessageBackupSimpleChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupSimpleChatUpdateTest.swift; sourceTree = ""; }; - D994C7CD2C45D100009ECEDA /* MessageBackupExpirationTimerChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupExpirationTimerChatUpdateTest.swift; sourceTree = ""; }; D994C7D02C45D24F009ECEDA /* MessageBackupProfileChangeChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupProfileChangeChatUpdateArchiver.swift; sourceTree = ""; }; - D994C7D22C45E764009ECEDA /* profile-change-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "profile-change-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/profile-change-chat-update-message.jsonproto"; sourceTree = ""; }; D994C7D32C45E764009ECEDA /* profile-change-chat-update-message.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "profile-change-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/profile-change-chat-update-message.binproto"; sourceTree = ""; }; - D994C7D62C45E7A0009ECEDA /* MessageBackupProfileChangeChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupProfileChangeChatUpdateTest.swift; sourceTree = ""; }; D994C7D82C486E54009ECEDA /* ContextMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuButton.swift; sourceTree = ""; }; D99554692AF4873E0001E15C /* ProfileBadgesSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileBadgesSnapshot.swift; sourceTree = ""; }; D995546D2AF563150001E15C /* ReceiptCredentialRedemptionSuccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiptCredentialRedemptionSuccess.swift; sourceTree = ""; }; @@ -5079,16 +5043,10 @@ D9B95A9929E8918200D7CB95 /* InMemoryDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryDB.swift; sourceTree = ""; }; D9B95A9C29E894A600D7CB95 /* ValidatableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableModel.swift; sourceTree = ""; }; D9BFB8BE2C4EE33C00D67881 /* MessageBackupThreadMergeChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupThreadMergeChatUpdateArchiver.swift; sourceTree = ""; }; - D9BFB8C02C4EF0B500D67881 /* MessageBackupThreadMergeChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupThreadMergeChatUpdateTest.swift; sourceTree = ""; }; - D9BFB8C32C4EF32A00D67881 /* thread-merge-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "thread-merge-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/thread-merge-chat-update-message.jsonproto"; sourceTree = ""; }; D9BFB8C42C4EF32A00D67881 /* thread-merge-chat-update-message.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "thread-merge-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/thread-merge-chat-update-message.binproto"; sourceTree = ""; }; D9BFB8C72C4F02C900D67881 /* MessageBackupSessionSwitchoverChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupSessionSwitchoverChatUpdateArchiver.swift; sourceTree = ""; }; D9BFB8CD2C4F052800D67881 /* session-switchover-chat-update-message.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "session-switchover-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/session-switchover-chat-update-message.binproto"; sourceTree = ""; }; - D9BFB8CE2C4F052800D67881 /* session-switchover-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "session-switchover-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/session-switchover-chat-update-message.jsonproto"; sourceTree = ""; }; - D9BFB8D12C4F054A00D67881 /* MessageBackupSessionSwitchoverChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupSessionSwitchoverChatUpdateTest.swift; sourceTree = ""; }; - D9C047012C6FC03600CF0286 /* MessageBackupOutgoingMessageWithEditsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupOutgoingMessageWithEditsTest.swift; sourceTree = ""; }; D9C047032C6FD8A600CF0286 /* outgoing-message-with-edits.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "outgoing-message-with-edits.binproto"; path = "Signal-Message-Backup-Tests/test-cases/outgoing-message-with-edits.binproto"; sourceTree = ""; }; - D9C047042C6FD8A600CF0286 /* outgoing-message-with-edits.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "outgoing-message-with-edits.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/outgoing-message-with-edits.jsonproto"; sourceTree = ""; }; D9C0AE612BD7102500FCB05E /* InactiveLinkedDeviceFinderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InactiveLinkedDeviceFinderTest.swift; sourceTree = ""; }; D9C0AE632BD7103100FCB05E /* OWSDeviceStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSDeviceStore.swift; sourceTree = ""; }; D9C0AE642BD7103100FCB05E /* InactiveLinkedDeviceFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InactiveLinkedDeviceFinder.swift; sourceTree = ""; }; @@ -5112,7 +5070,7 @@ D9C964072BE44D510058F143 /* XCTest+Thenable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Thenable.swift"; sourceTree = ""; }; D9C9640F2BE451CE0058F143 /* TSMessageStorageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSMessageStorageTest.swift; sourceTree = ""; }; D9C964132BE45A030058F143 /* SignedPreKeyDeletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedPreKeyDeletionTests.swift; sourceTree = ""; }; - D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupIntegrationTestCase.swift; sourceTree = ""; }; + D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupIntegrationTests.swift; sourceTree = ""; }; D9CA5BF629B3F61E00D9AAD1 /* LegacyChangePhoneNumber+ChangeTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyChangePhoneNumber+ChangeTokens.swift"; sourceTree = ""; }; D9CA61472C2E2D0000F99EA3 /* MessageBackupAdHocCallArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupAdHocCallArchiver.swift; sourceTree = ""; }; D9CA614A2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateStoryThreadDeletionManager.swift; sourceTree = ""; }; @@ -5150,11 +5108,7 @@ D9DCFDB12A3BDAB000C73C0B /* ExportableQRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableQRCodeGenerator.swift; sourceTree = ""; }; D9DCFDB32A3BDC7400C73C0B /* BasicDisplayQRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicDisplayQRCodeGenerator.swift; sourceTree = ""; }; D9DD64CA2C58514C007CE0C3 /* learned-profile-chat-update-message.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "learned-profile-chat-update-message.binproto"; path = "Signal-Message-Backup-Tests/test-cases/learned-profile-chat-update-message.binproto"; sourceTree = ""; }; - D9DD64CB2C58514C007CE0C3 /* learned-profile-chat-update-message.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "learned-profile-chat-update-message.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/learned-profile-chat-update-message.jsonproto"; sourceTree = ""; }; - D9DD64CE2C585173007CE0C3 /* MessageBackupLearnedProfileChatUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupLearnedProfileChatUpdateTest.swift; sourceTree = ""; }; - D9DD64D42C5ACEC6007CE0C3 /* MessageBackupIncomingMessageWithEditsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBackupIncomingMessageWithEditsTest.swift; sourceTree = ""; }; D9DD64D62C5AD38B007CE0C3 /* incoming-message-with-edits.binproto */ = {isa = PBXFileReference; lastKnownFileType = file; name = "incoming-message-with-edits.binproto"; path = "Signal-Message-Backup-Tests/test-cases/incoming-message-with-edits.binproto"; sourceTree = ""; }; - D9DD64D72C5AD38B007CE0C3 /* incoming-message-with-edits.jsonproto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "incoming-message-with-edits.jsonproto"; path = "Signal-Message-Backup-Tests/test-cases/incoming-message-with-edits.jsonproto"; sourceTree = ""; }; D9E335A829933B1A00825677 /* Usernames+HashedUsername.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Usernames+HashedUsername.swift"; sourceTree = ""; }; D9E7C8742B9A3FD1005BD3B9 /* CallRecordCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRecordCursor.swift; sourceTree = ""; }; D9E7C8762B9A4A9C005BD3B9 /* CallRecord+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallRecord+Sorting.swift"; sourceTree = ""; }; @@ -9998,29 +9952,17 @@ isa = PBXGroup; children = ( D9247E9B2BFBED2A00DFEF6F /* account-data.binproto */, - D9247E9A2BFBED2A00DFEF6F /* account-data.jsonproto */, D994C7C02C45C836009ECEDA /* expiration-timer-chat-update-message.binproto */, - D994C7BF2C45C836009ECEDA /* expiration-timer-chat-update-message.jsonproto */, D9DD64D62C5AD38B007CE0C3 /* incoming-message-with-edits.binproto */, - D9DD64D72C5AD38B007CE0C3 /* incoming-message-with-edits.jsonproto */, D9DD64CA2C58514C007CE0C3 /* learned-profile-chat-update-message.binproto */, - D9DD64CB2C58514C007CE0C3 /* learned-profile-chat-update-message.jsonproto */, D9C047032C6FD8A600CF0286 /* outgoing-message-with-edits.binproto */, - D9C047042C6FD8A600CF0286 /* outgoing-message-with-edits.jsonproto */, D994C7D32C45E764009ECEDA /* profile-change-chat-update-message.binproto */, - D994C7D22C45E764009ECEDA /* profile-change-chat-update-message.jsonproto */, D9247E9D2BFBED2A00DFEF6F /* registered-blocked-contact.binproto */, - D9247E982BFBED2A00DFEF6F /* registered-blocked-contact.jsonproto */, D9BFB8CD2C4F052800D67881 /* session-switchover-chat-update-message.binproto */, - D9BFB8CE2C4F052800D67881 /* session-switchover-chat-update-message.jsonproto */, D968F7192C3355AA00AB318B /* simple-chat-update-message.binproto */, - D968F71A2C3355AB00AB318B /* simple-chat-update-message.jsonproto */, C1D381482C08D34900E46008 /* story-distribution-list.binproto */, - C1D381492C08D34900E46008 /* story-distribution-list.jsonproto */, D9BFB8C42C4EF32A00D67881 /* thread-merge-chat-update-message.binproto */, - D9BFB8C32C4EF32A00D67881 /* thread-merge-chat-update-message.jsonproto */, D9247E9C2BFBED2A00DFEF6F /* unregistered-contact.binproto */, - D9247E992BFBED2A00DFEF6F /* unregistered-contact.jsonproto */, ); name = SharedTestCases; sourceTree = ""; @@ -10345,18 +10287,7 @@ isa = PBXGroup; children = ( D9247E4F2BFBE9B400DFEF6F /* SharedTestCases */, - D994C7C32C45D023009ECEDA /* MessageBackupAccountDataTest.swift */, - D994C7C52C45D07F009ECEDA /* MessageBackupContactTest.swift */, - D994C7C72C45D0B4009ECEDA /* MessageBackupDistributionListTest.swift */, - D994C7CD2C45D100009ECEDA /* MessageBackupExpirationTimerChatUpdateTest.swift */, - D9DD64D42C5ACEC6007CE0C3 /* MessageBackupIncomingMessageWithEditsTest.swift */, - D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTestCase.swift */, - D9DD64CE2C585173007CE0C3 /* MessageBackupLearnedProfileChatUpdateTest.swift */, - D9C047012C6FC03600CF0286 /* MessageBackupOutgoingMessageWithEditsTest.swift */, - D994C7D62C45E7A0009ECEDA /* MessageBackupProfileChangeChatUpdateTest.swift */, - D9BFB8D12C4F054A00D67881 /* MessageBackupSessionSwitchoverChatUpdateTest.swift */, - D994C7CB2C45D0D9009ECEDA /* MessageBackupSimpleChatUpdateTest.swift */, - D9BFB8C02C4EF0B500D67881 /* MessageBackupThreadMergeChatUpdateTest.swift */, + D9C964162BE56DFB0058F143 /* MessageBackupIntegrationTests.swift */, ); path = MessageBackup; sourceTree = ""; @@ -12775,33 +12706,21 @@ buildActionMask = 2147483647; files = ( D9247EA12BFBED2A00DFEF6F /* account-data.binproto in Resources */, - D9247EA02BFBED2A00DFEF6F /* account-data.jsonproto in Resources */, D994C7C22C45C836009ECEDA /* expiration-timer-chat-update-message.binproto in Resources */, - D994C7C12C45C836009ECEDA /* expiration-timer-chat-update-message.jsonproto in Resources */, D9DD64D82C5AD38B007CE0C3 /* incoming-message-with-edits.binproto in Resources */, - D9DD64D92C5AD38B007CE0C3 /* incoming-message-with-edits.jsonproto in Resources */, D9DD64CC2C58514C007CE0C3 /* learned-profile-chat-update-message.binproto in Resources */, - D9DD64CD2C58514C007CE0C3 /* learned-profile-chat-update-message.jsonproto in Resources */, D9C047052C6FD8A600CF0286 /* outgoing-message-with-edits.binproto in Resources */, - D9C047062C6FD8A600CF0286 /* outgoing-message-with-edits.jsonproto in Resources */, D994C7D52C45E765009ECEDA /* profile-change-chat-update-message.binproto in Resources */, - D994C7D42C45E765009ECEDA /* profile-change-chat-update-message.jsonproto in Resources */, D9247EA32BFBED2A00DFEF6F /* registered-blocked-contact.binproto in Resources */, - D9247E9E2BFBED2A00DFEF6F /* registered-blocked-contact.jsonproto in Resources */, F942628B289B1B5600460798 /* sample-sticker.encrypted in Resources */, F942628C289B1B5600460798 /* sample-sticker.webp in Resources */, D9BFB8CF2C4F052800D67881 /* session-switchover-chat-update-message.binproto in Resources */, - D9BFB8D02C4F052800D67881 /* session-switchover-chat-update-message.jsonproto in Resources */, D968F71B2C3355AB00AB318B /* simple-chat-update-message.binproto in Resources */, - D968F71C2C3355AB00AB318B /* simple-chat-update-message.jsonproto in Resources */, C1D3814C2C08D3FB00E46008 /* story-distribution-list.binproto in Resources */, - C1D3814D2C08D3FB00E46008 /* story-distribution-list.jsonproto in Resources */, F908AA7D28CE629700472E68 /* test-apng.png in Resources */, F927478828CFE9B10056EAFE /* test-png.png in Resources */, D9BFB8C62C4EF32A00D67881 /* thread-merge-chat-update-message.binproto in Resources */, - D9BFB8C52C4EF32A00D67881 /* thread-merge-chat-update-message.jsonproto in Resources */, D9247EA22BFBED2A00DFEF6F /* unregistered-contact.binproto in Resources */, - D9247E9F2BFBED2A00DFEF6F /* unregistered-contact.jsonproto in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -15487,18 +15406,7 @@ F942625F289B1B5500460798 /* LRUCacheTest.swift in Sources */, F9426269289B1B5500460798 /* MathOWSTests.swift in Sources */, 66AE8A872C169A900044D388 /* MediaGalleryAttachmentFinderTest.swift in Sources */, - D994C7C42C45D023009ECEDA /* MessageBackupAccountDataTest.swift in Sources */, - D994C7C92C45D0B9009ECEDA /* MessageBackupContactTest.swift in Sources */, - D994C7CA2C45D0BF009ECEDA /* MessageBackupDistributionListTest.swift in Sources */, - D994C7CE2C45D100009ECEDA /* MessageBackupExpirationTimerChatUpdateTest.swift in Sources */, - D9DD64D52C5ACEC6007CE0C3 /* MessageBackupIncomingMessageWithEditsTest.swift in Sources */, - D9C964172BE56DFB0058F143 /* MessageBackupIntegrationTestCase.swift in Sources */, - D9DD64CF2C585173007CE0C3 /* MessageBackupLearnedProfileChatUpdateTest.swift in Sources */, - D9C047022C6FC03600CF0286 /* MessageBackupOutgoingMessageWithEditsTest.swift in Sources */, - D994C7D72C45E7A0009ECEDA /* MessageBackupProfileChangeChatUpdateTest.swift in Sources */, - D9BFB8D22C4F054A00D67881 /* MessageBackupSessionSwitchoverChatUpdateTest.swift in Sources */, - D9EAB2A32C6AC6E100E26207 /* MessageBackupSimpleChatUpdateTest.swift in Sources */, - D9BFB8C22C4EF0C800D67881 /* MessageBackupThreadMergeChatUpdateTest.swift in Sources */, + D9C964172BE56DFB0058F143 /* MessageBackupIntegrationTests.swift in Sources */, 66FC637229DF7A1500F00DAC /* MessageBodyRangesTests.swift in Sources */, 668444822A3292AB00DBED7C /* MessageBodyStyleTests.swift in Sources */, 66883A3A29D7630A00E898CF /* MessageBodyTests.swift in Sources */, diff --git a/SignalServiceKit/Environment/AppSetup.swift b/SignalServiceKit/Environment/AppSetup.swift index 9772cc8d1bc..90d5bfcfc17 100644 --- a/SignalServiceKit/Environment/AppSetup.swift +++ b/SignalServiceKit/Environment/AppSetup.swift @@ -958,6 +958,9 @@ public class AppSetup { storyStore: storyStore, threadStore: threadStore ), + encryptedStreamProvider: MessageBackupEncryptedProtoStreamProviderImpl( + backupKeyMaterial: messageBackupKeyMaterial + ), groupRecipientArchiver: MessageBackupGroupRecipientArchiver( disappearingMessageConfigStore: disappearingMessagesConfigurationStore, groupsV2: groupsV2, @@ -970,9 +973,7 @@ public class AppSetup { localRecipientArchiver: MessageBackupLocalRecipientArchiver(), messageBackupKeyMaterial: messageBackupKeyMaterial, releaseNotesRecipientArchiver: MessageBackupReleaseNotesRecipientArchiver(), - streamProvider: MessageBackupProtoStreamProviderImpl( - backupKeyMaterial: messageBackupKeyMaterial - ) + plaintextStreamProvider: MessageBackupPlaintextProtoStreamProviderImpl() ) let externalPendingIDEALDonationStore = ExternalPendingIDEALDonationStoreImpl(keyStoreFactory: keyValueStoreFactory) diff --git a/SignalServiceKit/MessageBackup/FileStreams/MessageBackupProtoStreamProvider.swift b/SignalServiceKit/MessageBackup/FileStreams/MessageBackupProtoStreamProvider.swift index 1afe0b7ad9f..addc4e4db11 100644 --- a/SignalServiceKit/MessageBackup/FileStreams/MessageBackupProtoStreamProvider.swift +++ b/SignalServiceKit/MessageBackup/FileStreams/MessageBackupProtoStreamProvider.swift @@ -32,16 +32,39 @@ extension MessageBackup { } } -/** - * Creates input and output streams for reading and writing to a backup file on disk. - * - * The backup file is just a sequence of serialized proto bytes, back to back, delimited by varint - * byte sizes so we know how much to read into memory to deserialize the next proto. - * The input and output streams abstract over this, and allow callers to just think in terms of "frames", - * the individual proto objects that we read and write one at a time. - */ -public protocol MessageBackupProtoStreamProvider { +/// Creates streams for reading and writing to a plaintext Backup file on-disk. +/// +/// A Backup file is a sequence of concatenated serialized proto bytes delimited +/// by varint byte sizes, which tell us how much to read into memory to +/// deserialize the next proto. The streams provided by this type abstract over +/// this serialized format, allowing callers instead to think in terms of +/// "frames", or individual proto objects that we read or write individually. +/// +/// - SeeAlso: ``MessageBackupEncryptedProtoStreamProvider`` +public protocol MessageBackupPlaintextProtoStreamProvider { + typealias ProtoStream = MessageBackup.ProtoStream + + /// Open an output stream to write a plaintext backup to a file on disk. The + /// caller owns the returned stream, and is responsible for closing it once + /// finished. + func openPlaintextOutputFileStream() -> ProtoStream.OpenOutputStreamResult + + /// Open an input stream to read a plaintext backup from a file on disk. The + /// caller becomes the owner of the stream, and is responsible for closing + /// it once finished. + func openPlaintextInputFileStream(fileUrl: URL) -> ProtoStream.OpenInputStreamResult +} +/// Creates streams for reading and writing to an encrypted Backup file on-disk. +/// +/// A Backup file is a sequence of concatenated serialized proto bytes delimited +/// by varint byte sizes, which tell us how much to read into memory to +/// deserialize the next proto. The streams provided by this type abstract over +/// this serialized format, allowing callers instead to think in terms of +/// "frames", or individual proto objects that we read or write individually. +/// +/// - SeeAlso: ``MessageBackupPlaintextProtoStreamProvider`` +public protocol MessageBackupEncryptedProtoStreamProvider { typealias ProtoStream = MessageBackup.ProtoStream /// Open an output stream to write an encrypted backup to a file on disk. @@ -52,11 +75,6 @@ public protocol MessageBackupProtoStreamProvider { tx: DBReadTransaction ) -> ProtoStream.OpenOutputStreamResult - /// Open an output stream to write a plaintext backup to a file on disk. The - /// caller owns the returned stream, and is responsible for closing it once - /// finished. - func openPlaintextOutputFileStream() -> ProtoStream.OpenOutputStreamResult - /// Open an input stream to read an encrypted backup from a file on disk. /// The caller becomes the owner of the stream, and is responsible for /// closing it once finished. @@ -65,19 +83,17 @@ public protocol MessageBackupProtoStreamProvider { localAci: Aci, tx: DBReadTransaction ) -> ProtoStream.OpenInputStreamResult - - /// Open an input stream to read a plaintext backup from a file on disk. The - /// caller becomes the owner of the stream, and is responsible for closing - /// it once finished. - func openPlaintextInputFileStream(fileUrl: URL) -> ProtoStream.OpenInputStreamResult } -public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvider { +// MARK: - +public class MessageBackupEncryptedProtoStreamProviderImpl: MessageBackupEncryptedProtoStreamProvider { private let backupKeyMaterial: MessageBackupKeyMaterial + private let genericStreamProvider: GenericStreamProvider public init(backupKeyMaterial: MessageBackupKeyMaterial) { self.backupKeyMaterial = backupKeyMaterial + self.genericStreamProvider = GenericStreamProvider() } public func openEncryptedOutputFileStream( @@ -99,7 +115,9 @@ public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvi let outputStream: MessageBackupProtoOutputStream let fileUrl: URL - switch openOutputFileStream(transforms: transforms) { + switch genericStreamProvider.openOutputFileStream( + transforms: transforms + ) { case .success(let _outputStream, let _fileUrl): outputStream = _outputStream fileUrl = _fileUrl @@ -123,14 +141,6 @@ public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvi } } - public func openPlaintextOutputFileStream() -> ProtoStream.OpenOutputStreamResult { - let transforms: [any StreamTransform] = [ - ChunkedOutputStreamTransform(), - ] - - return openOutputFileStream(transforms: transforms) - } - public func openEncryptedInputFileStream( fileUrl: URL, localAci: Aci, @@ -148,21 +158,82 @@ public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvi ChunkedInputStreamTransform(), ] - return openInputFileStream(fileUrl: fileUrl, transforms: transforms) + return genericStreamProvider.openInputFileStream( + fileUrl: fileUrl, + transforms: transforms + ) } catch { return .unableToOpenFileStream } } + private func validateBackupHMAC(localAci: Aci, fileUrl: URL, tx: DBReadTransaction) -> Bool { + do { + let inputStreamResult = genericStreamProvider.openInputFileStream( + fileUrl: fileUrl, + transforms: [ + try backupKeyMaterial.createHmacGeneratingStreamTransform( + localAci: localAci, + tx: tx + ) + ] + ) + + switch inputStreamResult { + case .success(_, let rawInputStream): + // Read through the input stream. The HmacStreamTransform will both build + // an HMAC of the input data and read the HMAC from the end of the input file. + // Once the end of the stream is reached, the transform will compare the + // HMACs and throw an exception if they differ. + while try rawInputStream.read(maxLength: 32 * 1024).count > 0 {} + try rawInputStream.close() + return true + case .fileNotFound, .unableToOpenFileStream, .hmacValidationFailedOnEncryptedFile: + return false + } + } catch { + return false + } + } +} + +public class MessageBackupPlaintextProtoStreamProviderImpl: MessageBackupPlaintextProtoStreamProvider { + private let genericStreamProvider: GenericStreamProvider + + init() { + self.genericStreamProvider = GenericStreamProvider() + } + + public func openPlaintextOutputFileStream() -> ProtoStream.OpenOutputStreamResult { + let transforms: [any StreamTransform] = [ + ChunkedOutputStreamTransform(), + ] + + return genericStreamProvider.openOutputFileStream( + transforms: transforms + ) + } + public func openPlaintextInputFileStream(fileUrl: URL) -> ProtoStream.OpenInputStreamResult { let transforms: [any StreamTransform] = [ ChunkedInputStreamTransform(), ] - return openInputFileStream(fileUrl: fileUrl, transforms: transforms) + return genericStreamProvider.openInputFileStream( + fileUrl: fileUrl, + transforms: transforms + ) } +} - private func openOutputFileStream( +// MARK: - + +private class GenericStreamProvider { + typealias ProtoStream = MessageBackup.ProtoStream + + init() {} + + func openOutputFileStream( transforms: [any StreamTransform] ) -> ProtoStream.OpenOutputStreamResult { let fileUrl = OWSFileSystem.temporaryFileUrl(isAvailableWhileDeviceLocked: true) @@ -191,7 +262,7 @@ public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvi return .success(messageBackupOutputStream, fileUrl) } - private func openInputFileStream( + func openInputFileStream( fileUrl: URL, transforms: [any StreamTransform] ) -> ProtoStream.OpenInputStreamResult { @@ -224,29 +295,6 @@ public class MessageBackupProtoStreamProviderImpl: MessageBackupProtoStreamProvi return .success(messageBackupInputStream, rawStream: transformableInputStream) } - private func validateBackupHMAC(localAci: Aci, fileUrl: URL, tx: DBReadTransaction) -> Bool { - do { - let inputStreamResult = openInputFileStream(fileUrl: fileUrl, transforms: [ - try backupKeyMaterial.createHmacGeneratingStreamTransform(localAci: localAci, tx: tx) - ]) - - switch inputStreamResult { - case .success(_, let rawInputStream): - // Read through the input stream. The HmacStreamTransform will both build - // an HMAC of the input data and read the HMAC from the end of the input file. - // Once the end of the stream is reached, the transform will compare the - // HMACs and throw an exception if they differ. - while try rawInputStream.read(maxLength: 32 * 1024).count > 0 {} - try rawInputStream.close() - return true - case .fileNotFound, .unableToOpenFileStream, .hmacValidationFailedOnEncryptedFile: - return false - } - } catch { - return false - } - } - private class StreamDelegate: NSObject, Foundation.StreamDelegate { private let _hadError = AtomicBool(false, lock: .sharedGlobal) public var hadError: Bool { _hadError.get() } diff --git a/SignalServiceKit/MessageBackup/MessageBackupManagerImpl.swift b/SignalServiceKit/MessageBackup/MessageBackupManagerImpl.swift index 471762ac4b6..d6731d1b5cc 100644 --- a/SignalServiceKit/MessageBackup/MessageBackupManagerImpl.swift +++ b/SignalServiceKit/MessageBackup/MessageBackupManagerImpl.swift @@ -35,13 +35,14 @@ public class MessageBackupManagerImpl: MessageBackupManager { private let dateProvider: DateProvider private let db: DB private let distributionListRecipientArchiver: MessageBackupDistributionListRecipientArchiver + private let encryptedStreamProvider: MessageBackupEncryptedProtoStreamProvider private let groupRecipientArchiver: MessageBackupGroupRecipientArchiver private let incrementalTSAttachmentMigrator: IncrementalMessageTSAttachmentMigrator private let kvStore: KeyValueStore private let localRecipientArchiver: MessageBackupLocalRecipientArchiver private let messageBackupKeyMaterial: MessageBackupKeyMaterial private let releaseNotesRecipientArchiver: MessageBackupReleaseNotesRecipientArchiver - private let streamProvider: MessageBackupProtoStreamProvider + private let plaintextStreamProvider: MessageBackupPlaintextProtoStreamProvider public init( accountDataArchiver: MessageBackupAccountDataArchiver, @@ -55,13 +56,14 @@ public class MessageBackupManagerImpl: MessageBackupManager { dateProvider: @escaping DateProvider, db: DB, distributionListRecipientArchiver: MessageBackupDistributionListRecipientArchiver, + encryptedStreamProvider: MessageBackupEncryptedProtoStreamProvider, groupRecipientArchiver: MessageBackupGroupRecipientArchiver, incrementalTSAttachmentMigrator: IncrementalMessageTSAttachmentMigrator, kvStoreFactory: KeyValueStoreFactory, localRecipientArchiver: MessageBackupLocalRecipientArchiver, messageBackupKeyMaterial: MessageBackupKeyMaterial, releaseNotesRecipientArchiver: MessageBackupReleaseNotesRecipientArchiver, - streamProvider: MessageBackupProtoStreamProvider + plaintextStreamProvider: MessageBackupPlaintextProtoStreamProvider ) { self.accountDataArchiver = accountDataArchiver self.attachmentDownloadManager = attachmentDownloadManager @@ -74,13 +76,14 @@ public class MessageBackupManagerImpl: MessageBackupManager { self.dateProvider = dateProvider self.db = db self.distributionListRecipientArchiver = distributionListRecipientArchiver + self.encryptedStreamProvider = encryptedStreamProvider self.groupRecipientArchiver = groupRecipientArchiver self.incrementalTSAttachmentMigrator = incrementalTSAttachmentMigrator self.kvStore = kvStoreFactory.keyValueStore(collection: Constants.keyValueStoreCollectionName) self.localRecipientArchiver = localRecipientArchiver self.messageBackupKeyMaterial = messageBackupKeyMaterial self.releaseNotesRecipientArchiver = releaseNotesRecipientArchiver - self.streamProvider = streamProvider + self.plaintextStreamProvider = plaintextStreamProvider } // MARK: - Remote backups @@ -148,7 +151,7 @@ public class MessageBackupManagerImpl: MessageBackupManager { return try await db.awaitableWrite { tx in let outputStream: MessageBackupProtoOutputStream let metadataProvider: MessageBackup.ProtoStream.EncryptionMetadataProvider - switch self.streamProvider.openEncryptedOutputFileStream( + switch self.encryptedStreamProvider.openEncryptedOutputFileStream( localAci: localIdentifiers.aci, tx: tx ) { @@ -182,7 +185,7 @@ public class MessageBackupManagerImpl: MessageBackupManager { return try await db.awaitableWrite { tx in let outputStream: MessageBackupProtoOutputStream let fileUrl: URL - switch self.streamProvider.openPlaintextOutputFileStream() { + switch self.plaintextStreamProvider.openPlaintextOutputFileStream() { case .success(let _outputStream, let _fileUrl): outputStream = _outputStream fileUrl = _fileUrl @@ -362,7 +365,7 @@ public class MessageBackupManagerImpl: MessageBackupManager { try await db.awaitableWrite { tx in let inputStream: MessageBackupProtoInputStream - switch self.streamProvider.openEncryptedInputFileStream( + switch self.encryptedStreamProvider.openEncryptedInputFileStream( fileUrl: fileUrl, localAci: localIdentifiers.aci, tx: tx @@ -398,7 +401,7 @@ public class MessageBackupManagerImpl: MessageBackupManager { try await db.awaitableWrite { tx in let inputStream: MessageBackupProtoInputStream - switch self.streamProvider.openPlaintextInputFileStream( + switch self.plaintextStreamProvider.openPlaintextInputFileStream( fileUrl: fileUrl ) { case .success(let protoStream, _): diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupAccountDataTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupAccountDataTest.swift deleted file mode 100644 index 2d5874c26ac..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupAccountDataTest.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupAccountDataTest: MessageBackupIntegrationTestCase { - func testAccountData() async throws { - try await runTest(backupName: "account-data") { sdsTx, tx in - XCTAssertNotNil(profileManager.localProfileKey()) - - switch deps.localUsernameManager.usernameState(tx: tx) { - case .available(let username, let usernameLink): - XCTAssertEqual(username, "boba_fett.66") - XCTAssertEqual(usernameLink.handle, UUID(uuidString: "61C101A2-00D5-4217-89C2-0518D8497AF0")!) - XCTAssertEqual(deps.localUsernameManager.usernameLinkQRCodeColor(tx: tx), .olive) - case .unset, .linkCorrupted, .usernameAndLinkCorrupted: - XCTFail("Unexpected username state!") - } - - XCTAssertEqual(profileManager.localGivenName(), "Boba") - XCTAssertEqual(profileManager.localFamilyName(), "Fett") - XCTAssertNil(profileManager.localProfileAvatarData()) - - XCTAssertNotNil(subscriptionManager.getSubscriberID(transaction: sdsTx)) - XCTAssertEqual(subscriptionManager.getSubscriberCurrencyCode(transaction: sdsTx), "USD") - XCTAssertTrue(subscriptionManager.userManuallyCancelledSubscription(transaction: sdsTx)) - - XCTAssertTrue(receiptManager.areReadReceiptsEnabled(transaction: sdsTx)) - XCTAssertTrue(preferences.shouldShowUnidentifiedDeliveryIndicators(transaction: sdsTx)) - XCTAssertTrue(typingIndicatorsImpl.areTypingIndicatorsEnabled()) - XCTAssertFalse(deps.linkPreviewSettingStore.areLinkPreviewsEnabled(tx: tx)) - XCTAssertEqual(deps.phoneNumberDiscoverabilityManager.phoneNumberDiscoverability(tx: tx), .nobody) - XCTAssertTrue(SSKPreferences.preferContactAvatars(transaction: sdsTx)) - let universalExpireConfig = deps.disappearingMessagesConfigurationStore.fetch(for: .universal, tx: tx) - XCTAssertEqual(universalExpireConfig?.isEnabled, true) - XCTAssertEqual(universalExpireConfig?.durationSeconds, 3600) - XCTAssertEqual(ReactionManager.customEmojiSet(transaction: sdsTx), ["🏎️"]) - XCTAssertTrue(subscriptionManager.displayBadgesOnProfile(transaction: sdsTx)) - XCTAssertTrue(SSKPreferences.shouldKeepMutedChatsArchived(transaction: sdsTx)) - XCTAssertTrue(StoryManager.hasSetMyStoriesPrivacy(transaction: sdsTx)) - XCTAssertTrue(systemStoryManager.isOnboardingStoryRead(transaction: sdsTx)) - XCTAssertFalse(StoryManager.areStoriesEnabled(transaction: sdsTx)) - XCTAssertTrue(StoryManager.areViewReceiptsEnabled(transaction: sdsTx)) - XCTAssertTrue(systemStoryManager.isOnboardingOverlayViewed(transaction: sdsTx)) - XCTAssertFalse(deps.usernameEducationManager.shouldShowUsernameEducation(tx: tx)) - XCTAssertEqual(udManager.phoneNumberSharingMode(tx: tx), .nobody) - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupContactTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupContactTest.swift deleted file mode 100644 index b409372411a..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupContactTest.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupContactTest: MessageBackupIntegrationTestCase { - private let aciFixture = Aci.constantForTesting("4076995E-0531-4042-A9E4-1E67A77DF358") - private let pniFixture = Pni.constantForTesting("PNI:26FC02A2-BA58-4A7D-B081-9DA23971570A") - - private func assert( - recipient: SignalRecipient, - username: String = "han_solo.44", - phoneNumber: String = "+17735550199", - isBlocked: Bool, - isHidden: Bool, - isRegistered: Bool, - unregisteredAtTimestamp: UInt64?, - isWhitelisted: Bool, - givenName: String = "Han", - familyName: String = "Solo", - isStoryHidden: Bool = true, - sdsTx: SDSAnyReadTransaction, - tx: DBReadTransaction - ) { - XCTAssertEqual(recipient.aci, aciFixture) - XCTAssertEqual(recipient.pni, pniFixture) - XCTAssertEqual(deps.usernameLookupManager.fetchUsername(forAci: recipient.aci!, transaction: tx), username) - XCTAssertEqual(recipient.phoneNumber?.stringValue, phoneNumber) - XCTAssertEqual(blockingManager.isAddressBlocked(recipient.address, transaction: sdsTx), isBlocked) - XCTAssertEqual(deps.recipientHidingManager.isHiddenRecipient(recipient, tx: tx), isHidden) - XCTAssertEqual(recipient.isRegistered, isRegistered) - XCTAssertEqual(recipient.unregisteredAtTimestamp, unregisteredAtTimestamp) - let recipientProfile = profileManager.getUserProfile(for: recipient.address, transaction: sdsTx) - XCTAssertNotNil(recipientProfile?.profileKey) - XCTAssertEqual(profileManager.isUser(inProfileWhitelist: recipient.address, transaction: sdsTx), isWhitelisted) - XCTAssertEqual(recipientProfile?.givenName, givenName) - XCTAssertEqual(recipientProfile?.familyName, familyName) - XCTAssertEqual(StoryStoreImpl().getOrCreateStoryContextAssociatedData(for: recipient.aci!, tx: tx).isHidden, isStoryHidden) - } - - private func allRecipients(tx: any DBReadTransaction) -> [SignalRecipient] { - var result = [SignalRecipient]() - deps.recipientDatabaseTable.enumerateAll(tx: tx) { result.append($0) } - return result - } - - func testRegisteredBlockedContact() async throws { - try await runTest(backupName: "registered-blocked-contact") { sdsTx, tx in - let allRecipients = allRecipients(tx: tx) - XCTAssertEqual(allRecipients.count, 1) - - assert( - recipient: allRecipients.first!, - isBlocked: true, - isHidden: true, - isRegistered: true, - unregisteredAtTimestamp: nil, - isWhitelisted: false, - sdsTx: sdsTx, - tx: tx - ) - } - } - - func testUnregisteredContact() async throws { - try await runTest(backupName: "unregistered-contact") { sdsTx, tx in - let allRecipients = allRecipients(tx: tx) - XCTAssertEqual(allRecipients.count, 1) - - assert( - recipient: allRecipients.first!, - isBlocked: false, - isHidden: false, - isRegistered: false, - unregisteredAtTimestamp: 1713157772000, - isWhitelisted: true, - sdsTx: sdsTx, - tx: tx - ) - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupDistributionListTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupDistributionListTest.swift deleted file mode 100644 index 5f6b4ca7f07..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupDistributionListTest.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupDistributionListTest: MessageBackupIntegrationTestCase { - typealias ListValidationBlock = ((TSPrivateStoryThread) -> Void) - - private var privateStoryThreadDeletionManager: any PrivateStoryThreadDeletionManager { deps.privateStoryThreadDeletionManager } - private var threadStore: ThreadStore { deps.threadStore } - - func testDistributionList() async throws { - try await runTest( - backupName: "story-distribution-list", - dateProvider: { - /// "Deleted distribution list" logic relies on the distance - /// between timestamps in the Backup binary and the "current - /// time". To keep this test stable over time, we need to - /// hardcode the "current time"; the timestamp below is from the - /// time this test was originally committed. - return Date(millisecondsSince1970: 1717631700000) - } - ) { sdsTx, tx in - let deletedStories = privateStoryThreadDeletionManager.allDeletedIdentifiers(tx: tx) - XCTAssertEqual(deletedStories.count, 2) - - let validationBlocks: [UUID: ListValidationBlock] = [ - UUID(uuidString: TSPrivateStoryThread.myStoryUniqueId)!: { thread in - XCTAssertTrue(thread.allowsReplies) - }, - UUID(data: Data(base64Encoded: "me/ptJ9tRnyCWu/eg9uP7Q==")!)!: { thread in - XCTAssertEqual(thread.name, "Mandalorians") - XCTAssertTrue(thread.allowsReplies) - XCTAssertEqual(thread.storyViewMode, .blockList) - XCTAssertEqual(thread.addresses.count, 2) - }, - UUID(data: Data(base64Encoded: "ZYoHlxwxS8aBGSQJ1tL0sA==")!)!: { thread in - XCTAssertEqual(thread.name, "Hutts") - XCTAssertFalse(thread.allowsReplies) - // check member list - XCTAssertEqual(thread.storyViewMode, .explicit) - XCTAssertEqual(thread.addresses.count, 1) - } - ] - - try threadStore.enumerateStoryThreads(tx: tx) { thread in - do { - let validationBlock = try XCTUnwrap(validationBlocks[UUID(uuidString: thread.uniqueId)!]) - validationBlock(thread) - } catch { - XCTFail("Missing validation block") - } - - return true - } - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupExpirationTimerChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupExpirationTimerChatUpdateTest.swift deleted file mode 100644 index 8bb5e8fc94c..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupExpirationTimerChatUpdateTest.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupExpirationTimerChatUpdateTest: MessageBackupIntegrationTestCase { - func testExpirationTimerChatUpdates() async throws { - let contactAci = Aci.constantForTesting("5F8C568D-0119-47BD-81AA-BB87C9B71995") - - try await runTest(backupName: "expiration-timer-chat-update-message") { sdsTx, tx in - let fetchedContactThreads = deps.threadStore.fetchContactThreads(serviceId: contactAci, tx: tx) - XCTAssertEqual(fetchedContactThreads.count, 1) - let expectedContactThread = fetchedContactThreads.first! - - struct ExpectedExpirationTimerUpdate { - let authorIsLocalUser: Bool - let expiresInSeconds: UInt32 - var isEnabled: Bool { expiresInSeconds > 0 } - } - - var expectedUpdates: [ExpectedExpirationTimerUpdate] = [ - .init(authorIsLocalUser: false, expiresInSeconds: 0), - .init(authorIsLocalUser: true, expiresInSeconds: 9001), - ] - - try deps.interactionStore.enumerateAllInteractions(tx: tx) { interaction -> Bool in - XCTAssertEqual(interaction.uniqueThreadId, expectedContactThread.uniqueId) - - guard let dmUpdateInfoMessage = interaction as? OWSDisappearingConfigurationUpdateInfoMessage else { - XCTFail("Unexpected interaction type: \(type(of: interaction))") - return false - } - - guard let expectedUpdate = expectedUpdates.popFirst() else { - XCTFail("Unexpectedly missing expected timer change!") - return false - } - - XCTAssertEqual(dmUpdateInfoMessage.configurationDurationSeconds, expectedUpdate.expiresInSeconds) - XCTAssertEqual(dmUpdateInfoMessage.configurationIsEnabled, expectedUpdate.isEnabled) - if expectedUpdate.authorIsLocalUser { - XCTAssertNil(dmUpdateInfoMessage.createdByRemoteName) - } else { - XCTAssertNotNil(dmUpdateInfoMessage.createdByRemoteName) - } - - return true - } - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupIncomingMessageWithEditsTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupIncomingMessageWithEditsTest.swift deleted file mode 100644 index 8e8fa328015..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupIncomingMessageWithEditsTest.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupIncomingMessageWithEditsTest: MessageBackupIntegrationTestCase { - private var mentionPlaceholder: String { MessageBody.mentionPlaceholder } - - func testIncomingMessageWithEdits() async throws { - let hanAci = Aci.constantForTesting("5F8C568D-0119-47BD-81AA-BB87C9B71995") - let chewieAci = Aci.constantForTesting("0AE8C6C8-2707-4EA7-B224-7417227D3890") - - try await runTest(backupName: "incoming-message-with-edits") { sdsTx, tx in - let allGroupThreads = try deps.threadStore.fetchAllGroupThreads(tx: tx) - XCTAssertEqual(allGroupThreads.count, 1) - let groupThread = allGroupThreads.first! - - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - /// The message, and two previous revisions. - XCTAssertEqual(allInteractions.count, 3) - - /// The most recent revision will be the oldest message, or the last - /// one fetched. - let mostRecentRevision = allInteractions[2] as! TSIncomingMessage - /// Ordered newest -> oldest. - let editHistory = try deps.editMessageStore.findEditHistory(for: mostRecentRevision, tx: tx) - XCTAssertEqual(editHistory.count, 2) - /// Original revision is the oldest in edit history... - let originalRevision: TSIncomingMessage = editHistory[1].message! - /// ...and intermediate revision is the newest. - let middleRevision: TSIncomingMessage = editHistory[0].message! - - /// Confirm all the messages are from Han and in the expected group. - XCTAssertTrue([mostRecentRevision, middleRevision, originalRevision].allSatisfy { incomingMessage -> Bool in - return - incomingMessage.uniqueThreadId == groupThread.uniqueId - && incomingMessage.authorUUID == hanAci.serviceIdUppercaseString - }) - - /// Verify the original message contents are correct... - XCTAssertEqual(originalRevision.body, "Original message") - XCTAssertEqual(originalRevision.timestamp, 1000) - XCTAssertEqual(originalRevision.serverTimestamp, 1001) - XCTAssertEqual(originalRevision.receivedAtTimestamp, 1002) - XCTAssertEqual(originalRevision.editState, .pastRevision) - XCTAssertTrue(originalRevision.wasRead) - originalRevision.assertStyle(.italic, inRange: NSRange(location: 9, length: 7)) - - /// ...and that we got the intermediate revision... - XCTAssertEqual(middleRevision.body, "First revision: \(mentionPlaceholder)") - XCTAssertEqual(middleRevision.timestamp, 2000) - XCTAssertEqual(middleRevision.serverTimestamp, 2001) - XCTAssertEqual(middleRevision.receivedAtTimestamp, 2002) - XCTAssertTrue(middleRevision.wasRead) - XCTAssertEqual(middleRevision.editState, .pastRevision) - middleRevision.assertAciMention(chewieAci, atLocation: 16) - - /// ...and the final revision... - XCTAssertEqual(mostRecentRevision.body, "Latest revision: \(mentionPlaceholder)") - XCTAssertEqual(mostRecentRevision.timestamp, 3000) - XCTAssertEqual(mostRecentRevision.serverTimestamp, 3001) - XCTAssertEqual(mostRecentRevision.receivedAtTimestamp, 3002) - XCTAssertEqual(mostRecentRevision.editState, .latestRevisionRead) - XCTAssertTrue(mostRecentRevision.wasRead) - mostRecentRevision.assertAciMention(chewieAci, atLocation: 17) - mostRecentRevision.assertStyle(.italic, inRange: NSRange(location: 0, length: 6)) - - /// ...and its downstream reactions. - let reactions = deps.reactionStore - .allReactions(messageId: mostRecentRevision.uniqueId, tx: tx) - .sorted(by: { $0.sortOrder < $1.sortOrder }) - XCTAssertEqual(reactions.map { $0.emoji }, ["👀", "🥂"]) - XCTAssertEqual(reactions.map { $0.sentAtTimestamp }, [101, 102]) - XCTAssertEqual(reactions.map { $0.reactorAci }, [localIdentifiers.aci, hanAci]) - } - } -} - -private extension TSIncomingMessage { - func assertAciMention(_ aci: Aci, atLocation location: Int) { - guard - let mentions = bodyRanges?.mentions, - mentions.count == 1, - let singleMention = mentions.first - else { - XCTFail("Failed to extract single mention from body ranges!") - return - } - - XCTAssertEqual(singleMention.key, NSRange(location: location, length: 1)) - XCTAssertEqual(singleMention.value, aci) - } - - func assertStyle(_ style: MessageBodyRanges.Style, inRange range: NSRange) { - guard - let styles = bodyRanges?.collapsedStyles, - styles.count == 1, - let firstStyle = styles.first - else { - XCTFail("Failed to extract single style entry from body ranges!") - return - } - - XCTAssertEqual(firstStyle.range, range) - XCTAssertEqual(firstStyle.value.style, style) - } -} - -// MARK: - - -private extension InteractionStore { - /// Fetches all interactions, from newest -> oldest. - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results.sorted { lhs, rhs in - return lhs.sqliteRowId! > rhs.sqliteRowId! - } - } -} - -private extension ThreadStore { - func fetchAllGroupThreads(tx: any DBReadTransaction) throws -> [TSGroupThread] { - var results = [TSGroupThread]() - try enumerateGroupThreads(tx: tx) { groupThread in - results.append(groupThread) - return true - } - return results - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTestCase.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTests.swift similarity index 64% rename from SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTestCase.swift rename to SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTests.swift index 15f96378203..01bc99ccd0c 100644 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTestCase.swift +++ b/SignalServiceKit/tests/MessageBackup/MessageBackupIntegrationTests.swift @@ -8,20 +8,52 @@ import XCTest @testable import SignalServiceKit -class MessageBackupIntegrationTestCase: XCTestCase { +class MessageBackupIntegrationTests: XCTestCase { override func setUp() { DDLog.add(DDTTYLogger.sharedInstance!) } - var deps: DependenciesBridge { .shared } + /// Performs a round-trip import/export test on all `.binproto` integration + /// test cases. + func testAllIntegrationTestCases() async throws { + guard + let allBinprotoFileUrls = Bundle(for: type(of: self)).urls( + forResourcesWithExtension: "binproto", + subdirectory: nil + ) + else { + XCTFail("Failed to find binprotos in test bundle!") + return + } - // MARK: - + for binprotoFileUrl in allBinprotoFileUrls { + let filename = binprotoFileUrl + .lastPathComponent + .filenameWithoutExtension - private var messageBackupManager: MessageBackupManager { - deps.messageBackupManager + try await runRoundTripTest( + testCaseName: filename, + testCaseFileUrl: binprotoFileUrl + ) + } } - var localIdentifiers: LocalIdentifiers { + // MARK: - + + private var deps: DependenciesBridge { .shared } + + /// Runs a round-trip import/export test for the given `.binproto` file. + /// + /// The round-trip test imports the given `.binproto` into an empty app, + /// then exports the app's state into another `.binproto`. The + /// originally-imported and recently-exported `.binprotos` are then compared + /// by LibSignal. They should be equivalent; any disparity indicates that + /// some data was dropped or modified as part of the import/export process, + /// which should be idempotent. + private func runRoundTripTest( + testCaseName: String, + testCaseFileUrl: URL + ) async throws { /// A backup doesn't contain our own local identifiers. Rather, those /// are determined as part of registration for a backup import, and are /// already-known for a backup export. @@ -29,62 +61,36 @@ class MessageBackupIntegrationTestCase: XCTestCase { /// Consequently, we can use any local identifiers for our test /// purposes without worrying about the contents of each test case's /// backup file. - return .forUnitTests - } - - func runTest( - backupName: String, - dateProvider: DateProvider? = nil, - enableLibsignalComparator: Bool = true, - assertionsBlock: (SDSAnyReadTransaction, DBReadTransaction) throws -> Void - ) async throws { - let testCaseBackupUrl = backupFileUrl(named: backupName) - try await importAndAssert( - backupUrl: testCaseBackupUrl, - dateProvider: dateProvider, - localIdentifiers: localIdentifiers, - assertionsBlock: assertionsBlock - ) + let localIdentifiers: LocalIdentifiers = .forUnitTests + + /// Backup files hardcode timestamps, some of which are interpreted + /// relative to "now". For example, "deleted" story distribution lists + /// are marked as deleted for a period of time before being actually + /// deleted; when these frames are restored from a Backup, their + /// deletion timestamp is compared to "now" to determine if they should + /// be deleted. + /// + /// Consequently, in order for tests to remain stable over time we need + /// to "anchor" them with an unchanging timestamp. To that end, we'll + /// extract the `backupTimeMs` field from the Backup header, and use + /// that as our "now" during import. + let backupTimeMs = try readBackupTimeMs(testCaseFileUrl: testCaseFileUrl) - let exportedBackupUrl = try await messageBackupManager - .exportPlaintextBackup(localIdentifiers: localIdentifiers) + await initializeApp(dateProvider: { Date(millisecondsSince1970: backupTimeMs) }) - try await importAndAssert( - backupUrl: exportedBackupUrl, - dateProvider: dateProvider, - localIdentifiers: localIdentifiers, - assertionsBlock: assertionsBlock + try await deps.messageBackupManager.importPlaintextBackup( + fileUrl: testCaseFileUrl, + localIdentifiers: localIdentifiers ) - if enableLibsignalComparator { - try compareViaLibsignal( - sharedTestCaseBackupUrl: testCaseBackupUrl, - exportedBackupUrl: exportedBackupUrl - ) - } - } - - private func backupFileUrl(named backupName: String) -> URL { - let testBundle = Bundle(for: type(of: self)) - return testBundle.url(forResource: backupName, withExtension: "binproto")! - } - - private func importAndAssert( - backupUrl: URL, - dateProvider: DateProvider?, - localIdentifiers: LocalIdentifiers, - assertionsBlock: (SDSAnyReadTransaction, DBReadTransaction) throws -> Void - ) async throws { - await initializeApp(dateProvider: dateProvider) + let exportedBackupUrl = try await deps.messageBackupManager + .exportPlaintextBackup(localIdentifiers: localIdentifiers) - try await messageBackupManager.importPlaintextBackup( - fileUrl: backupUrl, - localIdentifiers: localIdentifiers + try compareViaLibsignal( + testCaseName: testCaseName, + sharedTestCaseBackupUrl: testCaseFileUrl, + exportedBackupUrl: exportedBackupUrl ) - - try NSObject.databaseStorage.read { tx in - try assertionsBlock(tx, tx.asV2Read) - } } /// Compare the canonical representation of the Backups at the two given @@ -94,6 +100,7 @@ class MessageBackupIntegrationTestCase: XCTestCase { /// If there are errors reading or validating either Backup, or if the /// Backups' canonical representations are not equal. private func compareViaLibsignal( + testCaseName: String, sharedTestCaseBackupUrl: URL, exportedBackupUrl: URL ) throws { @@ -101,7 +108,7 @@ class MessageBackupIntegrationTestCase: XCTestCase { let exportedBackup = try ComparableBackup(url: exportedBackupUrl) guard sharedTestCaseBackup.unknownFields.fields.isEmpty else { - XCTFail("Shared test case Backup had unknown fields: \(sharedTestCaseBackup.unknownFields)") + XCTFail("Test \(testCaseName) had unknown fields: \(sharedTestCaseBackup.unknownFields)!") return } @@ -112,7 +119,7 @@ class MessageBackupIntegrationTestCase: XCTestCase { XCTFail(""" ------------ - Test failed: Backups were different when compared via LibSignal! Copy the JSON lines below and run `pbpaste | parse-libsignal-comparator-failure.py`. + Test case failed: \(testCaseName). Copy the JSON lines below and run `pbpaste | parse-libsignal-comparator-failure.py`. \(sharedTestCaseBackupString.removeCharacters(characterSet: .whitespacesAndNewlines)) \(exportedBackupString.removeCharacters(characterSet: .whitespacesAndNewlines)) @@ -124,8 +131,42 @@ class MessageBackupIntegrationTestCase: XCTestCase { // MARK: - + /// Read the `backupTimeMs` field from the header of the Backup file at the + /// given local URL. + private func readBackupTimeMs(testCaseFileUrl: URL) throws -> UInt64 { + let plaintextStreamProvider = MessageBackupPlaintextProtoStreamProviderImpl() + + let stream: MessageBackupProtoInputStream + switch plaintextStreamProvider.openPlaintextInputFileStream( + fileUrl: testCaseFileUrl + ) { + case .success(let _stream, _): + stream = _stream + case .fileNotFound: + throw OWSAssertionError("Missing test case backup file!") + case .unableToOpenFileStream: + throw OWSAssertionError("Failed to open test case backup file!") + case .hmacValidationFailedOnEncryptedFile: + throw OWSAssertionError("Impossible – this is a plaintext stream!") + } + + let backupInfo: BackupProto_BackupInfo + switch stream.readHeader() { + case .success(let _backupInfo, _): + backupInfo = _backupInfo + case .invalidByteLengthDelimiter: + throw OWSAssertionError("Invalid byte length delimiter!") + case .protoDeserializationError(let error): + throw OWSAssertionError("Proto deserialization error: \(error)!") + } + + return backupInfo.backupTimeMs + } + + // MARK: - + @MainActor - final func initializeApp(dateProvider: DateProvider?) async { + private func initializeApp(dateProvider: DateProvider?) async { let testAppContext = TestAppContext() SetCurrentAppContext(testAppContext) @@ -162,6 +203,8 @@ class MessageBackupIntegrationTestCase: XCTestCase { } } +// MARK: - + private extension LibSignalClient.ComparableBackup { convenience init(url: URL) throws { let fileHandle = try FileHandle(forReadingFrom: url) @@ -176,7 +219,7 @@ private extension LibSignalClient.ComparableBackup { } } -// MARK: - +// MARK: - CrashyMocks private func failTest( _ type: T.Type, diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupLearnedProfileChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupLearnedProfileChatUpdateTest.swift deleted file mode 100644 index 7a60b1915cb..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupLearnedProfileChatUpdateTest.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupLearnedProfileChatUpdateTest: MessageBackupIntegrationTestCase { - func testLearnedProfileChatUpdates() async throws { - let expectedAci = Aci.parseFrom(aciString: "5F8C568D-0119-47BD-81AA-BB87C9B71995")! - - try await runTest(backupName: "learned-profile-chat-update-message") { sdsTx, tx in - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - XCTAssertEqual(allInteractions.count, 2) - - let expectedDisplayNames: [TSInfoMessage.DisplayNameBeforeLearningProfileName] = [ - .username("boba_fett.99"), - .phoneNumber("+17735550199"), - ] - - for (idx, interaction) in allInteractions.enumerated() { - guard - let contactThread = deps.threadStore.fetchThreadForInteraction(interaction, tx: tx) as? TSContactThread, - let contactAci = Aci.parseFrom(aciString: contactThread.contactUUID), - let infoMessage = interaction as? TSInfoMessage, - let displayNameBeforeLearningProfileName = infoMessage.displayNameBeforeLearningProfileName - else { - XCTFail("Unexpectedly missing expected info message and properties!") - return - } - - XCTAssertEqual(contactAci, expectedAci) - XCTAssertEqual(infoMessage.messageType, .learnedProfileName) - XCTAssertEqual(displayNameBeforeLearningProfileName, expectedDisplayNames[idx]) - } - } - } -} - -private extension InteractionStore { - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupOutgoingMessageWithEditsTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupOutgoingMessageWithEditsTest.swift deleted file mode 100644 index 4acaa70eb1f..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupOutgoingMessageWithEditsTest.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupOutgoingMessageWithEditsTest: MessageBackupIntegrationTestCase { - private struct FailTestError: Error { - init(_ message: String) { - XCTFail(message) - } - } - - func testOutgoingMessageWithEdits() async throws { - let hanAci = Aci.constantForTesting("5F8C568D-0119-47BD-81AA-BB87C9B71995") - - try await runTest(backupName: "outgoing-message-with-edits") { sdsTx, tx in - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - /// The message, and two previous revisions. - XCTAssertEqual(allInteractions.count, 3) - - /// The most recent revision will be the oldest message, or the last - /// one fetched. - let mostRecentRevision = allInteractions[2] as! TSOutgoingMessage - /// Ordered newest -> oldest. - let editHistory = try deps.editMessageStore.findEditHistory(for: mostRecentRevision, tx: tx) - XCTAssertEqual(editHistory.count, 2) - /// Original revision is the oldest in edit history... - let originalRevision: TSOutgoingMessage = editHistory[1].message! - /// ...and intermediate revision is the newest. - let middleRevision: TSOutgoingMessage = editHistory[0].message! - - /// Confirm all the messages are from Han and in the expected group. - XCTAssertTrue([mostRecentRevision, middleRevision, originalRevision].allSatisfy { outgoingMessage -> Bool in - guard let contactThread = deps.threadStore.fetchThread( - uniqueId: outgoingMessage.uniqueThreadId, - tx: tx - ) as? TSContactThread else { - XCTFail("Missing contact thread for outgoing message!") - return false - } - - return contactThread.contactUUID == hanAci.serviceIdUppercaseString - }) - - func singleRecipientState(_ outgoingMessage: TSOutgoingMessage) throws -> TSOutgoingMessageRecipientState { - guard - let recipientAddressStates = outgoingMessage.recipientAddressStates, - recipientAddressStates.count == 1, - let single = recipientAddressStates.first - else { throw FailTestError("Missing single recipient state!") } - - return single.value - } - - /// Verify the original message contents are correct... - XCTAssertEqual(originalRevision.body, "Original message") - XCTAssertEqual(originalRevision.timestamp, 1000) - XCTAssertNil(try singleRecipientState(originalRevision).errorCode) - XCTAssertEqual(try singleRecipientState(originalRevision).status, .read) - XCTAssertEqual(try singleRecipientState(originalRevision).statusTimestamp, 1001) - XCTAssertEqual(originalRevision.editState, .pastRevision) - - /// ...and that we got the intermediate revision... - XCTAssertEqual(middleRevision.body, "First revision") - XCTAssertEqual(middleRevision.timestamp, 2000) - XCTAssertNil(try singleRecipientState(middleRevision).errorCode) - XCTAssertEqual(try singleRecipientState(middleRevision).status, .delivered) - XCTAssertEqual(try singleRecipientState(middleRevision).statusTimestamp, 2001) - XCTAssertEqual(middleRevision.editState, .pastRevision) - - /// ...and the final revision... - XCTAssertEqual(mostRecentRevision.body, "Latest revision") - XCTAssertEqual(mostRecentRevision.timestamp, 3000) - XCTAssertNil(try singleRecipientState(mostRecentRevision).errorCode) - XCTAssertEqual(try singleRecipientState(mostRecentRevision).status, .sent) - XCTAssertEqual(try singleRecipientState(mostRecentRevision).statusTimestamp, 3001) - XCTAssertEqual(mostRecentRevision.editState, .latestRevisionRead) - - /// ...and its downstream reactions. - let reactions = deps.reactionStore - .allReactions(messageId: mostRecentRevision.uniqueId, tx: tx) - .sorted(by: { $0.sortOrder < $1.sortOrder }) - XCTAssertEqual(reactions.map { $0.emoji }, ["👀", "🥂"]) - XCTAssertEqual(reactions.map { $0.sentAtTimestamp }, [101, 102]) - XCTAssertEqual(reactions.map { $0.reactorAci }, [localIdentifiers.aci, hanAci]) - } - } -} - -// MARK: - - -private extension InteractionStore { - /// Fetches all interactions, from newest -> oldest. - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results.sorted { lhs, rhs in - return lhs.sqliteRowId! > rhs.sqliteRowId! - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupProfileChangeChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupProfileChangeChatUpdateTest.swift deleted file mode 100644 index 7b9bb9b2451..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupProfileChangeChatUpdateTest.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupProfileChangeChatUpdateTest: MessageBackupIntegrationTestCase { - func testProfileChange() async throws { - let expectedAci = Aci.parseFrom(aciString: "5F8C568D-0119-47BD-81AA-BB87C9B71995")! - - try await runTest(backupName: "profile-change-chat-update-message") { sdsTx, tx in - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - XCTAssertEqual(allInteractions.count, 1) - - guard - let profileChangeInfoMessage = allInteractions.first as? TSInfoMessage, - let profileChangeAddress = profileChangeInfoMessage.profileChangeAddress, - let oldName = profileChangeInfoMessage.profileChangesOldFullName, - let newName = profileChangeInfoMessage.profileChangesNewFullName - else { - XCTFail("Missing profile change properties!") - return - } - - XCTAssertEqual(profileChangeInfoMessage.messageType, .profileUpdate) - XCTAssertEqual(profileChangeAddress.aci, expectedAci) - XCTAssertEqual(oldName, "Snoop Dogg") - XCTAssertEqual(newName, "Snoop Lion") - } - } -} - -private extension InteractionStore { - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupSessionSwitchoverChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupSessionSwitchoverChatUpdateTest.swift deleted file mode 100644 index 7c3d5a2f471..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupSessionSwitchoverChatUpdateTest.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupSessionSwitchoverChatUpdateTest: MessageBackupIntegrationTestCase { - func testSessionSwitchoverChatUpdates() async throws { - let expectedAci = Aci.parseFrom(aciString: "5F8C568D-0119-47BD-81AA-BB87C9B71995")! - - try await runTest(backupName: "session-switchover-chat-update-message") { sdsTx, tx in - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - XCTAssertEqual(allInteractions.count, 1) - - guard - let infoMessage = allInteractions.first! as? TSInfoMessage, - let sessionSwitchoverPhoneNumber = infoMessage.sessionSwitchoverPhoneNumber - else { - XCTFail("Unexpectedly missing session switchover message and properties!") - return - } - - guard - let switchedOverThread = deps.threadStore.fetchThreadForInteraction(infoMessage, tx: tx) as? TSContactThread, - let switchedOverThreadAci = switchedOverThread.contactAddress.aci - else { - XCTFail("Unexpectedly missing merged thread and properties!") - return - } - - XCTAssertEqual(infoMessage.messageType, .sessionSwitchover) - XCTAssertEqual(sessionSwitchoverPhoneNumber, "+17735550199") - XCTAssertEqual(switchedOverThreadAci, expectedAci) - } - } -} - -private extension InteractionStore { - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupSimpleChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupSimpleChatUpdateTest.swift deleted file mode 100644 index 7ed8d918fdd..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupSimpleChatUpdateTest.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupSimpleChatUpdateTest: MessageBackupIntegrationTestCase { - private struct FailTestError: Error { - init(_ message: String) { XCTFail("Test failed! \(message)") } - } - - private var interactionStore: any InteractionStore { deps.interactionStore } - private var threadStore: any ThreadStore { deps.threadStore } - - private func insertAndAssert(_ element: T?, into set: inout Set) throws { - guard let element else { - throw FailTestError("Element was nil!") - } - let (inserted, _) = set.insert(element) - guard inserted else { - throw FailTestError("Multiple elements: \(element).") - } - } - - func testSimpleChatUpdates() async throws { - let expectedAci = Aci.constantForTesting("5F8C568D-0119-47BD-81AA-BB87C9B71995") - - try await runTest(backupName: "simple-chat-update-message") { sdsTx, tx in - var seenVerificationStates = Set() - var seenUnknownProtocolVersionAuthors = Set() - var seenPaymentsActivationRequestInfos = Set() - var seenPaymentsActivatedInfos = Set() - var seenInfoMessageTypes = Set() - var seenErrorMessageTypes = Set() - - /// We should have only exactly the info/error messages we expect. - try deps.interactionStore.enumerateAllInteractions(tx: tx) { interaction throws -> Bool in - if let verificationStateChange = interaction as? OWSVerificationStateChangeMessage { - /// Info messages for verification state changes are all - /// this subclass, rather than a generic info message. - owsPrecondition(verificationStateChange.messageType == .verificationStateChange) - - XCTAssertTrue(verificationStateChange.isLocalChange) - XCTAssertEqual(verificationStateChange.recipientAddress.aci, expectedAci) - try insertAndAssert( - verificationStateChange.verificationState, - into: &seenVerificationStates - ) - } else if let unknownProtocolVersion = interaction as? OWSUnknownProtocolVersionMessage { - /// Info messages for unknown protocol versions are all this - /// subclass, rather than a generic info message. - owsPrecondition(unknownProtocolVersion.messageType == .unknownProtocolVersion) - - XCTAssertTrue(unknownProtocolVersion.isProtocolVersionUnknown) - try insertAndAssert( - unknownProtocolVersion.sender?.aci! ?? localIdentifiers.aci, - into: &seenUnknownProtocolVersionAuthors - ) - } else if let infoMessage = interaction as? TSInfoMessage { - XCTAssertNil(infoMessage.customMessage) - XCTAssertNil(infoMessage.unregisteredAddress) - - switch infoMessage.messageType { - case - .userNotRegistered, - .typeUnsupportedMessage, - .typeGroupQuit, - .addToContactsOffer, - .addUserToProfileWhitelistOffer, - .addGroupToProfileWhitelistOffer: - throw FailTestError("Unexpectedly found deprecated info message type \(infoMessage.messageType).") - case - .typeGroupUpdate, - .typeDisappearingMessagesUpdate, - .profileUpdate, - .threadMerge, - .sessionSwitchover, - .learnedProfileName: - throw FailTestError("Unexpectedly found complex update message \(infoMessage.messageType).") - case .verificationStateChange, .unknownProtocolVersion: - throw FailTestError("Unexpected message type for specific subclass in generic TSInfoMessage.") - case - .syncedThread, - .recipientHidden: - throw FailTestError("Unexpected local-only update message.") - case .paymentsActivationRequest: - try insertAndAssert( - infoMessage.paymentsActivationRequestAuthor(localIdentifiers: localIdentifiers), - into: &seenPaymentsActivationRequestInfos - ) - case .paymentsActivated: - try insertAndAssert( - infoMessage.paymentsActivatedAuthor(localIdentifiers: localIdentifiers), - into: &seenPaymentsActivatedInfos - ) - case - .typeSessionDidEnd, - .userJoinedSignal, - .reportedSpam, - .blockedOtherUser, - .blockedGroup, - .unblockedOtherUser, - .unblockedGroup, - .acceptedMessageRequest: - XCTAssertNil(infoMessage.infoMessageUserInfo) - fallthrough - case .phoneNumberChange: - try insertAndAssert( - infoMessage.messageType, - into: &seenInfoMessageTypes - ) - } - } else if let errorMessage = interaction as? TSErrorMessage { - switch errorMessage.errorType { - case - .noSession, - .wrongTrustedIdentityKey, - .invalidKeyException, - .missingKeyId, - .invalidMessage, - .duplicateMessage, - .invalidVersion, - .unknownContactBlockOffer, - .groupCreationFailed: - throw FailTestError("Unexpectedly found deprecated error message type \(errorMessage.errorType).") - case .nonBlockingIdentityChange: - XCTAssertEqual( - errorMessage.recipientAddress?.aci, - expectedAci - ) - case .sessionRefresh: - break - case .decryptionFailure: - XCTAssertEqual( - errorMessage.sender?.aci, - expectedAci - ) - } - - try insertAndAssert( - errorMessage.errorType, - into: &seenErrorMessageTypes - ) - } else { - throw FailTestError("Interaction was \(type(of: interaction)), not an info message.") - } - - return true - } - - XCTAssertEqual( - seenVerificationStates, - [.default, .verified], - "Unexpected set of verification states from identity change updates: \(seenVerificationStates)." - ) - XCTAssertEqual( - seenUnknownProtocolVersionAuthors, - [localIdentifiers.aci, expectedAci] - ) - XCTAssertEqual( - seenPaymentsActivationRequestInfos, - [.localUser, .otherUser(expectedAci)] - ) - XCTAssertEqual( - seenPaymentsActivatedInfos, - [.localUser, .otherUser(expectedAci)] - ) - XCTAssertEqual( - seenInfoMessageTypes.count, - 9, - "Unexpected number of info messages: \(seenInfoMessageTypes.count)." - ) - XCTAssertEqual( - seenErrorMessageTypes.count, - 3, - "Unexpected number of error messages: \(seenErrorMessageTypes.count)." - ) - } - } -} diff --git a/SignalServiceKit/tests/MessageBackup/MessageBackupThreadMergeChatUpdateTest.swift b/SignalServiceKit/tests/MessageBackup/MessageBackupThreadMergeChatUpdateTest.swift deleted file mode 100644 index 809710dc79b..00000000000 --- a/SignalServiceKit/tests/MessageBackup/MessageBackupThreadMergeChatUpdateTest.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import LibSignalClient -import XCTest - -@testable import SignalServiceKit - -final class MessageBackupThreadMergeChatUpdateTest: MessageBackupIntegrationTestCase { - func testThreadMergeChatUpdates() async throws { - let expectedAci = Aci.parseFrom(aciString: "5F8C568D-0119-47BD-81AA-BB87C9B71995")! - - try await runTest(backupName: "thread-merge-chat-update-message") { sdsTx, tx in - let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx) - XCTAssertEqual(allInteractions.count, 1) - - guard - let infoMessage = allInteractions.first! as? TSInfoMessage, - let threadMergePhoneNumber = infoMessage.threadMergePhoneNumber - else { - XCTFail("Unexpectedly missing thread merge message and properties!") - return - } - - guard - let mergedThread = deps.threadStore.fetchThreadForInteraction(infoMessage, tx: tx) as? TSContactThread, - let mergedThreadAci = mergedThread.contactAddress.aci - else { - XCTFail("Unexpectedly missing merged thread and properties!") - return - } - - XCTAssertEqual(infoMessage.messageType, .threadMerge) - XCTAssertEqual(threadMergePhoneNumber, "+17735550199") - XCTAssertEqual(mergedThreadAci, expectedAci) - } - } -} - -private extension InteractionStore { - func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] { - var results = [TSInteraction]() - try enumerateAllInteractions(tx: tx) { interaction in - results.append(interaction) - return true - } - return results - } -}