Refactored the mocking code to use a better convention which also allows for call validation

Added a Nimble predicate for checking a function on a mock was called
Added the various remove methods to the Storage protocol
Updated the Ed25519Type to be an instance-based protocol (needed for mocking)
This commit is contained in:
Morgan Pretty 2022-03-11 16:57:28 +11:00
parent a39afd6037
commit 31ecd78737
28 changed files with 1073 additions and 719 deletions

View File

@ -57,7 +57,8 @@ abstract_target 'GlobalDependencies' do
inherit! :complete
pod 'Quick'
pod 'Nimble'
# FIXME: change this back to use the latest 'Nimble' once a version newer than 9.2.1 has been released
pod 'Nimble', :git => 'https://github.com/Quick/Nimble', :commit => 'cabe966'
end
end
@ -68,7 +69,8 @@ abstract_target 'GlobalDependencies' do
inherit! :complete
pod 'Quick'
pod 'Nimble'
# FIXME: change this back to use the latest 'Nimble' once a version newer than 9.2.1 has been released
pod 'Nimble', :git => 'https://github.com/Quick/Nimble', :commit => 'cabe966'
end
end
end

View File

@ -24,7 +24,7 @@ PODS:
- Mantle (2.1.0):
- Mantle/extobjc (= 2.1.0)
- Mantle/extobjc (2.1.0)
- Nimble (9.2.1)
- Nimble (9.2.0)
- NVActivityIndicatorView (5.1.1):
- NVActivityIndicatorView/Base (= 5.1.1)
- NVActivityIndicatorView/Base (5.1.1)
@ -126,7 +126,7 @@ DEPENDENCIES:
- CryptoSwift
- Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`)
- Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`)
- Nimble
- Nimble (from `https://github.com/Quick/Nimble`, commit `cabe966`)
- NVActivityIndicatorView
- PromiseKit
- PureLayout (~> 3.1.8)
@ -145,7 +145,6 @@ SPEC REPOS:
- AFNetworking
- CocoaLumberjack
- CryptoSwift
- Nimble
- NVActivityIndicatorView
- OpenSSL-Universal
- PromiseKit
@ -164,6 +163,9 @@ EXTERNAL SOURCES:
Mantle:
:branch: signal-master
:git: https://github.com/signalapp/Mantle
Nimble:
:commit: cabe966
:git: https://github.com/Quick/Nimble
SignalCoreKit:
:branch: session-version
:git: https://github.com/oxen-io/session-ios-core-kit
@ -183,6 +185,9 @@ CHECKOUT OPTIONS:
Mantle:
:commit: e7e46253bb01ce39525d90aa69ed9e85e758bfc4
:git: https://github.com/signalapp/Mantle
Nimble:
:commit: cabe966
:git: https://github.com/Quick/Nimble
SignalCoreKit:
:commit: 4590c2737a2b5dc0ef4ace9f9019b581caccc1de
:git: https://github.com/oxen-io/session-ios-core-kit
@ -202,7 +207,7 @@ SPEC CHECKSUMS:
CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
Nimble: e7e615c0335ee4bf5b0d786685451e62746117d5
Nimble: 0526ae760c851747ff4a682f7646af07a0cc2013
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
PromiseKit: 3b2b6995e51a954c46dbc550ce3da44fbfb563c5
@ -218,6 +223,6 @@ SPEC CHECKSUMS:
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: b95d8bb031996cffdb5d9b9b49bce3b24d6026d7
PODFILE CHECKSUM: cb9862059da2976422ff9c4fa94d406b68581456
COCOAPODS: 1.11.2

View File

@ -801,11 +801,11 @@
FD83B9D227D59495005E1583 /* TestUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* TestUserDefaults.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 /* TestSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* TestSodium.swift */; };
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* TestSign.swift */; };
FD859EF827C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */; };
FD859EFA27C2F5C500510D0C /* TestGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* TestGenericHash.swift */; };
FD859EFC27C2F60700510D0C /* TestEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* TestEd25519.swift */; };
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* MockSodium.swift */; };
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* MockSign.swift */; };
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */; };
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* MockGenericHash.swift */; };
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* MockEd25519.swift */; };
FD859F0027C4691300510D0C /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFF27C4691300510D0C /* MockDataGenerator.swift */; };
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
@ -820,6 +820,15 @@
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; };
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */; };
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */; };
FDC2909E27D85751005DAE71 /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; };
FDC290A027D85826005DAE71 /* TestThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909F27D85826005DAE71 /* TestThread.swift */; };
FDC290A227D85890005DAE71 /* TestInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A127D85890005DAE71 /* TestInteraction.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 */; };
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 */; };
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 */; };
@ -844,12 +853,10 @@
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; };
FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; };
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; };
FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389C27BA01F000C60D73 /* TestStorage.swift */; };
FDC4389D27BA01F000C60D73 /* MockStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389C27BA01F000C60D73 /* MockStorage.swift */; };
FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; };
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; };
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; };
FDC438AC27BB145200C60D73 /* UserDeleteMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438AB27BB145200C60D73 /* UserDeleteMessagesRequest.swift */; };
FDC438AE27BB148700C60D73 /* UserDeleteMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438AD27BB148700C60D73 /* UserDeleteMessagesResponse.swift */; };
FDC438B127BB159600C60D73 /* RequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B027BB159600C60D73 /* RequestInfo.swift */; };
FDC438B327BB15B400C60D73 /* ResponseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B227BB15B400C60D73 /* ResponseInfo.swift */; };
FDC438B527BB15D400C60D73 /* Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B427BB15D400C60D73 /* Destination.swift */; };
@ -863,7 +870,6 @@
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; };
FDC438CF27BCA45400C60D73 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CE27BCA45400C60D73 /* Server.swift */; };
FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -1937,11 +1943,11 @@
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>"; };
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
FD859EF327C2F49200510D0C /* TestSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSodium.swift; sourceTree = "<group>"; };
FD859EF527C2F52C00510D0C /* TestSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSign.swift; sourceTree = "<group>"; };
FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAeadXChaCha20Poly1305Ietf.swift; sourceTree = "<group>"; };
FD859EF927C2F5C500510D0C /* TestGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGenericHash.swift; sourceTree = "<group>"; };
FD859EFB27C2F60700510D0C /* TestEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEd25519.swift; sourceTree = "<group>"; };
FD859EF327C2F49200510D0C /* MockSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSodium.swift; sourceTree = "<group>"; };
FD859EF527C2F52C00510D0C /* MockSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSign.swift; sourceTree = "<group>"; };
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAeadXChaCha20Poly1305Ietf.swift; sourceTree = "<group>"; };
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGenericHash.swift; sourceTree = "<group>"; };
FD859EFB27C2F60700510D0C /* MockEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEd25519.swift; sourceTree = "<group>"; };
FD859EFF27C4691300510D0C /* MockDataGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = "<group>"; };
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
@ -1957,6 +1963,12 @@
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalizationSpec.swift; sourceTree = "<group>"; };
FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGeneratorSpec.swift; sourceTree = "<group>"; };
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocolsSpec.swift; sourceTree = "<group>"; };
FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerSpec.swift; sourceTree = "<group>"; };
FDC2909F27D85826005DAE71 /* TestThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestThread.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@ -1980,12 +1992,10 @@
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = "<group>"; };
FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = "<group>"; };
FDC4389C27BA01F000C60D73 /* TestStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStorage.swift; sourceTree = "<group>"; };
FDC4389C27BA01F000C60D73 /* MockStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStorage.swift; sourceTree = "<group>"; };
FDC438A327BB107F00C60D73 /* UserBanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBanRequest.swift; sourceTree = "<group>"; };
FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUnbanRequest.swift; sourceTree = "<group>"; };
FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModeratorRequest.swift; sourceTree = "<group>"; };
FDC438AB27BB145200C60D73 /* UserDeleteMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDeleteMessagesRequest.swift; sourceTree = "<group>"; };
FDC438AD27BB148700C60D73 /* UserDeleteMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDeleteMessagesResponse.swift; sourceTree = "<group>"; };
FDC438B027BB159600C60D73 /* RequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInfo.swift; sourceTree = "<group>"; };
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseInfo.swift; sourceTree = "<group>"; };
FDC438B427BB15D400C60D73 /* Destination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Destination.swift; sourceTree = "<group>"; };
@ -3876,7 +3886,9 @@
FD83B9BC27CF2215005E1583 /* SharedTest */ = {
isa = PBXGroup;
children = (
FDC290A527D860CE005DAE71 /* Mock.swift */,
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
);
path = SharedTest;
sourceTree = "<group>";
@ -3956,8 +3968,6 @@
FDC438A327BB107F00C60D73 /* UserBanRequest.swift */,
FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */,
FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */,
FDC438AB27BB145200C60D73 /* UserDeleteMessagesRequest.swift */,
FDC438AD27BB148700C60D73 /* UserDeleteMessagesResponse.swift */,
);
path = Models;
sourceTree = "<group>";
@ -4013,6 +4023,7 @@
FD83B9C127CF33EE005E1583 /* Models */,
FDC2909227D710A9005DAE71 /* Types */,
FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */,
FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */,
);
path = "Open Groups";
sourceTree = "<group>";
@ -4021,13 +4032,16 @@
isa = PBXGroup;
children = (
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
FDC4389C27BA01F000C60D73 /* TestStorage.swift */,
FD859EF327C2F49200510D0C /* TestSodium.swift */,
FD859EF527C2F52C00510D0C /* TestSign.swift */,
FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */,
FD859EF927C2F5C500510D0C /* TestGenericHash.swift */,
FD859EFB27C2F60700510D0C /* TestEd25519.swift */,
FDC4389C27BA01F000C60D73 /* MockStorage.swift */,
FD859EF327C2F49200510D0C /* MockSodium.swift */,
FD859EF527C2F52C00510D0C /* MockSign.swift */,
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */,
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
FD859EFB27C2F60700510D0C /* MockEd25519.swift */,
FD83B9D127D59495005E1583 /* TestUserDefaults.swift */,
FDC2909F27D85826005DAE71 /* TestThread.swift */,
FDC290A127D85890005DAE71 /* TestInteraction.swift */,
FDC290AB27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift */,
);
path = _TestUtilities;
sourceTree = "<group>";
@ -5285,7 +5299,6 @@
B8B32021258B1A650020074B /* Contact.swift in Sources */,
C32C5C89256DD0D2003C73A2 /* Storage+Jobs.swift in Sources */,
C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */,
FDC438AC27BB145200C60D73 /* UserDeleteMessagesRequest.swift in Sources */,
7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */,
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */,
FDC4383E27B4708600C60D73 /* Atomic.swift in Sources */,
@ -5393,7 +5406,6 @@
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
FDC438AE27BB148700C60D73 /* UserDeleteMessagesResponse.swift in Sources */,
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */,
B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */,
@ -5611,8 +5623,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FDC290AD27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */,
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5620,7 +5635,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FD859EFA27C2F5C500510D0C /* TestGenericHash.swift in Sources */,
FDC290AC27DB0B1C005DAE71 /* BoxKeyPair+Mocked.swift in Sources */,
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
@ -5629,20 +5645,25 @@
FD83B9C327CF33F7005E1583 /* ServerSpec.swift in Sources */,
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
FD859EF827C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift in Sources */,
FDC290A027D85826005DAE71 /* TestThread.swift in Sources */,
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */,
FD859EFC27C2F60700510D0C /* TestEd25519.swift in Sources */,
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */,
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */,
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */,
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
FDC2909E27D85751005DAE71 /* OpenGroupManagerSpec.swift in Sources */,
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */,
FDC290A227D85890005DAE71 /* TestInteraction.swift in Sources */,
FDC4389D27BA01F000C60D73 /* MockStorage.swift in Sources */,
FD83B9D227D59495005E1583 /* TestUserDefaults.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -58,6 +58,10 @@ extension Storage {
public func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(server, forKey: "SOGS.\(server.name)", inCollection: Storage.openGroupCollection)
}
public func removeOpenGroupServer(name: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "SOGS.\(name)", inCollection: Storage.openGroupCollection)
}

View File

@ -1,10 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
extension OpenGroupAPI {
struct UserDeleteMessagesRequest: Codable {
let rooms: [String]?
let global: Bool?
}
}

View File

@ -1,15 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
extension OpenGroupAPI {
public struct UserDeleteMessagesResponse: Codable, Equatable {
enum CodingKeys: String, CodingKey {
case id
case messagesDeleted = "messages_deleted"
}
let id: String
let messagesDeleted: Int64
}
}

View File

@ -28,7 +28,7 @@ public protocol AeadXChaCha20Poly1305IetfType {
}
public protocol Ed25519Type {
static func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
}
public protocol SignType {
@ -80,4 +80,9 @@ extension Sodium: SodiumType {
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
extension Sign: SignType {}
extension GenericHash: GenericHashType {}
extension Ed25519: Ed25519Type {}
struct Ed25519Wrapper: Ed25519Type {
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
return try Ed25519.verifySignature(signature, publicKey: publicKey, data: data)
}
}

View File

@ -60,17 +60,20 @@ public protocol SessionMessagingKitStorageProtocol {
func getOpenGroup(for threadID: String) -> OpenGroup?
func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any)
func removeOpenGroup(for threadID: String, using transaction: Any)
func getUserCount(forOpenGroupWithID openGroupID: String) -> UInt64?
func setUserCount(to newValue: UInt64, forOpenGroupWithID openGroupID: String, using transaction: Any)
func getOpenGroupServer(name: String) -> OpenGroupAPI.Server?
func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any)
func removeOpenGroupServer(name: String, using transaction: Any)
// MARK: - -- Open Group Public Keys
func getOpenGroupPublicKey(for server: String) -> String?
func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any)
func removeOpenGroupPublicKey(for server: String, using transaction: Any)
// MARK: - -- Open Group Sequence Number
@ -96,6 +99,7 @@ public protocol SessionMessagingKitStorageProtocol {
func getAllMessageRequestThreads(using transaction: YapDatabaseReadTransaction) -> [String: TSContactThread]
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64]
func removeReceivedMessageTimestamps(_ timestamps: Set<UInt64>, using transaction: Any)
func addReceivedMessageTimestamp(_ timestamp: UInt64, using transaction: Any)
/// Returns the ID of the thread.
func getOrCreateThread(for publicKey: String, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String?

View File

@ -44,9 +44,9 @@ public class Dependencies {
set { _genericHash = newValue }
}
private var _ed25519: Ed25519Type.Type?
public var ed25519: Ed25519Type.Type {
get { getValueSettingIfNull(&_ed25519) { Ed25519.self } }
private var _ed25519: Ed25519Type?
public var ed25519: Ed25519Type {
get { getValueSettingIfNull(&_ed25519) { Ed25519Wrapper() } }
set { _ed25519 = newValue }
}
@ -83,7 +83,7 @@ public class Dependencies {
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
genericHash: GenericHashType? = nil,
ed25519: Ed25519Type.Type? = nil,
ed25519: Ed25519Type? = nil,
nonceGenerator16: NonceGenerator16ByteType? = nil,
nonceGenerator24: NonceGenerator24ByteType? = nil,
standardUserDefaults: UserDefaultsType? = nil,
@ -111,7 +111,7 @@ public class Dependencies {
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
genericHash: GenericHashType? = nil,
ed25519: Ed25519Type.Type? = nil,
ed25519: Ed25519Type? = nil,
nonceGenerator16: NonceGenerator16ByteType? = nil,
nonceGenerator24: NonceGenerator24ByteType? = nil,
standardUserDefaults: UserDefaultsType? = nil,

View File

@ -15,7 +15,8 @@ class SOGSMessageSpec: QuickSpec {
var messageJson: String!
var messageData: Data!
var decoder: JSONDecoder!
var testSign: TestSign!
var mockSign: MockSign!
var mockEd25519: MockEd25519!
var dependencies: Dependencies!
beforeEach {
@ -29,19 +30,24 @@ class SOGSMessageSpec: QuickSpec {
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdERhdGE="
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
testSign = TestSign()
mockSign = MockSign()
mockEd25519 = MockEd25519()
dependencies = Dependencies(
sign: testSign,
ed25519: TestEd25519.self
sign: mockSign,
ed25519: mockEd25519
)
decoder = JSONDecoder()
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
}
afterEach {
mockSign = nil
}
context("when decoding") {
it("defaults the whisper values to false") {
messageJson = """
@ -91,7 +97,7 @@ class SOGSMessageSpec: QuickSpec {
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdERhdGE="
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
@ -113,7 +119,7 @@ class SOGSMessageSpec: QuickSpec {
"whisper_mods": false,
"data": "Test!!!",
"signature": "VGVzdERhdGE="
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
@ -166,7 +172,7 @@ class SOGSMessageSpec: QuickSpec {
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdERhdGE="
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
@ -190,14 +196,14 @@ class SOGSMessageSpec: QuickSpec {
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdERhdGE="
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
}
it("succeeds if it succeeds verification") {
testSign.mockData[.verify] = true
mockSign.when { $0.verify(message: any(), publicKey: any(), signature: any()) }.thenReturn(true)
expect {
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
@ -205,8 +211,23 @@ class SOGSMessageSpec: QuickSpec {
.toNot(beNil())
}
it("provides the correct values as parameters") {
mockSign.when { $0.verify(message: any(), publicKey: any(), signature: any()) }.thenReturn(true)
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
expect(mockSign)
.to(call(matchingParameters: true) {
$0.verify(
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
publicKey: Data(hex: TestConstants.publicKey).bytes,
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
)
})
}
it("throws if it fails verification") {
testSign.mockData[.verify] = false
mockSign.when { $0.verify(message: any(), publicKey: any(), signature: any()) }.thenReturn(false)
expect {
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
@ -217,13 +238,7 @@ class SOGSMessageSpec: QuickSpec {
context("that is unblinded") {
it("succeeds if it succeeds verification") {
TestEd25519.mockData[
.verifySignature(
signature: Data(base64Encoded: "VGVzdERhdGE=")!,
publicKey: Data(hex: TestConstants.publicKey),
data: Data(base64Encoded: "VGVzdERhdGE=")!
)
] = true
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
expect {
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
@ -231,14 +246,23 @@ class SOGSMessageSpec: QuickSpec {
.toNot(beNil())
}
it("provides the correct values as parameters") {
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
expect(mockEd25519)
.to(call(matchingParameters: true) {
try $0.verifySignature(
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
publicKey: Data(hex: TestConstants.publicKey),
data: Data(base64Encoded: "VGVzdERhdGE=")!
)
})
}
it("throws if it fails verification") {
TestEd25519.mockData[
.verifySignature(
signature: Data(base64Encoded: "VGVzdERhdGE=")!,
publicKey: Data(hex: TestConstants.publicKey),
data: Data(base64Encoded: "VGVzdERhdGE=")!
)
] = false
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(false)
expect {
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)

View File

@ -78,11 +78,11 @@ class OpenGroupAPISpec: QuickSpec {
// MARK: - Spec
override func spec() {
var testStorage: TestStorage!
var testSodium: TestSodium!
var testAeadXChaCha20Poly1305Ietf: TestAeadXChaCha20Poly1305Ietf!
var testGenericHash: TestGenericHash!
var testSign: TestSign!
var mockStorage: MockStorage!
var mockSodium: MockSodium!
var mockAeadXChaCha20Poly1305Ietf: MockAeadXChaCha20Poly1305Ietf!
var mockGenericHash: MockGenericHash!
var mockSign: MockSign!
var testUserDefaults: TestUserDefaults!
var dependencies: Dependencies!
@ -94,64 +94,110 @@ class OpenGroupAPISpec: QuickSpec {
// MARK: - Configuration
beforeEach {
testStorage = TestStorage()
testSodium = TestSodium()
testAeadXChaCha20Poly1305Ietf = TestAeadXChaCha20Poly1305Ietf()
testGenericHash = TestGenericHash()
testSign = TestSign()
mockStorage = MockStorage()
mockSodium = MockSodium()
mockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
mockGenericHash = MockGenericHash()
mockSign = MockSign()
testUserDefaults = TestUserDefaults()
dependencies = Dependencies(
api: TestApi.self,
storage: testStorage,
sodium: testSodium,
aeadXChaCha20Poly1305Ietf: testAeadXChaCha20Poly1305Ietf,
sign: testSign,
genericHash: testGenericHash,
ed25519: TestEd25519.self,
storage: mockStorage,
sodium: mockSodium,
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha20Poly1305Ietf,
sign: mockSign,
genericHash: mockGenericHash,
ed25519: MockEd25519(),
nonceGenerator16: TestNonce16Generator(),
nonceGenerator24: TestNonce24Generator(),
standardUserDefaults: testUserDefaults,
date: Date(timeIntervalSince1970: 1234567890)
)
testStorage.mockData[.allOpenGroups] = [
"0": OpenGroup(
server: "testServer",
room: "testRoom",
publicKey: TestConstants.publicKey,
name: "Test",
groupDescription: nil,
imageID: nil,
infoUpdates: 0
mockStorage
.when { $0.write(with: { _ in }) }
.then { args in (args.first as? ((Any) -> Void))?(any()) }
.thenReturn(Promise.value(()))
mockStorage
.when { $0.write(with: { _ in }, completion: { }) }
.then { args in
(args.first as? ((Any) -> Void))?(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)!
)
)
]
testStorage.mockData[.openGroupPublicKeys] = [
"testServer": TestConstants.publicKey
]
testStorage.mockData[.userKeyPair] = try! ECKeyPair(
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
)
testStorage.mockData[.userEdKeyPair] = Box.KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
)
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.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
)
mockStorage
.when { $0.getOpenGroupPublicKey(for: any()) }
.thenReturn(TestConstants.publicKey)
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
mockStorage.when { $0.addReceivedMessageTimestamp(any(), using: any()) }.thenReturn(())
testGenericHash.mockData[.hashOutputLength] = []
testSodium.mockData[.blindedKeyPair] = Box.KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
)
testSodium.mockData[.sogsSignature] = "TestSogsSignature".bytes
testSign.mockData[.signature] = "TestSignature".bytes
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 {
testStorage = nil
testSodium = nil
testAeadXChaCha20Poly1305Ietf = nil
testGenericHash = nil
testSign = nil
mockStorage = nil
mockSodium = nil
mockAeadXChaCha20Poly1305Ietf = nil
mockGenericHash = nil
mockSign = nil
testUserDefaults = nil
dependencies = nil
@ -280,7 +326,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago") {
testStorage.mockData[.openGroupSequenceNumber] = ["testServer.testRoom": Int64(123)]
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(123)
OpenGroupAPI
.poll(
@ -303,7 +349,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") {
testStorage.mockData[.openGroupSequenceNumber] = ["testServer.testRoom": Int64(123)]
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(123)
OpenGroupAPI
.poll(
@ -326,7 +372,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("retrieves recent messages if there was a last message and there has already been a poll this session") {
testStorage.mockData[.openGroupSequenceNumber] = ["testServer.testRoom": Int64(123)]
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(123)
OpenGroupAPI
.poll(
@ -370,7 +416,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("retrieves inbox messages since the last message if there was one") {
testStorage.mockData[.openGroupInboxLatestMessageId] = ["testServer": Int64(124)]
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(124)
OpenGroupAPI
.poll(
@ -414,7 +460,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("retrieves outbox messages since the last message if there was one") {
testStorage.mockData[.openGroupOutboxLatestMessageId] = ["testServer": Int64(125)]
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(125)
OpenGroupAPI
.poll(
@ -439,7 +485,7 @@ class OpenGroupAPISpec: QuickSpec {
context("and given an invalid response") {
it("does not update the poll state") {
testStorage.mockData[.openGroupSequenceNumber] = ["testServer.testRoom": Int64(123)]
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(123)
OpenGroupAPI.poll("testServer", hasPerformedInitialPoll: false, timeSinceLastPoll: 0, using: dependencies)
.get { result in pollResponse = result }
@ -1028,7 +1074,7 @@ class OpenGroupAPISpec: QuickSpec {
messageData = LocalTestApi.data
dependencies = dependencies.with(api: LocalTestApi.self)
testStorage.mockData[.userEdKeyPair] = Box.KeyPair(publicKey: [], secretKey: [])
mockStorage.when { $0.getUserED25519KeyPair() }.thenReturn(Box.KeyPair(publicKey: [], secretKey: []))
}
afterEach {
@ -1092,15 +1138,22 @@ class OpenGroupAPISpec: QuickSpec {
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
expect(testStorage.mockData[.receivedMessageTimestamp] as? UInt64).to(equal(321000))
expect(mockStorage)
.to(call(matchingParameters: true) {
$0.addReceivedMessageTimestamp(321000, using: any())
})
}
context("when unblinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
)
}
it("signs the message correctly") {
@ -1136,7 +1189,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails to sign if there is no public key") {
testStorage.mockData[.openGroupPublicKeys] = [:]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn(nil)
var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)?
@ -1166,10 +1219,14 @@ class OpenGroupAPISpec: QuickSpec {
context("when blinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
)
}
it("signs the message correctly") {
@ -1205,7 +1262,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails to sign if there is no public key") {
testStorage.mockData[.openGroupPublicKeys] = [:]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn(nil)
var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)?
@ -1286,7 +1343,7 @@ class OpenGroupAPISpec: QuickSpec {
}
dependencies = dependencies.with(api: LocalTestApi.self)
testStorage.mockData[.userEdKeyPair] = Box.KeyPair(publicKey: [], secretKey: [])
mockStorage.when { $0.getUserED25519KeyPair() }.thenReturn(Box.KeyPair(publicKey: [], secretKey: []))
}
it("correctly sends the update") {
@ -1321,10 +1378,14 @@ class OpenGroupAPISpec: QuickSpec {
context("when unblinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
)
}
it("signs the message correctly") {
@ -1359,7 +1420,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails to sign if there is no public key") {
testStorage.mockData[.openGroupPublicKeys] = [:]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn(nil)
var response: (info: OnionRequestResponseInfoType, data: Data?)?
@ -1388,10 +1449,14 @@ class OpenGroupAPISpec: QuickSpec {
context("when blinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
)
}
it("signs the message correctly") {
@ -1426,7 +1491,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails to sign if there is no public key") {
testStorage.mockData[.openGroupPublicKeys] = [:]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn(nil)
var response: (info: OnionRequestResponseInfoType, data: Data?)?
@ -1483,6 +1548,47 @@ class OpenGroupAPISpec: QuickSpec {
}
}
context("when deleting all messages for a user") {
var response: (info: OnionRequestResponseInfoType, data: Data?)?
beforeEach {
class LocalTestApi: TestApi {
override class var mockResponse: Data? { return Data() }
}
dependencies = dependencies.with(api: LocalTestApi.self)
}
afterEach {
response = nil
}
it("generates the request and handles the response correctly") {
OpenGroupAPI
.messagesDeleteAll(
"testUserId",
in: "testRoom",
on: "testServer",
using: dependencies
)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate request data
let requestData: TestApi.RequestData? = (response?.info as? TestResponseInfo)?.requestData
expect(requestData?.httpMethod).to(equal("DELETE"))
expect(requestData?.server).to(equal("testServer"))
expect(requestData?.urlString).to(equal("testServer/room/testRoom/all/testUserId"))
}
}
// MARK: - Pinning
context("when pinning a message") {
@ -1759,7 +1865,10 @@ class OpenGroupAPISpec: QuickSpec {
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
expect(testStorage.mockData[.receivedMessageTimestamp] as? UInt64).to(equal(321000))
expect(mockStorage)
.to(call(matchingParameters: true) {
$0.addReceivedMessageTimestamp(321000, using: any())
})
}
}
@ -2086,121 +2195,11 @@ class OpenGroupAPISpec: QuickSpec {
}
}
context("when deleting a users messages") {
var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.UserDeleteMessagesResponse)?
var messageData: OpenGroupAPI.UserDeleteMessagesResponse!
beforeEach {
class LocalTestApi: TestApi {
static let data: OpenGroupAPI.UserDeleteMessagesResponse = OpenGroupAPI.UserDeleteMessagesResponse(
id: "testId",
messagesDeleted: 10
)
override class var mockResponse: Data? { return try! JSONEncoder().encode(data) }
}
messageData = LocalTestApi.data
dependencies = dependencies.with(api: LocalTestApi.self)
}
afterEach {
response = nil
}
it("generates the request and handles the response correctly") {
OpenGroupAPI
.userDeleteMessages(
"testUserId",
from: nil,
on: "testServer",
using: dependencies
)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate the response data
expect(response?.data).to(equal(messageData))
// Validate request data
let requestData: TestApi.RequestData? = (response?.info as? TestResponseInfo)?.requestData
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.server).to(equal("testServer"))
expect(requestData?.urlString).to(equal("testServer/user/testUserId/deleteMessages"))
}
it("does a global delete if no room tokens are provided") {
OpenGroupAPI
.userDeleteMessages(
"testUserId",
from: nil,
on: "testServer",
using: dependencies
)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate request data
let requestData: TestApi.RequestData? = (response?.info as? TestResponseInfo)?.requestData
let requestBody: OpenGroupAPI.UserDeleteMessagesRequest = try! JSONDecoder().decode(OpenGroupAPI.UserDeleteMessagesRequest.self, from: requestData!.body!)
expect(requestBody.global).to(beTrue())
expect(requestBody.rooms).to(beNil())
}
it("does room specific bans if room tokens are provided") {
OpenGroupAPI
.userDeleteMessages(
"testUserId",
from: ["testRoom"],
on: "testServer",
using: dependencies
)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate request data
let requestData: TestApi.RequestData? = (response?.info as? TestResponseInfo)?.requestData
let requestBody: OpenGroupAPI.UserDeleteMessagesRequest = try! JSONDecoder().decode(OpenGroupAPI.UserDeleteMessagesRequest.self, from: requestData!.body!)
expect(requestBody.global).to(beNil())
expect(requestBody.rooms).to(equal(["testRoom"]))
}
}
context("when banning and deleting all messages for a user") {
var response: [OnionRequestResponseInfoType]?
beforeEach {
class LocalTestApi: TestApi {
static let deleteMessagesData: OpenGroupAPI.UserDeleteMessagesResponse = OpenGroupAPI.UserDeleteMessagesResponse(
id: "123",
messagesDeleted: 10
)
override class var mockResponse: Data? {
let responses: [Data] = [
try! JSONEncoder().encode(
@ -2212,10 +2211,10 @@ class OpenGroupAPISpec: QuickSpec {
)
),
try! JSONEncoder().encode(
OpenGroupAPI.BatchSubResponse(
OpenGroupAPI.BatchSubResponse<NoResponse>(
code: 200,
headers: [:],
body: deleteMessagesData,
body: nil,
failedToParseBody: false
)
)
@ -2233,9 +2232,9 @@ class OpenGroupAPISpec: QuickSpec {
it("generates the request and handles the response correctly") {
OpenGroupAPI
.userBanAndDeleteAllMessage(
.userBanAndDeleteAllMessages(
"testUserId",
from: nil,
in: "testRoom",
on: "testServer",
using: dependencies
)
@ -2257,11 +2256,11 @@ class OpenGroupAPISpec: QuickSpec {
expect(requestData?.urlString).to(equal("testServer/sequence"))
}
it("does a global ban and delete if no room tokens are provided") {
it("bans the user from the specified room rather than globally") {
OpenGroupAPI
.userBanAndDeleteAllMessage(
.userBanAndDeleteAllMessages(
"testUserId",
from: nil,
in: "testRoom",
on: "testServer",
using: dependencies
)
@ -2282,59 +2281,13 @@ class OpenGroupAPISpec: QuickSpec {
with: requestData!.body!,
options: [.fragmentsAllowed]
)
let anyArray: [Any] = jsonObject as! [Any]
let dataArray: [Data] = anyArray.compactMap {
try! JSONSerialization.data(withJSONObject: ($0 as! [String: Any])["json"]!)
}
let firstJsonObject: Any = ((jsonObject as! [Any]).first as! [String: Any])["json"]!
let firstJsonData: Data = try! JSONSerialization.data(withJSONObject: firstJsonObject)
let firstRequestBody: OpenGroupAPI.UserBanRequest = try! JSONDecoder()
.decode(OpenGroupAPI.UserBanRequest.self, from: dataArray.first!)
let lastRequestBody: OpenGroupAPI.UserDeleteMessagesRequest = try! JSONDecoder()
.decode(OpenGroupAPI.UserDeleteMessagesRequest.self, from: dataArray.last!)
expect(firstRequestBody.global).to(beTrue())
expect(firstRequestBody.rooms).to(beNil())
expect(lastRequestBody.global).to(beTrue())
expect(lastRequestBody.rooms).to(beNil())
}
it("does room specific bans and deletes if room tokens are provided") {
OpenGroupAPI
.userBanAndDeleteAllMessage(
"testUserId",
from: ["testRoom"],
on: "testServer",
using: dependencies
)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate request data
let requestData: TestApi.RequestData? = (response?.first as? TestResponseInfo)?.requestData
let jsonObject: Any = try! JSONSerialization.jsonObject(
with: requestData!.body!,
options: [.fragmentsAllowed]
)
let anyArray: [Any] = jsonObject as! [Any]
let dataArray: [Data] = anyArray.compactMap {
try! JSONSerialization.data(withJSONObject: ($0 as! [String: Any])["json"]!)
}
let firstRequestBody: OpenGroupAPI.UserBanRequest = try! JSONDecoder()
.decode(OpenGroupAPI.UserBanRequest.self, from: dataArray.first!)
let lastRequestBody: OpenGroupAPI.UserDeleteMessagesRequest = try! JSONDecoder()
.decode(OpenGroupAPI.UserDeleteMessagesRequest.self, from: dataArray.last!)
.decode(OpenGroupAPI.UserBanRequest.self, from: firstJsonData)
expect(firstRequestBody.global).to(beNil())
expect(firstRequestBody.rooms).to(equal(["testRoom"]))
expect(lastRequestBody.global).to(beNil())
expect(lastRequestBody.rooms).to(equal(["testRoom"]))
}
}
@ -2352,7 +2305,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when there is no userEdKeyPair") {
testStorage.mockData[.userEdKeyPair] = nil
mockStorage.when { $0.getUserED25519KeyPair() }.thenReturn(nil)
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
@ -2369,7 +2322,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when there is no serverPublicKey") {
testStorage.mockData[.openGroupPublicKeys] = [:]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn(nil)
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
@ -2386,7 +2339,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when the serverPublicKey is not a hex string") {
testStorage.mockData[.openGroupPublicKeys] = ["testServer": "TestString!!!"]
mockStorage.when { $0.getOpenGroupPublicKey(for: any()) }.thenReturn("TestString!!!")
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
@ -2404,10 +2357,14 @@ class OpenGroupAPISpec: QuickSpec {
context("when unblinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
)
}
it("signs correctly") {
@ -2438,7 +2395,7 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when the signature is not generated") {
testSign.mockData[.signature] = nil
mockSign.when { $0.signature(message: any(), secretKey: any()) }.thenReturn(nil)
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
@ -2457,10 +2414,14 @@ class OpenGroupAPISpec: QuickSpec {
context("when blinded") {
beforeEach {
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
mockStorage
.when { $0.getOpenGroupServer(name: any()) }
.thenReturn(
OpenGroupAPI.Server(
name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blind], missing: [])
)
)
}
it("signs correctly") {
@ -2490,7 +2451,9 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when the blindedKeyPair is not generated") {
testSodium.mockData[.blindedKeyPair] = nil
mockSodium
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
.thenReturn(nil)
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
@ -2507,7 +2470,9 @@ class OpenGroupAPISpec: QuickSpec {
}
it("fails when the sogsSignature is not generated") {
testSodium.mockData[.sogsSignature] = nil
mockSodium
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
.thenReturn(nil)
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
.get { result in response = result }

View File

@ -0,0 +1,3 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation

View File

@ -15,15 +15,24 @@ class SodiumProtocolsSpec: QuickSpec {
let testValue: [UInt8] = [1, 2, 3]
it("provides the default values in it's extensions") {
let testAead: TestAeadXChaCha20Poly1305Ietf = TestAeadXChaCha20Poly1305Ietf()
testAead.mockData[.encrypt] = testValue
testAead.mockData[.decrypt] = testValue
let mockAead: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
mockAead
.when { $0.encrypt(message: any(), secretKey: any(), nonce: any(), additionalData: any()) }
.thenReturn(testValue)
mockAead
.when { $0.decrypt(authenticatedCipherText: any(), secretKey: any(), nonce: any(), additionalData: any()) }
.thenReturn(testValue)
expect(testAead.encrypt(message: [], secretKey: [], nonce: [])).to(equal(testValue))
expect(testAead.encrypt(message: [], secretKey: [], nonce: [], additionalData: nil)).to(equal(testValue))
expect(testAead.decrypt(authenticatedCipherText: [], secretKey: [], nonce: [])).to(equal(testValue))
expect(testAead.decrypt(authenticatedCipherText: [], secretKey: [], nonce: [], additionalData: nil))
.to(equal(testValue))
_ = mockAead.encrypt(message: [], secretKey: [], nonce: [])
_ = mockAead.decrypt(authenticatedCipherText: [], secretKey: [], nonce: [])
expect(mockAead)
.to(call { $0.encrypt(message: any(), secretKey: any(), nonce: any(), additionalData: any()) })
expect(mockAead)
.to(call {
$0.decrypt(authenticatedCipherText: any(), secretKey: any(), nonce: any(), additionalData: any())
})
}
}
@ -31,16 +40,21 @@ class SodiumProtocolsSpec: QuickSpec {
let testValue: [UInt8] = [1, 2, 3]
it("provides the default values in it's extensions") {
let testGenericHash: TestGenericHash = TestGenericHash()
testGenericHash.mockData[.hash] = testValue
testGenericHash.mockData[.hashSaltPersonal] = testValue
let mockGenericHash: MockGenericHash = MockGenericHash()
mockGenericHash.when { $0.hash(message: any(), key: any()) }.thenReturn(testValue)
mockGenericHash
.when { $0.hashSaltPersonal(message: any(), outputLength: any(), key: any(), salt: any(), personal: any()) }
.thenReturn(testValue)
expect(testGenericHash.hash(message: [])).to(equal(testValue))
expect(testGenericHash.hash(message: [], key: nil)).to(equal(testValue))
expect(testGenericHash.hashSaltPersonal(message: [], outputLength: 0, salt: [], personal: []))
.to(equal(testValue))
expect(testGenericHash.hashSaltPersonal(message: [], outputLength: 0, key: nil, salt: [], personal: []))
.to(equal(testValue))
_ = mockGenericHash.hash(message: [])
_ = mockGenericHash.hashSaltPersonal(message: [], outputLength: 0, salt: [], personal: [])
expect(mockGenericHash)
.to(call { $0.hash(message: any(), key: any()) })
expect(mockGenericHash)
.to(call {
$0.hashSaltPersonal(message: any(), outputLength: any(), key: any(), salt: any(), personal: any())
})
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Sodium
extension Box.KeyPair: Mocked {
static var mockValue: Box.KeyPair = Box.KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
)
}

View File

@ -6,28 +6,15 @@ import Sodium
@testable import SessionMessagingKit
class TestAeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType, Mockable {
class MockAeadXChaCha20Poly1305Ietf: Mock<AeadXChaCha20Poly1305IetfType>, AeadXChaCha20Poly1305IetfType {
var KeyBytes: Int = 32
var ABytes: Int = 16
// MARK: - Mockable
enum DataKey: Hashable {
case encrypt
case decrypt
}
typealias Key = DataKey
var mockData: [DataKey: Any] = [:]
// MARK: - SignType
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
return (mockData[.encrypt] as? Bytes)
return accept(args: [message, secretKey, nonce, additionalData]) as? Bytes
}
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
return (mockData[.decrypt] as? Bytes)
return accept(args: [authenticatedCipherText, secretKey, nonce, additionalData]) as? Bytes
}
}

View File

@ -0,0 +1,13 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class MockEd25519: Mock<Ed25519Type>, Ed25519Type {
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
return accept(args: [signature, publicKey, data]) as! Bool
}
}

View File

@ -0,0 +1,21 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class MockGenericHash: Mock<GenericHashType>, GenericHashType {
func hash(message: Bytes, key: Bytes?) -> Bytes? {
return accept(args: [message, key]) as? Bytes
}
func hash(message: Bytes, outputLength: Int) -> Bytes? {
return accept(args: [message, outputLength]) as? Bytes
}
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
return accept(args: [message, outputLength, key, salt, personal]) as? Bytes
}
}

View File

@ -6,32 +6,18 @@ import Sodium
@testable import SessionMessagingKit
class TestSign: SignType, Mockable {
class MockSign: Mock<SignType>, SignType {
var PublicKeyBytes: Int = 32
// MARK: - Mockable
enum DataKey: Hashable {
case signature
case verify
case toX25519
}
typealias Key = DataKey
var mockData: [DataKey: Any] = [:]
// MARK: - SignType
func signature(message: Bytes, secretKey: Bytes) -> Bytes? {
return (mockData[.signature] as? Bytes)
return accept(args: [message, secretKey]) as? Bytes
}
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool {
return (mockData[.verify] as! Bool)
return accept(args: [message, publicKey, signature]) as! Bool
}
func toX25519(ed25519PublicKey: Bytes) -> Bytes? {
return (mockData[.toX25519] as? Bytes)
return accept(args: [ed25519PublicKey]) as? Bytes
}
}

View File

@ -0,0 +1,37 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class MockSodium: Mock<SodiumType>, SodiumType {
func getGenericHash() -> GenericHashType { return accept() as! GenericHashType }
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return accept() as! AeadXChaCha20Poly1305IetfType }
func getSign() -> SignType { return accept() as! SignType }
func generateBlindingFactor(serverPublicKey: String) -> Bytes? {
return accept(args: [serverPublicKey]) as? Bytes
}
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
return accept(args: [serverPublicKey, edKeyPair, genericHash]) as? Box.KeyPair
}
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
return accept(args: [message, secretKey, ka, kA]) as? Bytes
}
func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? {
return accept(args: [lhsKeyBytes, rhsKeyBytes]) as? Bytes
}
func sharedBlindedEncryptionKey(secretKey a: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
return accept(args: [a, otherBlindedPublicKey, kA, kB, genericHash]) as? Bytes
}
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String) -> Bool {
return accept(args: [sessionId, blindedSessionId, serverPublicKey]) as! Bool
}
}

View File

@ -0,0 +1,179 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class MockStorage: Mock<SessionMessagingKitStorageProtocol>, SessionMessagingKitStorageProtocol {
// MARK: - Shared
@discardableResult func write(with block: @escaping (Any) -> Void) -> Promise<Void> {
return accept(args: [block]) as! Promise<Void>
}
@discardableResult func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise<Void> {
return accept(args: [block, completion]) as! Promise<Void>
}
func writeSync(with block: @escaping (Any) -> Void) {
accept(args: [block])
}
// MARK: - General
func getUserPublicKey() -> String? { return accept() as? String }
func getUserKeyPair() -> ECKeyPair? { return accept() as? ECKeyPair }
func getUserED25519KeyPair() -> Box.KeyPair? { return accept() as? Box.KeyPair }
func getUser() -> Contact? { return accept() as? Contact }
func getAllContacts() -> Set<Contact> { return accept() as! Set<Contact> }
func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact> { return accept() as! Set<Contact> }
// MARK: - Blinded Id cache
func getBlindedIdMapping(with blindedId: String) -> BlindedIdMapping? {
return accept(args: [blindedId]) as? BlindedIdMapping
}
func getBlindedIdMapping(with blindedId: String, using transaction: YapDatabaseReadTransaction) -> BlindedIdMapping? {
return accept(args: [blindedId, transaction]) as? BlindedIdMapping
}
func cacheBlindedIdMapping(_ mapping: BlindedIdMapping) { accept(args: [mapping]) }
func cacheBlindedIdMapping(_ mapping: BlindedIdMapping, using transaction: YapDatabaseReadWriteTransaction) {
accept(args: [mapping, transaction])
}
func enumerateBlindedIdMapping(with block: @escaping (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> ()) {
accept(args: [block])
}
func enumerateBlindedIdMapping(using transaction: YapDatabaseReadTransaction, with block: @escaping (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> ()) {
accept(args: [transaction, block])
}
// MARK: - Closed Groups
func getUserClosedGroupPublicKeys() -> Set<String> { return accept() as! Set<String> }
func getZombieMembers(for groupPublicKey: String) -> Set<String> { return accept() as! Set<String> }
func setZombieMembers(for groupPublicKey: String, to zombies: Set<String>, using transaction: Any) {
accept(args: [groupPublicKey, zombies, transaction])
}
func isClosedGroup(_ publicKey: String) -> Bool { return accept() as! Bool }
// MARK: - Jobs
func persist(_ job: Job, using transaction: Any) { accept(args: [job, transaction]) }
func markJobAsSucceeded(_ job: Job, using transaction: Any) { accept(args: [job, transaction]) }
func markJobAsFailed(_ job: Job, using transaction: Any) { accept(args: [job, transaction]) }
func getAllPendingJobs(of type: Job.Type) -> [Job] {
return accept(args: [type]) as! [Job]
}
func getAttachmentUploadJob(for attachmentID: String) -> AttachmentUploadJob? {
return accept(args: [attachmentID]) as? AttachmentUploadJob
}
func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob? {
return accept(args: [messageSendJobID]) as? MessageSendJob
}
func getMessageSendJob(for messageSendJobID: String, using transaction: Any) -> MessageSendJob? {
return accept(args: [messageSendJobID, transaction]) as? MessageSendJob
}
func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) { accept(args: [messageSendJobID]) }
func isJobCanceled(_ job: Job) -> Bool {
return accept(args: [job]) as! Bool
}
// MARK: - Open Groups
func getAllOpenGroups() -> [String: OpenGroup] { return accept() as! [String: OpenGroup] }
func getThreadID(for v2OpenGroupID: String) -> String? { return accept(args: [v2OpenGroupID]) as? String }
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {
accept(args: [messageIDs, transaction])
}
func getOpenGroupImage(for room: String, on server: String) -> Data? { return accept(args: [room, server]) as? Data }
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {
accept(args: [data, room, server, transaction])
}
func getOpenGroup(for threadID: String) -> OpenGroup? { return accept(args: [threadID]) as? OpenGroup }
func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) {
accept(args: [openGroup, threadID, transaction])
}
func removeOpenGroup(for threadID: String, using transaction: Any) { accept(args: [threadID, transaction]) }
func getOpenGroupServer(name: String) -> OpenGroupAPI.Server? { return accept(args: [name]) as? OpenGroupAPI.Server }
func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { accept(args: [server, transaction]) }
func removeOpenGroupServer(name: String, using transaction: Any) {
accept(args: [name, transaction])
}
func getUserCount(forOpenGroupWithID openGroupID: String) -> UInt64? { return accept(args: [openGroupID]) as? UInt64 }
func setUserCount(to newValue: UInt64, forOpenGroupWithID openGroupID: String, using transaction: Any) {
accept(args: [newValue, openGroupID, transaction])
}
func getOpenGroupSequenceNumber(for room: String, on server: String) -> Int64? {
return accept(args: [room, server]) as? Int64
}
func setOpenGroupSequenceNumber(for room: String, on server: String, to newValue: Int64, using transaction: Any) {
accept(args: [room, server, newValue, transaction])
}
func removeOpenGroupSequenceNumber(for room: String, on server: String, using transaction: Any) {
accept(args: [room, server, transaction])
}
func getOpenGroupInboxLatestMessageId(for server: String) -> Int64? { return accept(args: [server]) as? Int64 }
func setOpenGroupInboxLatestMessageId(for server: String, to newValue: Int64, using transaction: Any) {
accept(args: [server, newValue, transaction])
}
func removeOpenGroupInboxLatestMessageId(for server: String, using transaction: Any) { accept(args: [server, transaction]) }
func getOpenGroupOutboxLatestMessageId(for server: String) -> Int64? { return accept(args: [server]) as? Int64 }
func setOpenGroupOutboxLatestMessageId(for server: String, to newValue: Int64, using transaction: Any) {
accept(args: [server, newValue, transaction])
}
func removeOpenGroupOutboxLatestMessageId(for server: String, using transaction: Any) {
accept(args: [server, transaction])
}
// MARK: - Open Group Public Keys
func getOpenGroupPublicKey(for server: String) -> String? { return accept(args: [server]) as? String }
func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any) {
accept(args: [server, newValue, transaction])
}
func removeOpenGroupPublicKey(for server: String, using transaction: Any) { accept(args: [server, transaction]) }
// MARK: - Message Handling
func getAllMessageRequestThreads() -> [String: TSContactThread] { return accept() as! [String: TSContactThread] }
func getAllMessageRequestThreads(using transaction: YapDatabaseReadTransaction) -> [String: TSContactThread] {
return accept(args: [transaction]) as! [String: TSContactThread]
}
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64] {
return accept(args: [transaction]) as! [UInt64]
}
func removeReceivedMessageTimestamps(_ timestamps: Set<UInt64>, using transaction: Any) {
accept(args: [timestamps, transaction])
}
func addReceivedMessageTimestamp(_ timestamp: UInt64, using transaction: Any) {
accept(args: [timestamp, transaction])
}
func getOrCreateThread(for publicKey: String, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String? {
return accept(args: [publicKey, groupPublicKey, openGroupID, transaction]) as? String
}
func persist(_ message: VisibleMessage, quotedMessage: TSQuotedMessage?, linkPreview: OWSLinkPreview?, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String? {
return accept(args: [message, quotedMessage, linkPreview, groupPublicKey, openGroupID, transaction]) as? String
}
func persist(_ attachments: [VisibleMessage.Attachment], using transaction: Any) -> [String] {
return accept(args: [attachments, transaction]) as! [String]
}
func setAttachmentState(to state: TSAttachmentPointerState, for pointer: TSAttachmentPointer, associatedWith tsIncomingMessageID: String, using transaction: Any) {
accept(args: [state, pointer, tsIncomingMessageID, transaction])
}
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any) {
accept(args: [stream, tsIncomingMessageID, transaction])
}
}

View File

@ -1,25 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class TestEd25519: Ed25519Type, StaticMockable {
// MARK: - Mockable
enum DataKey: Hashable {
case verifySignature(signature: Data, publicKey: Data, data: Data) // TODO: Test the uniqueness of this
}
typealias Key = DataKey
static var mockData: [DataKey: Any] = [:]
// MARK: - SignType
static func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
return (mockData[.verifySignature(signature: signature, publicKey: publicKey, data: data)] as! Bool)
}
}

View File

@ -1,35 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class TestGenericHash: GenericHashType, Mockable {
// MARK: - Mockable
enum DataKey: Hashable {
case hash
case hashOutputLength
case hashSaltPersonal
}
typealias Key = DataKey
var mockData: [DataKey: Any] = [:]
// MARK: - SignType
func hash(message: Bytes, key: Bytes?) -> Bytes? {
return (mockData[.hash] as? Bytes)
}
func hash(message: Bytes, outputLength: Int) -> Bytes? {
return (mockData[.hashOutputLength] as? Bytes)
}
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
return (mockData[.hashSaltPersonal] as? Bytes)
}
}

View File

@ -0,0 +1,3 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation

View File

@ -1,59 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class TestSodium: SodiumType, Mockable {
// MARK: - Mockable
enum DataKey: Hashable {
case genericHash
case aeadXChaCha20Poly1305Ietf
case sign
case blindingFactor
case blindedKeyPair
case sogsSignature
case combinedKeys
case sharedBlindedEncryptionKey
case sessionIdMatches
}
typealias Key = DataKey
var mockData: [DataKey: Any] = [:]
// MARK: - SodiumType
func getGenericHash() -> GenericHashType { return (mockData[.genericHash] as! GenericHashType) }
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType {
return (mockData[.aeadXChaCha20Poly1305Ietf] as! AeadXChaCha20Poly1305IetfType)
}
func getSign() -> SignType { return (mockData[.sign] as! SignType) }
func generateBlindingFactor(serverPublicKey: String) -> Bytes? { return (mockData[.blindingFactor] as? Bytes) }
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
return (mockData[.blindedKeyPair] as? Box.KeyPair)
}
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
return (mockData[.sogsSignature] as? Bytes)
}
func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? {
return (mockData[.combinedKeys] as? Bytes)
}
func sharedBlindedEncryptionKey(secretKey a: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
return (mockData[.sharedBlindedEncryptionKey] as? Bytes)
}
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String) -> Bool {
return ((mockData[.sessionIdMatches] as? Bool) ?? false)
}
}

View File

@ -1,193 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import Sodium
@testable import SessionMessagingKit
class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
// MARK: - Mockable
enum DataKey: Hashable {
case allOpenGroups
case openGroupPublicKeys
case userKeyPair
case userEdKeyPair
case openGroup
case openGroupServer
case openGroupImage
case openGroupUserCount
case openGroupSequenceNumber
case openGroupInboxLatestMessageId
case openGroupOutboxLatestMessageId
case receivedMessageTimestamp
}
typealias Key = DataKey
var mockData: [DataKey: Any] = [:]
// MARK: - Shared
@discardableResult func write(with block: @escaping (Any) -> Void) -> Promise<Void> {
block(()) // TODO: Pass Transaction type to prevent force-cast crashes throughout codebase
return Promise.value(())
}
@discardableResult func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise<Void> {
block(()) // TODO: Pass Transaction type to prevent force-cast crashes throughout codebase
return Promise.value(())
}
func writeSync(with block: @escaping (Any) -> Void) {
block(()) // TODO: Pass Transaction type to prevent force-cast crashes throughout codebase
}
// MARK: - General
func getUserPublicKey() -> String? { return nil }
func getUserKeyPair() -> ECKeyPair? { return (mockData[.userKeyPair] as? ECKeyPair) }
func getUserED25519KeyPair() -> Box.KeyPair? { return (mockData[.userEdKeyPair] as? Box.KeyPair) }
func getUser() -> Contact? { return nil }
func getAllContacts() -> Set<Contact> { return Set() }
func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact> { return Set() }
// MARK: - Blinded Id cache
func getBlindedIdMapping(with blindedId: String) -> BlindedIdMapping? { return nil }
func getBlindedIdMapping(with blindedId: String, using transaction: YapDatabaseReadTransaction) -> BlindedIdMapping? {
return nil
}
func cacheBlindedIdMapping(_ mapping: BlindedIdMapping) {}
func cacheBlindedIdMapping(_ mapping: BlindedIdMapping, using transaction: YapDatabaseReadWriteTransaction) {}
func enumerateBlindedIdMapping(with block: @escaping (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> ()) {}
func enumerateBlindedIdMapping(using transaction: YapDatabaseReadTransaction, with block: @escaping (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> ()) {
}
// MARK: - Closed Groups
func getUserClosedGroupPublicKeys() -> Set<String> { return Set() }
func getZombieMembers(for groupPublicKey: String) -> Set<String> { return Set() }
func setZombieMembers(for groupPublicKey: String, to zombies: Set<String>, using transaction: Any) {}
func isClosedGroup(_ publicKey: String) -> Bool { return false }
// MARK: - Jobs
func persist(_ job: Job, using transaction: Any) {}
func markJobAsSucceeded(_ job: Job, using transaction: Any) {}
func markJobAsFailed(_ job: Job, using transaction: Any) {}
func getAllPendingJobs(of type: Job.Type) -> [Job] { return [] }
func getAttachmentUploadJob(for attachmentID: String) -> AttachmentUploadJob? { return nil }
func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob? { return nil }
func getMessageSendJob(for messageSendJobID: String, using transaction: Any) -> MessageSendJob? { return nil }
func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) {}
func isJobCanceled(_ job: Job) -> Bool { return true }
// MARK: - Open Groups
func getAllOpenGroups() -> [String: OpenGroup] { return (mockData[.allOpenGroups] as! [String: OpenGroup]) }
func getThreadID(for v2OpenGroupID: String) -> String? { return nil }
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {}
func getOpenGroupImage(for room: String, on server: String) -> Data? { return (mockData[.openGroupImage] as? Data) }
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {
mockData[.openGroupImage] = data
}
func getOpenGroup(for threadID: String) -> OpenGroup? { return (mockData[.openGroup] as? OpenGroup) }
func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) { mockData[.openGroup] = openGroup }
func getOpenGroupServer(name: String) -> OpenGroupAPI.Server? { return mockData[.openGroupServer] as? OpenGroupAPI.Server }
func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { mockData[.openGroupServer] = server }
func getUserCount(forOpenGroupWithID openGroupID: String) -> UInt64? {
return (mockData[.openGroupUserCount] as? UInt64)
}
func setUserCount(to newValue: UInt64, forOpenGroupWithID openGroupID: String, using transaction: Any) {
mockData[.openGroupUserCount] = newValue
}
func getOpenGroupSequenceNumber(for room: String, on server: String) -> Int64? {
let data: [String: Int64] = ((mockData[.openGroupSequenceNumber] as? [String: Int64]) ?? [:])
return data["\(server).\(room)"]
}
func setOpenGroupSequenceNumber(for room: String, on server: String, to newValue: Int64, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupSequenceNumber] as? [String: Int64]) ?? [:])
updatedData["\(server).\(room)"] = newValue
mockData[.openGroupSequenceNumber] = updatedData
}
func removeOpenGroupSequenceNumber(for room: String, on server: String, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupSequenceNumber] as? [String: Int64]) ?? [:])
updatedData["\(server).\(room)"] = nil
mockData[.openGroupSequenceNumber] = updatedData
}
func getOpenGroupInboxLatestMessageId(for server: String) -> Int64? {
let data: [String: Int64] = ((mockData[.openGroupInboxLatestMessageId] as? [String: Int64]) ?? [:])
return data[server]
}
func setOpenGroupInboxLatestMessageId(for server: String, to newValue: Int64, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupInboxLatestMessageId] as? [String: Int64]) ?? [:])
updatedData[server] = newValue
mockData[.openGroupInboxLatestMessageId] = updatedData
}
func removeOpenGroupInboxLatestMessageId(for server: String, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupInboxLatestMessageId] as? [String: Int64]) ?? [:])
updatedData[server] = nil
mockData[.openGroupInboxLatestMessageId] = updatedData
}
func getOpenGroupOutboxLatestMessageId(for server: String) -> Int64? {
let data: [String: Int64] = ((mockData[.openGroupOutboxLatestMessageId] as? [String: Int64]) ?? [:])
return data[server]
}
func setOpenGroupOutboxLatestMessageId(for server: String, to newValue: Int64, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupOutboxLatestMessageId] as? [String: Int64]) ?? [:])
updatedData[server] = newValue
mockData[.openGroupOutboxLatestMessageId] = updatedData
}
func removeOpenGroupOutboxLatestMessageId(for server: String, using transaction: Any) {
var updatedData: [String: Int64] = ((mockData[.openGroupOutboxLatestMessageId] as? [String: Int64]) ?? [:])
updatedData[server] = nil
mockData[.openGroupOutboxLatestMessageId] = updatedData
}
// MARK: - Open Group Public Keys
func getOpenGroupPublicKey(for server: String) -> String? {
guard let publicKeyMap: [String: String] = mockData[.openGroupPublicKeys] as? [String: String] else {
return (mockData[.openGroupPublicKeys] as? String)
}
return publicKeyMap[server]
}
func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any) {}
// MARK: - Message Handling
func getAllMessageRequestThreads() -> [String: TSContactThread] { return [:] }
func getAllMessageRequestThreads(using transaction: YapDatabaseReadTransaction) -> [String: TSContactThread] { return [:] }
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64] {
return ((mockData[.receivedMessageTimestamp] as? UInt64).map { [$0] } ?? [])
}
func addReceivedMessageTimestamp(_ timestamp: UInt64, using transaction: Any) {
mockData[.receivedMessageTimestamp] = timestamp
}
func getOrCreateThread(for publicKey: String, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String? { return nil }
func persist(_ message: VisibleMessage, quotedMessage: TSQuotedMessage?, linkPreview: OWSLinkPreview?, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String? { return nil }
func persist(_ attachments: [VisibleMessage.Attachment], using transaction: Any) -> [String] { return [] }
func setAttachmentState(to state: TSAttachmentPointerState, for pointer: TSAttachmentPointer, associatedWith tsIncomingMessageID: String, using transaction: Any) {}
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any) {}
}

View File

@ -0,0 +1,3 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation

181
SharedTest/Mock.swift Normal file
View File

@ -0,0 +1,181 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
// MARK: - Mocked
protocol Mocked { static var mockValue: Self { get } }
func any<R: Mocked>() -> R { R.mockValue }
func any<R: FixedWidthInteger>() -> R { unsafeBitCast(0, to: R.self) }
func any<R>() -> [R] { [] }
func any<K: Hashable, V>() -> [K: V] { [:] }
func any() -> Any { 0 }
func any() -> Float { 0 }
func any() -> Double { 0 }
func any() -> String { "" }
func any() -> Data { Data() }
// MARK: - Mock<T>
public class Mock<T> {
private let functionHandler: MockFunctionHandler
internal let functionConsumer: FunctionConsumer
internal required init(functionHandler: MockFunctionHandler? = nil) {
self.functionConsumer = FunctionConsumer()
self.functionHandler = (functionHandler ?? self.functionConsumer)
}
@discardableResult internal func accept(funcName: String = #function, args: [Any?] = []) -> Any? {
return accept(funcName: funcName, checkArgs: args, actionArgs: args)
}
@discardableResult internal func accept(funcName: String = #function, checkArgs: [Any?], actionArgs: [Any?]) -> Any? {
return functionHandler.accept(funcName, parameterSummary: summary(for: checkArgs), actionArgs: actionArgs)
}
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)
return builder
}
private func summary(for argument: Any) -> String {
switch argument {
case let string as String: return string
case let array as [Any]: return "[\(array.map { summary(for: $0) }.joined(separator: ", "))]"
case let dict as [String: Any]:
return "[\(dict.map { key, value in "\(summary(for: key)):\(summary(for: value))" }.joined(separator: ", "))]"
default: return String(describing: argument)
}
}
}
// MARK: - MockFunctionHandler
protocol MockFunctionHandler {
func accept(_ functionName: String, parameterSummary: String, actionArgs: [Any?]) -> Any?
}
// MARK: - MockFunction
internal class MockFunction {
var name: String
var parameterSummary: String
var actions: [([Any?]) -> Void]
var returnValue: Any?
init(name: String, parameterSummary: String, actions: [([Any?]) -> Void], returnValue: Any?) {
self.name = name
self.parameterSummary = parameterSummary
self.actions = actions
self.returnValue = returnValue
}
}
// MARK: - MockFunctionBuilder
internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
private let callBlock: (T) throws -> R
private let mockInit: (MockFunctionHandler?) -> Mock<T>
private var functionName: String?
private var parameterSummary: String?
private var actions: [([Any?]) -> Void] = []
private var returnValue: R?
internal var returnValueGenerator: ((String, String) -> R?)?
// MARK: - Initialization
init(_ callBlock: @escaping (T) throws -> R, mockInit: @escaping (MockFunctionHandler?) -> Mock<T>) {
self.callBlock = callBlock
self.mockInit = mockInit
}
// MARK: - Behaviours
@discardableResult func then(_ block: @escaping ([Any?]) -> Void) -> MockFunctionBuilder<T, R> {
actions.append(block)
return self
}
func thenReturn(_ value: R?) {
returnValue = value
}
// MARK: - MockFunctionHandler
func accept(_ functionName: String, parameterSummary: String, actionArgs: [Any?]) -> Any? {
self.functionName = functionName
self.parameterSummary = parameterSummary
return (returnValue ?? returnValueGenerator?(functionName, parameterSummary))
}
// MARK: - Build
func build() throws -> MockFunction {
let completionMock = mockInit(self) as! T
_ = try callBlock(completionMock)
guard let name: String = functionName, let parameterSummary: String = parameterSummary else {
preconditionFailure("Attempted to build the MockFunction before it was called")
}
return MockFunction(name: name, parameterSummary: parameterSummary, actions: actions, returnValue: returnValue)
}
}
// MARK: - FunctionConsumer
internal class FunctionConsumer: MockFunctionHandler {
var trackCalls: Bool = true
var functionBuilders: [() throws -> MockFunction?] = []
var functionHandlers: [String: [String: MockFunction]] = [:]
var calls: [String: [String]] = [:]
func accept(_ functionName: String, parameterSummary: String, actionArgs: [Any?]) -> Any? {
if !functionBuilders.isEmpty {
functionBuilders
.compactMap { try? $0() }
.forEach { function in
functionHandlers[function.name] = (functionHandlers[function.name] ?? [:])
.setting(function.parameterSummary, function)
}
functionBuilders.removeAll()
}
guard let expectation: MockFunction = firstFunction(for: functionName, matchingParameterSummaryIfPossible: parameterSummary) else {
preconditionFailure("No expectations found for \(functionName)")
}
// Record the call so it can be validated later (assuming we are tracking calls)
if trackCalls {
calls[functionName] = (calls[functionName] ?? []).appending(parameterSummary)
}
for action in expectation.actions {
action(actionArgs)
}
return expectation.returnValue
}
func firstFunction(for name: String, matchingParameterSummaryIfPossible parameterSummary: String) -> MockFunction? {
guard let possibleExpectations: [String: MockFunction] = functionHandlers[name] else { return nil }
guard let expectation: MockFunction = possibleExpectations[parameterSummary] else {
// A `nil` response might be value but in a lot of places we will need to force-cast
// so try to find a non-nil response first
return (
possibleExpectations.values.first(where: { $0.returnValue != nil }) ??
possibleExpectations.values.first
)
}
return expectation
}
}

View File

@ -0,0 +1,223 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Nimble
public enum CallAmount {
case atLeast(times: Int)
case exactly(times: Int)
case noMoreThan(times: Int)
}
fileprivate func timeStr(_ value: Int) -> String {
return "\(value) time\(value == 1 ? "" : "s")"
}
/// Validates whether the function called in `functionBlock` has been called according to the parameter constraints
///
/// - Parameters:
/// - amount: An enum constraining the number of times the function can be called (Default is `.atLeast(times: 1)`
///
/// - matchingParameters: A boolean indicating whether the parameters for the function call need to match exactly
///
/// - exclusive: A boolean indicating whether no other functions should be called
///
/// - functionBlock: A closure in which the function to be validated should be called
public func call<M, T, R>(
_ amount: CallAmount = .atLeast(times: 1),
matchingParameters: Bool = false,
exclusive: Bool = false,
functionBlock: @escaping (T) throws -> R
) -> Predicate<M> where M: Mock<T> {
return Predicate.define { actualExpression in
let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock)
let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls
.filter { !matchingParameters || callInfo.hasMatchingParameters($0) }
let exclusiveCallsValid: Bool = (!exclusive || callInfo.allFunctionsCalled.count <= 1) // '<=' to support '0' case
let (numParamMatchingCallsValid, timesError): (Bool, String?) = {
switch amount {
case .atLeast(let times):
return (
(matchingParameterRecords.count >= times),
(times <= 1 ? nil : "at least \(timeStr(times))")
)
case .exactly(let times):
return (
(matchingParameterRecords.count == times),
"exactly \(timeStr(times))"
)
case .noMoreThan(let times):
return (
(matchingParameterRecords.count <= times),
(times <= 0 ? nil : "no more than \(timeStr(times))")
)
}
}()
let result = (
numParamMatchingCallsValid &&
exclusiveCallsValid
)
let matchingParametersError: String? = (matchingParameters ?
"matching the parameters\(callInfo.desiredParameters.map { ": \($0)" } ?? "")" :
nil
)
let distinctParameterCombinations: Set<String> = Set(callInfo.desiredFunctionCalls)
let actualMessage: String
if callInfo.caughtException != nil {
actualMessage = "a thrown assertion (might not have been called or has no mocked return value)"
}
else if callInfo.function == nil {
actualMessage = "no call details"
}
else if callInfo.desiredFunctionCalls.isEmpty {
actualMessage = "no calls"
}
else if !exclusiveCallsValid {
let otherFunctionsCalled: [String] = callInfo.allFunctionsCalled.filter { $0 != callInfo.functionName }
actualMessage = "calls to other functions: [\(otherFunctionsCalled.joined(separator: ", "))]"
}
else {
let onlyMadeMatchingCalls: Bool = (matchingParameterRecords.count == callInfo.desiredFunctionCalls.count)
switch (numParamMatchingCallsValid, onlyMadeMatchingCalls, distinctParameterCombinations.count) {
case (false, false, 1):
// No calls with the matching parameter requirements but only one parameter combination
// so include the param info
actualMessage = "called \(timeStr(callInfo.desiredFunctionCalls.count)) with different parameters: \(callInfo.desiredFunctionCalls[0])"
case (false, true, _):
actualMessage = "called \(timeStr(callInfo.desiredFunctionCalls.count))"
case (false, false, _):
actualMessage = "called \(timeStr(matchingParameterRecords.count)) with matching parameters, \(timeStr(callInfo.desiredFunctionCalls.count)) total"
default: actualMessage = ""
}
}
return PredicateResult(
bool: result,
message: .expectedCustomValueTo(
[
"call '\(callInfo.functionName)'\(exclusive ? " exclusively" : "")",
timesError,
matchingParametersError
]
.compactMap { $0 }
.joined(separator: " "),
actual: actualMessage
)
)
}
}
// MARK: - Shared Code
fileprivate struct CallInfo {
let didError: Bool
let caughtException: BadInstructionException?
let function: MockFunction?
let allFunctionsCalled: [String]
let desiredFunctionCalls: [String]
var functionName: String { "\((function?.name).map { "\($0)" } ?? "a function")" }
var desiredParameters: String? { function?.parameterSummary }
static var error: CallInfo {
CallInfo(
didError: true,
caughtException: nil,
function: nil,
allFunctionsCalled: [],
desiredFunctionCalls: []
)
}
init(
didError: Bool = false,
caughtException: BadInstructionException?,
function: MockFunction?,
allFunctionsCalled: [String],
desiredFunctionCalls: [String]
) {
self.didError = didError
self.caughtException = caughtException
self.function = function
self.allFunctionsCalled = allFunctionsCalled
self.desiredFunctionCalls = desiredFunctionCalls
}
func hasMatchingParameters(_ parameters: String) -> Bool {
return (parameters == (function?.parameterSummary ?? "FALLBACK_NOT_FOUND"))
}
}
fileprivate func generateCallInfo<M, T, R>(_ actualExpression: Expression<M>, _ functionBlock: @escaping (T) throws -> R) -> CallInfo where M: Mock<T> {
var maybeFunction: MockFunction?
var allFunctionsCalled: [String] = []
var desiredFunctionCalls: [String] = []
let builderCreator: ((M) -> MockFunctionBuilder<T, R>) = { validInstance in
let builder: MockFunctionBuilder<T, R> = MockFunctionBuilder(functionBlock, mockInit: type(of: validInstance).init)
builder.returnValueGenerator = { name, parameterSummary in
validInstance.functionConsumer
.firstFunction(for: name, matchingParameterSummaryIfPossible: parameterSummary)?
.returnValue as? R
}
return builder
}
#if (arch(x86_64) || arch(arm64)) && (canImport(Darwin) || canImport(Glibc))
var didError: Bool = false
let caughtException: BadInstructionException? = catchBadInstruction {
do {
guard let validInstance: M = try actualExpression.evaluate() else {
didError = true
return
}
allFunctionsCalled = Array(validInstance.functionConsumer.calls.keys)
let builder: MockFunctionBuilder<T, R> = builderCreator(validInstance)
validInstance.functionConsumer.trackCalls = false
maybeFunction = try? builder.build()
desiredFunctionCalls = (validInstance.functionConsumer.calls[maybeFunction?.name ?? ""] ?? [])
validInstance.functionConsumer.trackCalls = true
}
catch {
didError = true
}
}
// Make sure to switch this back on in case an assertion was thrown (which would meant this
// wouldn't have been reset)
(try? actualExpression.evaluate())?.functionConsumer.trackCalls = true
guard !didError else { return CallInfo.error }
#else
let caughtException: BadInstructionException? = nil
// 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)
let builder: MockExpectationBuilder<T, R> = builderCreator(validInstance)
validInstance.functionConsumer.trackCalls = false
maybeFunction = try? builder.build()
desiredFunctionCalls = (validInstance.functionConsumer.calls[maybeFunction?.name ?? ""] ?? [])
validInstance.functionConsumer.trackCalls = true
#endif
return CallInfo(
caughtException: caughtException,
function: maybeFunction,
allFunctionsCalled: allFunctionsCalled,
desiredFunctionCalls: desiredFunctionCalls
)
}