diff --git a/.drone.jsonnet b/.drone.jsonnet index 0c6259513..3f3e76365 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,3 +1,13 @@ +// Log a bunch of version information to make it easier for debugging +local version_info = { + name: 'Version Information', + commands: [ + 'git --version', + 'pod --version', + 'xcodebuild -version' + ] +}; + // Intentionally doing a depth of 2 as libSession-util has it's own submodules (and libLokinet likely will as well) local clone_submodules = { name: 'Clone Submodules', @@ -15,7 +25,10 @@ local install_cocoapods = { name: 'Install CocoaPods', commands: [' LANG=en_US.UTF-8 pod install || rm -rf ./Pods && LANG=en_US.UTF-8 pod install - '] + '], + depends_on: [ + 'Load CocoaPods Cache' + ] }; // Load from the cached CocoaPods directory (to speed up the build) @@ -40,6 +53,9 @@ local load_cocoapods_cache = { fi |||, 'rm -f /Users/drone/.cocoapods_cache.lock' + ], + depends_on: [ + 'Clone Submodules' ] }; @@ -69,6 +85,27 @@ local update_cocoapods_cache = { ] }; +// Run specified unit tests +local run_tests(testName, testBuildStepName) = { + name: 'Run ' + testName, + commands: [ + 'NSUnbufferedIO=YES set -o pipefail && xcodebuild test-without-building' + + ' -workspace Session.xcworkspace' + + ' -scheme Session' + + ' -derivedDataPath ./build/derivedData' + + ' -destination "platform=iOS Simulator,name=iPhone 14"' + + ' -test-timeouts-enabled YES' + + ' -maximum-test-execution-time-allowance 10' + + ' -only-testing ' + testName + + ' -collect-test-diagnostics never' + + ' 2>&1' + + ' | ./Pods/xcbeautify/xcbeautify --is-ci' + ], + depends_on: [ + testBuildStepName + ], +}; + [ // Unit tests @@ -78,6 +115,7 @@ local update_cocoapods_cache = { name: 'Unit Tests', platform: { os: 'darwin', arch: 'amd64' }, steps: [ + version_info, clone_submodules, load_cocoapods_cache, install_cocoapods, @@ -87,17 +125,40 @@ local update_cocoapods_cache = { 'xcrun simctl shutdown all', 'xcrun simctl erase all' ], + depends_on: [ + 'Install CocoaPods' + ] }, { - name: 'Run Unit Tests', + name: 'Build For Testing', commands: [ 'mkdir build', - 'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -parallelizeTargets -destination "platform=iOS Simulator,name=iPhone 14" -parallel-testing-enabled YES -parallel-testing-worker-count 2 -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci' + 'xcodebuild build-for-testing' + + ' -workspace Session.xcworkspace' + + ' -scheme Session' + + ' -derivedDataPath ./build/derivedData' + + ' -parallelizeTargets' + + ' -destination "platform=iOS Simulator,name=iPhone 14"' + + ' | ./Pods/xcbeautify/xcbeautify --is-ci' + ], + depends_on: [ + 'Reset Simulators' ], }, + run_tests('SessionTests', 'Build For Testing'), + run_tests('SessionMessagingKitTests', 'Build For Testing'), + run_tests('SessionSnodeKitTests', 'Build For Testing'), + run_tests('SessionUtilitiesKitTests', 'Build For Testing'), { name: 'Shutdown Simulators', commands: [ 'xcrun simctl shutdown all' ], + depends_on: [ + 'Build For Testing', + 'Run SessionTests', + 'Run SessionMessagingKitTests', + 'Run SessionSnodeKitTests', + 'Run SessionUtilitiesKitTests' + ], when: { status: ['failure', 'success'] } @@ -113,6 +174,7 @@ local update_cocoapods_cache = { platform: { os: 'darwin', arch: 'amd64' }, trigger: { event: { exclude: [ 'pull_request' ] } }, steps: [ + version_info, clone_submodules, load_cocoapods_cache, install_cocoapods, @@ -120,7 +182,16 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -parallelizeTargets -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci' + 'xcodebuild archive' + + ' -workspace Session.xcworkspace' + + ' -scheme Session' + + ' -derivedDataPath ./build/derivedData' + + ' -parallelizeTargets' + + ' -configuration "App Store Release"' + + ' -sdk iphonesimulator' + + ' -archivePath ./build/Session_sim.xcarchive' + + ' -destination "generic/platform=iOS Simulator"' + + ' | ./Pods/xcbeautify/xcbeautify --is-ci' ], }, update_cocoapods_cache, @@ -141,6 +212,7 @@ local update_cocoapods_cache = { platform: { os: 'darwin', arch: 'amd64' }, trigger: { event: { exclude: [ 'pull_request' ] } }, steps: [ + version_info, clone_submodules, load_cocoapods_cache, install_cocoapods, @@ -148,7 +220,17 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -parallelizeTargets -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO | ./Pods/xcbeautify/xcbeautify --is-ci' + 'xcodebuild archive' + + ' -workspace Session.xcworkspace' + + ' -scheme Session' + + ' -derivedDataPath ./build/derivedData' + + ' -parallelizeTargets' + + ' -configuration "App Store Release"' + ' -sdk iphoneos' + + ' -archivePath ./build/Session.xcarchive' + + ' -destination "generic/platform=iOS"' + + ' -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO' + + ' | ./Pods/xcbeautify/xcbeautify --is-ci' ], }, update_cocoapods_cache, diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index ab002c108..9cecb7499 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -536,7 +536,6 @@ FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; }; FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; }; FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; }; - FD23EA5C28ED00F80058676E /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23EA6028ED0B260058676E /* CombineExtensions.swift */; }; @@ -805,7 +804,6 @@ FDAA167B2AC28E2F00DDBF77 /* SnodeRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAA167A2AC28E2F00DDBF77 /* SnodeRequestSpec.swift */; }; FDAA167D2AC528A200DDBF77 /* Preferences+Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAA167C2AC528A200DDBF77 /* Preferences+Sound.swift */; }; FDAA167F2AC5290000DDBF77 /* Preferences+NotificationPreviewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAA167E2AC5290000DDBF77 /* Preferences+NotificationPreviewType.swift */; }; - FDAED05C2A7C6CE600091B25 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAED05B2A7C6CE600091B25 /* MigrationRequirement.swift */; }; FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */; }; FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC02A9443A5002C8721 /* MessageSender+Groups.swift */; }; FDB5DAC32A944504002C8721 /* SessionUtil+SharedGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC22A944504002C8721 /* SessionUtil+SharedGroup.swift */; }; @@ -827,7 +825,6 @@ FDB5DAF32A96DD4F002C8721 /* PreparedRequest+OnionRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAF22A96DD4F002C8721 /* PreparedRequest+OnionRequest.swift */; }; FDB5DAFE2A981C43002C8721 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; platformFilter = ios; }; FDB5DB062A981C67002C8721 /* PreparedRequestOnionRequestsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DB052A981C67002C8721 /* PreparedRequestOnionRequestsSpec.swift */; }; - FDB5DB072A981F88002C8721 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; FDB5DB082A981F8B002C8721 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; }; FDB5DB092A981F8D002C8721 /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; }; FDB5DB0B2A981F92002C8721 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; @@ -869,10 +866,8 @@ FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; }; FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; }; FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */; }; - FDC290A627D860CE005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; - FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; FDC383392A93411100FFD6A2 /* Setting+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */; }; FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */; }; FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; @@ -897,7 +892,6 @@ FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; }; - FDC439632AC2492700A56963 /* MessageReceiverSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6ACADD2A32D3A9009AFB73 /* MessageReceiverSpec.swift */; }; FDC498B32ABD82D000EDD897 /* MessageReceiverSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6ACADD2A32D3A9009AFB73 /* MessageReceiverSpec.swift */; }; FDC498B72AC15F7D00EDD897 /* AppNotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */; }; FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */; }; @@ -919,6 +913,10 @@ FDD383732AFDD6D7001367F2 /* BencodeResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD383722AFDD6D7001367F2 /* BencodeResponseSpec.swift */; }; FDD383752AFDFEBB001367F2 /* BencodeDecoderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD383742AFDFEBB001367F2 /* BencodeDecoderSpec.swift */; }; FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; + FDD897D32B0D7E0300ABA2EF /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; + FDD897D42B0D7E0400ABA2EF /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; + FDD897D52B0D7E0500ABA2EF /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; + FDD897D62B0D7E3700ABA2EF /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; }; FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; }; FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; }; @@ -6257,7 +6255,6 @@ FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */, FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */, 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */, - FDAED05C2A7C6CE600091B25 /* MigrationRequirement.swift in Sources */, FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */, C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, @@ -6790,7 +6787,6 @@ FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */, FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */, FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */, - FD23EA5C28ED00F80058676E /* Mock.swift in Sources */, FD3FAB6C2AF1B28B00DC5421 /* MockFileManager.swift in Sources */, FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */, FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */, @@ -6800,6 +6796,7 @@ FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */, FDB5DB0D2A981F9D002C8721 /* MockJobRunner.swift in Sources */, FD49E2462B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, + FDD897D52B0D7E0500ABA2EF /* Mock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6822,6 +6819,7 @@ FDD383752AFDFEBB001367F2 /* BencodeDecoderSpec.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, + FDD897D42B0D7E0400ABA2EF /* Mock.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */, FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */, @@ -6835,7 +6833,6 @@ FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */, FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */, FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */, - FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */, FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */, FD4C4EA52B03093300C72199 /* GRDBExtensions.swift in Sources */, FDB947102A982EF2001F271A /* BatchRequestSpec.swift in Sources */, @@ -6860,10 +6857,10 @@ FDB5DB102A981FA3002C8721 /* TestConstants.swift in Sources */, FDB5DB0C2A981F96002C8721 /* MockNetwork.swift in Sources */, FDB5DB0F2A981FA1002C8721 /* MockJobRunner.swift in Sources */, - FDB5DB072A981F88002C8721 /* Mock.swift in Sources */, FDB5DB0B2A981F92002C8721 /* MockGeneralCache.swift in Sources */, FDB5DB122A981FA8002C8721 /* NimbleExtensions.swift in Sources */, FDB5DB152A981FB0002C8721 /* SynchronousStorage.swift in Sources */, + FDD897D32B0D7E0300ABA2EF /* Mock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6871,6 +6868,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FDD897D62B0D7E3700ABA2EF /* Mock.swift in Sources */, FDC498B32ABD82D000EDD897 /* MessageReceiverSpec.swift in Sources */, FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */, FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */, @@ -6891,11 +6889,9 @@ FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */, FD3765E02AD8F05100DC1489 /* MockSnodeAPICache.swift in Sources */, FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */, - FDC290A627D860CE005DAE71 /* Mock.swift in Sources */, FDF01FAB2A9EBAD500CAF969 /* MessageSenderGroupsSpec.swift in Sources */, FD3765DC2AD8CC8B00DC1489 /* MockPoller.swift in Sources */, FD49E24C2B05D00900FFBBB5 /* MessageReceiverGroupsSpec.swift in Sources */, - FDC290A627D860CE005DAE71 /* Mock.swift in Sources */, FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */, FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */, FD65318B2AA025C500DFEEAA /* TestDependencies.swift in Sources */, @@ -6909,7 +6905,6 @@ FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */, - FDC439632AC2492700A56963 /* MessageReceiverSpec.swift in Sources */, FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */, FD3FAB702AF1F2F800DC5421 /* MockDisplayPictureCache.swift in Sources */, FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index a19110edd..04bb05eff 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -987,7 +987,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { db, sessionIdHexString: userSessionId.hexString, using: dependencies - ) + ), + using: dependencies ) .map { _, _ in () }), .deleteFromDatabase(cellViewModel.id) @@ -1027,7 +1028,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { db, sessionIdHexString: userSessionId.hexString, using: dependencies - ) + ), + using: dependencies ) .map { _, _ in () }), .deleteFromDatabase(cellViewModel.id) diff --git a/SessionMessagingKit/Messages/Control Messages/Group Update Messages/GroupUpdateDeleteMemberContentMessage.swift b/SessionMessagingKit/Messages/Control Messages/Group Update Messages/GroupUpdateDeleteMemberContentMessage.swift index d2b3bc07a..48666d95c 100644 --- a/SessionMessagingKit/Messages/Control Messages/Group Update Messages/GroupUpdateDeleteMemberContentMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/Group Update Messages/GroupUpdateDeleteMemberContentMessage.swift @@ -46,7 +46,7 @@ public final class GroupUpdateDeleteMemberContentMessage: ControlMessage { ) } - private init( + internal init( memberSessionIds: [String], messageHashes: [String], adminSignature: Authentication.Signature? diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index fc080e1a7..3294f5483 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -669,7 +669,8 @@ extension MessageReceiver { .preparedDeleteMessages( serverHashes: messageHashesToDeleteFromServer, requireSuccessfulDeletion: false, - authMethod: authMethod + authMethod: authMethod, + using: dependencies ) .send(using: dependencies) .subscribe(on: DispatchQueue.global(qos: .background), using: dependencies) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index e7390e82b..8e844d64a 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -6,10 +6,10 @@ import GRDB import Quick import Nimble import SessionUtil -import SessionSnodeKit import SessionUtilitiesKit import SessionUIKit +@testable import SessionSnodeKit @testable import SessionMessagingKit class MessageReceiverGroupsSpec: QuickSpec { @@ -72,6 +72,9 @@ class MessageReceiverGroupsSpec: QuickSpec { network .when { $0.send(.selectedNetworkRequest(.any, to: .any, with: .any, timeout: .any, using: .any)) } .thenReturn(MockNetwork.response(with: FileUploadResponse(id: "1"))) + network + .when { $0.send(.selectedNetworkRequest(.any, to: .any, timeout: .any, using: .any)) } + .thenReturn(MockNetwork.nullResponse()) } ) @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( @@ -174,6 +177,33 @@ class MessageReceiverGroupsSpec: QuickSpec { .thenReturn(Atomic(groupKeysConfig)) } ) + @TestState var mockSwarmCache: Set! = [ + Snode( + address: "test", + port: 0, + ed25519PublicKey: TestConstants.edPublicKey, + x25519PublicKey: TestConstants.publicKey + ), + Snode( + address: "test", + port: 1, + ed25519PublicKey: TestConstants.edPublicKey, + x25519PublicKey: TestConstants.publicKey + ), + Snode( + address: "test", + port: 2, + ed25519PublicKey: TestConstants.edPublicKey, + x25519PublicKey: TestConstants.publicKey + ) + ] + @TestState(cache: .snodeAPI, in: dependencies) var mockSnodeAPICache: MockSnodeAPICache! = MockSnodeAPICache( + initialSetup: { cache in + cache.when { $0.clockOffsetMs }.thenReturn(0) + cache.when { $0.loadedSwarms }.thenReturn([groupId.hexString]) + cache.when { $0.swarmCache }.thenReturn([groupId.hexString: mockSwarmCache]) + } + ) @TestState(singleton: .groupsPoller, in: dependencies) var mockGroupsPoller: MockPoller! = MockPoller( initialSetup: { poller in poller @@ -202,7 +232,7 @@ class MessageReceiverGroupsSpec: QuickSpec { groupSessionId: groupId, groupName: "TestGroup", memberAuthData: Data([1, 2, 3]), - sentTimestamp: 1234567890, + sentTimestamp: 1234567890000, authMethod: Authentication.groupAdmin( groupSessionId: groupId, ed25519SecretKey: [] @@ -216,7 +246,7 @@ class MessageReceiverGroupsSpec: QuickSpec { @TestState var promoteMessage: GroupUpdatePromoteMessage! = { let result: GroupUpdatePromoteMessage = GroupUpdatePromoteMessage( groupIdentitySeed: groupSeed, - sentTimestamp: 1234567890 + sentTimestamp: 1234567890000 ) result.sender = "051111111111111111111111111111111111111111111111111111111111111111" @@ -230,7 +260,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) result.sender = "051111111111111111111111111111111111111111111111111111111111111111" - result.sentTimestamp = 1234567800 + result.sentTimestamp = 1234567800000 return result }() @@ -241,14 +271,14 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) result.sender = "051111111111111111111111111111111111111111111111111111111111111111" - result.sentTimestamp = 1234567800 + result.sentTimestamp = 1234567800000 return result }() @TestState var memberLeftMessage: GroupUpdateMemberLeftMessage! = { let result: GroupUpdateMemberLeftMessage = GroupUpdateMemberLeftMessage() result.sender = "051111111111111111111111111111111111111111111111111111111111111112" - result.sentTimestamp = 1234567800 + result.sentTimestamp = 1234567800000 return result }() @@ -256,7 +286,7 @@ class MessageReceiverGroupsSpec: QuickSpec { let result: GroupUpdateInviteResponseMessage = GroupUpdateInviteResponseMessage( isApproved: true, profile: VisibleMessage.VMProfile(displayName: "TestOtherMember"), - sentTimestamp: 1234567800 + sentTimestamp: 1234567800000 ) result.sender = "051111111111111111111111111111111111111111111111111111111111111112" @@ -266,6 +296,17 @@ class MessageReceiverGroupsSpec: QuickSpec { memberId: "05\(TestConstants.publicKey)", groupKeysGen: 1 ).1 + @TestState var deleteContentMessage: GroupUpdateDeleteMemberContentMessage! = { + let result: GroupUpdateDeleteMemberContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: ["051111111111111111111111111111111111111111111111111111111111111112"], + messageHashes: [], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + result.sender = "051111111111111111111111111111111111111111111111111111111111111112" + result.sentTimestamp = 1234567800000 + + return result + }() @TestState var visibleMessageProto: SNProtoContent! = { let proto = SNProtoContent.builder() let dataMessage = SNProtoDataMessage.builder() @@ -288,8 +329,8 @@ class MessageReceiverGroupsSpec: QuickSpec { describe("a MessageReceiver dealing with Groups") { // MARK: -- when receiving a group invitation context("when receiving a group invitation") { - // MARK: ---- ignores the invitation if the signature is invalid - it("ignores the invitation if the signature is invalid") { + // MARK: ---- throws if the admin signature fails to verify + it("throws if the admin signature fails to verify") { mockCrypto .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } .thenReturn(false) @@ -519,8 +560,8 @@ class MessageReceiverGroupsSpec: QuickSpec { body: ClosedGroup.MessageInfo .invited("0511...1111", "TestGroup") .infoString(using: dependencies), - timestampMs: 1234567890, - receivedAtTimestampMs: 1234567890, + timestampMs: 1234567890000, + receivedAtTimestampMs: 1234567890000, wasRead: false, hasMention: false, expiresInSeconds: 0, @@ -1070,8 +1111,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no sender - it("fails if there is no sender") { + // MARK: ---- throws if there is no sender + it("throws if there is no sender") { infoChangedMessage.sender = nil mockStorage.write { db in @@ -1087,8 +1128,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no timestamp - it("fails if there is no timestamp") { + // MARK: ---- throws if there is no timestamp + it("throws if there is no timestamp") { infoChangedMessage.sentTimestamp = nil mockStorage.write { db in @@ -1104,8 +1145,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails the the admin signature fails to verify - it("fails the the admin signature fails to verify") { + // MARK: ---- throws if the admin signature fails to verify + it("throws if the admin signature fails to verify") { mockCrypto .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } .thenReturn(false) @@ -1138,7 +1179,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .updatedName("TestGroup Rename") @@ -1157,7 +1198,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) infoChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - infoChangedMessage.sentTimestamp = 1234567800 + infoChangedMessage.sentTimestamp = 1234567800000 } // MARK: ------ creates the correct control message @@ -1173,7 +1214,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .updatedDisplayPicture @@ -1192,7 +1233,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) infoChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - infoChangedMessage.sentTimestamp = 1234567800 + infoChangedMessage.sentTimestamp = 1234567800000 } // MARK: ------ creates the correct control message @@ -1208,7 +1249,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( DisappearingMessagesConfiguration( threadId: groupId.hexString, @@ -1242,8 +1283,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no sender - it("fails if there is no sender") { + // MARK: ---- throws if there is no sender + it("throws if there is no sender") { memberChangedMessage.sender = nil mockStorage.write { db in @@ -1259,8 +1300,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no timestamp - it("fails if there is no timestamp") { + // MARK: ---- throws if there is no timestamp + it("throws if there is no timestamp") { memberChangedMessage.sentTimestamp = nil mockStorage.write { db in @@ -1276,8 +1317,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails the the admin signature fails to verify - it("fails the the admin signature fails to verify") { + // MARK: ---- throws if the admin signature fails to verify + it("throws if the admin signature fails to verify") { mockCrypto .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } .thenReturn(false) @@ -1315,7 +1356,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .addedUsers(names: ["TestOtherProfile"]) @@ -1335,7 +1376,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1348,7 +1389,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .addedUsers(names: ["0511...1112"]) @@ -1367,7 +1408,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1380,7 +1421,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .addedUsers(names: ["0511...1112", "0511...1113"]) @@ -1400,7 +1441,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1413,7 +1454,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .addedUsers(names: ["0511...1112", "0511...1113", "0511...1114"]) @@ -1434,7 +1475,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1447,7 +1488,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .removedUsers(names: ["0511...1112"]) @@ -1466,7 +1507,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1479,7 +1520,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .removedUsers(names: ["0511...1112", "0511...1113"]) @@ -1499,7 +1540,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1512,7 +1553,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .removedUsers(names: ["0511...1112", "0511...1113", "0511...1114"]) @@ -1533,7 +1574,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1546,7 +1587,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .promotedUsers(names: ["0511...1112"]) @@ -1565,7 +1606,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1578,7 +1619,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .promotedUsers(names: ["0511...1112", "0511...1113"]) @@ -1598,7 +1639,7 @@ class MessageReceiverGroupsSpec: QuickSpec { adminSignature: .standard(signature: "TestSignature".bytes) ) memberChangedMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" - memberChangedMessage.sentTimestamp = 1234567800 + memberChangedMessage.sentTimestamp = 1234567800000 mockStorage.write { db in try MessageReceiver.handleGroupUpdateMessage( @@ -1611,7 +1652,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .promotedUsers(names: ["0511...1112", "0511...1113", "0511...1114"]) @@ -1649,7 +1690,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .memberLeft(name: "0511...1112") @@ -1677,7 +1718,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } let interaction: Interaction? = mockStorage.read { db in try Interaction.fetchOne(db) } - expect(interaction?.timestampMs).to(equal(1234567800)) + expect(interaction?.timestampMs).to(equal(1234567800000)) expect(interaction?.body).to(equal( ClosedGroup.MessageInfo .memberLeft(name: "TestOtherProfile") @@ -1685,8 +1726,8 @@ class MessageReceiverGroupsSpec: QuickSpec { )) } - // MARK: ---- fails if there is no sender - it("fails if there is no sender") { + // MARK: ---- throws if there is no sender + it("throws if there is no sender") { memberLeftMessage.sender = nil mockStorage.write { db in @@ -1702,8 +1743,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no timestamp - it("fails if there is no timestamp") { + // MARK: ---- throws if there is no timestamp + it("throws if there is no timestamp") { memberLeftMessage.sentTimestamp = nil mockStorage.write { db in @@ -1804,7 +1845,7 @@ class MessageReceiverGroupsSpec: QuickSpec { variant: .processPendingGroupMemberRemovals, threadId: groupId.hexString, details: ProcessPendingGroupMemberRemovalsJob.Details( - changeTimestampMs: 1234567800 + changeTimestampMs: 1234567800000 ) ), canStartJob: true, @@ -1889,8 +1930,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no sender - it("fails if there is no sender") { + // MARK: ---- throws if there is no sender + it("throws if there is no sender") { inviteResponseMessage.sender = nil mockStorage.write { db in @@ -1906,8 +1947,8 @@ class MessageReceiverGroupsSpec: QuickSpec { } } - // MARK: ---- fails if there is no timestamp - it("fails if there is no timestamp") { + // MARK: ---- throws if there is no timestamp + it("throws if there is no timestamp") { inviteResponseMessage.sentTimestamp = nil mockStorage.write { db in @@ -2069,6 +2110,622 @@ class MessageReceiverGroupsSpec: QuickSpec { } } + // MARK: -- when receiving a delete content message + context("when receiving a delete content message") { + beforeEach { + mockStorage.write { db in + try SessionThread.fetchOrCreate( + db, + id: groupId.hexString, + variant: .group, + shouldBeVisible: true, + calledFromConfigHandling: false, + using: dependencies + ) + + _ = try Interaction( + id: 1, + serverHash: "TestMessageHash1", + messageUuid: nil, + threadId: groupId.hexString, + authorId: "051111111111111111111111111111111111111111111111111111111111111111", + variant: .standardIncoming, + body: "Test", + timestampMs: 1234560000001, + receivedAtTimestampMs: 1234560000001, + wasRead: false, + hasMention: false, + expiresInSeconds: 0, + expiresStartedAtMs: nil, + linkPreviewUrl: nil, + openGroupServerMessageId: nil, + openGroupWhisperMods: false, + openGroupWhisperTo: nil + ).inserted(db) + + _ = try Interaction( + id: 2, + serverHash: "TestMessageHash2", + messageUuid: nil, + threadId: groupId.hexString, + authorId: "051111111111111111111111111111111111111111111111111111111111111111", + variant: .standardIncoming, + body: "Test", + timestampMs: 1234567890002, + receivedAtTimestampMs: 1234567890002, + wasRead: false, + hasMention: false, + expiresInSeconds: 0, + expiresStartedAtMs: nil, + linkPreviewUrl: nil, + openGroupServerMessageId: nil, + openGroupWhisperMods: false, + openGroupWhisperTo: nil + ).inserted(db) + + _ = try Interaction( + id: 3, + serverHash: "TestMessageHash3", + messageUuid: nil, + threadId: groupId.hexString, + authorId: "051111111111111111111111111111111111111111111111111111111111111112", + variant: .standardIncoming, + body: "Test", + timestampMs: 1234560000003, + receivedAtTimestampMs: 1234560000003, + wasRead: false, + hasMention: false, + expiresInSeconds: 0, + expiresStartedAtMs: nil, + linkPreviewUrl: nil, + openGroupServerMessageId: nil, + openGroupWhisperMods: false, + openGroupWhisperTo: nil + ).inserted(db) + + _ = try Interaction( + id: 4, + serverHash: "TestMessageHash4", + messageUuid: nil, + threadId: groupId.hexString, + authorId: "051111111111111111111111111111111111111111111111111111111111111112", + variant: .standardIncoming, + body: "Test", + timestampMs: 1234567890004, + receivedAtTimestampMs: 1234567890004, + wasRead: false, + hasMention: false, + expiresInSeconds: 0, + expiresStartedAtMs: nil, + linkPreviewUrl: nil, + openGroupServerMessageId: nil, + openGroupWhisperMods: false, + openGroupWhisperTo: nil + ).inserted(db) + } + } + + // MARK: ---- throws if there is no sender and no admin signature + it("throws if there is no sender and no admin signature") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: ["051111111111111111111111111111111111111111111111111111111111111112"], + messageHashes: [], + adminSignature: nil + ) + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + expect { + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + }.to(throwError(MessageReceiverError.invalidMessage)) + } + } + + // MARK: ---- throws if there is no timestamp + it("throws if there is no timestamp") { + deleteContentMessage.sentTimestamp = nil + + mockStorage.write { db in + expect { + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + }.to(throwError(MessageReceiverError.invalidMessage)) + } + } + + // MARK: ---- throws if the admin signature fails to verify + it("throws if the admin signature fails to verify") { + mockCrypto + .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } + .thenReturn(false) + + mockStorage.write { db in + expect { + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + }.to(throwError(MessageReceiverError.invalidMessage)) + } + } + + // MARK: ---- and there is no admin signature + context("and there is no admin signature") { + // MARK: ------ removes specific messages from the database + it("removes specific messages from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ removes all messages from the sender from the database + it("removes all messages from the sender from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [ + "051111111111111111111111111111111111111111111111111111111111111112" + ], + messageHashes: [], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ ignores messages not sent by the sender + it("ignores messages not sent by the sender") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash1", "TestMessageHash3"], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ ignores messages sent after the delete content message was sent + it("ignores messages sent after the delete content message was sent") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3", "TestMessageHash4"], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + } + + // MARK: ---- and there is no admin signature + context("and there is no admin signature") { + // MARK: ------ removes specific messages from the database + it("removes specific messages from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ removes all messages for a given id from the database + it("removes all messages for a given id from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [ + "051111111111111111111111111111111111111111111111111111111111111112" + ], + messageHashes: [], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ removes specific messages sent from a user that is not the sender from the database + it("removes specific messages sent from a user that is not the sender from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ removes all messages for a given id that is not the sender from the database + it("removes all messages for a given id that is not the sender from the database") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [ + "051111111111111111111111111111111111111111111111111111111111111112" + ], + messageHashes: [], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(3)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash1", "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234560000001, + 1234567890002, + 1234567890004 + ])) + } + + // MARK: ------ ignores messages sent after the delete content message was sent + it("ignores messages sent after the delete content message was sent") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ], + messageHashes: [], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111111" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + let interactions: [Interaction]? = mockStorage.read { db in try Interaction.fetchAll(db) } + expect(interactions?.count).to(equal(2)) + expect(interactions?.map { $0.serverHash }).to(equal([ + "TestMessageHash2", "TestMessageHash4" + ])) + expect(interactions?.map { $0.authorId }).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111", + "051111111111111111111111111111111111111111111111111111111111111112" + ])) + expect(interactions?.map { $0.timestampMs }).to(equal([ + 1234567890002, + 1234567890004 + ])) + } + } + + // MARK: ---- and the current user is an admin + context("and the current user is an admin") { + beforeEach { + mockStorage.write { db in + try ClosedGroup( + threadId: groupId.hexString, + name: "TestGroup", + formationTimestamp: 1234567890, + shouldPoll: true, + groupIdentityPrivateKey: groupSecretKey, + authData: nil, + invited: false + ).upsert(db) + } + } + + // MARK: ------ deletes the messages from the swarm if the sender was not an admin + it("deletes the messages from the swarm if the sender was not an admin") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + let expectedRequest: URLRequest = (try? SnodeAPI + .preparedDeleteMessages( + serverHashes: ["TestMessageHash3"], + requireSuccessfulDeletion: false, + authMethod: Authentication.groupAdmin( + groupSessionId: groupId, + ed25519SecretKey: Array(groupSecretKey) + ), + using: dependencies + ))!.request + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + expect(mockNetwork) + .to(call(.exactly(times: 1), matchingParameters: .all) { network in + network.send( + .selectedNetworkRequest( + expectedRequest.httpBody!, + to: dependencies.randomElement(mockSwarmCache)!, + timeout: HTTP.defaultTimeout, + using: .any + ) + ) + }) + } + + // MARK: ------ does not delete the messages from the swarm if the sender was an admin + it("does not delete the messages from the swarm if the sender was an admin") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: .standard(signature: "TestSignature".bytes) + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + expect(mockNetwork) + .toNot(call { network in + network.send(.selectedNetworkRequest(.any, to: .any, timeout: .any, using: .any)) + }) + } + } + + // MARK: ---- and the current user is not an admin + context("and the current user is not an admin") { + // MARK: ------ does not delete the messages from the swarm + it("does not delete the messages from the swarm") { + deleteContentMessage = GroupUpdateDeleteMemberContentMessage( + memberSessionIds: [], + messageHashes: ["TestMessageHash3"], + adminSignature: nil + ) + deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" + deleteContentMessage.sentTimestamp = 1234567800000 + + mockStorage.write { db in + try MessageReceiver.handleGroupUpdateMessage( + db, + threadId: groupId.hexString, + threadVariant: .group, + message: deleteContentMessage, + using: dependencies + ) + } + + expect(mockNetwork) + .toNot(call { network in + network.send(.selectedNetworkRequest(.any, to: .any, timeout: .any, using: .any)) + }) + } + } + } + // MARK: -- when receiving a delete message context("when receiving a delete message") { beforeEach { diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index 53353ce5d..dd5a08f2b 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -342,7 +342,7 @@ class MessageSenderGroupsSpec: QuickSpec { // MARK: ---- syncs the group configuration messages it("syncs the group configuration messages") { - let expectedSendData: Data = mockStorage + let expectedRequest: URLRequest = mockStorage .write(using: dependencies) { db in // Need the auth data to exist in the database to prepare the request _ = try SessionThread.fetchOrCreate( @@ -393,7 +393,7 @@ class MessageSenderGroupsSpec: QuickSpec { try SessionThread.filter(id: groupId.hexString).deleteAll(db) return preparedRequest - }!.request.httpBody! + }!.request MessageSender .createGroup( @@ -411,7 +411,7 @@ class MessageSenderGroupsSpec: QuickSpec { .to(call(.exactly(times: 1), matchingParameters: .all) { network in network.send( .selectedNetworkRequest( - expectedSendData, + expectedRequest.httpBody!, to: dependencies.randomElement(mockSwarmCache)!, timeout: HTTP.defaultTimeout, using: .any diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 4c8b0a697..18e0d3463 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -204,7 +204,7 @@ public final class SnodeAPI { public static func getSwarm( for publicKey: String, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) -> AnyPublisher, Error> { loadSwarmIfNeeded(for: publicKey, using: dependencies) @@ -248,7 +248,7 @@ public final class SnodeAPI { refreshingConfigHashes: [String] = [], from snode: Snode, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { // Determine the maxSize each namespace in the request should take up var requests: [any ErasedPreparedRequest] = [] @@ -405,7 +405,7 @@ public final class SnodeAPI { snode: Snode, maxSize: Int64? = nil, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { // Prune expired message hashes for this namespace on this service node try SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( @@ -548,7 +548,7 @@ public final class SnodeAPI { public static func preparedGetExpiries( of serverHashes: [String], authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { // FIXME: There is a bug on SS now that a single-hash lookup is not working. Remove it when the bug is fixed let serverHashes: [String] = serverHashes.appending("fakehash") @@ -629,7 +629,7 @@ public final class SnodeAPI { shortenOnly: Bool? = nil, extendOnly: Bool? = nil, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest<[String: UpdateExpiryResponseResult]> { // ShortenOnly and extendOnly cannot be true at the same time guard shortenOnly == nil || extendOnly == nil else { throw SnodeAPIError.generic } @@ -662,7 +662,7 @@ public final class SnodeAPI { public static func preparedRevokeSubaccounts( subaccountsToRevoke: [[UInt8]], authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { let timestampMs: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) @@ -694,7 +694,7 @@ public final class SnodeAPI { public static func preparedUnrevokeSubaccounts( subaccountsToUnrevoke: [[UInt8]], authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { let timestampMs: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) @@ -729,7 +729,7 @@ public final class SnodeAPI { serverHashes: [String], requireSuccessfulDeletion: Bool, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest<[String: Bool]> { return try SnodeAPI .prepareRequest( @@ -771,7 +771,7 @@ public final class SnodeAPI { public static func preparedDeleteAllMessages( namespace: SnodeAPI.Namespace, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest<[String: Bool]> { return try SnodeAPI .prepareRequest( @@ -807,7 +807,7 @@ public final class SnodeAPI { beforeMs: UInt64, namespace: SnodeAPI.Namespace, authMethod: AuthenticationMethod, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest<[String: Bool]> { return try SnodeAPI .prepareRequest( @@ -839,7 +839,7 @@ public final class SnodeAPI { public static func preparedGetNetworkTime( from snode: Snode, - using dependencies: Dependencies = Dependencies() + using dependencies: Dependencies ) throws -> HTTP.PreparedRequest { return try SnodeAPI .prepareRequest( @@ -1208,7 +1208,7 @@ public extension Publisher where Output == Set { maxPublishers: Subscribers.Demand = .unlimited, retry retries: Int = 0, drainBehaviour: Atomic = .alwaysRandom, - using dependencies: Dependencies = Dependencies(), + using dependencies: Dependencies, _ transform: @escaping (Snode) throws -> P ) -> AnyPublisher where T == P.Output, P: Publisher, P.Failure == Error { return self