Finalised the OpenGroupAPI and more tests
Fixed an issue where messages where signed incorrectly when blinding wasn't enabled on a SOGS Fixed an issue where a single invalid message would result in all messages in that request being dropped Updated the final legacy endpoint (ban and delete all messages) Moved the OpenGroupManager poller values into the 'Cache' (so they are thread safe) Started adding unit tests for the OpenGroupManager Removed some redundant parameters from the 'Request' type
This commit is contained in:
parent
c415fc9e06
commit
f9c2655df4
|
@ -773,6 +773,10 @@
|
|||
F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; };
|
||||
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
|
||||
FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; };
|
||||
FD078E4627E02406000769AF /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383D27B4708600C60D73 /* Atomic.swift */; };
|
||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4B27E02C5D000769AF /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4A27E02C5D000769AF /* Failable.swift */; };
|
||||
FD0BA51B27CD88EC00CC6805 /* BlindedIdMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */; };
|
||||
FD0BA51D27CDC34600CC6805 /* SOGSV4Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */; };
|
||||
FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; };
|
||||
|
@ -797,7 +801,7 @@
|
|||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
||||
FD83B9CE27D17A04005E1583 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CD27D17A04005E1583 /* Request.swift */; };
|
||||
FD83B9D227D59495005E1583 /* TestUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* TestUserDefaults.swift */; };
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
|
||||
FD83B9D427D5A7D5005E1583 /* ConversationViewItem+Refactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D327D5A7D5005E1583 /* ConversationViewItem+Refactor.swift */; };
|
||||
FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; };
|
||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* MockSodium.swift */; };
|
||||
|
@ -826,8 +830,10 @@
|
|||
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 */; };
|
||||
FDC290AC27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290AB27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift */; };
|
||||
FDC290AD27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290AB27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift */; };
|
||||
FDC290AC27DB0B1C005DAE71 /* MockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290AB27DB0B1C005DAE71 /* MockedExtensions.swift */; };
|
||||
FDC290AF27DFEE97005DAE71 /* TestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290AE27DFEE97005DAE71 /* TestTransaction.swift */; };
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */; };
|
||||
FDC290B727E00FDB005DAE71 /* TestGroupThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290B627E00FDB005DAE71 /* TestGroupThread.swift */; };
|
||||
FDC4380927B31D4E00C60D73 /* SOGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* SOGSError.swift */; };
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator.swift */; };
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||
|
@ -835,7 +841,6 @@
|
|||
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* UnregisterResponse.swift */; };
|
||||
FDC4383127B3841C00C60D73 /* RegisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383027B3841C00C60D73 /* RegisterResponse.swift */; };
|
||||
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* VersionResponse.swift */; };
|
||||
FDC4383E27B4708600C60D73 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383D27B4708600C60D73 /* Atomic.swift */; };
|
||||
FDC4384C27B47F7700C60D73 /* OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384B27B47F7700C60D73 /* OpenGroup.swift */; };
|
||||
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* Header.swift */; };
|
||||
FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* QueryParam.swift */; };
|
||||
|
@ -1913,6 +1918,8 @@
|
|||
F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
|
||||
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMockedExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E4A27E02C5D000769AF /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; };
|
||||
FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdMapping.swift; sourceTree = "<group>"; };
|
||||
FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSV4Migration.swift; sourceTree = "<group>"; };
|
||||
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
|
||||
|
@ -1936,7 +1943,7 @@
|
|||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = "<group>"; };
|
||||
FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FD83B9CD27D17A04005E1583 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||
FD83B9D127D59495005E1583 /* TestUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUserDefaults.swift; sourceTree = "<group>"; };
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
|
||||
FD83B9D327D5A7D5005E1583 /* ConversationViewItem+Refactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationViewItem+Refactor.swift"; sourceTree = "<group>"; };
|
||||
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
|
||||
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
|
||||
|
@ -1966,7 +1973,10 @@
|
|||
FDC290A127D85890005DAE71 /* TestInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestInteraction.swift; sourceTree = "<group>"; };
|
||||
FDC290A527D860CE005DAE71 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; sourceTree = "<group>"; };
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleExtensions.swift; sourceTree = "<group>"; };
|
||||
FDC290AB27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BoxKeyPair+Mocked.swift"; sourceTree = "<group>"; };
|
||||
FDC290AB27DB0B1C005DAE71 /* MockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockedExtensions.swift; sourceTree = "<group>"; };
|
||||
FDC290AE27DFEE97005DAE71 /* TestTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransaction.swift; sourceTree = "<group>"; };
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestOnionRequestAPI.swift; sourceTree = "<group>"; };
|
||||
FDC290B627E00FDB005DAE71 /* TestGroupThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGroupThread.swift; sourceTree = "<group>"; };
|
||||
FDC4380827B31D4E00C60D73 /* SOGSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSError.swift; sourceTree = "<group>"; };
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.swift; sourceTree = "<group>"; };
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = "<group>"; };
|
||||
|
@ -2542,6 +2552,7 @@
|
|||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */,
|
||||
|
@ -3422,13 +3433,13 @@
|
|||
children = (
|
||||
C33FDB01255A580700E217F9 /* AppReadiness.h */,
|
||||
C33FDB75255A581000E217F9 /* AppReadiness.m */,
|
||||
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
||||
FD83B9A927CF149D005E1583 /* ContactUtilities.swift */,
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
|
||||
FDC438C027BB4E6800C60D73 /* Dependencies.swift */,
|
||||
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
|
||||
C37F53E8255BA9BB002AEA92 /* Environment.h */,
|
||||
C37F5402255BA9ED002AEA92 /* Environment.m */,
|
||||
FD078E4A27E02C5D000769AF /* Failable.swift */,
|
||||
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
||||
C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */,
|
||||
C33FDBC1255A581700E217F9 /* General.swift */,
|
||||
|
@ -3886,6 +3897,7 @@
|
|||
FDC290A527D860CE005DAE71 /* Mock.swift */,
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
|
||||
);
|
||||
path = SharedTest;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4035,10 +4047,13 @@
|
|||
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */,
|
||||
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
|
||||
FD859EFB27C2F60700510D0C /* MockEd25519.swift */,
|
||||
FD83B9D127D59495005E1583 /* TestUserDefaults.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */,
|
||||
FDC290AE27DFEE97005DAE71 /* TestTransaction.swift */,
|
||||
FDC2909F27D85826005DAE71 /* TestThread.swift */,
|
||||
FDC290B627E00FDB005DAE71 /* TestGroupThread.swift */,
|
||||
FDC290A127D85890005DAE71 /* TestInteraction.swift */,
|
||||
FDC290AB27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift */,
|
||||
FDC290AB27DB0B1C005DAE71 /* MockedExtensions.swift */,
|
||||
);
|
||||
path = _TestUtilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5241,6 +5256,7 @@
|
|||
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
|
||||
C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */,
|
||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||
FD078E4627E02406000769AF /* Atomic.swift in Sources */,
|
||||
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
|
||||
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
|
||||
C3D9E41F25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift in Sources */,
|
||||
|
@ -5297,7 +5313,6 @@
|
|||
C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */,
|
||||
7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */,
|
||||
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */,
|
||||
FDC4383E27B4708600C60D73 /* Atomic.swift in Sources */,
|
||||
C32C5A76256DBBCF003C73A2 /* SignalAttachment.swift in Sources */,
|
||||
C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */,
|
||||
C352A3892557876500338F3E /* JobQueue.swift in Sources */,
|
||||
|
@ -5400,6 +5415,7 @@
|
|||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
|
||||
FD078E4B27E02C5D000769AF /* Failable.swift in Sources */,
|
||||
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
||||
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
|
||||
|
@ -5619,7 +5635,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FDC290AD27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */,
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
|
@ -5631,9 +5647,11 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FDC290AC27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */,
|
||||
FDC290AC27DB0B1C005DAE71 /* MockedExtensions.swift in Sources */,
|
||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
|
||||
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
|
||||
FDC290AF27DFEE97005DAE71 /* TestTransaction.swift in Sources */,
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
|
||||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||
|
@ -5641,6 +5659,7 @@
|
|||
FD83B9C327CF33F7005E1583 /* ServerSpec.swift in Sources */,
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
|
||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FDC290A027D85826005DAE71 /* TestThread.swift in Sources */,
|
||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
|
||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||
|
@ -5652,6 +5671,7 @@
|
|||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FDC290B727E00FDB005DAE71 /* TestGroupThread.swift in Sources */,
|
||||
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
|
||||
|
@ -5660,7 +5680,7 @@
|
|||
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
|
||||
FDC290A227D85890005DAE71 /* TestInteraction.swift in Sources */,
|
||||
FDC4389D27BA01F000C60D73 /* MockStorage.swift in Sources */,
|
||||
FD83B9D227D59495005E1583 /* TestUserDefaults.swift in Sources */,
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -815,11 +815,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
let publicKey = message.authorId
|
||||
guard let openGroup = Storage.shared.getOpenGroup(for: threadID) else { return }
|
||||
|
||||
let promise = OpenGroupAPI.userBanAndDeleteAllMessage(publicKey, from: [openGroup.room], on: openGroup.server)
|
||||
let promise = OpenGroupAPI.userBanAndDeleteAllMessages(publicKey, in: openGroup.room, on: openGroup.server)
|
||||
promise.catch(on: DispatchQueue.main) { _ in
|
||||
OWSAlerts.showErrorAlert(message: NSLocalizedString("context_menu_ban_user_error_alert_message", comment: ""))
|
||||
}
|
||||
promise.retainUntilComplete() // TODO: Test This
|
||||
promise.retainUntilComplete()
|
||||
self?.becomeFirstResponder()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { [weak self] _ in
|
||||
|
|
|
@ -25,10 +25,6 @@ struct Request<T: Encodable, Endpoint: EndpointType> {
|
|||
/// **Warning:** The `bodyData` value should be used to when making the actual request instead of this as there
|
||||
/// is custom handling for certain data types
|
||||
let body: T?
|
||||
let isAuthRequired: Bool
|
||||
/// Always `true` under normal circumstances. You might want to disable
|
||||
/// this when running over Lokinet.
|
||||
let useOnionRouting: Bool
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
@ -38,9 +34,7 @@ struct Request<T: Encodable, Endpoint: EndpointType> {
|
|||
endpoint: Endpoint,
|
||||
queryParameters: [QueryParam: String] = [:],
|
||||
headers: [Header: String] = [:],
|
||||
body: T? = nil,
|
||||
isAuthRequired: Bool = true,
|
||||
useOnionRouting: Bool = true
|
||||
body: T? = nil
|
||||
) {
|
||||
self.method = method
|
||||
self.server = server
|
||||
|
@ -48,8 +42,6 @@ struct Request<T: Encodable, Endpoint: EndpointType> {
|
|||
self.queryParameters = queryParameters
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.isAuthRequired = isAuthRequired
|
||||
self.useOnionRouting = useOnionRouting
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
|
|
@ -75,10 +75,6 @@ public final class FileServerAPI: NSObject {
|
|||
// MARK: - Convenience
|
||||
|
||||
private static func send<T: Encodable>(_ request: Request<T, Endpoint>, serverPublicKey: String) -> Promise<Data> {
|
||||
guard request.useOnionRouting else {
|
||||
preconditionFailure("It's currently not allowed to send non onion routed requests.")
|
||||
}
|
||||
|
||||
let urlRequest: URLRequest
|
||||
|
||||
do {
|
||||
|
|
|
@ -76,7 +76,7 @@ public enum OpenGroupAPI {
|
|||
.roomMessagesSince(openGroup.room, seqNo: targetSeqNo)
|
||||
)
|
||||
),
|
||||
responseType: [Message].self
|
||||
responseType: [Failable<Message>].self
|
||||
)
|
||||
]
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ public enum OpenGroupAPI {
|
|||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(capabilities: (OnionRequestResponseInfoType, Capabilities?), room: (OnionRequestResponseInfoType, Room?))> {
|
||||
) -> Promise<(capabilities: (info: OnionRequestResponseInfoType, data: Capabilities), room: (info: OnionRequestResponseInfoType, data: Room))> {
|
||||
let requestResponseType: [BatchRequestInfoType] = [
|
||||
// Get the latest capabilities for the server (in case it's a new server or the cached ones are stale)
|
||||
BatchRequestInfo(
|
||||
|
@ -264,8 +264,8 @@ public enum OpenGroupAPI {
|
|||
]
|
||||
|
||||
return sequence(server, requests: requestResponseType, using: dependencies)
|
||||
.map { (response: [Endpoint: (OnionRequestResponseInfoType, Codable?)]) -> (capabilities: (OnionRequestResponseInfoType, Capabilities?), room: (OnionRequestResponseInfoType, Room?)) in
|
||||
let maybeCapabilities: (OnionRequestResponseInfoType, Capabilities?)? = response[.capabilities]
|
||||
.map { (response: [Endpoint: (OnionRequestResponseInfoType, Codable?)]) -> (capabilities: (OnionRequestResponseInfoType, Capabilities), room: (OnionRequestResponseInfoType, Room)) in
|
||||
let maybeCapabilities: (info: OnionRequestResponseInfoType, data: Capabilities?)? = response[.capabilities]
|
||||
.map { info, data in (info, (data as? BatchSubResponse<Capabilities>)?.body) }
|
||||
let maybeRoomResponse: (OnionRequestResponseInfoType, Codable?)? = response
|
||||
.first(where: { key, _ in
|
||||
|
@ -275,14 +275,22 @@ public enum OpenGroupAPI {
|
|||
}
|
||||
})
|
||||
.map { _, value in value }
|
||||
let maybeRoom: (OnionRequestResponseInfoType, Room?)? = maybeRoomResponse
|
||||
let maybeRoom: (info: OnionRequestResponseInfoType, data: Room?)? = maybeRoomResponse
|
||||
.map { info, data in (info, (data as? BatchSubResponse<Room>)?.body) }
|
||||
|
||||
guard let capabilities: (OnionRequestResponseInfoType, Capabilities?) = maybeCapabilities, let room: (OnionRequestResponseInfoType, Room?) = maybeRoom else {
|
||||
guard
|
||||
let capabilitiesInfo: OnionRequestResponseInfoType = maybeCapabilities?.info,
|
||||
let capabilities: Capabilities = maybeCapabilities?.data,
|
||||
let roomInfo: OnionRequestResponseInfoType = maybeRoom?.info,
|
||||
let room: Room = maybeRoom?.data
|
||||
else {
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
return (capabilities, room)
|
||||
return (
|
||||
(capabilitiesInfo, capabilities),
|
||||
(roomInfo, room)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,7 +306,7 @@ public enum OpenGroupAPI {
|
|||
fileIds: [String]?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
|
@ -352,7 +360,7 @@ public enum OpenGroupAPI {
|
|||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
|
@ -429,6 +437,34 @@ public enum OpenGroupAPI {
|
|||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sessionId: The sessionId (either standard or blinded) of the user whose messages should be deleted
|
||||
///
|
||||
/// - roomToken: The room token from which the messages should be deleted
|
||||
///
|
||||
/// The invoking user **must** be a moderator of the given room or an admin if trying to delete the messages
|
||||
/// of another admin.
|
||||
///
|
||||
/// - server: The server to delete messages from
|
||||
///
|
||||
/// - dependencies: Injected dependencies (used for unit testing)
|
||||
public static func messagesDeleteAll(
|
||||
_ sessionId: String,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
method: .delete,
|
||||
server: server,
|
||||
endpoint: Endpoint.roomDeleteMessages(roomToken, sessionId: sessionId)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Pinning
|
||||
|
||||
/// Adds a pinned message to this room
|
||||
|
@ -791,65 +827,19 @@ public enum OpenGroupAPI {
|
|||
return send(request, using: dependencies)
|
||||
}
|
||||
|
||||
// TODO: Need to test this once the API has been implemented
|
||||
// TODO: Update docs to align with the API documentation once implemented
|
||||
/// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sessionId: The sessionId (either standard or blinded) of the user whose messages should be deleted
|
||||
///
|
||||
/// - roomTokens: List of one or more room tokens from which the messages should be deleted
|
||||
///
|
||||
/// The invoking user **must** be an admin of all of the given rooms.
|
||||
///
|
||||
/// This may be set to the single-element list `["*"]` to add or remove the moderator from all rooms in which the current user has admin
|
||||
/// permissions (the call will succeed if the calling user is an admin in at least one channel)
|
||||
///
|
||||
/// **Note:** You can delete messages from all rooms on a server by providing a `nil` value for this parameter
|
||||
///
|
||||
/// - server: The server to delete messages from
|
||||
///
|
||||
/// - dependencies: Injected dependencies (used for unit testing)
|
||||
public static func userDeleteMessages(
|
||||
_ sessionId: String,
|
||||
from roomTokens: [String]?,
|
||||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, UserDeleteMessagesResponse)> {
|
||||
let requestBody: UserDeleteMessagesRequest = UserDeleteMessagesRequest(
|
||||
rooms: roomTokens,
|
||||
global: (roomTokens == nil ? true : nil)
|
||||
)
|
||||
|
||||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: Endpoint.userDeleteMessages(sessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// TODO: Need to test this once the API has been implemented
|
||||
/// This is a convenience method which constructs a `/sequence` of the `userBan` and `userDeleteMessages` requests, refer to those
|
||||
/// methods for the documented behaviour of each method
|
||||
public static func userBanAndDeleteAllMessage(
|
||||
public static func userBanAndDeleteAllMessages(
|
||||
_ sessionId: String,
|
||||
from roomTokens: [String]?,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<[OnionRequestResponseInfoType]> {
|
||||
let banRequestBody: UserBanRequest = UserBanRequest(
|
||||
rooms: roomTokens,
|
||||
global: (roomTokens == nil ? true : nil),
|
||||
rooms: [roomToken],
|
||||
global: nil,
|
||||
timeout: nil
|
||||
)
|
||||
let deleteMessageRequestBody: UserDeleteMessagesRequest = UserDeleteMessagesRequest(
|
||||
rooms: roomTokens,
|
||||
global: (roomTokens == nil ? true : nil)
|
||||
)
|
||||
|
||||
// Generate the requests
|
||||
let requestResponseType: [BatchRequestInfoType] = [
|
||||
|
@ -862,27 +852,22 @@ public enum OpenGroupAPI {
|
|||
)
|
||||
),
|
||||
BatchRequestInfo(
|
||||
request: Request(
|
||||
method: .post,
|
||||
request: Request<NoBody, Endpoint>(
|
||||
method: .delete,
|
||||
server: server,
|
||||
endpoint: .userDeleteMessages(sessionId),
|
||||
body: deleteMessageRequestBody
|
||||
),
|
||||
responseType: UserDeleteMessagesResponse.self
|
||||
endpoint: Endpoint.roomDeleteMessages(roomToken, sessionId: sessionId)
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
return sequence(server, requests: requestResponseType, using: dependencies)
|
||||
.map { results in
|
||||
// TODO: Handle deletions...???? Hand off to OpenGroupAPIManager?
|
||||
return results.values.map { responseInfo, _ in responseInfo }
|
||||
}
|
||||
.map { $0.values.map { responseInfo, _ in responseInfo } }
|
||||
}
|
||||
|
||||
// MARK: - Authentication
|
||||
|
||||
/// Sign a message to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities)
|
||||
private static func sign(_ messageBytes: Bytes, for serverName: String, using dependencies: Dependencies = Dependencies()) -> (publicKey: String, signature: Bytes)? {
|
||||
private static func sign(_ messageBytes: Bytes, for serverName: String, fallbackSigningType signingType: SessionId.Prefix, using dependencies: Dependencies = Dependencies()) -> (publicKey: String, signature: Bytes)? {
|
||||
guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil }
|
||||
guard let serverPublicKey: String = dependencies.storage.getOpenGroupPublicKey(for: serverName) else {
|
||||
return nil
|
||||
|
@ -906,15 +891,30 @@ public enum OpenGroupAPI {
|
|||
)
|
||||
}
|
||||
|
||||
// Otherwise fall back to sign using the unblinded key
|
||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
// Otherwise sign using the fallback type
|
||||
switch signingType {
|
||||
case .unblinded:
|
||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
||||
signature: signatureResult
|
||||
)
|
||||
|
||||
// Default to using the 'standard' key
|
||||
default:
|
||||
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else { return nil }
|
||||
guard let signatureResult: Bytes = try? dependencies.ed25519.sign(data: messageBytes, keyPair: userKeyPair) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.standard, publicKey: userKeyPair.publicKey.bytes).hexString,
|
||||
signature: signatureResult
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
||||
signature: signatureResult
|
||||
)
|
||||
}
|
||||
|
||||
/// Sign a request to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities)
|
||||
|
@ -954,7 +954,7 @@ public enum OpenGroupAPI {
|
|||
.appending(bodyHash ?? [])
|
||||
|
||||
/// Sign the above message
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(messageBytes, for: serverName, using: dependencies) else {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(messageBytes, for: serverName, fallbackSigningType: .unblinded, using: dependencies) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -981,23 +981,15 @@ public enum OpenGroupAPI {
|
|||
return Promise(error: error)
|
||||
}
|
||||
|
||||
if request.useOnionRouting {
|
||||
guard let publicKey = dependencies.storage.getOpenGroupPublicKey(for: request.server) else {
|
||||
return Promise(error: Error.noPublicKey)
|
||||
}
|
||||
|
||||
if request.isAuthRequired {
|
||||
// Attempt to sign the request with the new auth
|
||||
guard let signedRequest: URLRequest = sign(urlRequest, for: request.server, with: publicKey, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
return dependencies.api.sendOnionRequest(signedRequest, to: request.server, with: publicKey)
|
||||
}
|
||||
|
||||
return dependencies.api.sendOnionRequest(urlRequest, to: request.server, with: publicKey)
|
||||
guard let publicKey = dependencies.storage.getOpenGroupPublicKey(for: request.server) else {
|
||||
return Promise(error: Error.noPublicKey)
|
||||
}
|
||||
|
||||
preconditionFailure("It's currently not allowed to send non onion routed requests.")
|
||||
// Attempt to sign the request with the new auth
|
||||
guard let signedRequest: URLRequest = sign(urlRequest, for: request.server, with: publicKey, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
return dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ public final class OpenGroupManager: NSObject {
|
|||
public var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>?
|
||||
fileprivate var groupImagePromises: [String: Promise<Data>] = [:]
|
||||
|
||||
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
public var isPolling: Bool = false
|
||||
|
||||
/// Server URL to room ID to set of user IDs
|
||||
fileprivate var moderators: [String: [String: Set<String>]] = [:]
|
||||
fileprivate var admins: [String: [String: Set<String>]] = [:]
|
||||
|
@ -40,29 +43,31 @@ public final class OpenGroupManager: NSObject {
|
|||
public let mutableCache: Atomic<Cache> = Atomic(Cache())
|
||||
public var cache: Cache { return mutableCache.wrappedValue }
|
||||
|
||||
private var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
private var isPolling = false
|
||||
|
||||
// MARK: - Polling
|
||||
|
||||
@objc public func startPolling() {
|
||||
guard !isPolling else { return }
|
||||
|
||||
public func startPolling(using dependencies: Dependencies = Dependencies()) {
|
||||
guard !cache.isPolling else { return }
|
||||
|
||||
isPolling = true
|
||||
pollers = Set(Storage.shared.getAllOpenGroups().values.map { $0.server })
|
||||
.reduce(into: [:]) { prev, server in
|
||||
pollers[server]?.stop() // Should never occur
|
||||
|
||||
let poller = OpenGroupAPI.Poller(for: server)
|
||||
poller.startIfNeeded()
|
||||
|
||||
prev[server] = poller
|
||||
}
|
||||
mutableCache.mutate { cache in
|
||||
cache.isPolling = true
|
||||
cache.pollers = Set(dependencies.storage.getAllOpenGroups().values.map { openGroup in openGroup.server })
|
||||
.reduce(into: [:]) { prev, server in
|
||||
cache.pollers[server]?.stop() // Should never occur
|
||||
|
||||
let poller = OpenGroupAPI.Poller(for: server)
|
||||
poller.startIfNeeded(using: dependencies)
|
||||
|
||||
prev[server] = poller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func stopPolling() {
|
||||
pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
||||
pollers.removeAll()
|
||||
mutableCache.mutate {
|
||||
$0.pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
||||
$0.pollers.removeAll()
|
||||
$0.isPolling = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Adding & Removing
|
||||
|
@ -71,7 +76,7 @@ public final class OpenGroupManager: NSObject {
|
|||
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
||||
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(server).\(roomToken)")
|
||||
|
||||
if OpenGroupManager.shared.pollers[server] != nil && TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupId), transaction: transaction) != nil {
|
||||
if OpenGroupManager.shared.cache.pollers[server] != nil && TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupId), transaction: transaction) != nil {
|
||||
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!isConfigMessage)")
|
||||
return Promise.value(())
|
||||
}
|
||||
|
@ -86,19 +91,13 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
||||
OpenGroupAPI.capabilitiesAndRoom(for: roomToken, on: server, using: dependencies)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { (capabilitiesResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), roomResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?)) in
|
||||
guard let capabilities: OpenGroupAPI.Capabilities = capabilitiesResponse.data, let room: OpenGroupAPI.Room = roomResponse.data else {
|
||||
SNLog("Failed to join open group due to invalid data.")
|
||||
seal.reject(HTTP.Error.generic)
|
||||
return
|
||||
}
|
||||
|
||||
dependencies.storage.write { anyTransactionas in
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = anyTransactionas as? YapDatabaseReadWriteTransaction else { return }
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { response in
|
||||
dependencies.storage.write { anyTransaction in
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = anyTransaction as? YapDatabaseReadWriteTransaction else { return }
|
||||
|
||||
// Store the capabilities first
|
||||
OpenGroupManager.handleCapabilities(
|
||||
capabilities,
|
||||
response.capabilities.data,
|
||||
on: server,
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
|
@ -106,7 +105,7 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
// Then the room
|
||||
OpenGroupManager.handleRoom(
|
||||
room,
|
||||
response.room.data,
|
||||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
|
@ -118,6 +117,7 @@ public final class OpenGroupManager: NSObject {
|
|||
}
|
||||
}
|
||||
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
||||
SNLog("Failed to join open group.")
|
||||
seal.reject(error)
|
||||
}
|
||||
}
|
||||
|
@ -125,15 +125,13 @@ public final class OpenGroupManager: NSObject {
|
|||
return promise
|
||||
}
|
||||
|
||||
public func delete(_ openGroup: OpenGroup, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
|
||||
public func delete(_ openGroup: OpenGroup, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction, dependencies: Dependencies = Dependencies()) {
|
||||
// Stop the poller if needed
|
||||
let openGroups = storage.getAllOpenGroups().values.filter { $0.server == openGroup.server }
|
||||
let openGroups = dependencies.storage.getAllOpenGroups().values.filter { $0.server == openGroup.server }
|
||||
if openGroups.count == 1 && openGroups.last == openGroup {
|
||||
let poller = pollers[openGroup.server]
|
||||
let poller = cache.pollers[openGroup.server]
|
||||
poller?.stop()
|
||||
pollers[openGroup.server] = nil
|
||||
mutableCache.mutate { $0.pollers[openGroup.server] = nil }
|
||||
}
|
||||
|
||||
// Remove all data
|
||||
|
@ -143,17 +141,18 @@ public final class OpenGroupManager: NSObject {
|
|||
messageIDs.insert(interaction.uniqueId!)
|
||||
messageTimestamps.insert(interaction.timestamp)
|
||||
}
|
||||
storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
|
||||
Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
|
||||
Storage.shared.removeOpenGroupSequenceNumber(for: openGroup.room, on: openGroup.server, using: transaction)
|
||||
dependencies.storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
|
||||
dependencies.storage.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
|
||||
dependencies.storage.removeOpenGroupSequenceNumber(for: openGroup.room, on: openGroup.server, using: transaction)
|
||||
|
||||
thread.removeAllThreadInteractions(with: transaction)
|
||||
thread.remove(with: transaction)
|
||||
Storage.shared.removeOpenGroup(for: thread.uniqueId!, using: transaction)
|
||||
dependencies.storage.removeOpenGroup(for: thread.uniqueId!, using: transaction)
|
||||
|
||||
// Only remove the open group public key if the user isn't in any other rooms
|
||||
// Only remove the open group public key and server info if the user isn't in any other rooms
|
||||
if openGroups.count <= 1 {
|
||||
Storage.shared.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
|
||||
dependencies.storage.removeOpenGroupServer(name: openGroup.server, using: transaction)
|
||||
dependencies.storage.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,9 +261,11 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
||||
// Start the poller if needed
|
||||
if OpenGroupManager.shared.pollers[server] == nil {
|
||||
OpenGroupManager.shared.pollers[server] = OpenGroupAPI.Poller(for: server)
|
||||
OpenGroupManager.shared.pollers[server]?.startIfNeeded()
|
||||
if OpenGroupManager.shared.cache.pollers[server] == nil {
|
||||
OpenGroupManager.shared.mutableCache.mutate {
|
||||
$0.pollers[server] = OpenGroupAPI.Poller(for: server)
|
||||
$0.pollers[server]?.startIfNeeded(using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
// - Moderators
|
||||
|
@ -649,6 +650,11 @@ public final class OpenGroupManager: NSObject {
|
|||
}
|
||||
|
||||
extension OpenGroupManager {
|
||||
@objc(startPolling)
|
||||
public func objc_startPolling() {
|
||||
startPolling()
|
||||
}
|
||||
|
||||
@objc(getDefaultRoomsIfNeeded)
|
||||
public static func objc_getDefaultRoomsIfNeeded() {
|
||||
getDefaultRoomsIfNeeded()
|
||||
|
|
|
@ -24,6 +24,7 @@ extension OpenGroupAPI {
|
|||
case roomMessagesRecent(String)
|
||||
case roomMessagesBefore(String, id: UInt64)
|
||||
case roomMessagesSince(String, seqNo: Int64)
|
||||
case roomDeleteMessages(String, sessionId: String)
|
||||
|
||||
// Pinning
|
||||
|
||||
|
@ -50,7 +51,6 @@ extension OpenGroupAPI {
|
|||
case userBan(String)
|
||||
case userUnban(String)
|
||||
case userModerator(String)
|
||||
case userDeleteMessages(String)
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
|
@ -84,6 +84,9 @@ extension OpenGroupAPI {
|
|||
case .roomMessagesSince(let roomToken, let seqNo):
|
||||
return "room/\(roomToken)/messages/since/\(seqNo)"
|
||||
|
||||
case .roomDeleteMessages(let roomToken, let sessionId):
|
||||
return "room/\(roomToken)/all/\(sessionId)"
|
||||
|
||||
// Pinning
|
||||
|
||||
case .roomPinMessage(let roomToken, let messageId):
|
||||
|
@ -114,7 +117,6 @@ extension OpenGroupAPI {
|
|||
case .userBan(let sessionId): return "user/\(sessionId)/ban"
|
||||
case .userUnban(let sessionId): return "user/\(sessionId)/unban"
|
||||
case .userModerator(let sessionId): return "user/\(sessionId)/moderator"
|
||||
case .userDeleteMessages(let sessionId): return "user/\(sessionId)/deleteMessages"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public protocol AeadXChaCha20Poly1305IetfType {
|
|||
}
|
||||
|
||||
public protocol Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: ECKeyPair) throws -> Bytes?
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
|
||||
}
|
||||
|
||||
|
@ -82,6 +83,10 @@ extension Sign: SignType {}
|
|||
extension GenericHash: GenericHashType {}
|
||||
|
||||
struct Ed25519Wrapper: Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: ECKeyPair) throws -> Bytes? {
|
||||
return try Ed25519.sign(Data(data), with: keyPair).bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return try Ed25519.verifySignature(signature, publicKey: publicKey, data: data)
|
||||
}
|
||||
|
|
|
@ -391,7 +391,8 @@ public final class MessageSender : NSObject {
|
|||
on: server,
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods,
|
||||
fileIds: fileIds
|
||||
fileIds: fileIds,
|
||||
using: dependencies
|
||||
)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in
|
||||
message.openGroupServerMessageID = UInt64(data.id)
|
||||
|
|
|
@ -18,15 +18,15 @@ extension OpenGroupAPI {
|
|||
public init(for server: String) {
|
||||
self.server = server
|
||||
}
|
||||
|
||||
@objc public func startIfNeeded() {
|
||||
|
||||
public func startIfNeeded(using dependencies: Dependencies = Dependencies()) {
|
||||
guard !hasStarted else { return }
|
||||
|
||||
hasStarted = true
|
||||
timer = Timer.scheduledTimerOnMainThread(withTimeInterval: Poller.pollInterval, repeats: true) { _ in
|
||||
self.poll().retainUntilComplete()
|
||||
self.poll(using: dependencies).retainUntilComplete()
|
||||
}
|
||||
poll().retainUntilComplete()
|
||||
poll(using: dependencies).retainUntilComplete()
|
||||
}
|
||||
|
||||
@objc public func stop() {
|
||||
|
@ -37,12 +37,12 @@ extension OpenGroupAPI {
|
|||
// MARK: - Polling
|
||||
|
||||
@discardableResult
|
||||
public func poll() -> Promise<Void> {
|
||||
return poll(isBackgroundPoll: false)
|
||||
public func poll(using dependencies: Dependencies = Dependencies()) -> Promise<Void> {
|
||||
return poll(isBackgroundPoll: false, using: dependencies)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func poll(isBackgroundPoll: Bool) -> Promise<Void> {
|
||||
public func poll(isBackgroundPoll: Bool, using dependencies: Dependencies = Dependencies()) -> Promise<Void> {
|
||||
guard !self.isPolling else { return Promise.value(()) }
|
||||
|
||||
self.isPolling = true
|
||||
|
@ -57,12 +57,13 @@ extension OpenGroupAPI {
|
|||
hasPerformedInitialPoll: OpenGroupManager.shared.cache.hasPerformedInitialPoll[server] == true,
|
||||
timeSinceLastPoll: (
|
||||
OpenGroupManager.shared.cache.timeSinceLastPoll[server] ??
|
||||
OpenGroupManager.shared.cache.getTimeSinceLastOpen()
|
||||
)
|
||||
OpenGroupManager.shared.cache.getTimeSinceLastOpen(using: dependencies)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.done(on: OpenGroupAPI.workQueue) { [weak self] response in
|
||||
self?.isPolling = false
|
||||
self?.handlePollResponse(response, isBackgroundPoll: isBackgroundPoll)
|
||||
self?.handlePollResponse(response, isBackgroundPoll: isBackgroundPoll, using: dependencies)
|
||||
|
||||
OpenGroupManager.shared.mutableCache.mutate { cache in
|
||||
cache.hasPerformedInitialPoll[server] = true
|
||||
|
@ -81,10 +82,10 @@ extension OpenGroupAPI {
|
|||
return promise
|
||||
}
|
||||
|
||||
private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable?)], isBackgroundPoll: Bool) {
|
||||
private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable?)], isBackgroundPoll: Bool, using dependencies: Dependencies = Dependencies()) {
|
||||
let server: String = self.server
|
||||
|
||||
Storage.shared.write { anyTransaction in
|
||||
dependencies.storage.write { anyTransaction in
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = anyTransaction as? YapDatabaseReadWriteTransaction else {
|
||||
SNLog("Open group polling failed due to invalid database transaction.")
|
||||
return
|
||||
|
@ -101,21 +102,30 @@ extension OpenGroupAPI {
|
|||
OpenGroupManager.handleCapabilities(
|
||||
responseBody,
|
||||
on: server,
|
||||
using: transaction
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
guard let responseData: BatchSubResponse<[Message]> = endpointResponse.data as? BatchSubResponse<[Message]>, let responseBody: [Message] = responseData.body else {
|
||||
guard let responseData: BatchSubResponse<[Failable<Message>]> = endpointResponse.data as? BatchSubResponse<[Failable<Message>]>, let responseBody: [Failable<Message>] = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
let successfulMessages: [Message] = responseBody.compactMap { $0.value }
|
||||
|
||||
if successfulMessages.count != responseBody.count {
|
||||
let droppedCount: Int = (responseBody.count - successfulMessages.count)
|
||||
|
||||
SNLog("Dropped \(droppedCount) invalid open group message\(droppedCount == 1 ? "" : "s").")
|
||||
}
|
||||
|
||||
OpenGroupManager.handleMessages(
|
||||
responseBody,
|
||||
successfulMessages,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll,
|
||||
using: transaction
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
case .roomPollInfo(let roomToken, _):
|
||||
|
@ -129,7 +139,8 @@ extension OpenGroupAPI {
|
|||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
using: transaction
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||
|
@ -150,7 +161,8 @@ extension OpenGroupAPI {
|
|||
fromOutbox: fromOutbox,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll,
|
||||
using: transaction
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
|
@ -160,3 +172,10 @@ extension OpenGroupAPI {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenGroupAPI.Poller {
|
||||
@objc(startIfNeeded)
|
||||
public func objc_startIfNeeded() {
|
||||
startIfNeeded()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import SessionUtilitiesKit
|
|||
// MARK: - Dependencies
|
||||
|
||||
public class Dependencies {
|
||||
private var _api: OnionRequestAPIType.Type?
|
||||
public var api: OnionRequestAPIType.Type {
|
||||
get { getValueSettingIfNull(&_api) { OnionRequestAPI.self } }
|
||||
set { _api = newValue }
|
||||
private var _onionApi: OnionRequestAPIType.Type?
|
||||
public var onionApi: OnionRequestAPIType.Type {
|
||||
get { getValueSettingIfNull(&_onionApi) { OnionRequestAPI.self } }
|
||||
set { _onionApi = newValue }
|
||||
}
|
||||
|
||||
private var _storage: SessionMessagingKitStorageProtocol?
|
||||
|
@ -77,7 +77,7 @@ public class Dependencies {
|
|||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
api: OnionRequestAPIType.Type? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
storage: SessionMessagingKitStorageProtocol? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
|
@ -89,7 +89,7 @@ public class Dependencies {
|
|||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_api = api
|
||||
_onionApi = onionApi
|
||||
_storage = storage
|
||||
_sodium = sodium
|
||||
_aeadXChaCha20Poly1305Ietf = aeadXChaCha20Poly1305Ietf
|
||||
|
@ -105,7 +105,7 @@ public class Dependencies {
|
|||
// MARK: - Convenience
|
||||
|
||||
public func with(
|
||||
api: OnionRequestAPIType.Type? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
storage: SessionMessagingKitStorageProtocol? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
|
@ -118,7 +118,7 @@ public class Dependencies {
|
|||
date: Date? = nil
|
||||
) -> Dependencies {
|
||||
return Dependencies(
|
||||
api: (api ?? self._api),
|
||||
onionApi: (onionApi ?? self._onionApi),
|
||||
storage: (storage ?? self._storage),
|
||||
sodium: (sodium ?? self._sodium),
|
||||
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf),
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Failable<T: Codable>: Codable {
|
||||
let value: T?
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard let container = try? decoder.singleValueContainer() else {
|
||||
self.value = nil
|
||||
return
|
||||
}
|
||||
|
||||
self.value = try? container.decode(T.self)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
guard let value: T = value else { return }
|
||||
|
||||
var container: SingleValueEncodingContainer = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,559 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
import Sodium
|
||||
import SessionSnodeKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class OpenGroupManagerSpec: QuickSpec {
|
||||
class TestCapabilitiesAndRoomApi: TestOnionRequestAPI {
|
||||
static let capabilitiesData: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: nil)
|
||||
static let roomData: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
||||
token: "test",
|
||||
name: "test",
|
||||
description: nil,
|
||||
infoUpdates: 0,
|
||||
messageSequence: 0,
|
||||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: nil,
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
admins: [],
|
||||
hiddenAdmins: nil,
|
||||
moderator: false,
|
||||
globalModerator: false,
|
||||
moderators: [],
|
||||
hiddenModerators: nil,
|
||||
read: false,
|
||||
defaultRead: nil,
|
||||
defaultAccessible: nil,
|
||||
write: false,
|
||||
defaultWrite: nil,
|
||||
upload: false,
|
||||
defaultUpload: nil
|
||||
)
|
||||
|
||||
override class var mockResponse: Data? {
|
||||
let responses: [Data] = [
|
||||
try! JSONEncoder().encode(
|
||||
OpenGroupAPI.BatchSubResponse(
|
||||
code: 200,
|
||||
headers: [:],
|
||||
body: capabilitiesData,
|
||||
failedToParseBody: false
|
||||
)
|
||||
),
|
||||
try! JSONEncoder().encode(
|
||||
OpenGroupAPI.BatchSubResponse(
|
||||
code: 200,
|
||||
headers: [:],
|
||||
body: roomData,
|
||||
failedToParseBody: false
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: MockStorage!
|
||||
var mockSodium: MockSodium!
|
||||
var mockAeadXChaCha20Poly1305Ietf: MockAeadXChaCha20Poly1305Ietf!
|
||||
var mockGenericHash: MockGenericHash!
|
||||
var mockSign: MockSign!
|
||||
var mockUserDefaults: MockUserDefaults!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
var testInteraction: TestInteraction!
|
||||
var testGroupThread: TestGroupThread!
|
||||
var testTransaction: TestTransaction!
|
||||
|
||||
describe("an OpenGroupAPI") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = MockStorage()
|
||||
mockSodium = MockSodium()
|
||||
mockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
||||
mockGenericHash = MockGenericHash()
|
||||
mockSign = MockSign()
|
||||
mockUserDefaults = MockUserDefaults()
|
||||
dependencies = Dependencies(
|
||||
onionApi: TestCapabilitiesAndRoomApi.self,
|
||||
storage: mockStorage,
|
||||
sodium: mockSodium,
|
||||
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha20Poly1305Ietf,
|
||||
sign: mockSign,
|
||||
genericHash: mockGenericHash,
|
||||
ed25519: MockEd25519(),
|
||||
nonceGenerator16: OpenGroupAPISpec.TestNonce16Generator(),
|
||||
nonceGenerator24: OpenGroupAPISpec.TestNonce24Generator(),
|
||||
standardUserDefaults: mockUserDefaults,
|
||||
date: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
testInteraction = TestInteraction()
|
||||
testInteraction.mockData[.uniqueId] = "TestInteractionId"
|
||||
testInteraction.mockData[.timestamp] = UInt64(123)
|
||||
|
||||
testGroupThread = TestGroupThread()
|
||||
testGroupThread.mockData[.groupModel] = TSGroupModel(
|
||||
title: "TestTitle",
|
||||
memberIds: [],
|
||||
image: nil,
|
||||
groupId: LKGroupUtilities.getEncodedOpenGroupIDAsData("testServer.testRoom"),
|
||||
groupType: .openGroup,
|
||||
adminIds: [],
|
||||
moderatorIds: []
|
||||
)
|
||||
testGroupThread.mockData[.interactions] = [testInteraction]
|
||||
|
||||
testTransaction = TestTransaction()
|
||||
testTransaction.mockData[.objectForKey] = testGroupThread
|
||||
|
||||
mockStorage
|
||||
.when { $0.write(with: { _ in }) }
|
||||
.then { args in (args.first as? ((Any) -> Void))?(testTransaction as Any) }
|
||||
.thenReturn(Promise.value(()))
|
||||
mockStorage
|
||||
.when { $0.write(with: { _ in }, completion: { }) }
|
||||
.then { args in
|
||||
(args.first as? ((Any) -> Void))?(testTransaction as Any)
|
||||
(args.last as? (() -> Void))?()
|
||||
}
|
||||
.thenReturn(Promise.value(()))
|
||||
mockStorage
|
||||
.when { $0.getUserKeyPair() }
|
||||
.thenReturn(
|
||||
try! ECKeyPair(
|
||||
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
|
||||
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
||||
)
|
||||
)
|
||||
mockStorage
|
||||
.when { $0.getUserED25519KeyPair() }
|
||||
.thenReturn(
|
||||
Box.KeyPair(
|
||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
)
|
||||
mockStorage
|
||||
.when { $0.getAllOpenGroups() }
|
||||
.thenReturn([
|
||||
"0": OpenGroup(
|
||||
server: "testServer",
|
||||
room: "testRoom",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
)
|
||||
])
|
||||
mockStorage
|
||||
.when { $0.getOpenGroup(for: any()) }
|
||||
.thenReturn(
|
||||
OpenGroup(
|
||||
server: "testServer",
|
||||
room: "testRoom",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
)
|
||||
)
|
||||
mockStorage
|
||||
.when { $0.getOpenGroupServer(name: any()) }
|
||||
.thenReturn(
|
||||
OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
|
||||
)
|
||||
)
|
||||
mockStorage
|
||||
.when { $0.getOpenGroupPublicKey(for: any()) }
|
||||
.thenReturn(TestConstants.publicKey)
|
||||
|
||||
mockGenericHash.when { $0.hash(message: any(), outputLength: any()) }.thenReturn([])
|
||||
mockSodium
|
||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
||||
.thenReturn(
|
||||
Box.KeyPair(
|
||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
)
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sogsSignature(
|
||||
message: any(),
|
||||
secretKey: any(),
|
||||
blindedSecretKey: any(),
|
||||
blindedPublicKey: any()
|
||||
)
|
||||
}
|
||||
.thenReturn("TestSogsSignature".bytes)
|
||||
mockSign.when { $0.signature(message: any(), secretKey: any()) }.thenReturn("TestSignature".bytes)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
OpenGroupManager.shared.stopPolling() // Need to stop any pollers which get created during tests
|
||||
|
||||
mockStorage = nil
|
||||
mockSodium = nil
|
||||
mockAeadXChaCha20Poly1305Ietf = nil
|
||||
mockGenericHash = nil
|
||||
mockSign = nil
|
||||
mockUserDefaults = nil
|
||||
dependencies = nil
|
||||
|
||||
testInteraction = nil
|
||||
testGroupThread = nil
|
||||
testTransaction = nil
|
||||
}
|
||||
|
||||
// MARK: - Polling
|
||||
|
||||
context("when starting polling") {
|
||||
beforeEach {
|
||||
mockStorage
|
||||
.when { $0.getAllOpenGroups() }
|
||||
.thenReturn([
|
||||
"0": OpenGroup(
|
||||
server: "testServer",
|
||||
room: "testRoom",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
),
|
||||
"1": OpenGroup(
|
||||
server: "testServer1",
|
||||
room: "testRoom1",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test1",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
)
|
||||
])
|
||||
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupServer(any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
||||
|
||||
mockUserDefaults
|
||||
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
||||
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
||||
}
|
||||
|
||||
it("creates pollers for all of the open groups") {
|
||||
OpenGroupManager.shared.startPolling(using: dependencies)
|
||||
|
||||
expect(OpenGroupManager.shared.cache.pollers.keys.map { String($0) }.sorted)
|
||||
.to(equal(["testserver", "testserver1"]))
|
||||
}
|
||||
|
||||
it("updates the isPolling flag") {
|
||||
OpenGroupManager.shared.startPolling(using: dependencies)
|
||||
|
||||
expect(OpenGroupManager.shared.cache.isPolling).to(beTrue())
|
||||
}
|
||||
|
||||
it("does nothing if already polling") {
|
||||
OpenGroupManager.shared.mutableCache.mutate { $0.isPolling = true }
|
||||
OpenGroupManager.shared.startPolling(using: dependencies)
|
||||
|
||||
expect(OpenGroupManager.shared.cache.pollers.keys.map { String($0) })
|
||||
.to(equal([]))
|
||||
}
|
||||
}
|
||||
|
||||
context("when stopping polling") {
|
||||
beforeEach {
|
||||
mockStorage
|
||||
.when { $0.getAllOpenGroups() }
|
||||
.thenReturn([
|
||||
"0": OpenGroup(
|
||||
server: "testServer",
|
||||
room: "testRoom",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
),
|
||||
"1": OpenGroup(
|
||||
server: "testServer1",
|
||||
room: "testRoom1",
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: "Test1",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
)
|
||||
])
|
||||
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupServer(any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
||||
|
||||
mockUserDefaults
|
||||
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
||||
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
||||
|
||||
OpenGroupManager.shared.startPolling(using: dependencies)
|
||||
}
|
||||
|
||||
it("removes all pollers") {
|
||||
OpenGroupManager.shared.stopPolling()
|
||||
|
||||
expect(OpenGroupManager.shared.cache.pollers.keys.map { String($0) })
|
||||
.to(equal([]))
|
||||
}
|
||||
|
||||
it("updates the isPolling flag") {
|
||||
OpenGroupManager.shared.stopPolling()
|
||||
|
||||
expect(OpenGroupManager.shared.cache.isPolling).to(beFalse())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Adding & Removing
|
||||
|
||||
context("when adding") {
|
||||
beforeEach {
|
||||
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroupServer(any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: any()) }.thenReturn(())
|
||||
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
||||
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
||||
|
||||
mockUserDefaults
|
||||
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
||||
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
||||
}
|
||||
|
||||
it("resets the sequence number of the open group") {
|
||||
OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(mockStorage)
|
||||
.to(
|
||||
call(.exactly(times: 1)) {
|
||||
$0.removeOpenGroupSequenceNumber(
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: testTransaction as Any
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
it("sets the public key of the open group server") {
|
||||
OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(mockStorage)
|
||||
.to(
|
||||
call(.exactly(times: 1)) {
|
||||
$0.setOpenGroupPublicKey(
|
||||
for: "testRoom",
|
||||
to: "testKey",
|
||||
using: testTransaction as Any
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
it("adds a poller") {
|
||||
OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(OpenGroupManager.shared.cache.pollers["testServer"])
|
||||
.toEventuallyNot(
|
||||
beNil(),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
}
|
||||
|
||||
context("an existing room") {
|
||||
beforeEach {
|
||||
OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
.retainUntilComplete()
|
||||
|
||||
// There is a bunch of async code in this function so we need to wait until if finishes
|
||||
// processing before doing the actual tests
|
||||
expect(mockStorage)
|
||||
.toEventually(
|
||||
call { $0.setOpenGroup(any(), for: "testServer", using: testTransaction as Any) },
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
mockStorage.resetCallCounts()
|
||||
}
|
||||
|
||||
it("does not reset the sequence number or update the public key") {
|
||||
OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(mockStorage)
|
||||
.toEventuallyNot(
|
||||
call {
|
||||
$0.removeOpenGroupSequenceNumber(
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: testTransaction as Any
|
||||
)
|
||||
},
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
expect(mockStorage)
|
||||
.toEventuallyNot(
|
||||
call {
|
||||
$0.setOpenGroupPublicKey(
|
||||
for: "testRoom",
|
||||
to: "testKey",
|
||||
using: testTransaction as Any
|
||||
)
|
||||
},
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("with an invalid response") {
|
||||
beforeEach {
|
||||
class TestApi: TestOnionRequestAPI {
|
||||
override class var mockResponse: Data? { return Data() }
|
||||
}
|
||||
dependencies = dependencies.with(onionApi: TestApi.self)
|
||||
|
||||
mockUserDefaults
|
||||
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
||||
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
||||
}
|
||||
|
||||
it("fails with the error") {
|
||||
var error: Error?
|
||||
|
||||
let promise = OpenGroupManager.shared
|
||||
.add(
|
||||
roomToken: "testRoom",
|
||||
server: "testServer",
|
||||
publicKey: "testKey",
|
||||
isConfigMessage: false,
|
||||
using: testTransaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
promise.catch { error = $0 }
|
||||
promise.retainUntilComplete()
|
||||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("when removing") {
|
||||
|
||||
// public func delete(_ openGroup: OpenGroup, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction, dependencies: Dependencies = Dependencies()) {
|
||||
// // Stop the poller if needed
|
||||
// let openGroups = dependencies.storage.getAllOpenGroups().values.filter { $0.server == openGroup.server }
|
||||
// if openGroups.count == 1 && openGroups.last == openGroup {
|
||||
// let poller = pollers[openGroup.server]
|
||||
// poller?.stop()
|
||||
// pollers[openGroup.server] = nil
|
||||
// }
|
||||
//
|
||||
// // Remove all data
|
||||
// var messageIDs: Set<String> = []
|
||||
// var messageTimestamps: Set<UInt64> = []
|
||||
// thread.enumerateInteractions(with: transaction) { interaction, _ in
|
||||
// messageIDs.insert(interaction.uniqueId!)
|
||||
// messageTimestamps.insert(interaction.timestamp)
|
||||
// }
|
||||
// dependencies.storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
|
||||
// dependencies.storage.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
|
||||
// dependencies.storage.removeOpenGroupSequenceNumber(for: openGroup.room, on: openGroup.server, using: transaction)
|
||||
//
|
||||
// thread.removeAllThreadInteractions(with: transaction)
|
||||
// thread.remove(with: transaction)
|
||||
// dependencies.storage.removeOpenGroup(for: thread.uniqueId!, using: transaction)
|
||||
//
|
||||
// // Only remove the open group public key and server info if the user isn't in any other rooms
|
||||
// if openGroups.count <= 1 {
|
||||
// dependencies.storage.removeOpenGroupServer(name: openGroup.server, using: transaction)
|
||||
// dependencies.storage.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ class SOGSEndpointSpec: QuickSpec {
|
|||
expect(OpenGroupAPI.Endpoint.roomMessagesBefore("test", id: 123).path).to(equal("room/test/messages/before/123"))
|
||||
expect(OpenGroupAPI.Endpoint.roomMessagesSince("test", seqNo: 123).path)
|
||||
.to(equal("room/test/messages/since/123"))
|
||||
expect(OpenGroupAPI.Endpoint.roomDeleteMessages("test", sessionId: "testId").path)
|
||||
.to(equal("room/test/all/testId"))
|
||||
|
||||
// Pinning
|
||||
|
||||
|
@ -60,7 +62,6 @@ class SOGSEndpointSpec: QuickSpec {
|
|||
expect(OpenGroupAPI.Endpoint.userBan("test").path).to(equal("user/test/ban"))
|
||||
expect(OpenGroupAPI.Endpoint.userUnban("test").path).to(equal("user/test/unban"))
|
||||
expect(OpenGroupAPI.Endpoint.userModerator("test").path).to(equal("user/test/moderator"))
|
||||
expect(OpenGroupAPI.Endpoint.userDeleteMessages("test").path).to(equal("user/test/deleteMessages"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ import Sodium
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class MockEd25519: Mock<Ed25519Type>, Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: ECKeyPair) throws -> Bytes? {
|
||||
return accept(args: [data, keyPair]) as? Bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return accept(args: [signature, publicKey, data]) as! Bool
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
class MockUserDefaults: Mock<UserDefaultsType>, UserDefaultsType {
|
||||
var storage: [String: Any] = [:]
|
||||
|
||||
func object(forKey defaultName: String) -> Any? { return accept(args: [defaultName]) }
|
||||
func string(forKey defaultName: String) -> String? { return accept(args: [defaultName]) as? String }
|
||||
func array(forKey defaultName: String) -> [Any]? { return accept(args: [defaultName]) as? [Any] }
|
||||
func dictionary(forKey defaultName: String) -> [String: Any]? { return accept(args: [defaultName]) as? [String: Any] }
|
||||
func data(forKey defaultName: String) -> Data? { return accept(args: [defaultName]) as? Data }
|
||||
func stringArray(forKey defaultName: String) -> [String]? { return accept(args: [defaultName]) as? [String] }
|
||||
func integer(forKey defaultName: String) -> Int { return ((accept(args: [defaultName]) as? Int) ?? 0) }
|
||||
func float(forKey defaultName: String) -> Float { return ((accept(args: [defaultName]) as? Float) ?? 0) }
|
||||
func double(forKey defaultName: String) -> Double { return ((accept(args: [defaultName]) as? Double) ?? 0) }
|
||||
func bool(forKey defaultName: String) -> Bool { return ((accept(args: [defaultName]) as? Bool) ?? false) }
|
||||
func url(forKey defaultName: String) -> URL? { return accept(args: [defaultName]) as? URL }
|
||||
|
||||
func set(_ value: Any?, forKey defaultName: String) { accept(args: [value, defaultName]) }
|
||||
func set(_ value: Int, forKey defaultName: String) { accept(args: [value, defaultName]) }
|
||||
func set(_ value: Float, forKey defaultName: String) { accept(args: [value, defaultName]) }
|
||||
func set(_ value: Double, forKey defaultName: String) { accept(args: [value, defaultName]) }
|
||||
func set(_ value: Bool, forKey defaultName: String) { accept(args: [value, defaultName]) }
|
||||
func set(_ url: URL?, forKey defaultName: String) { accept(args: [url, defaultName]) }
|
||||
}
|
|
@ -7,9 +7,3 @@ protocol Mockable {
|
|||
|
||||
var mockData: [Key: Any] { get }
|
||||
}
|
||||
|
||||
protocol StaticMockable {
|
||||
associatedtype Key: Hashable
|
||||
|
||||
static var mockData: [Key: Any] { get }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionMessagingKit
|
||||
|
||||
extension OpenGroup: Mocked {
|
||||
static var mockValue: OpenGroup = OpenGroup(
|
||||
server: any(),
|
||||
room: any(),
|
||||
publicKey: TestConstants.publicKey,
|
||||
name: any(),
|
||||
groupDescription: any(),
|
||||
imageID: any(),
|
||||
infoUpdates: any()
|
||||
)
|
||||
}
|
||||
|
||||
extension OpenGroupAPI.Server: Mocked {
|
||||
static var mockValue: OpenGroupAPI.Server = OpenGroupAPI.Server(
|
||||
name: any(),
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: any(), missing: any())
|
||||
)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionMessagingKit
|
||||
|
||||
// FIXME: Turn this into a protocol to make mocking possible
|
||||
class TestGroupThread: TSGroupThread, Mockable {
|
||||
// MARK: - Mockable
|
||||
|
||||
enum DataKey: Hashable {
|
||||
case groupModel
|
||||
case interactions
|
||||
}
|
||||
|
||||
typealias Key = DataKey
|
||||
|
||||
var mockData: [DataKey: Any] = [:]
|
||||
var didCallSave: Bool = false
|
||||
|
||||
// MARK: - TSGroupThread
|
||||
|
||||
override var groupModel: TSGroupModel {
|
||||
get { (mockData[.groupModel] as! TSGroupModel) }
|
||||
set {}
|
||||
}
|
||||
|
||||
override func enumerateInteractions(_ block: @escaping (TSInteraction) -> Void) {
|
||||
((mockData[.interactions] as? [TSInteraction]) ?? []).forEach(block)
|
||||
}
|
||||
|
||||
override func save(with transaction: YapDatabaseReadWriteTransaction) { didCallSave = true }
|
||||
}
|
|
@ -1,3 +1,32 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionMessagingKit
|
||||
|
||||
// FIXME: Turn this into a protocol to make mocking possible
|
||||
class TestInteraction: TSInteraction, Mockable {
|
||||
// MARK: - Mockable
|
||||
|
||||
enum DataKey: Hashable {
|
||||
case uniqueId
|
||||
case timestamp
|
||||
}
|
||||
|
||||
typealias Key = DataKey
|
||||
|
||||
var mockData: [DataKey: Any] = [:]
|
||||
var didCallSave: Bool = true
|
||||
|
||||
// MARK: - TSInteraction
|
||||
|
||||
override var uniqueId: String? {
|
||||
get { (mockData[.uniqueId] as? String) }
|
||||
set { mockData[.uniqueId] = newValue }
|
||||
}
|
||||
|
||||
override var timestamp: UInt64 {
|
||||
(mockData[.timestamp] as! UInt64)
|
||||
}
|
||||
|
||||
override func save(with transaction: YapDatabaseReadWriteTransaction) { didCallSave = true }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
import SessionSnodeKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
// FIXME: Change 'OnionRequestAPIType' to have instance methods instead of static methods once everything is updated to use 'Dependencies'
|
||||
class TestOnionRequestAPI: OnionRequestAPIType {
|
||||
struct RequestData: Codable {
|
||||
let urlString: String?
|
||||
let httpMethod: String
|
||||
let headers: [String: String]
|
||||
let snodeMethod: String?
|
||||
let body: Data?
|
||||
|
||||
let server: String
|
||||
let version: OnionRequestAPI.Version
|
||||
let publicKey: String?
|
||||
}
|
||||
class ResponseInfo: OnionRequestResponseInfoType {
|
||||
let requestData: RequestData
|
||||
let code: Int
|
||||
let headers: [String: String]
|
||||
|
||||
init(requestData: RequestData, code: Int, headers: [String: String]) {
|
||||
self.requestData = requestData
|
||||
self.code = code
|
||||
self.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
class var mockResponse: Data? { return nil }
|
||||
|
||||
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPI.Version, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
let responseInfo: ResponseInfo = ResponseInfo(
|
||||
requestData: RequestData(
|
||||
urlString: request.url?.absoluteString,
|
||||
httpMethod: (request.httpMethod ?? "GET"),
|
||||
headers: (request.allHTTPHeaderFields ?? [:]),
|
||||
snodeMethod: nil,
|
||||
body: request.httpBody,
|
||||
|
||||
server: server,
|
||||
version: version,
|
||||
publicKey: x25519PublicKey
|
||||
),
|
||||
code: 200,
|
||||
headers: [:]
|
||||
)
|
||||
|
||||
return Promise.value((responseInfo, mockResponse))
|
||||
}
|
||||
|
||||
static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, using version: OnionRequestAPI.Version, associatedWith publicKey: String?) -> Promise<Data> {
|
||||
// TODO: Test the 'responseInfo' somehow?
|
||||
return Promise.value(mockResponse!)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,26 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionMessagingKit
|
||||
|
||||
// FIXME: Turn this into a protocol to make mocking possible
|
||||
class TestThread: TSThread, Mockable {
|
||||
// MARK: - Mockable
|
||||
|
||||
enum DataKey: Hashable {
|
||||
case interactions
|
||||
}
|
||||
|
||||
typealias Key = DataKey
|
||||
|
||||
var mockData: [DataKey: Any] = [:]
|
||||
var didCallSave: Bool = false
|
||||
|
||||
// MARK: - TSThread
|
||||
|
||||
override func enumerateInteractions(_ block: @escaping (TSInteraction) -> Void) {
|
||||
((mockData[.interactions] as? [TSInteraction]) ?? []).forEach(block)
|
||||
}
|
||||
|
||||
override func save(with transaction: YapDatabaseReadWriteTransaction) { didCallSave = true }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import YapDatabase
|
||||
|
||||
// FIXME: Turn this into a protocol to make mocking possible
|
||||
final class TestTransaction: YapDatabaseReadWriteTransaction, Mockable {
|
||||
// MARK: - Mockable
|
||||
|
||||
enum DataKey: Hashable {
|
||||
case objectForKey
|
||||
}
|
||||
|
||||
typealias Key = DataKey
|
||||
|
||||
var mockData: [DataKey: Any] = [:]
|
||||
|
||||
// MARK: - YapDatabaseReadWriteTransaction
|
||||
|
||||
override func object(forKey key: String, inCollection collection: String?) -> Any? {
|
||||
return mockData[.objectForKey]
|
||||
}
|
||||
|
||||
override func addCompletionQueue(_ completionQueue: DispatchQueue?, completionBlock: @escaping () -> Void) {
|
||||
completionBlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension TestTransaction: Mocked {
|
||||
static var mockValue: TestTransaction = TestTransaction()
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
class TestUserDefaults: UserDefaultsType {
|
||||
var storage: [String: Any] = [:]
|
||||
|
||||
func object(forKey defaultName: String) -> Any? { return storage[defaultName] }
|
||||
func string(forKey defaultName: String) -> String? { return storage[defaultName] as? String }
|
||||
func array(forKey defaultName: String) -> [Any]? { return storage[defaultName] as? [Any] }
|
||||
func dictionary(forKey defaultName: String) -> [String: Any]? { return storage[defaultName] as? [String: Any] }
|
||||
func data(forKey defaultName: String) -> Data? { return storage[defaultName] as? Data }
|
||||
func stringArray(forKey defaultName: String) -> [String]? { return storage[defaultName] as? [String] }
|
||||
func integer(forKey defaultName: String) -> Int { return ((storage[defaultName] as? Int) ?? 0) }
|
||||
func float(forKey defaultName: String) -> Float { return ((storage[defaultName] as? Float) ?? 0) }
|
||||
func double(forKey defaultName: String) -> Double { return ((storage[defaultName] as? Double) ?? 0) }
|
||||
func bool(forKey defaultName: String) -> Bool { return ((storage[defaultName] as? Bool) ?? false) }
|
||||
func url(forKey defaultName: String) -> URL? { return storage[defaultName] as? URL }
|
||||
|
||||
func set(_ value: Any?, forKey defaultName: String) { storage[defaultName] = value }
|
||||
func set(_ value: Int, forKey defaultName: String) { storage[defaultName] = value }
|
||||
func set(_ value: Float, forKey defaultName: String) { storage[defaultName] = value }
|
||||
func set(_ value: Double, forKey defaultName: String) { storage[defaultName] = value }
|
||||
func set(_ value: Bool, forKey defaultName: String) { storage[defaultName] = value }
|
||||
func set(_ url: URL?, forKey defaultName: String) { storage[defaultName] = url }
|
||||
}
|
|
@ -27,13 +27,13 @@ public class Atomic<Value> {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(_ initialValue: Value) {
|
||||
public init(_ initialValue: Value) {
|
||||
self.value = initialValue
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
func mutate(_ mutation: (inout Value) -> Void) {
|
||||
public func mutate(_ mutation: (inout Value) -> Void) {
|
||||
return queue.sync {
|
||||
mutation(&value)
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
|
||||
extension Box.KeyPair: Mocked {
|
||||
static var mockValue: Box.KeyPair = Box.KeyPair(
|
||||
|
@ -9,3 +10,12 @@ extension Box.KeyPair: Mocked {
|
|||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
}
|
||||
|
||||
extension ECKeyPair: Mocked {
|
||||
static var mockValue: Self {
|
||||
try! Self.init(
|
||||
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
|
||||
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Mocked
|
||||
|
||||
|
@ -22,11 +23,15 @@ public class Mock<T> {
|
|||
private let functionHandler: MockFunctionHandler
|
||||
internal let functionConsumer: FunctionConsumer
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
internal required init(functionHandler: MockFunctionHandler? = nil) {
|
||||
self.functionConsumer = FunctionConsumer()
|
||||
self.functionHandler = (functionHandler ?? self.functionConsumer)
|
||||
}
|
||||
|
||||
// MARK: - MockFunctionHandler
|
||||
|
||||
@discardableResult internal func accept(funcName: String = #function, args: [Any?] = []) -> Any? {
|
||||
return accept(funcName: funcName, checkArgs: args, actionArgs: args)
|
||||
}
|
||||
|
@ -35,6 +40,19 @@ public class Mock<T> {
|
|||
return functionHandler.accept(funcName, parameterSummary: summary(for: checkArgs), actionArgs: actionArgs)
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
internal func reset() {
|
||||
functionConsumer.trackCalls = true
|
||||
functionConsumer.functionBuilders = []
|
||||
functionConsumer.functionHandlers = [:]
|
||||
functionConsumer.calls.mutate { $0 = [:] }
|
||||
}
|
||||
|
||||
internal func resetCallCounts() {
|
||||
functionConsumer.calls.mutate { $0 = [:] }
|
||||
}
|
||||
|
||||
internal func when<R>(_ callBlock: @escaping (T) throws -> R) -> MockFunctionBuilder<T, R> {
|
||||
let builder: MockFunctionBuilder<T, R> = MockFunctionBuilder(callBlock, mockInit: type(of: self).init)
|
||||
functionConsumer.functionBuilders.append(builder.build)
|
||||
|
@ -42,6 +60,8 @@ public class Mock<T> {
|
|||
return builder
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
private func summary(for argument: Any) -> String {
|
||||
switch argument {
|
||||
case let string as String: return string
|
||||
|
@ -134,7 +154,7 @@ internal class FunctionConsumer: MockFunctionHandler {
|
|||
var trackCalls: Bool = true
|
||||
var functionBuilders: [() throws -> MockFunction?] = []
|
||||
var functionHandlers: [String: [String: MockFunction]] = [:]
|
||||
var calls: [String: [String]] = [:]
|
||||
var calls: Atomic<[String: [String]]> = Atomic([:])
|
||||
|
||||
func accept(_ functionName: String, parameterSummary: String, actionArgs: [Any?]) -> Any? {
|
||||
if !functionBuilders.isEmpty {
|
||||
|
@ -154,7 +174,7 @@ internal class FunctionConsumer: MockFunctionHandler {
|
|||
|
||||
// Record the call so it can be validated later (assuming we are tracking calls)
|
||||
if trackCalls {
|
||||
calls[functionName] = (calls[functionName] ?? []).appending(parameterSummary)
|
||||
calls.mutate { $0[functionName] = ($0[functionName] ?? []).appending(parameterSummary) }
|
||||
}
|
||||
|
||||
for action in expectation.actions {
|
||||
|
|
|
@ -181,12 +181,12 @@ fileprivate func generateCallInfo<M, T, R>(_ actualExpression: Expression<M>, _
|
|||
return
|
||||
}
|
||||
|
||||
allFunctionsCalled = Array(validInstance.functionConsumer.calls.keys)
|
||||
allFunctionsCalled = Array(validInstance.functionConsumer.calls.wrappedValue.keys)
|
||||
|
||||
let builder: MockFunctionBuilder<T, R> = builderCreator(validInstance)
|
||||
validInstance.functionConsumer.trackCalls = false
|
||||
maybeFunction = try? builder.build()
|
||||
desiredFunctionCalls = (validInstance.functionConsumer.calls[maybeFunction?.name ?? ""] ?? [])
|
||||
desiredFunctionCalls = (validInstance.functionConsumer.calls.wrappedValue[maybeFunction?.name ?? ""] ?? [])
|
||||
validInstance.functionConsumer.trackCalls = true
|
||||
}
|
||||
catch {
|
||||
|
@ -205,12 +205,12 @@ fileprivate func generateCallInfo<M, T, R>(_ actualExpression: Expression<M>, _
|
|||
// Just hope for the best and if there is a force-cast there's not much we can do
|
||||
guard let validInstance: M = try? actualExpression.evaluate() else { return CallInfo.error }
|
||||
|
||||
allFunctionsCalled = Array(validInstance.functionConsumer.calls.keys)
|
||||
allFunctionsCalled = Array(validInstance.functionConsumer.calls.wrappedValue.keys)
|
||||
|
||||
let builder: MockExpectationBuilder<T, R> = builderCreator(validInstance)
|
||||
validInstance.functionConsumer.trackCalls = false
|
||||
maybeFunction = try? builder.build()
|
||||
desiredFunctionCalls = (validInstance.functionConsumer.calls[maybeFunction?.name ?? ""] ?? [])
|
||||
desiredFunctionCalls = (validInstance.functionConsumer.calls.wrappedValue[maybeFunction?.name ?? ""] ?? [])
|
||||
validInstance.functionConsumer.trackCalls = true
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in New Issue