Fixed the broken tests and updated test dependencies
Properly fixed the busted migration issue Updated to the latest version of Quick and Nimble (unit testing libraries) Updated the tests based on the above
This commit is contained in:
parent
20ce1deb23
commit
c4aadaff1c
12
Podfile.lock
12
Podfile.lock
|
@ -25,13 +25,13 @@ PODS:
|
|||
- libwebp/sharpyuv (1.3.2)
|
||||
- libwebp/webp (1.3.2):
|
||||
- libwebp/sharpyuv
|
||||
- Nimble (10.0.0)
|
||||
- Nimble (12.3.0)
|
||||
- NVActivityIndicatorView (5.1.1):
|
||||
- NVActivityIndicatorView/Base (= 5.1.1)
|
||||
- NVActivityIndicatorView/Base (5.1.1)
|
||||
- OpenSSL-Universal (1.1.1300)
|
||||
- PureLayout (3.1.9)
|
||||
- Quick (5.0.1)
|
||||
- Quick (7.3.0)
|
||||
- Reachability (3.2)
|
||||
- SAMKeychain (1.5.3)
|
||||
- SignalCoreKit (1.0.0):
|
||||
|
@ -137,11 +137,9 @@ SPEC REPOS:
|
|||
- CocoaLumberjack
|
||||
- DifferenceKit
|
||||
- GRDB.swift
|
||||
- Nimble
|
||||
- NVActivityIndicatorView
|
||||
- OpenSSL-Universal
|
||||
- PureLayout
|
||||
- Quick
|
||||
- Reachability
|
||||
- SAMKeychain
|
||||
- SQLCipher
|
||||
|
@ -149,6 +147,8 @@ SPEC REPOS:
|
|||
- WebRTC-lib
|
||||
trunk:
|
||||
- libwebp
|
||||
- Nimble
|
||||
- Quick
|
||||
- xcbeautify
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
|
@ -190,11 +190,11 @@ SPEC CHECKSUMS:
|
|||
DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca
|
||||
GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84
|
||||
Nimble: f8a8219d16f176429b951e8f7e72df5c23ceddc0
|
||||
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
|
||||
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
|
||||
PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88
|
||||
Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179
|
||||
Quick: d32871931c05547cb4e0bc9009d66a18b50d8558
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d
|
||||
|
|
|
@ -575,7 +575,6 @@
|
|||
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; };
|
||||
FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; };
|
||||
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; };
|
||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */; };
|
||||
FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */; };
|
||||
FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */; };
|
||||
FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */; };
|
||||
|
@ -726,7 +725,6 @@
|
|||
FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; };
|
||||
FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; };
|
||||
FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; };
|
||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; };
|
||||
FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; };
|
||||
FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */; };
|
||||
FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */; };
|
||||
|
@ -747,7 +745,6 @@
|
|||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; };
|
||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; };
|
||||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; };
|
||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */; };
|
||||
|
@ -758,7 +755,6 @@
|
|||
FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; };
|
||||
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; };
|
||||
FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; };
|
||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */; };
|
||||
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; };
|
||||
FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; };
|
||||
FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; };
|
||||
|
@ -921,6 +917,11 @@
|
|||
FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; };
|
||||
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
||||
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; };
|
||||
FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */; };
|
||||
FDFE75B22ABD469500655640 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
|
||||
FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
|
||||
FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
|
||||
FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
|
||||
FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; };
|
||||
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; };
|
||||
FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
|
||||
|
@ -1697,7 +1698,6 @@
|
|||
FD29598F2A43BE5F00888A17 /* VersionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionSpec.swift; sourceTree = "<group>"; };
|
||||
FD2959912A4417A900888A17 /* PreparedSendData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedSendData.swift; sourceTree = "<group>"; };
|
||||
FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = "<group>"; };
|
||||
FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigContactsSpec.swift; sourceTree = "<group>"; };
|
||||
FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Contacts.swift"; sourceTree = "<group>"; };
|
||||
FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationSyncJob.swift; sourceTree = "<group>"; };
|
||||
FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1846,7 +1846,6 @@
|
|||
FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = "<group>"; };
|
||||
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = "<group>"; };
|
||||
FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = "<group>"; };
|
||||
FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = "<group>"; };
|
||||
FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = "<group>"; };
|
||||
FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigMessage.swift; sourceTree = "<group>"; };
|
||||
FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserProfile.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1861,7 +1860,6 @@
|
|||
FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = "<group>"; };
|
||||
FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSessionUtil.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = "<group>"; };
|
||||
FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = "<group>"; };
|
||||
FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilSpec.swift; sourceTree = "<group>"; };
|
||||
|
@ -1872,7 +1870,6 @@
|
|||
FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManagerError.swift; sourceTree = "<group>"; };
|
||||
FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = "<group>"; };
|
||||
FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigConvoInfoVolatileSpec.swift; sourceTree = "<group>"; };
|
||||
FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = "<group>"; };
|
||||
FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = "<group>"; };
|
||||
FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeRequest.swift; sourceTree = "<group>"; };
|
||||
|
@ -2038,6 +2035,7 @@
|
|||
FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = "<group>"; };
|
||||
FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = "<group>"; };
|
||||
FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = "<group>"; };
|
||||
FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_MakeBrokenProfileTimestampsNullable.swift; sourceTree = "<group>"; };
|
||||
FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -3616,6 +3614,7 @@
|
|||
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */,
|
||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */,
|
||||
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */,
|
||||
FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4044,6 +4043,7 @@
|
|||
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
|
@ -4095,7 +4095,6 @@
|
|||
FD8ECF802934385900C0D1BB /* LibSessionUtil */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDA1E83729A5770C00C5C3BD /* Configs */,
|
||||
FDDC08F029A300D500BF9681 /* Utilities */,
|
||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */,
|
||||
FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */,
|
||||
|
@ -4148,17 +4147,6 @@
|
|||
path = Networking;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDA1E83729A5770C00C5C3BD /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */,
|
||||
FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */,
|
||||
FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */,
|
||||
FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDC13D4E2A16EE41007267C7 /* Types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -4290,7 +4278,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||
);
|
||||
path = _TestUtilities;
|
||||
|
@ -5935,6 +5922,7 @@
|
|||
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
|
||||
FD245C642850664F00B966DD /* Threading.swift in Sources */,
|
||||
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */,
|
||||
FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */,
|
||||
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */,
|
||||
FD09799B27FFC82D00936362 /* Quote.swift in Sources */,
|
||||
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */,
|
||||
|
@ -6193,9 +6181,11 @@
|
|||
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
||||
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
||||
FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */,
|
||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
||||
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */,
|
||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
||||
);
|
||||
|
@ -6210,10 +6200,12 @@
|
|||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */,
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FDFE75B22ABD469500655640 /* MockJobRunner.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
||||
FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */,
|
||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
||||
|
@ -6240,7 +6232,6 @@
|
|||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||
|
@ -6253,9 +6244,7 @@
|
|||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||
|
@ -6264,7 +6253,6 @@
|
|||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
||||
|
|
|
@ -151,7 +151,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self?.oldDisplayName = (updatedNickname.isEmpty ? nil : editedDisplayName)
|
||||
|
||||
dependencies.storage.writeAsync { db in
|
||||
dependencies.storage.writeAsync(using: dependencies) { db in
|
||||
try Profile
|
||||
.filter(id: threadId)
|
||||
.updateAllAndConfig(
|
||||
|
|
|
@ -98,9 +98,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -179,9 +177,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -309,9 +305,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
|
|||
_012_AddFTSIfNeeded.self,
|
||||
_013_SessionUtilChanges.self,
|
||||
_014_GenerateInitialUserConfigDumps.self,
|
||||
_015_BlockCommunityMessageRequests.self
|
||||
_015_BlockCommunityMessageRequests.self,
|
||||
_016_MakeBrokenProfileTimestampsNullable.self
|
||||
]
|
||||
]
|
||||
)
|
||||
|
|
|
@ -417,12 +417,10 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
try Profile(
|
||||
id: legacyContact.sessionID,
|
||||
name: (legacyContact.name ?? legacyContact.sessionID),
|
||||
lastNameUpdate: 0,
|
||||
nickname: legacyContact.nickname,
|
||||
profilePictureUrl: legacyContact.profilePictureURL,
|
||||
profilePictureFileName: legacyContact.profilePictureFileName,
|
||||
profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData,
|
||||
lastProfilePictureUpdate: 0
|
||||
profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData
|
||||
).migrationSafeInsert(db)
|
||||
|
||||
/// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they
|
||||
|
@ -643,9 +641,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
// constraint violation
|
||||
try? Profile(
|
||||
id: profileId,
|
||||
name: profileId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
name: profileId
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
@ -1057,9 +1053,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
// constraint violation
|
||||
try Profile(
|
||||
id: quotedMessage.authorId,
|
||||
name: quotedMessage.authorId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
name: quotedMessage.authorId
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,12 +22,8 @@ enum _013_SessionUtilChanges: Migration {
|
|||
|
||||
// Add `lastNameUpdate` and `lastProfilePictureUpdate` columns to the profile table
|
||||
try db.alter(table: Profile.self) { t in
|
||||
t.add(.lastNameUpdate, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastProfilePictureUpdate, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastNameUpdate, .integer).defaults(to: 0)
|
||||
t.add(.lastProfilePictureUpdate, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// SQLite doesn't support adding a new primary key after creation so we need to create a new table with
|
||||
|
|
|
@ -16,9 +16,7 @@ enum _015_BlockCommunityMessageRequests: Migration {
|
|||
// Add the new 'Profile' properties
|
||||
try db.alter(table: Profile.self) { t in
|
||||
t.add(.blocksCommunityMessageRequests, .boolean)
|
||||
t.add(.lastBlocksCommunityMessageRequests, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastBlocksCommunityMessageRequests, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// If the user exists and the 'checkForCommunityMessageRequests' hasn't already been set then default it to "false"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// This migration updates the tiemstamps added to the `Profile` in earlier migrations to be nullable (having it not null
|
||||
/// results in migration issues when a user jumps between multiple versions)
|
||||
enum _016_MakeBrokenProfileTimestampsNullable: Migration {
|
||||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "MakeBrokenProfileTimestampsNullable"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
/// SQLite doesn't support altering columns after creation so we need to create a new table with the setup we
|
||||
/// want, copy data from the old table over, drop the old table and rename the new table
|
||||
struct TmpProfile: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible {
|
||||
static var databaseTableName: String { "tmpProfile" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case id
|
||||
|
||||
case name
|
||||
case lastNameUpdate
|
||||
case nickname
|
||||
|
||||
case profilePictureUrl
|
||||
case profilePictureFileName
|
||||
case profileEncryptionKey
|
||||
case lastProfilePictureUpdate
|
||||
|
||||
case blocksCommunityMessageRequests
|
||||
case lastBlocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let lastNameUpdate: TimeInterval?
|
||||
public let nickname: String?
|
||||
public let profilePictureUrl: String?
|
||||
public let profilePictureFileName: String?
|
||||
public let profileEncryptionKey: Data?
|
||||
public let lastProfilePictureUpdate: TimeInterval?
|
||||
public let blocksCommunityMessageRequests: Bool?
|
||||
public let lastBlocksCommunityMessageRequests: TimeInterval?
|
||||
}
|
||||
|
||||
try db.create(table: TmpProfile.self) { t in
|
||||
t.column(.id, .text)
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
t.column(.name, .text).notNull()
|
||||
t.column(.nickname, .text)
|
||||
t.column(.profilePictureUrl, .text)
|
||||
t.column(.profilePictureFileName, .text)
|
||||
t.column(.profileEncryptionKey, .blob)
|
||||
t.column(.lastNameUpdate, .integer).defaults(to: 0)
|
||||
t.column(.lastProfilePictureUpdate, .integer).defaults(to: 0)
|
||||
t.column(.blocksCommunityMessageRequests, .boolean)
|
||||
t.column(.lastBlocksCommunityMessageRequests, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// Insert into the new table, drop the old table and rename the new table to be the old one
|
||||
try db.execute(sql: """
|
||||
INSERT INTO \(TmpProfile.databaseTableName)
|
||||
SELECT \(Profile.databaseTableName).*
|
||||
FROM \(Profile.databaseTableName)
|
||||
""")
|
||||
|
||||
try db.drop(table: Profile.self)
|
||||
try db.rename(table: TmpProfile.databaseTableName, to: Profile.databaseTableName)
|
||||
|
||||
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
public let name: String
|
||||
|
||||
/// The timestamp (in seconds since epoch) that the name was last updated
|
||||
public let lastNameUpdate: TimeInterval
|
||||
public let lastNameUpdate: TimeInterval?
|
||||
|
||||
/// A custom name for the profile set by the current user
|
||||
public let nickname: String?
|
||||
|
@ -54,7 +54,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
public let profileEncryptionKey: Data?
|
||||
|
||||
/// The timestamp (in seconds since epoch) that the profile picture was last updated
|
||||
public let lastProfilePictureUpdate: TimeInterval
|
||||
public let lastProfilePictureUpdate: TimeInterval?
|
||||
|
||||
/// A flag indicating whether this profile has reported that it blocks community message requests
|
||||
public let blocksCommunityMessageRequests: Bool?
|
||||
|
@ -67,12 +67,12 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
public init(
|
||||
id: String,
|
||||
name: String,
|
||||
lastNameUpdate: TimeInterval,
|
||||
lastNameUpdate: TimeInterval? = nil,
|
||||
nickname: String? = nil,
|
||||
profilePictureUrl: String? = nil,
|
||||
profilePictureFileName: String? = nil,
|
||||
profileEncryptionKey: Data? = nil,
|
||||
lastProfilePictureUpdate: TimeInterval,
|
||||
lastProfilePictureUpdate: TimeInterval? = nil,
|
||||
blocksCommunityMessageRequests: Bool? = nil,
|
||||
lastBlocksCommunityMessageRequests: TimeInterval? = nil
|
||||
) {
|
||||
|
@ -122,12 +122,12 @@ public extension Profile {
|
|||
self = Profile(
|
||||
id: try container.decode(String.self, forKey: .id),
|
||||
name: try container.decode(String.self, forKey: .name),
|
||||
lastNameUpdate: try container.decode(TimeInterval.self, forKey: .lastNameUpdate),
|
||||
lastNameUpdate: try? container.decode(TimeInterval.self, forKey: .lastNameUpdate),
|
||||
nickname: try? container.decode(String.self, forKey: .nickname),
|
||||
profilePictureUrl: profilePictureUrl,
|
||||
profilePictureFileName: try? container.decode(String.self, forKey: .profilePictureFileName),
|
||||
profileEncryptionKey: profileKey,
|
||||
lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate),
|
||||
lastProfilePictureUpdate: try? container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate),
|
||||
blocksCommunityMessageRequests: try? container.decode(Bool.self, forKey: .blocksCommunityMessageRequests),
|
||||
lastBlocksCommunityMessageRequests: try? container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests)
|
||||
)
|
||||
|
@ -256,12 +256,12 @@ public extension Profile {
|
|||
return Profile(
|
||||
id: id,
|
||||
name: "",
|
||||
lastNameUpdate: 0,
|
||||
lastNameUpdate: nil,
|
||||
nickname: nil,
|
||||
profilePictureUrl: nil,
|
||||
profilePictureFileName: nil,
|
||||
profileEncryptionKey: nil,
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastProfilePictureUpdate: nil,
|
||||
blocksCommunityMessageRequests: nil,
|
||||
lastBlocksCommunityMessageRequests: nil
|
||||
)
|
||||
|
|
|
@ -83,7 +83,9 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
public static func isSessionRunOpenGroup(server: String) -> Bool {
|
||||
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
||||
guard let serverUrl: URL = (URL(string: server.lowercased()) ?? URL(string: "http://\(server.lowercased())")) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
||||
let serverHost: String = serverUrl.host
|
||||
|
|
|
@ -58,14 +58,14 @@ internal extension SessionUtil {
|
|||
let profileNameShouldBeUpdated: Bool = (
|
||||
!data.profile.name.isEmpty &&
|
||||
profile.name != data.profile.name &&
|
||||
profile.lastNameUpdate < data.profile.lastNameUpdate
|
||||
(profile.lastNameUpdate ?? 0) < (data.profile.lastNameUpdate ?? 0)
|
||||
)
|
||||
let profilePictureShouldBeUpdated: Bool = (
|
||||
(
|
||||
profile.profilePictureUrl != data.profile.profilePictureUrl ||
|
||||
profile.profileEncryptionKey != data.profile.profileEncryptionKey
|
||||
) &&
|
||||
profile.lastProfilePictureUpdate < data.profile.lastProfilePictureUpdate
|
||||
(profile.lastProfilePictureUpdate ?? 0) < (data.profile.lastProfilePictureUpdate ?? 0)
|
||||
)
|
||||
|
||||
if
|
||||
|
|
|
@ -510,7 +510,7 @@ public struct ProfileManager {
|
|||
|
||||
// Name
|
||||
if let name: String = name, !name.isEmpty, name != profile.name {
|
||||
if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && calledFromConfigHandling) {
|
||||
if sentTimestamp > (profile.lastNameUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) {
|
||||
profileChanges.append(Profile.Columns.name.set(to: name))
|
||||
profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp))
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ public struct ProfileManager {
|
|||
var avatarNeedsDownload: Bool = false
|
||||
var targetAvatarUrl: String? = nil
|
||||
|
||||
if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) {
|
||||
if sentTimestamp > (profile.lastProfilePictureUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) {
|
||||
switch avatarUpdate {
|
||||
case .none: break
|
||||
case .uploadImageData: preconditionFailure("Invalid options for this function")
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class FileUploadResponseSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a FileUploadResponse
|
||||
describe("a FileUploadResponse") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- handles a string id value
|
||||
it("handles a string id value") {
|
||||
let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)!
|
||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||
|
@ -20,6 +21,7 @@ class FileUploadResponseSpec: QuickSpec {
|
|||
expect(response?.id).to(equal("123"))
|
||||
}
|
||||
|
||||
// MARK: ---- handles an int id value
|
||||
it("handles an int id value") {
|
||||
let jsonData: Data = "{\"id\":124}".data(using: .utf8)!
|
||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class BlindedIdLookupSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a BlindedIdLookup
|
||||
describe("a BlindedIdLookup") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- sets the values correctly
|
||||
it("sets the values correctly") {
|
||||
let lookup: BlindedIdLookup = BlindedIdLookup(
|
||||
blindedId: "testBlindedId",
|
||||
|
|
|
@ -10,49 +10,37 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class MessageSendJobSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var job: Job!
|
||||
var interaction: Interaction!
|
||||
var attachment: Attachment!
|
||||
var interactionAttachment: InteractionAttachment!
|
||||
var mockStorage: Storage!
|
||||
var mockJobRunner: MockJobRunner!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
// MARK: - JobRunner
|
||||
|
||||
describe("a MessageSendJob") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockJobRunner = MockJobRunner()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
jobRunner: mockJobRunner,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
attachment = Attachment(
|
||||
@TestState var job: Job!
|
||||
@TestState var interaction: Interaction!
|
||||
@TestState var attachment: Attachment! = Attachment(
|
||||
id: "200",
|
||||
variant: .standard,
|
||||
state: .failedDownload,
|
||||
contentType: "text/plain",
|
||||
byteCount: 200
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try SessionThread.fetchOrCreate(db, id: "Test1", variant: .contact, shouldBeVisible: true)
|
||||
@TestState var interactionAttachment: InteractionAttachment!
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
],
|
||||
initialData: { db in
|
||||
try SessionThread.fetchOrCreate(
|
||||
db,
|
||||
id: "Test1",
|
||||
variant: .contact,
|
||||
shouldBeVisible: true
|
||||
)
|
||||
}
|
||||
|
||||
mockJobRunner
|
||||
)
|
||||
@TestState var mockJobRunner: MockJobRunner! = MockJobRunner(
|
||||
initialSetup: { jobRunner in
|
||||
jobRunner
|
||||
.when {
|
||||
$0.jobInfoFor(
|
||||
jobs: nil,
|
||||
|
@ -61,7 +49,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
.thenReturn([:])
|
||||
mockJobRunner
|
||||
jobRunner
|
||||
.when { $0.insert(any(), job: any(), before: any()) }
|
||||
.then { args in
|
||||
let db: Database = args[0] as! Database
|
||||
|
@ -72,14 +60,16 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
.thenReturn((1000, Job(variant: .messageSend)))
|
||||
}
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
jobRunner: mockJobRunner,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
|
||||
afterEach {
|
||||
job = nil
|
||||
mockStorage = nil
|
||||
dependencies = nil
|
||||
}
|
||||
|
||||
// MARK: - fails when not given any details
|
||||
// MARK: - a MessageSendJob
|
||||
describe("a MessageSendJob") {
|
||||
// MARK: -- fails when not given any details
|
||||
it("fails when not given any details") {
|
||||
job = Job(variant: .messageSend)
|
||||
|
||||
|
@ -102,7 +92,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: - fails when given incorrect details
|
||||
// MARK: -- fails when given incorrect details
|
||||
it("fails when given incorrect details") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -128,7 +118,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: - of VisibleMessage
|
||||
// MARK: -- of VisibleMessage
|
||||
context("of VisibleMessage") {
|
||||
beforeEach {
|
||||
interaction = Interaction(
|
||||
|
@ -167,7 +157,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no job id
|
||||
// MARK: ---- fails when there is no job id
|
||||
it("fails when there is no job id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -199,7 +189,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction id
|
||||
// MARK: ---- fails when there is no interaction id
|
||||
it("fails when there is no interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -230,7 +220,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction for the provided interaction id
|
||||
// MARK: ---- fails when there is no interaction for the provided interaction id
|
||||
it("fails when there is no interaction for the provided interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -263,7 +253,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- with an attachment
|
||||
// MARK: ---- with an attachment
|
||||
context("with an attachment") {
|
||||
beforeEach {
|
||||
interactionAttachment = InteractionAttachment(
|
||||
|
@ -278,7 +268,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- it fails when trying to send with an attachment which previously failed to download
|
||||
// MARK: ------ it fails when trying to send with an attachment which previously failed to download
|
||||
it("it fails when trying to send with an attachment which previously failed to download") {
|
||||
mockStorage.write { db in
|
||||
try attachment.with(state: .failedDownload).save(db)
|
||||
|
@ -303,7 +293,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- with a pending upload
|
||||
// MARK: ------ with a pending upload
|
||||
context("with a pending upload") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -311,7 +301,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an attachment which is still pending upload
|
||||
// MARK: -------- it defers when trying to send with an attachment which is still pending upload
|
||||
it("it defers when trying to send with an attachment which is still pending upload") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
|
@ -331,7 +321,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an uploaded attachment that has an invalid downloadUrl
|
||||
// MARK: -------- it defers when trying to send with an uploaded attachment that has an invalid downloadUrl
|
||||
it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
|
@ -356,7 +346,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ inserts an attachment upload job before the message send job
|
||||
// MARK: -------- inserts an attachment upload job before the message send job
|
||||
it("inserts an attachment upload job before the message send job") {
|
||||
mockJobRunner
|
||||
.when {
|
||||
|
@ -397,7 +387,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
})
|
||||
}
|
||||
|
||||
// MARK: ------ creates a dependency between the new job and the existing one
|
||||
// MARK: -------- creates a dependency between the new job and the existing one
|
||||
it("creates a dependency between the new job and the existing one") {
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
|
|
|
@ -1,547 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigContactsSpec {
|
||||
enum ContactProperty: CaseIterable {
|
||||
case name
|
||||
case nickname
|
||||
case approved
|
||||
case approved_me
|
||||
case blocked
|
||||
case profile_pic
|
||||
case created
|
||||
case notifications
|
||||
case mute_until
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("CONTACTS") {
|
||||
// MARK: - when checking error catching
|
||||
context("when checking error catching") {
|
||||
var seed: Data!
|
||||
var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)!
|
||||
var edSK: [UInt8]!
|
||||
var error: UnsafeMutablePointer<CChar>?
|
||||
var conf: UnsafeMutablePointer<config_object>?
|
||||
|
||||
beforeEach {
|
||||
seed = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
identity = try! Identity.generate(from: seed)
|
||||
edSK = identity.ed25519KeyPair.secretKey
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
error = nil
|
||||
conf = nil
|
||||
_ = contacts_init(&conf, &edSK, nil, 0, error)
|
||||
error?.deallocate()
|
||||
}
|
||||
|
||||
// MARK: -- it can catch size limit errors thrown when pushing
|
||||
it("can catch size limit errors thrown when pushing") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
try (0..<10000).forEach { index in
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: .allProperties
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
}
|
||||
|
||||
expect(contacts_size(conf)).to(equal(10000))
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
expect {
|
||||
try CExceptionHelper.performSafely { config_push(conf).deallocate() }
|
||||
}
|
||||
.to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"])))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when checking size limits
|
||||
context("when checking size limits") {
|
||||
var numRecords: Int!
|
||||
var seed: Data!
|
||||
var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)!
|
||||
var edSK: [UInt8]!
|
||||
var error: UnsafeMutablePointer<CChar>?
|
||||
var conf: UnsafeMutablePointer<config_object>?
|
||||
|
||||
beforeEach {
|
||||
numRecords = 0
|
||||
seed = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
identity = try! Identity.generate(from: seed)
|
||||
edSK = identity.ed25519KeyPair.secretKey
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
error = nil
|
||||
conf = nil
|
||||
_ = contacts_init(&conf, &edSK, nil, 0, error)
|
||||
error?.deallocate()
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max empty records
|
||||
it("has not changed the max empty records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(2370))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max name only records
|
||||
it("has not changed the max name only records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: [.name]
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(796))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max name and profile pic only records
|
||||
it("has not changed the max name and profile pic only records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: [.name, .profile_pic]
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(290))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max filled records
|
||||
it("has not changed the max filled records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: .allProperties
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(236))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - generates config correctly
|
||||
|
||||
it("generates config correctly") {
|
||||
let createdTs: Int64 = 1680064059
|
||||
let nowTs: Int64 = Int64(Date().timeIntervalSince1970)
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
let contactPtr: UnsafeMutablePointer<contacts_contact>? = nil
|
||||
expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse())
|
||||
|
||||
expect(contacts_size(conf)).to(equal(0))
|
||||
|
||||
var contact2: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact2.name)).to(beEmpty())
|
||||
expect(String(libSessionVal: contact2.nickname)).to(beEmpty())
|
||||
expect(contact2.approved).to(beFalse())
|
||||
expect(contact2.approved_me).to(beFalse())
|
||||
expect(contact2.blocked).to(beFalse())
|
||||
expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty())
|
||||
expect(contact2.created).to(equal(0))
|
||||
expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT))
|
||||
expect(contact2.mute_until).to(equal(0))
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
pushData1.deallocate()
|
||||
|
||||
// Update the contact data
|
||||
contact2.name = "Joe".toLibSession()
|
||||
contact2.nickname = "Joey".toLibSession()
|
||||
contact2.approved = true
|
||||
contact2.approved_me = true
|
||||
contact2.created = createdTs
|
||||
contact2.notifications = CONVO_NOTIFY_ALL
|
||||
contact2.mute_until = nowTs + 1800
|
||||
|
||||
// Update the contact
|
||||
contacts_set(conf, &contact2)
|
||||
|
||||
// Ensure the contact details were updated
|
||||
var contact3: contacts_contact = contacts_contact()
|
||||
expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact3.name)).to(equal("Joe"))
|
||||
expect(String(libSessionVal: contact3.nickname)).to(equal("Joey"))
|
||||
expect(contact3.approved).to(beTrue())
|
||||
expect(contact3.approved_me).to(beTrue())
|
||||
expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty())
|
||||
expect(contact3.blocked).to(beFalse())
|
||||
expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId))
|
||||
expect(contact3.created).to(equal(createdTs))
|
||||
expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL))
|
||||
expect(contact2.mute_until).to(equal(nowTs + 1800))
|
||||
|
||||
|
||||
// Since we've made changes, we should need to push new config to the swarm, *and* should need
|
||||
// to dump the updated state:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed multiple fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed multiple fields here).
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
pushData2.deallocate()
|
||||
|
||||
// NB: Not going to check encrypted data and decryption here because that's general (not
|
||||
// specific to contacts) and is covered already in the user profile tests.
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData3.pointee.seqno).to(equal(1))
|
||||
pushData3.deallocate()
|
||||
|
||||
// Because we just called dump() above, to load up contacts2
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Ensure the contact details were updated
|
||||
var contact4: contacts_contact = contacts_contact()
|
||||
expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact4.name)).to(equal("Joe"))
|
||||
expect(String(libSessionVal: contact4.nickname)).to(equal("Joey"))
|
||||
expect(contact4.approved).to(beTrue())
|
||||
expect(contact4.approved_me).to(beTrue())
|
||||
expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty())
|
||||
expect(contact4.blocked).to(beFalse())
|
||||
expect(contact4.created).to(equal(createdTs))
|
||||
|
||||
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
|
||||
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
|
||||
var contact5: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact5.name)).to(beEmpty())
|
||||
expect(String(libSessionVal: contact5.nickname)).to(beEmpty())
|
||||
expect(contact5.approved).to(beFalse())
|
||||
expect(contact5.approved_me).to(beFalse())
|
||||
expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty())
|
||||
expect(contact5.blocked).to(beFalse())
|
||||
|
||||
// We're not setting any fields, but we should still keep a record of the session id
|
||||
contacts_set(conf2, &contact5)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(2))
|
||||
|
||||
// Check the merging
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
|
||||
var mergeSize: [Int] = [pushData4.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2)
|
||||
mergeHashes.forEach { $0?.deallocate() }
|
||||
pushData4.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData5.pointee.seqno).to(equal(2))
|
||||
pushData5.deallocate()
|
||||
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var sessionIds: [String] = []
|
||||
var nicknames: [String] = []
|
||||
expect(contacts_size(conf)).to(equal(2))
|
||||
|
||||
var contact6: contacts_contact = contacts_contact()
|
||||
let contactIterator: UnsafeMutablePointer<contacts_iterator> = contacts_iterator_new(conf)
|
||||
while !contacts_iterator_done(contactIterator, &contact6) {
|
||||
sessionIds.append(String(libSessionVal: contact6.session_id))
|
||||
nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)")
|
||||
contacts_iterator_advance(contactIterator)
|
||||
}
|
||||
contacts_iterator_free(contactIterator) // Need to free the iterator
|
||||
|
||||
expect(sessionIds.count).to(equal(2))
|
||||
expect(sessionIds.count).to(equal(contacts_size(conf)))
|
||||
expect(sessionIds.first).to(equal(definitelyRealId))
|
||||
expect(sessionIds.last).to(equal(anotherId))
|
||||
expect(nicknames.first).to(equal("Joey"))
|
||||
expect(nicknames.last).to(equal("(N/A)"))
|
||||
|
||||
// Conflict! Oh no!
|
||||
|
||||
// On client 1 delete a contact:
|
||||
contacts_erase(conf, definitelyRealId)
|
||||
|
||||
// Client 2 adds a new friend:
|
||||
let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222"
|
||||
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
|
||||
var contact7: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue())
|
||||
contact7.nickname = "Nickname 3".toLibSession()
|
||||
contact7.approved = true
|
||||
contact7.approved_me = true
|
||||
contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession()
|
||||
contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
contacts_set(conf2, &contact7)
|
||||
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData6.pointee.seqno).to(equal(3))
|
||||
|
||||
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData7.pointee.seqno).to(equal(3))
|
||||
|
||||
let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)!
|
||||
let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)!
|
||||
expect(pushData6Str).toNot(equal(pushData7Str))
|
||||
expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
|
||||
let fakeHash3a: String = "fakehash3a"
|
||||
var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated()
|
||||
let fakeHash3b: String = "fakehash3b"
|
||||
var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a)
|
||||
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b)
|
||||
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3b].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData7.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData7.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3a].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData6.pointee.config)]
|
||||
var mergeSize3: [Int] = [pushData6.pointee.config_len]
|
||||
expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1))
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
mergeHashes2.forEach { $0?.deallocate() }
|
||||
mergeHashes3.forEach { $0?.deallocate() }
|
||||
pushData6.deallocate()
|
||||
pushData7.deallocate()
|
||||
|
||||
let pushData8: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData8.pointee.seqno).to(equal(4))
|
||||
|
||||
let pushData9: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno))
|
||||
|
||||
let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)!
|
||||
let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)!
|
||||
expect(pushData8Str).to(equal(pushData9Str))
|
||||
expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3b, fakeHash3a]))
|
||||
expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3a, fakeHash3b]))
|
||||
|
||||
let fakeHash4: String = "fakeHash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4)
|
||||
config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4)
|
||||
pushData8.deallocate()
|
||||
pushData9.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Validate the changes
|
||||
var sessionIds2: [String] = []
|
||||
var nicknames2: [String] = []
|
||||
expect(contacts_size(conf)).to(equal(2))
|
||||
|
||||
var contact8: contacts_contact = contacts_contact()
|
||||
let contactIterator2: UnsafeMutablePointer<contacts_iterator> = contacts_iterator_new(conf)
|
||||
while !contacts_iterator_done(contactIterator2, &contact8) {
|
||||
sessionIds2.append(String(libSessionVal: contact8.session_id))
|
||||
nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)")
|
||||
contacts_iterator_advance(contactIterator2)
|
||||
}
|
||||
contacts_iterator_free(contactIterator2) // Need to free the iterator
|
||||
|
||||
expect(sessionIds2.count).to(equal(2))
|
||||
expect(sessionIds2.first).to(equal(anotherId))
|
||||
expect(sessionIds2.last).to(equal(thirdId))
|
||||
expect(nicknames2.first).to(equal("(N/A)"))
|
||||
expect(nicknames2.last).to(equal("Nickname 3"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
private static func createContact(
|
||||
for index: Int,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
rand: inout ARC4RandomNumberGenerator,
|
||||
maxing properties: [ContactProperty] = []
|
||||
) throws -> contacts_contact {
|
||||
let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())"
|
||||
let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count))
|
||||
var cSessionId: [CChar] = sessionId.cArray.nullTerminated()
|
||||
var contact: contacts_contact = contacts_contact()
|
||||
|
||||
guard contacts_get_or_construct(conf, &contact, &cSessionId) else {
|
||||
throw SessionUtilError.getOrConstructFailedUnexpectedly
|
||||
}
|
||||
|
||||
// Set the values to the maximum data that can fit
|
||||
properties.forEach { property in
|
||||
switch property {
|
||||
case .approved: contact.approved = true
|
||||
case .approved_me: contact.approved_me = true
|
||||
case .blocked: contact.blocked = true
|
||||
case .created: contact.created = Int64.max
|
||||
case .notifications: contact.notifications = CONVO_NOTIFY_MENTIONS_ONLY
|
||||
case .mute_until: contact.mute_until = Int64.max
|
||||
|
||||
case .name:
|
||||
contact.name = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength)
|
||||
.toHexString()
|
||||
.toLibSession()
|
||||
|
||||
case .nickname:
|
||||
contact.nickname = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength)
|
||||
.toHexString()
|
||||
.toLibSession()
|
||||
|
||||
case .profile_pic:
|
||||
contact.profile_pic = user_profile_pic(
|
||||
url: rand.nextBytes(count: SessionUtil.libSessionMaxProfileUrlByteLength)
|
||||
.toHexString()
|
||||
.toLibSession(),
|
||||
key: Data(rand.nextBytes(count: 32))
|
||||
.toLibSession()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension Array where Element == ConfigContactsSpec.ContactProperty {
|
||||
static var allProperties: [ConfigContactsSpec.ContactProperty] = ConfigContactsSpec.ContactProperty.allCases
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigConvoInfoVolatileSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("CONVO_INFO_VOLATILE") {
|
||||
it("generates config correctly") {
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse())
|
||||
expect(convo_info_volatile_size(conf)).to(equal(0))
|
||||
|
||||
var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId))
|
||||
expect(oneToOne2.last_read).to(equal(0))
|
||||
expect(oneToOne2.unread).to(beFalse())
|
||||
|
||||
// No need to sync a conversation with a default state
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Update the last read
|
||||
let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000))
|
||||
oneToOne2.last_read = nowTimestampMs
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
convo_info_volatile_set_1to1(conf, &oneToOne2)
|
||||
|
||||
var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId))
|
||||
.to(beFalse())
|
||||
expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(oneToOne3.last_read).to(equal(nowTimestampMs))
|
||||
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
let openGroupBaseUrl: String = "http://Example.ORG:5678"
|
||||
var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated()
|
||||
let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased()
|
||||
// ("http://Example.ORG:5678"
|
||||
// .lowercased()
|
||||
// .cArray +
|
||||
// [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count))
|
||||
// )
|
||||
let openGroupRoom: String = "SudokuRoom"
|
||||
var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated()
|
||||
let openGroupRoomResult: String = openGroupRoom.lowercased()
|
||||
// ("SudokuRoom"
|
||||
// .lowercased()
|
||||
// .cArray +
|
||||
// [CChar](repeating: 0, count: (65 - openGroupRoom.count))
|
||||
// )
|
||||
var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
.bytes
|
||||
var community1: convo_info_volatile_community = convo_info_volatile_community()
|
||||
expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue())
|
||||
expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult))
|
||||
expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult))
|
||||
expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community1.unread = true
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
convo_info_volatile_set_community(conf, &community1);
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(1))
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
pushData1.deallocate()
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true))
|
||||
expect(oneToOne4.last_read).to(equal(nowTimestampMs))
|
||||
expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId))
|
||||
expect(oneToOne4.unread).to(beFalse())
|
||||
|
||||
var community2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue())
|
||||
expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult))
|
||||
expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult))
|
||||
expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community2.unread = true
|
||||
|
||||
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
|
||||
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
|
||||
var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue())
|
||||
oneToOne5.unread = true
|
||||
convo_info_volatile_set_1to1(conf2, &oneToOne5)
|
||||
|
||||
let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
|
||||
var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue())
|
||||
legacyGroup2.last_read = (nowTimestampMs - 50)
|
||||
convo_info_volatile_set_legacy_group(conf2, &legacyGroup2)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData2.pointee.seqno).to(equal(2))
|
||||
|
||||
// Check the merging
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData2.pointee.config)]
|
||||
var mergeSize: [Int] = [pushData2.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2)
|
||||
pushData2.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
expect(convo_info_volatile_size(conf)).to(equal(4))
|
||||
expect(convo_info_volatile_size_1to1(conf)).to(equal(2))
|
||||
expect(convo_info_volatile_size_communities(conf)).to(equal(1))
|
||||
expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
var c2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it) {
|
||||
if convo_info_volatile_it_is_1to1(it, &c1) {
|
||||
seen.append("1-to-1: \(String(libSessionVal: c1.session_id))")
|
||||
}
|
||||
else if convo_info_volatile_it_is_community(it, &c2) {
|
||||
seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else if convo_info_volatile_it_is_legacy_group(it, &c3) {
|
||||
seen.append("cl: \(String(libSessionVal: c3.group_id))")
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_advance(it)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"1-to-1: 051111111111111111111111111111111111111111111111111111111111111111",
|
||||
"1-to-1: 055000000000000000000000000000000000000000000000000000000000000000",
|
||||
"og: http://example.org:5678/r/sudokuroom",
|
||||
"cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
]))
|
||||
}
|
||||
|
||||
let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cFourthId: [CChar] = fourthId.cArray.nullTerminated()
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
convo_info_volatile_erase_1to1(conf, &cFourthId)
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId)
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(convo_info_volatile_size(conf)).to(equal(3))
|
||||
expect(convo_info_volatile_size_1to1(conf)).to(equal(1))
|
||||
|
||||
// Check the single-type iterators:
|
||||
var seen1: [String?] = []
|
||||
var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it1) {
|
||||
expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue())
|
||||
|
||||
seen1.append(String(libSessionVal: c1.session_id))
|
||||
convo_info_volatile_iterator_advance(it1)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it1)
|
||||
expect(seen1).to(equal([
|
||||
"051111111111111111111111111111111111111111111111111111111111111111"
|
||||
]))
|
||||
|
||||
var seen2: [String?] = []
|
||||
var c2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it2) {
|
||||
expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue())
|
||||
|
||||
seen2.append(String(libSessionVal: c2.base_url))
|
||||
convo_info_volatile_iterator_advance(it2)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it2)
|
||||
expect(seen2).to(equal([
|
||||
"http://example.org:5678"
|
||||
]))
|
||||
|
||||
var seen3: [String?] = []
|
||||
var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it3) {
|
||||
expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue())
|
||||
|
||||
seen3.append(String(libSessionVal: c3.group_id))
|
||||
convo_info_volatile_iterator_advance(it3)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it3)
|
||||
expect(seen3).to(equal([
|
||||
"05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,589 +0,0 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigUserGroupsSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
it("parses community URLs correctly") {
|
||||
let result1 = SessionUtil.parseCommunity(url: [
|
||||
"https://example.com/",
|
||||
"SomeRoom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
].joined())
|
||||
let result2 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.COM/",
|
||||
"sOMErOOM?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result3 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.COM/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result4 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result5 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.com:443/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result6 = SessionUtil.parseCommunity(url: [
|
||||
"HTTP://EXAMPLE.com:80/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result7 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com:80/r/",
|
||||
"someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8"
|
||||
].joined())
|
||||
let result8 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com:80/r/",
|
||||
"someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo"
|
||||
].joined())
|
||||
|
||||
expect(result1?.server).to(equal("https://example.com"))
|
||||
expect(result1?.server).to(equal(result2?.server))
|
||||
expect(result1?.server).to(equal(result3?.server))
|
||||
expect(result1?.server).toNot(equal(result4?.server))
|
||||
expect(result4?.server).to(equal("http://example.com"))
|
||||
expect(result1?.server).to(equal(result5?.server))
|
||||
expect(result4?.server).to(equal(result6?.server))
|
||||
expect(result4?.server).to(equal(result7?.server))
|
||||
expect(result4?.server).to(equal(result8?.server))
|
||||
expect(result1?.room).to(equal("SomeRoom"))
|
||||
expect(result2?.room).to(equal("sOMErOOM"))
|
||||
expect(result3?.room).to(equal("someroom"))
|
||||
expect(result4?.room).to(equal("someroom"))
|
||||
expect(result5?.room).to(equal("someroom"))
|
||||
expect(result6?.room).to(equal("someroom"))
|
||||
expect(result7?.room).to(equal("someroom"))
|
||||
expect(result8?.room).to(equal("someroom"))
|
||||
expect(result1?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result2?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result3?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result4?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result5?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result6?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result7?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result8?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
}
|
||||
|
||||
context("USER_GROUPS") {
|
||||
it("generates config correctly") {
|
||||
let createdTs: Int64 = 1680064059
|
||||
let nowTs: Int64 = Int64(Date().timeIntervalSince1970)
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
let legacyGroup1: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup1?.pointee).to(beNil())
|
||||
expect(user_groups_size(conf)).to(equal(0))
|
||||
|
||||
let legacyGroup2: UnsafeMutablePointer<ugroups_legacy_group_info> = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup2.pointee).toNot(beNil())
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.session_id))
|
||||
.to(equal(definitelyRealId))
|
||||
expect(legacyGroup2.pointee.disappearing_timer).to(equal(0))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal(""))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal(""))
|
||||
expect(legacyGroup2.pointee.priority).to(equal(0))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal(""))
|
||||
expect(legacyGroup2.pointee.joined_at).to(equal(0))
|
||||
expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT))
|
||||
expect(legacyGroup2.pointee.mute_until).to(equal(0))
|
||||
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var membersSeen1: [String: Bool] = [:]
|
||||
var memberSessionId1: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin1: Bool = false
|
||||
let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) {
|
||||
membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt1)
|
||||
|
||||
expect(membersSeen1).to(beEmpty())
|
||||
|
||||
// No need to sync a conversation with a default state
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
expect(pushData1.pointee.config_len).to(equal(256))
|
||||
pushData1.deallocate()
|
||||
|
||||
let users: [String] = [
|
||||
"050000000000000000000000000000000000000000000000000000000000000000",
|
||||
"051111111111111111111111111111111111111111111111111111111111111111",
|
||||
"052222222222222222222222222222222222222222222222222222222222222222",
|
||||
"053333333333333333333333333333333333333333333333333333333333333333",
|
||||
"054444444444444444444444444444444444444444444444444444444444444444",
|
||||
"055555555555555555555555555555555555555555555555555555555555555555",
|
||||
"056666666666666666666666666666666666666666666666666666666666666666"
|
||||
]
|
||||
var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() }
|
||||
legacyGroup2.pointee.name = "Englishmen".toLibSession()
|
||||
legacyGroup2.pointee.disappearing_timer = 60
|
||||
legacyGroup2.pointee.joined_at = createdTs
|
||||
legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL
|
||||
legacyGroup2.pointee.mute_until = (nowTs + 3600)
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse())
|
||||
|
||||
// Flip to and from admin
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue())
|
||||
|
||||
expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue())
|
||||
expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue())
|
||||
|
||||
var membersSeen2: [String: Bool] = [:]
|
||||
var memberSessionId2: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin2: Bool = false
|
||||
let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) {
|
||||
membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt2)
|
||||
|
||||
expect(membersSeen2).to(equal([
|
||||
"050000000000000000000000000000000000000000000000000000000000000000": false,
|
||||
"051111111111111111111111111111111111111111111111111111111111111111": false,
|
||||
"052222222222222222222222222222222222222222222222222222222222222222": true
|
||||
]))
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
|
||||
let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)!
|
||||
let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)!
|
||||
|
||||
// Note: this isn't exactly what Session actually does here for legacy closed
|
||||
// groups (rather it uses X25519 keys) but for this test the distinction doesn't matter.
|
||||
legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession()
|
||||
legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession()
|
||||
legacyGroup2.pointee.priority = 3
|
||||
|
||||
expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString())
|
||||
.to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"))
|
||||
expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString())
|
||||
.to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"))
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
user_groups_set_free_legacy_group(conf, legacyGroup2)
|
||||
|
||||
let legacyGroup3: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup3?.pointee).toNot(beNil())
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
ugroups_legacy_group_free(legacyGroup3)
|
||||
|
||||
let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray
|
||||
var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated()
|
||||
var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated()
|
||||
var community1: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey))
|
||||
.to(beTrue())
|
||||
|
||||
expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case
|
||||
expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving
|
||||
expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community1.priority = 14
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
user_groups_set_community(conf, &community1)
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed two fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData3.pointee.seqno).to(equal(1))
|
||||
expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
pushData3.deallocate()
|
||||
|
||||
let currentHashes1: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf)
|
||||
expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len))
|
||||
.to(equal(["fakehash1"]))
|
||||
currentHashes1?.deallocate()
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
pushData4.deallocate()
|
||||
|
||||
let currentHashes2: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len))
|
||||
.to(equal(["fakehash1"]))
|
||||
currentHashes2?.deallocate()
|
||||
|
||||
expect(user_groups_size(conf2)).to(equal(2))
|
||||
expect(user_groups_size_communities(conf2)).to(equal(1))
|
||||
expect(user_groups_size_legacy_groups(conf2)).to(equal(1))
|
||||
|
||||
let legacyGroup4: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId)
|
||||
expect(legacyGroup4?.pointee).toNot(beNil())
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal(""))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal(""))
|
||||
expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId))
|
||||
expect(legacyGroup4?.pointee.priority).to(equal(3))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen"))
|
||||
expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs))
|
||||
expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL))
|
||||
expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600))
|
||||
|
||||
var membersSeen3: [String: Bool] = [:]
|
||||
var memberSessionId3: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin3: Bool = false
|
||||
let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) {
|
||||
membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt3)
|
||||
ugroups_legacy_group_free(legacyGroup4)
|
||||
|
||||
expect(membersSeen3).to(equal([
|
||||
"050000000000000000000000000000000000000000000000000000000000000000": false,
|
||||
"051111111111111111111111111111111111111111111111111111111111111111": false,
|
||||
"052222222222222222222222222222222222222222222222222222222222222222": true
|
||||
]))
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData5.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData5.deallocate()
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
|
||||
var c1: ugroups_legacy_group_info = ugroups_legacy_group_info()
|
||||
var c2: ugroups_community_info = ugroups_community_info()
|
||||
let it: OpaquePointer = user_groups_iterator_new(targetConf)
|
||||
|
||||
while !user_groups_iterator_done(it) {
|
||||
if user_groups_it_is_legacy_group(it, &c1) {
|
||||
var memberCount: Int = 0
|
||||
var adminCount: Int = 0
|
||||
ugroups_legacy_members_count(&c1, &memberCount, &adminCount)
|
||||
seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members")
|
||||
}
|
||||
else if user_groups_it_is_community(it, &c2) {
|
||||
seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else {
|
||||
seen.append("unknown")
|
||||
}
|
||||
|
||||
user_groups_iterator_advance(it)
|
||||
}
|
||||
|
||||
user_groups_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"community: http://example.org:5678/r/SudokuRoom",
|
||||
"legacy: Englishmen, 1 admins, 2 members"
|
||||
]))
|
||||
}
|
||||
|
||||
var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
|
||||
var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated()
|
||||
var community2: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678"))
|
||||
expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value
|
||||
expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(community2.priority).to(equal(14))
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData6.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData6.deallocate()
|
||||
|
||||
community2.room = "sudokuRoom".toLibSession() // Change capitalization
|
||||
user_groups_set_community(conf2, &community2)
|
||||
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData7.pointee.seqno).to(equal(2))
|
||||
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2)
|
||||
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
|
||||
.to(equal([fakeHash1]))
|
||||
|
||||
let currentHashes3: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len))
|
||||
.to(equal([fakeHash2]))
|
||||
currentHashes3?.deallocate()
|
||||
|
||||
var dump2: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump2Len: Int = 0
|
||||
config_dump(conf2, &dump2, &dump2Len)
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData8: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData8.pointee.seqno).to(equal(2))
|
||||
config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2)
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
var mergeHashes1: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData1: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData8.pointee.config)]
|
||||
var mergeSize1: [Int] = [pushData8.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1))
|
||||
pushData8.deallocate()
|
||||
|
||||
var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
|
||||
var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated()
|
||||
var community3: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(2))
|
||||
expect(user_groups_size_communities(conf)).to(equal(1))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
let legacyGroup5: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId)
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue())
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData9: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData9.pointee.seqno).to(equal(2))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData9.deallocate()
|
||||
|
||||
user_groups_set_free_legacy_group(conf2, legacyGroup5)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
|
||||
var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated()
|
||||
var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated()
|
||||
user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room)
|
||||
|
||||
let fakeHash3: String = "fakehash3"
|
||||
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
|
||||
let pushData10: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3)
|
||||
|
||||
expect(pushData10.pointee.seqno).to(equal(3))
|
||||
expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
|
||||
let currentHashes4: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len))
|
||||
.to(equal([fakeHash3]))
|
||||
currentHashes4?.deallocate()
|
||||
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData10.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData10.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(1))
|
||||
expect(user_groups_size_communities(conf)).to(equal(0))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
var prio: Int32 = 0
|
||||
var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated()
|
||||
var cBeanstalkPubkey: [UInt8] = Data(
|
||||
hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
|
||||
).cArray
|
||||
|
||||
["fee", "fi", "fo", "fum"].forEach { room in
|
||||
var cRoom: [CChar] = room.cArray.nullTerminated()
|
||||
prio += 1
|
||||
|
||||
var community4: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey))
|
||||
.to(beTrue())
|
||||
community4.priority = prio
|
||||
user_groups_set_community(conf, &community4)
|
||||
}
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(5))
|
||||
expect(user_groups_size_communities(conf)).to(equal(4))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
let fakeHash4: String = "fakehash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
let pushData11: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4)
|
||||
expect(pushData11.pointee.seqno).to(equal(4))
|
||||
expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3, fakeHash2, fakeHash1]))
|
||||
|
||||
// Load some obsolete ones in just to check that they get immediately obsoleted
|
||||
let fakeHash10: String = "fakehash10"
|
||||
let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated()
|
||||
let fakeHash11: String = "fakehash11"
|
||||
let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated()
|
||||
let fakeHash12: String = "fakehash12"
|
||||
let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated()
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [
|
||||
UnsafePointer(pushData10.pointee.config),
|
||||
UnsafePointer(pushData2.pointee.config),
|
||||
UnsafePointer(pushData7.pointee.config),
|
||||
UnsafePointer(pushData11.pointee.config)
|
||||
]
|
||||
var mergeSize3: [Int] = [
|
||||
pushData10.pointee.config_len,
|
||||
pushData2.pointee.config_len,
|
||||
pushData7.pointee.config_len,
|
||||
pushData11.pointee.config_len
|
||||
]
|
||||
expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4))
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
pushData2.deallocate()
|
||||
pushData7.deallocate()
|
||||
pushData10.deallocate()
|
||||
pushData11.deallocate()
|
||||
|
||||
let currentHashes5: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len))
|
||||
.to(equal([fakeHash4]))
|
||||
currentHashes5?.deallocate()
|
||||
|
||||
let pushData12: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData12.pointee.seqno).to(equal(4))
|
||||
expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len))
|
||||
.to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3]))
|
||||
pushData12.deallocate()
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
|
||||
var c1: ugroups_legacy_group_info = ugroups_legacy_group_info()
|
||||
var c2: ugroups_community_info = ugroups_community_info()
|
||||
let it: OpaquePointer = user_groups_iterator_new(targetConf)
|
||||
|
||||
while !user_groups_iterator_done(it) {
|
||||
if user_groups_it_is_legacy_group(it, &c1) {
|
||||
var memberCount: Int = 0
|
||||
var adminCount: Int = 0
|
||||
ugroups_legacy_members_count(&c1, &memberCount, &adminCount)
|
||||
|
||||
seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members")
|
||||
}
|
||||
else if user_groups_it_is_community(it, &c2) {
|
||||
seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else {
|
||||
seen.append("unknown")
|
||||
}
|
||||
|
||||
user_groups_iterator_advance(it)
|
||||
}
|
||||
|
||||
user_groups_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"community: http://jacksbeanstalk.org/r/fee",
|
||||
"community: http://jacksbeanstalk.org/r/fi",
|
||||
"community: http://jacksbeanstalk.org/r/fo",
|
||||
"community: http://jacksbeanstalk.org/r/fum",
|
||||
"legacy: Englishmen, 3 admins, 2 members"
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,414 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigUserProfileSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("USER_PROFILE") {
|
||||
it("generates config correctly") {
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// We don't need to push anything, since this is an empty config
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
// And we haven't changed anything so don't need to dump to db
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Since it's empty there shouldn't be a name.
|
||||
let namePtr: UnsafePointer<CChar>? = user_profile_get_name(conf)
|
||||
expect(namePtr).to(beNil())
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee).toNot(beNil())
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
expect(pushData1.pointee.config_len).to(equal(256))
|
||||
|
||||
let encDomain: [CChar] = "UserProfile"
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile"))
|
||||
|
||||
var toPushDecSize: Int = 0
|
||||
let toPushDecrypted: UnsafeMutablePointer<UInt8>? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize)
|
||||
let prefixPadding: String = (0..<193)
|
||||
.map { _ in "\0" }
|
||||
.joined()
|
||||
expect(toPushDecrypted).toNot(beNil())
|
||||
expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead
|
||||
expect(String(pointer: toPushDecrypted, length: toPushDecSize))
|
||||
.to(equal("\(prefixPadding)d1:#i0e1:&de1:<le1:=dee"))
|
||||
pushData1.deallocate()
|
||||
toPushDecrypted?.deallocate()
|
||||
|
||||
// This should also be unset:
|
||||
let pic: user_profile_pic = user_profile_get_pic(conf)
|
||||
expect(String(libSessionVal: pic.url)).to(beEmpty())
|
||||
|
||||
// Now let's go set a profile name and picture:
|
||||
expect(user_profile_set_name(conf, "Kallie")).to(equal(0))
|
||||
let p: user_profile_pic = user_profile_pic(
|
||||
url: "http://example.org/omg-pic-123.bmp".toLibSession(),
|
||||
key: "secret78901234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
)
|
||||
expect(user_profile_set_pic(conf, p)).to(equal(0))
|
||||
user_profile_set_nts_priority(conf, 9)
|
||||
|
||||
// Retrieve them just to make sure they set properly:
|
||||
let namePtr2: UnsafePointer<CChar>? = user_profile_get_name(conf)
|
||||
expect(namePtr2).toNot(beNil())
|
||||
expect(String(cString: namePtr2!)).to(equal("Kallie"))
|
||||
|
||||
let pic2: user_profile_pic = user_profile_get_pic(conf);
|
||||
expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp"))
|
||||
expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength))
|
||||
.to(equal("secret78901234567890123456789012".data(using: .utf8)))
|
||||
expect(user_profile_get_nts_priority(conf)).to(equal(9))
|
||||
|
||||
// Since we've made changes, we should need to push new config to the swarm, *and* should need
|
||||
// to dump the updated state:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed two fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
|
||||
// Note: This hex value differs from the value in the library tests because
|
||||
// it looks like the library has an "end of cell mark" character added at the
|
||||
// end (0x07 or '0007') so we need to manually add it to work
|
||||
let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965")
|
||||
.bytes
|
||||
// The data to be actually pushed, expanded like this to make it somewhat human-readable:
|
||||
let expPush1Decrypted: [UInt8] = ["""
|
||||
d
|
||||
1:#i1e
|
||||
1:& d
|
||||
1:+ i9e
|
||||
1:n 6:Kallie
|
||||
1:p 34:http://example.org/omg-pic-123.bmp
|
||||
1:q 32:secret78901234567890123456789012
|
||||
e
|
||||
1:< l
|
||||
l i0e 32:
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability
|
||||
.bytes,
|
||||
expHash0,
|
||||
"""
|
||||
de e
|
||||
e
|
||||
1:= d
|
||||
1:+ 0:
|
||||
1:n 0:
|
||||
1:p 0:
|
||||
1:q 0:
|
||||
e
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability
|
||||
.bytes
|
||||
].flatMap { $0 }
|
||||
let expPush1Encrypted: [UInt8] = Data(hex: [
|
||||
"9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f",
|
||||
"1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84",
|
||||
"aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868",
|
||||
"2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d",
|
||||
"e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656",
|
||||
"056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea",
|
||||
"49bf122762d7bc1d6d9c02f6d54f8384"
|
||||
].joined()).bytes
|
||||
|
||||
let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)!
|
||||
let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)!
|
||||
expect(pushData2Str).to(equal(expPush1EncryptedStr))
|
||||
|
||||
// Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data)
|
||||
var pushData2DecSize: Int = 0
|
||||
let pushData2Decrypted: UnsafeMutablePointer<UInt8>? = config_decrypt(
|
||||
pushData2.pointee.config,
|
||||
pushData2.pointee.config_len,
|
||||
edSK,
|
||||
encDomain,
|
||||
&pushData2DecSize
|
||||
)
|
||||
let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count))
|
||||
.map { _ in "\0" }
|
||||
.joined()
|
||||
expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead
|
||||
|
||||
let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)!
|
||||
let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii)
|
||||
.map { "\(prefixPadding2)\($0)" }!
|
||||
expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr))
|
||||
pushData2Decrypted?.deallocate()
|
||||
|
||||
// We haven't dumped, so still need to dump:
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
// We did call push, but we haven't confirmed it as stored yet, so this will still return true:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
// (in a real client we'd now store this to disk)
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
let expDump1: [CChar] = [
|
||||
"""
|
||||
d
|
||||
1:! i2e
|
||||
1:$ \(expPush1Decrypted.count):
|
||||
"""
|
||||
.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) },
|
||||
expPush1Decrypted
|
||||
.map { CChar(bitPattern: $0) },
|
||||
"""
|
||||
1:(0:
|
||||
1:)le
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
].flatMap { $0 }
|
||||
expect(String(pointer: dump1, length: dump1Len, encoding: .ascii))
|
||||
.to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii)))
|
||||
dump1?.deallocate()
|
||||
|
||||
// So now imagine we got back confirmation from the swarm that the push has been stored:
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
pushData2.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump
|
||||
|
||||
var dump2: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump2Len: Int = 0
|
||||
config_dump(conf, &dump2, &dump2Len)
|
||||
|
||||
let expDump2: [CChar] = [
|
||||
"""
|
||||
d
|
||||
1:! i0e
|
||||
1:$ \(expPush1Decrypted.count):
|
||||
"""
|
||||
.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) },
|
||||
expPush1Decrypted
|
||||
.map { CChar(bitPattern: $0) },
|
||||
"""
|
||||
1:(9:fakehash1
|
||||
1:)le
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
].flatMap { $0 }
|
||||
expect(String(pointer: dump2, length: dump2Len, encoding: .ascii))
|
||||
.to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii)))
|
||||
dump2?.deallocate()
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Now we're going to set up a second, competing config object (in the real world this would be
|
||||
// another Session client somewhere).
|
||||
|
||||
// Start with an empty config, as above:
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
error2?.deallocate()
|
||||
|
||||
// Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into
|
||||
// conf2:
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash1].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [expPush1Encrypted].unsafeCopy()
|
||||
var mergeSize: [Int] = [expPush1Encrypted.count]
|
||||
expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
mergeHashes.forEach { $0?.deallocate() }
|
||||
mergeData.forEach { $0?.deallocate() }
|
||||
|
||||
// Our state has changed, so we need to dump:
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
var dump3: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump3Len: Int = 0
|
||||
config_dump(conf2, &dump3, &dump3Len)
|
||||
// (store in db)
|
||||
dump3?.deallocate()
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
// We *don't* need to push: even though we updated, all we did is update to the merged data (and
|
||||
// didn't have any sort of merge conflict needed):
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Now let's create a conflicting update:
|
||||
|
||||
// Change the name on both clients:
|
||||
user_profile_set_name(conf, "Nibbler")
|
||||
user_profile_set_name(conf2, "Raz")
|
||||
|
||||
// And, on conf2, we're also going to change the profile pic:
|
||||
let p2: user_profile_pic = user_profile_pic(
|
||||
url: "http://new.example.com/pic".toLibSession(),
|
||||
key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
)
|
||||
user_profile_set_pic(conf2, p2)
|
||||
|
||||
user_profile_set_nts_expiry(conf2, 86400)
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 0)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0))
|
||||
user_profile_set_blinded_msgreqs(conf2, -1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
// Both have changes, so push need a push
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change
|
||||
config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2)
|
||||
|
||||
let fakeHash3: String = "fakehash3"
|
||||
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change
|
||||
config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3)
|
||||
|
||||
var dump4: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump4Len: Int = 0
|
||||
config_dump(conf, &dump4, &dump4Len);
|
||||
var dump5: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump5Len: Int = 0
|
||||
config_dump(conf2, &dump5, &dump5Len);
|
||||
// (store in db)
|
||||
dump4?.deallocate()
|
||||
dump5?.deallocate()
|
||||
|
||||
// Since we set different things, we're going to get back different serialized data to be
|
||||
// pushed:
|
||||
let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii)
|
||||
let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii)
|
||||
expect(pushData3Str).toNot(equal(pushData4Str))
|
||||
|
||||
// Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client
|
||||
// also fetches new messages and pulls down the other client's `seqno=2` value.
|
||||
|
||||
// Feed the new config into each other. (This array could hold multiple configs if we pulled
|
||||
// down more than one).
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData3.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData3.pointee.config_len]
|
||||
expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
pushData3.deallocate()
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
|
||||
var mergeSize3: [Int] = [pushData4.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1))
|
||||
pushData4.deallocate()
|
||||
|
||||
// Now after the merge we *will* want to push from both client, since both will have generated a
|
||||
// merge conflict update (with seqno = 3).
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData5.pointee.seqno).to(equal(3))
|
||||
expect(pushData6.pointee.seqno).to(equal(3))
|
||||
|
||||
// They should have resolved the conflict to the same thing:
|
||||
expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler"))
|
||||
expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler"))
|
||||
// (Note that they could have also both resolved to "Raz" here, but the hash of the serialized
|
||||
// message just happens to have a higher hash -- and thus gets priority -- for this particular
|
||||
// test).
|
||||
|
||||
// Since only one of them set a profile pic there should be no conflict there:
|
||||
let pic3: user_profile_pic = user_profile_get_pic(conf)
|
||||
expect(pic3.url).toNot(beNil())
|
||||
expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic"))
|
||||
expect(pic3.key).toNot(beNil())
|
||||
expect(Data(libSessionVal: pic3.key, count: 32).toHexString())
|
||||
.to(equal("7177657274007975696f31323334353637383930313233343536373839303132"))
|
||||
let pic4: user_profile_pic = user_profile_get_pic(conf2)
|
||||
expect(pic4.url).toNot(beNil())
|
||||
expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic"))
|
||||
expect(pic4.key).toNot(beNil())
|
||||
expect(Data(libSessionVal: pic4.key, count: 32).toHexString())
|
||||
.to(equal("7177657274007975696f31323334353637383930313233343536373839303132"))
|
||||
expect(user_profile_get_nts_priority(conf)).to(equal(9))
|
||||
expect(user_profile_get_nts_priority(conf2)).to(equal(9))
|
||||
expect(user_profile_get_nts_expiry(conf)).to(equal(86400))
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1))
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
let fakeHash4: String = "fakehash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
let fakeHash5: String = "fakehash5"
|
||||
var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4)
|
||||
config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5)
|
||||
pushData5.deallocate()
|
||||
pushData6.deallocate()
|
||||
|
||||
var dump6: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump6Len: Int = 0
|
||||
config_dump(conf, &dump6, &dump6Len);
|
||||
var dump7: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump7Len: Int = 0
|
||||
config_dump(conf2, &dump7, &dump7Len);
|
||||
// (store in db)
|
||||
dump6?.deallocate()
|
||||
dump7?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Wouldn't do this in a normal session but doing it here to properly clean up
|
||||
// after the test
|
||||
conf?.deallocate()
|
||||
conf2?.deallocate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -12,13 +12,12 @@ import Quick
|
|||
import Nimble
|
||||
|
||||
class SessionUtilSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - SessionUtil
|
||||
describe("SessionUtil") {
|
||||
// MARK: - Parsing URLs
|
||||
|
||||
// MARK: -- when parsing a community url
|
||||
context("when parsing a community url") {
|
||||
// MARK: ---- handles the example urls correctly
|
||||
it("handles the example urls correctly") {
|
||||
let validUrls: [String] = [
|
||||
[
|
||||
|
@ -82,6 +81,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(processedPublicKeys).to(equal(expectedPublicKeys))
|
||||
}
|
||||
|
||||
// MARK: ---- handles the r prefix if present
|
||||
it("handles the r prefix if present") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -95,6 +95,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
|
||||
}
|
||||
|
||||
// MARK: ---- fails if no scheme is provided
|
||||
it("fails if no scheme is provided") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -108,6 +109,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if there is no room
|
||||
it("fails if there is no room") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -121,6 +123,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if there is no public key parameter
|
||||
it("fails if there is no public key parameter") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: "https://sessionopengroup.co/r/main"
|
||||
|
@ -131,6 +134,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if the public key parameter is not 64 characters
|
||||
it("fails if the public key parameter is not 64 characters") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -144,6 +148,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if the public key parameter is not a hex string
|
||||
it("fails if the public key parameter is not a hex string") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -157,6 +162,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the same TLS
|
||||
it("maintains the same TLS") {
|
||||
let server1 = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -175,6 +181,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(server2).to(equal("https://sessionopengroup.co"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the same port
|
||||
it("maintains the same port") {
|
||||
let server1 = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -194,14 +201,15 @@ class SessionUtilSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Generating URLs
|
||||
|
||||
// MARK: -- when generating a url
|
||||
context("when generating a url") {
|
||||
// MARK: ---- generates the url correctly
|
||||
it("generates the url correctly") {
|
||||
expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
.to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the casing provided
|
||||
it("maintains the casing provided") {
|
||||
expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
.to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
|
|
|
@ -9,16 +9,15 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
// MARK: - String
|
||||
|
||||
override class func spec() {
|
||||
// MARK: - a String
|
||||
describe("a String") {
|
||||
// MARK: -- can convert to a cArray
|
||||
it("can convert to a cArray") {
|
||||
expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51]))
|
||||
}
|
||||
|
||||
// MARK: -- can contain emoji
|
||||
it("can contain emoji") {
|
||||
let original: String = "Hi 👋"
|
||||
let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession()
|
||||
|
@ -27,7 +26,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(original))
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a pointer and length
|
||||
context("when initialised with a pointer and length") {
|
||||
// MARK: ---- returns null when given a null pointer
|
||||
it("returns null when given a null pointer") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -37,6 +38,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns a truncated string when given an incorrect length
|
||||
it("returns a truncated string when given an incorrect length") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -46,6 +48,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns a string when valid
|
||||
it("returns a string when valid") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -56,7 +59,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a libSession value
|
||||
context("when initialised with a libSession value") {
|
||||
// MARK: ---- returns a string when valid and has no fixed length
|
||||
it("returns a string when valid and has no fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -64,6 +69,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Test"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns a string when valid and has a fixed length
|
||||
it("returns a string when valid and has a fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||
let result = String(libSessionVal: value, fixedLength: 5)
|
||||
|
@ -71,6 +77,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te\0st"))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates at the first null termination character when fixed length is none
|
||||
it("truncates at the first null termination character when fixed length is none") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -78,6 +85,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- parses successfully if there is no null termination character and there is no fixed length
|
||||
it("parses successfully if there is no null termination character and there is no fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -85,6 +93,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("TestT"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty string when given a value only containing null termination characters with a fixed length
|
||||
it("returns an empty string when given a value only containing null termination characters with a fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, fixedLength: 5)
|
||||
|
@ -92,6 +101,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(""))
|
||||
}
|
||||
|
||||
// MARK: ---- defaults the fixed length value to none
|
||||
it("defaults the fixed length value to none") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0)
|
||||
let result = String(libSessionVal: value)
|
||||
|
@ -99,6 +109,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty string when null and not set to return null
|
||||
it("returns an empty string when null and not set to return null") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, nullIfEmpty: false)
|
||||
|
@ -106,6 +117,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(""))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when specified and empty
|
||||
it("returns null when specified and empty") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, nullIfEmpty: true)
|
||||
|
@ -113,6 +125,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- defaults the null if empty flag to false
|
||||
it("defaults the null if empty flag to false") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value)
|
||||
|
@ -121,7 +134,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when converting to a libSession value
|
||||
context("when converting to a libSession value") {
|
||||
// MARK: ---- succeeeds with a valid value
|
||||
it("succeeeds with a valid value") {
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession()
|
||||
expect(result.0).to(equal(84))
|
||||
|
@ -131,6 +146,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates when too long
|
||||
it("truncates when too long") {
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession()
|
||||
expect(result.0).to(equal(84))
|
||||
|
@ -140,7 +156,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(84))
|
||||
}
|
||||
|
||||
// MARK: ---- when optional
|
||||
context("when optional") {
|
||||
// MARK: ------ returns empty when null
|
||||
context("returns empty when null") {
|
||||
let value: String? = nil
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||
|
@ -152,6 +170,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ------ returns a libSession value when not null
|
||||
context("returns a libSession value when not null") {
|
||||
let value: String? = "Test"
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||
|
@ -167,13 +186,15 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
describe("Data") {
|
||||
// MARK: -- can convert to a cArray
|
||||
it("can convert to a cArray") {
|
||||
expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a libSession value
|
||||
context("when initialised with a libSession value") {
|
||||
// MARK: ---- returns truncated data when given the wrong length
|
||||
it("returns truncated data when given the wrong length") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||
let result = Data(libSessionVal: value, count: 2)
|
||||
|
@ -181,6 +202,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([1, 2])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns data when valid
|
||||
it("returns data when valid") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||
let result = Data(libSessionVal: value, count: 5)
|
||||
|
@ -188,6 +210,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([1, 2, 3, 4, 5])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns data when all bytes are zero and nullIfEmpty is false
|
||||
it("returns data when all bytes are zero and nullIfEmpty is false") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false)
|
||||
|
@ -195,6 +218,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([0, 0, 0, 0, 0])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when all bytes are zero and nullIfEmpty is true
|
||||
it("returns null when all bytes are zero and nullIfEmpty is true") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true)
|
||||
|
@ -203,7 +227,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when converting to a libSession value
|
||||
context("when converting to a libSession value") {
|
||||
// MARK: ---- succeeeds with a valid value
|
||||
it("succeeeds with a valid value") {
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession()
|
||||
expect(result.0).to(equal(1))
|
||||
|
@ -213,6 +239,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(5))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates when too long
|
||||
it("truncates when too long") {
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession()
|
||||
expect(result.0).to(equal(1))
|
||||
|
@ -222,6 +249,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(1))
|
||||
}
|
||||
|
||||
// MARK: ---- fills with empty data when too short
|
||||
context("fills with empty data when too short") {
|
||||
let value: Data? = Data([1, 2, 3])
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -233,7 +261,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ---- when optional
|
||||
context("when optional") {
|
||||
// MARK: ------ returns null when null
|
||||
context("returns null when null") {
|
||||
let value: Data? = nil
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -245,6 +275,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ------ returns a libSession value when not null
|
||||
context("returns a libSession value when not null") {
|
||||
let value: Data? = Data([1, 2, 3, 4, 5])
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -259,10 +290,11 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Array
|
||||
|
||||
// MARK: - an Array
|
||||
describe("an Array") {
|
||||
// MARK: -- when initialised with a 2D C array
|
||||
context("when initialised with a 2D C array") {
|
||||
// MARK: ---- returns the correct array
|
||||
it("returns the correct array") {
|
||||
var test: [CChar] = (
|
||||
"Test1".cArray.nullTerminated() +
|
||||
|
@ -278,6 +310,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"]))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty array if given one
|
||||
it("returns an empty array if given one") {
|
||||
var test = [CChar]()
|
||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||
|
@ -289,6 +322,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal([]))
|
||||
}
|
||||
|
||||
// MARK: ---- handles empty strings without issues
|
||||
it("handles empty strings without issues") {
|
||||
var test: [CChar] = (
|
||||
"Test1".cArray.nullTerminated() +
|
||||
|
@ -304,10 +338,12 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(["Test1", "", "Test2"]))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when given a null pointer
|
||||
it("returns null when given a null pointer") {
|
||||
expect([String](pointer: nil, count: 5)).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when given a null count
|
||||
it("returns null when given a null count") {
|
||||
var test: [CChar] = "Test1".cArray.nullTerminated()
|
||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||
|
@ -319,19 +355,23 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns the default value if given null values
|
||||
it("returns the default value if given null values") {
|
||||
expect([String](pointer: nil, count: 5, defaultValue: ["Test"]))
|
||||
.to(equal(["Test"]))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- when adding a null terminated character
|
||||
context("when adding a null terminated character") {
|
||||
// MARK: ---- adds a null termination character when not present
|
||||
it("adds a null termination character when not present") {
|
||||
let value: [CChar] = [1, 2, 3, 4, 5]
|
||||
|
||||
expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0]))
|
||||
}
|
||||
|
||||
// MARK: ---- adds nothing when already present
|
||||
it("adds nothing when already present") {
|
||||
let value: [CChar] = [1, 2, 3, 4, 0]
|
||||
|
||||
|
|
|
@ -9,22 +9,19 @@ import Quick
|
|||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
import AVFoundation
|
||||
|
||||
class BatchRequestInfoSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let stringValue: String
|
||||
}
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
// MARK: - Spec
|
||||
@TestState var request: OpenGroupAPI.BatchRequest!
|
||||
|
||||
override func spec() {
|
||||
// MARK: - BatchRequest.Child
|
||||
// MARK: - a BatchRequest.Child
|
||||
|
||||
describe("a BatchRequest.Child") {
|
||||
var request: OpenGroupAPI.BatchRequest!
|
||||
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- successfully encodes a string body
|
||||
it("successfully encodes a string body") {
|
||||
request = OpenGroupAPI.BatchRequest(
|
||||
requests: [
|
||||
|
@ -53,6 +50,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))
|
||||
}
|
||||
|
||||
// MARK: ---- successfully encodes a byte body
|
||||
it("successfully encodes a byte body") {
|
||||
request = OpenGroupAPI.BatchRequest(
|
||||
requests: [
|
||||
|
@ -81,6 +79,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: ---- successfully encodes a JSON body
|
||||
it("successfully encodes a JSON body") {
|
||||
request = OpenGroupAPI.BatchRequest(
|
||||
requests: [
|
||||
|
@ -109,6 +108,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"]))
|
||||
}
|
||||
|
||||
// MARK: ---- strips authentication headers
|
||||
it("strips authentication headers") {
|
||||
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
||||
method: .get,
|
||||
|
@ -149,6 +149,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- does not strip non authentication headers
|
||||
it("does not strip non authentication headers") {
|
||||
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
||||
method: .get,
|
||||
|
@ -185,3 +186,9 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Codable, Equatable {
|
||||
let stringValue: String
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class CapabilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - Capabilities
|
||||
describe("Capabilities") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- assigns values correctly
|
||||
it("assigns values correctly") {
|
||||
let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(
|
||||
capabilities: [.sogs],
|
||||
|
@ -34,8 +35,11 @@ class CapabilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - a Capability
|
||||
describe("a Capability") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- succeeeds with a valid case
|
||||
it("succeeeds with a valid case") {
|
||||
let capability: Capability.Variant = Capability.Variant(
|
||||
from: "sogs"
|
||||
|
@ -44,6 +48,7 @@ class CapabilitiesSpec: QuickSpec {
|
|||
expect(capability).to(equal(.sogs))
|
||||
}
|
||||
|
||||
// MARK: ---- wraps an unknown value in the unsupported case
|
||||
it("wraps an unknown value in the unsupported case") {
|
||||
let capability: Capability.Variant = Capability.Variant(
|
||||
from: "test"
|
||||
|
@ -53,18 +58,23 @@ class CapabilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when accessing the rawValue
|
||||
context("when accessing the rawValue") {
|
||||
// MARK: ---- provides known cases exactly
|
||||
it("provides known cases exactly") {
|
||||
expect(Capability.Variant.sogs.rawValue).to(equal("sogs"))
|
||||
expect(Capability.Variant.blind.rawValue).to(equal("blind"))
|
||||
}
|
||||
|
||||
// MARK: ---- provides the wrapped value for unsupported cases
|
||||
it("provides the wrapped value for unsupported cases") {
|
||||
expect(Capability.Variant.unsupported("test").rawValue).to(equal("test"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- when Decoding
|
||||
context("when Decoding") {
|
||||
// MARK: ---- decodes known cases exactly
|
||||
it("decodes known cases exactly") {
|
||||
expect(
|
||||
try? JSONDecoder().decode(
|
||||
|
@ -82,6 +92,7 @@ class CapabilitiesSpec: QuickSpec {
|
|||
.to(equal(.blind))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes unknown cases into the unsupported case
|
||||
it("decodes unknown cases into the unsupported case") {
|
||||
expect(
|
||||
try? JSONDecoder().decode(
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class OpenGroupSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - an Open Group
|
||||
describe("an Open Group") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- generates the id
|
||||
it("generates the id") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -34,7 +35,9 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when describing
|
||||
context("when describing") {
|
||||
// MARK: ---- includes relevant information
|
||||
it("includes relevant information") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -57,7 +60,9 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when describing in debug
|
||||
context("when describing in debug") {
|
||||
// MARK: ---- includes relevant information
|
||||
it("includes relevant information") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -80,15 +85,19 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when generating an id
|
||||
context("when generating an id") {
|
||||
// MARK: ---- generates correctly
|
||||
it("generates correctly") {
|
||||
expect(OpenGroup.idFor(roomToken: "room", server: "server")).to(equal("server.room"))
|
||||
}
|
||||
|
||||
// MARK: ---- converts the server to lowercase
|
||||
it("converts the server to lowercase") {
|
||||
expect(OpenGroup.idFor(roomToken: "room", server: "SeRVeR")).to(equal("server.room"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the casing of the roomToken
|
||||
it("maintains the casing of the roomToken") {
|
||||
expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM"))
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class RoomPollInfoSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a RoomPollInfo
|
||||
describe("a RoomPollInfo") {
|
||||
// MARK: -- when initializing with a room
|
||||
context("when initializing with a room") {
|
||||
// MARK: ---- copies all the relevant values across
|
||||
it("copies all the relevant values across") {
|
||||
let room: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
||||
token: "testToken",
|
||||
|
@ -60,7 +61,9 @@ class RoomPollInfoSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- defaults admin and moderator values to false if omitted
|
||||
it("defaults admin and moderator values to false if omitted") {
|
||||
let roomPollInfoJson: String = """
|
||||
{
|
||||
|
@ -87,6 +90,7 @@ class RoomPollInfoSpec: QuickSpec {
|
|||
expect(result.globalModerator).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- sets the admin and moderator values when provided
|
||||
it("sets the admin and moderator values when provided") {
|
||||
let roomPollInfoJson: String = """
|
||||
{
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class RoomSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Room
|
||||
describe("a Room") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- defaults admin and moderator values to false if omitted
|
||||
it("defaults admin and moderator values to false if omitted") {
|
||||
let roomJson: String = """
|
||||
{
|
||||
|
@ -52,6 +53,7 @@ class RoomSpec: QuickSpec {
|
|||
expect(result.globalModerator).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- sets the admin and moderator values when provided
|
||||
it("sets the admin and moderator values when provided") {
|
||||
let roomJson: String = """
|
||||
{
|
||||
|
|
|
@ -9,18 +9,10 @@ import SessionUtilitiesKit
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SOGSMessageSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
describe("a SOGSMessage") {
|
||||
var messageJson: String!
|
||||
var messageData: Data!
|
||||
var decoder: JSONDecoder!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
messageJson = """
|
||||
@TestState var messageJson: String! = """
|
||||
{
|
||||
"id": 123,
|
||||
"session_id": "05\(TestConstants.publicKey)",
|
||||
|
@ -33,20 +25,22 @@ class SOGSMessageSpec: QuickSpec {
|
|||
"signature": "VGVzdFNpZ25hdHVyZQ=="
|
||||
}
|
||||
"""
|
||||
messageData = messageJson.data(using: .utf8)!
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
@TestState var messageData: Data! = messageJson.data(using: .utf8)!
|
||||
@TestState var mockCrypto: MockCrypto! = MockCrypto()
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
crypto: mockCrypto
|
||||
)
|
||||
decoder = JSONDecoder()
|
||||
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||
}
|
||||
|
||||
afterEach {
|
||||
mockCrypto = nil
|
||||
}
|
||||
@TestState var decoder: JSONDecoder! = {
|
||||
let result = JSONDecoder()
|
||||
result.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - a SOGSMessage
|
||||
describe("a SOGSMessage") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- defaults the whisper values to false
|
||||
it("defaults the whisper values to false") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -63,7 +57,9 @@ class SOGSMessageSpec: QuickSpec {
|
|||
expect(result?.whisperMods).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- and there is no content
|
||||
context("and there is no content") {
|
||||
// MARK: ------ does not need a sender
|
||||
it("does not need a sender") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -84,7 +80,9 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- and there is content
|
||||
context("and there is content") {
|
||||
// MARK: ------ errors if there is no sender
|
||||
it("errors if there is no sender") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -106,6 +104,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ------ errors if the data is not a base64 encoded string
|
||||
it("errors if the data is not a base64 encoded string") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -128,6 +127,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ------ errors if the signature is not a base64 encoded string
|
||||
it("errors if the signature is not a base64 encoded string") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -150,6 +150,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ------ errors if the dependencies are not provided to the JSONDecoder
|
||||
it("errors if the dependencies are not provided to the JSONDecoder") {
|
||||
decoder = JSONDecoder()
|
||||
|
||||
|
@ -159,6 +160,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ------ errors if the session_id value is not valid
|
||||
it("errors if the session_id value is not valid") {
|
||||
messageJson = """
|
||||
{
|
||||
|
@ -181,7 +183,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
|
||||
// MARK: ------ that is blinded
|
||||
context("that is blinded") {
|
||||
beforeEach {
|
||||
messageJson = """
|
||||
|
@ -200,6 +202,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
messageData = messageJson.data(using: .utf8)!
|
||||
}
|
||||
|
||||
// MARK: -------- succeeds if it succeeds verification
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -213,6 +216,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: -------- provides the correct values as parameters
|
||||
it("provides the correct values as parameters") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -234,6 +238,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
})
|
||||
}
|
||||
|
||||
// MARK: -------- throws if it fails verification
|
||||
it("throws if it fails verification") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -248,7 +253,9 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ------ that is unblinded
|
||||
context("that is unblinded") {
|
||||
// MARK: -------- succeeds if it succeeds verification
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
|
@ -260,6 +267,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
.toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: -------- provides the correct values as parameters
|
||||
it("provides the correct values as parameters") {
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
|
@ -279,6 +287,7 @@ class SOGSMessageSpec: QuickSpec {
|
|||
})
|
||||
}
|
||||
|
||||
// MARK: -------- throws if it fails verification
|
||||
it("throws if it fails verification") {
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SendDirectMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SendDirectMessageRequest
|
||||
describe("a SendDirectMessageRequest") {
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest(
|
||||
message: "TestData".data(using: .utf8)!
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SendMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SendMessageRequest
|
||||
describe("a SendMessageRequest") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- defaults the optional values to nil
|
||||
it("defaults the optional values to nil") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -25,7 +26,9 @@ class SendMessageRequestSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -41,6 +44,7 @@ class SendMessageRequestSpec: QuickSpec {
|
|||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||
}
|
||||
|
||||
// MARK: ---- encodes the signature as a base64 string
|
||||
it("encodes the signature as a base64 string") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class UpdateMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("a UpdateMessageRequest") {
|
||||
override class func spec() {
|
||||
// MARK: - an UpdateMessageRequest
|
||||
describe("an UpdateMessageRequest") {
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -26,6 +27,7 @@ class UpdateMessageRequestSpec: QuickSpec {
|
|||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||
}
|
||||
|
||||
// MARK: ---- encodes the signature as a base64 string
|
||||
it("encodes the signature as a base64 string") {
|
||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
|
|
@ -13,38 +13,16 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class OpenGroupAPISpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockNetwork: MockNetwork!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
var disposables: [AnyCancellable] = []
|
||||
|
||||
var error: Error?
|
||||
|
||||
describe("an OpenGroupAPI") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockNetwork = MockNetwork()
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
network: mockNetwork,
|
||||
crypto: mockCrypto,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
],
|
||||
initialData: { db in
|
||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db)
|
||||
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db)
|
||||
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
|
||||
|
@ -66,13 +44,18 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
).insert(db)
|
||||
try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db)
|
||||
}
|
||||
|
||||
mockCrypto
|
||||
)
|
||||
@TestState var mockNetwork: MockNetwork! = MockNetwork()
|
||||
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||
initialSetup: { crypto in
|
||||
crypto
|
||||
.when { try $0.perform(.hash(message: anyArray(), outputLength: any())) }
|
||||
.thenReturn([])
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies))
|
||||
crypto
|
||||
.when { crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: any())
|
||||
)
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
|
@ -80,7 +63,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.sogsSignature(
|
||||
|
@ -92,35 +75,34 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
.thenReturn("TestSogsSignature".bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
.thenReturn("TestSignature".bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.signEd25519(data: anyArray(), keyPair: any())) }
|
||||
.thenReturn("TestStandardSignature".bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.generateNonce16()) }
|
||||
.thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
network: mockNetwork,
|
||||
crypto: mockCrypto,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
@TestState var disposables: [AnyCancellable]! = []
|
||||
@TestState var error: Error?
|
||||
|
||||
afterEach {
|
||||
disposables.forEach { $0.cancel() }
|
||||
|
||||
mockStorage = nil
|
||||
mockNetwork = nil
|
||||
mockCrypto = nil
|
||||
dependencies = nil
|
||||
disposables = []
|
||||
|
||||
error = nil
|
||||
}
|
||||
|
||||
// MARK: - when preparing a poll request
|
||||
// MARK: - an OpenGroupAPI
|
||||
describe("an OpenGroupAPI") {
|
||||
// MARK: -- when preparing a poll request
|
||||
context("when preparing a poll request") {
|
||||
// MARK: -- generates the correct request
|
||||
// MARK: ---- generates the correct request
|
||||
it("generates the correct request") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -140,7 +122,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
||||
}
|
||||
|
||||
// MARK: -- retrieves recent messages if there was no last message
|
||||
// MARK: ---- retrieves recent messages if there was no last message
|
||||
it("retrieves recent messages if there was no last message") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -155,7 +137,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
||||
}
|
||||
|
||||
// MARK: -- 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
|
||||
// MARK: ---- 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
|
||||
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") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -175,7 +157,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
||||
}
|
||||
|
||||
// MARK: -- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago
|
||||
// MARK: ---- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago
|
||||
it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -196,7 +178,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.to(equal(.roomMessagesSince("testRoom", seqNo: 122)))
|
||||
}
|
||||
|
||||
// MARK: -- retrieves recent messages if there was a last message and there has already been a poll this session
|
||||
// MARK: ---- retrieves recent messages if there was a last message and there has already been a poll this session
|
||||
it("retrieves recent messages if there was a last message and there has already been a poll this session") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -217,7 +199,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.to(equal(.roomMessagesSince("testRoom", seqNo: 123)))
|
||||
}
|
||||
|
||||
// MARK: -- when unblinded
|
||||
// MARK: ---- when unblinded
|
||||
context("when unblinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -226,7 +208,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- does not call the inbox and outbox endpoints
|
||||
// MARK: ------ does not call the inbox and outbox endpoints
|
||||
it("does not call the inbox and outbox endpoints") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -243,7 +225,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when blinded and checking for message requests
|
||||
// MARK: ---- when blinded and checking for message requests
|
||||
context("when blinded and checking for message requests") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -255,7 +237,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- includes the inbox and outbox endpoints
|
||||
// MARK: ------ includes the inbox and outbox endpoints
|
||||
it("includes the inbox and outbox endpoints") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -271,7 +253,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).to(contain(.outbox))
|
||||
}
|
||||
|
||||
// MARK: ---- retrieves recent inbox messages if there was no last message
|
||||
// MARK: ------ retrieves recent inbox messages if there was no last message
|
||||
it("retrieves recent inbox messages if there was no last message") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -286,7 +268,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).to(contain(.inbox))
|
||||
}
|
||||
|
||||
// MARK: ---- retrieves inbox messages since the last message if there was one
|
||||
// MARK: ------ retrieves inbox messages since the last message if there was one
|
||||
it("retrieves inbox messages since the last message if there was one") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -306,7 +288,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).to(contain(.inboxSince(id: 124)))
|
||||
}
|
||||
|
||||
// MARK: ---- retrieves recent outbox messages if there was no last message
|
||||
// MARK: ------ retrieves recent outbox messages if there was no last message
|
||||
it("retrieves recent outbox messages if there was no last message") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -321,7 +303,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).to(contain(.outbox))
|
||||
}
|
||||
|
||||
// MARK: ---- retrieves outbox messages since the last message if there was one
|
||||
// MARK: ------ retrieves outbox messages since the last message if there was one
|
||||
it("retrieves outbox messages since the last message if there was one") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -342,7 +324,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when blinded and not checking for message requests
|
||||
// MARK: ---- when blinded and not checking for message requests
|
||||
context("when blinded and not checking for message requests") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -354,7 +336,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- includes the inbox and outbox endpoints
|
||||
// MARK: ------ includes the inbox and outbox endpoints
|
||||
it("does not include the inbox endpoint") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -369,7 +351,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox))
|
||||
}
|
||||
|
||||
// MARK: ---- does not retrieve recent inbox messages if there was no last message
|
||||
// MARK: ------ does not retrieve recent inbox messages if there was no last message
|
||||
it("does not retrieve recent inbox messages if there was no last message") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPoll(
|
||||
|
@ -384,7 +366,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox))
|
||||
}
|
||||
|
||||
// MARK: ---- does not retrieve inbox messages since the last message if there was one
|
||||
// MARK: ------ does not retrieve inbox messages since the last message if there was one
|
||||
it("does not retrieve inbox messages since the last message if there was one") {
|
||||
mockStorage.write { db in
|
||||
try OpenGroup
|
||||
|
@ -406,9 +388,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a capabilities request
|
||||
// MARK: -- when preparing a capabilities request
|
||||
context("when preparing a capabilities request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request and handles the response correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Capabilities>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedCapabilities(
|
||||
|
@ -423,10 +405,10 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a rooms request
|
||||
// MARK: -- when preparing a rooms request
|
||||
context("when preparing a rooms request") {
|
||||
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedRooms(
|
||||
|
@ -441,9 +423,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a capabilitiesAndRoom request
|
||||
// MARK: -- when preparing a capabilitiesAndRoom request
|
||||
context("when preparing a capabilitiesAndRoom request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedCapabilitiesAndRoom(
|
||||
|
@ -462,7 +444,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- processes a valid response correctly
|
||||
// MARK: ---- processes a valid response correctly
|
||||
it("processes a valid response correctly") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -488,10 +470,10 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(error).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- and given an invalid response
|
||||
// MARK: ---- and given an invalid response
|
||||
|
||||
context("and given an invalid response") {
|
||||
// MARK: ---- errors when not given a room response
|
||||
// MARK: ------ errors when not given a room response
|
||||
it("errors when not given a room response") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -517,7 +499,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- errors when not given a capabilities response
|
||||
// MARK: ------ errors when not given a capabilities response
|
||||
it("errors when not given a capabilities response") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -545,9 +527,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a capabilitiesAndRooms request
|
||||
// MARK: -- when preparing a capabilitiesAndRooms request
|
||||
context("when preparing a capabilitiesAndRooms request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
||||
|
@ -565,7 +547,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- processes a valid response correctly
|
||||
// MARK: ---- processes a valid response correctly
|
||||
it("processes a valid response correctly") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -590,10 +572,10 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(error).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- and given an invalid response
|
||||
// MARK: ---- and given an invalid response
|
||||
|
||||
context("and given an invalid response") {
|
||||
// MARK: ---- errors when not given a room response
|
||||
// MARK: ------ errors when not given a room response
|
||||
it("errors when not given a room response") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -618,7 +600,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- errors when not given a capabilities response
|
||||
// MARK: ------ errors when not given a capabilities response
|
||||
it("errors when not given a capabilities response") {
|
||||
mockNetwork
|
||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||
|
@ -645,9 +627,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a send message request
|
||||
// MARK: -- when preparing a send message request
|
||||
context("when preparing a send message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedSend(
|
||||
|
@ -666,7 +648,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- when unblinded
|
||||
// MARK: ---- when unblinded
|
||||
context("when unblinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -675,7 +657,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs the message correctly
|
||||
// MARK: ------ signs the message correctly
|
||||
it("signs the message correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedSend(
|
||||
|
@ -696,7 +678,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8)))
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no open group
|
||||
// MARK: ------ fails to sign if there is no open group
|
||||
it("fails to sign if there is no open group") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.deleteAll(db)
|
||||
|
@ -726,7 +708,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no user key pair
|
||||
// MARK: ------ fails to sign if there is no user key pair
|
||||
it("fails to sign if there is no user key pair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
||||
|
@ -757,7 +739,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if no signature is generated
|
||||
// MARK: ------ fails to sign if no signature is generated
|
||||
it("fails to sign if no signature is generated") {
|
||||
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
||||
mockCrypto
|
||||
|
@ -789,7 +771,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when blinded
|
||||
// MARK: ---- when blinded
|
||||
context("when blinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -799,7 +781,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs the message correctly
|
||||
// MARK: ------ signs the message correctly
|
||||
it("signs the message correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedSend(
|
||||
|
@ -820,7 +802,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8)))
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no open group
|
||||
// MARK: ------ fails to sign if there is no open group
|
||||
it("fails to sign if there is no open group") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.deleteAll(db)
|
||||
|
@ -850,7 +832,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no ed key pair key
|
||||
// MARK: ------ fails to sign if there is no ed key pair key
|
||||
it("fails to sign if there is no ed key pair key") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
|
@ -881,7 +863,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if no signature is generated
|
||||
// MARK: ------ fails to sign if no signature is generated
|
||||
it("fails to sign if no signature is generated") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -922,9 +904,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an individual message request
|
||||
// MARK: -- when preparing an individual message request
|
||||
context("when preparing an individual message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessage(
|
||||
|
@ -941,7 +923,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an update message request
|
||||
// MARK: -- when preparing an update message request
|
||||
context("when preparing an update message request") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -954,7 +936,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessageUpdate(
|
||||
|
@ -972,7 +954,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("PUT"))
|
||||
}
|
||||
|
||||
// MARK: -- when unblinded
|
||||
// MARK: ---- when unblinded
|
||||
context("when unblinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -981,7 +963,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs the message correctly
|
||||
// MARK: ------ signs the message correctly
|
||||
it("signs the message correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessageUpdate(
|
||||
|
@ -1001,7 +983,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8)))
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no open group
|
||||
// MARK: ------ fails to sign if there is no open group
|
||||
it("fails to sign if there is no open group") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.deleteAll(db)
|
||||
|
@ -1030,7 +1012,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no user key pair
|
||||
// MARK: ------ fails to sign if there is no user key pair
|
||||
it("fails to sign if there is no user key pair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
||||
|
@ -1060,7 +1042,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if no signature is generated
|
||||
// MARK: ------ fails to sign if no signature is generated
|
||||
it("fails to sign if no signature is generated") {
|
||||
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
||||
mockCrypto
|
||||
|
@ -1091,7 +1073,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when blinded
|
||||
// MARK: ---- when blinded
|
||||
context("when blinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -1101,7 +1083,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs the message correctly
|
||||
// MARK: ------ signs the message correctly
|
||||
it("signs the message correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessageUpdate(
|
||||
|
@ -1121,7 +1103,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8)))
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no open group
|
||||
// MARK: ------ fails to sign if there is no open group
|
||||
it("fails to sign if there is no open group") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.deleteAll(db)
|
||||
|
@ -1150,7 +1132,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if there is no ed key pair key
|
||||
// MARK: ------ fails to sign if there is no ed key pair key
|
||||
it("fails to sign if there is no ed key pair key") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
|
@ -1180,7 +1162,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails to sign if no signature is generated
|
||||
// MARK: ------ fails to sign if no signature is generated
|
||||
it("fails to sign if no signature is generated") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -1220,9 +1202,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a delete message request
|
||||
// MARK: -- when preparing a delete message request
|
||||
context("when preparing a delete message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessageDelete(
|
||||
|
@ -1239,9 +1221,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a delete all messages request
|
||||
// MARK: -- when preparing a delete all messages request
|
||||
context("when preparing a delete all messages request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedMessagesDeleteAll(
|
||||
|
@ -1258,9 +1240,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a pin message request
|
||||
// MARK: -- when preparing a pin message request
|
||||
context("when preparing a pin message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedPinMessage(
|
||||
|
@ -1277,9 +1259,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an unpin message request
|
||||
// MARK: -- when preparing an unpin message request
|
||||
context("when preparing an unpin message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUnpinMessage(
|
||||
|
@ -1296,9 +1278,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an unpin all request
|
||||
// MARK: -- when preparing an unpin all request
|
||||
context("when preparing an unpin all request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUnpinAll(
|
||||
|
@ -1314,9 +1296,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an upload file request
|
||||
// MARK: -- when preparing an upload file request
|
||||
context("when preparing an upload file request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUploadFile(
|
||||
|
@ -1332,7 +1314,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- doesn't add a fileName to the content-disposition header when not provided
|
||||
// MARK: ---- doesn't add a fileName to the content-disposition header when not provided
|
||||
it("doesn't add a fileName to the content-disposition header when not provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUploadFile(
|
||||
|
@ -1348,7 +1330,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.toNot(contain("filename"))
|
||||
}
|
||||
|
||||
// MARK: -- adds the fileName to the content-disposition header when provided
|
||||
// MARK: ---- adds the fileName to the content-disposition header when provided
|
||||
it("adds the fileName to the content-disposition header when provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUploadFile(
|
||||
|
@ -1366,9 +1348,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a download file request
|
||||
// MARK: -- when preparing a download file request
|
||||
context("when preparing a download file request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<Data>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedDownloadFile(
|
||||
|
@ -1385,9 +1367,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a send direct message request
|
||||
// MARK: -- when preparing a send direct message request
|
||||
context("when preparing a send direct message request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.SendDirectMessageResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedSend(
|
||||
|
@ -1404,9 +1386,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a ban user request
|
||||
// MARK: -- when preparing a ban user request
|
||||
context("when preparing a ban user request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserBan(
|
||||
|
@ -1423,7 +1405,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- does a global ban if no room tokens are provided
|
||||
// MARK: ---- does a global ban if no room tokens are provided
|
||||
it("does a global ban if no room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserBan(
|
||||
|
@ -1442,7 +1424,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.rooms).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- does room specific bans if room tokens are provided
|
||||
// MARK: ---- does room specific bans if room tokens are provided
|
||||
it("does room specific bans if room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserBan(
|
||||
|
@ -1462,9 +1444,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing an unban user request
|
||||
// MARK: -- when preparing an unban user request
|
||||
context("when preparing an unban user request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserUnban(
|
||||
|
@ -1480,7 +1462,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- does a global unban if no room tokens are provided
|
||||
// MARK: ---- does a global unban if no room tokens are provided
|
||||
it("does a global unban if no room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserUnban(
|
||||
|
@ -1498,7 +1480,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.rooms).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- does room specific unbans if room tokens are provided
|
||||
// MARK: ---- does room specific unbans if room tokens are provided
|
||||
it("does room specific unbans if room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserUnban(
|
||||
|
@ -1517,9 +1499,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a user permissions request
|
||||
// MARK: -- when preparing a user permissions request
|
||||
context("when preparing a user permissions request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||
|
@ -1538,7 +1520,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||
}
|
||||
|
||||
// MARK: -- does a global update if no room tokens are provided
|
||||
// MARK: ---- does a global update if no room tokens are provided
|
||||
it("does a global update if no room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||
|
@ -1559,7 +1541,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.rooms).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- does room specific updates if room tokens are provided
|
||||
// MARK: ---- does room specific updates if room tokens are provided
|
||||
it("does room specific updates if room tokens are provided") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||
|
@ -1580,7 +1562,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestBody?.rooms).to(equal(["testRoom"]))
|
||||
}
|
||||
|
||||
// MARK: -- fails if neither moderator or admin are set
|
||||
// MARK: ---- fails if neither moderator or admin are set
|
||||
it("fails if neither moderator or admin are set") {
|
||||
var preparationError: Error?
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||
|
@ -1607,9 +1589,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when preparing a ban and delete all request
|
||||
// MARK: -- when preparing a ban and delete all request
|
||||
context("when preparing a ban and delete all request") {
|
||||
// MARK: -- generates the request correctly
|
||||
// MARK: ---- generates the request correctly
|
||||
it("generates the request correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
||||
|
@ -1629,7 +1611,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.to(equal(.roomDeleteMessages("testRoom", sessionId: "testUserId")))
|
||||
}
|
||||
|
||||
// // MARK: -- bans the user from the specified room rather than globally
|
||||
// // MARK: ---- bans the user from the specified room rather than globally
|
||||
// it("bans the user from the specified room rather than globally") {
|
||||
// let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||
// try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
||||
|
@ -1648,9 +1630,9 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
// }
|
||||
}
|
||||
|
||||
// MARK: - when signing
|
||||
// MARK: -- when signing
|
||||
context("when signing") {
|
||||
// MARK: -- fails when there is no serverPublicKey
|
||||
// MARK: ---- fails when there is no serverPublicKey
|
||||
it("fails when there is no serverPublicKey") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.deleteAll(db)
|
||||
|
@ -1675,7 +1657,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no userEdKeyPair
|
||||
// MARK: ---- fails when there is no userEdKeyPair
|
||||
it("fails when there is no userEdKeyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
|
@ -1701,7 +1683,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- fails when the serverPublicKey is not a hex string
|
||||
// MARK: ---- fails when the serverPublicKey is not a hex string
|
||||
it("fails when the serverPublicKey is not a hex string") {
|
||||
mockStorage.write { db in
|
||||
_ = try OpenGroup.updateAll(db, OpenGroup.Columns.publicKey.set(to: "TestString!!!"))
|
||||
|
@ -1726,7 +1708,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- when unblinded
|
||||
// MARK: ---- when unblinded
|
||||
context("when unblinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -1735,7 +1717,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs correctly
|
||||
// MARK: ------ signs correctly
|
||||
it("signs correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedRooms(
|
||||
|
@ -1759,7 +1741,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.to(equal("TestSignature".bytes.toBase64()))
|
||||
}
|
||||
|
||||
// MARK: ---- fails when the signature is not generated
|
||||
// MARK: ------ fails when the signature is not generated
|
||||
it("fails when the signature is not generated") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
|
@ -1785,7 +1767,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when blinded
|
||||
// MARK: ---- when blinded
|
||||
context("when blinded") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -1795,7 +1777,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- signs correctly
|
||||
// MARK: ------ signs correctly
|
||||
it("signs correctly") {
|
||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||
try OpenGroupAPI.preparedRooms(
|
||||
|
@ -1819,7 +1801,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.to(equal("TestSogsSignature".bytes.toBase64()))
|
||||
}
|
||||
|
||||
// MARK: ---- fails when the blindedKeyPair is not generated
|
||||
// MARK: ------ fails when the blindedKeyPair is not generated
|
||||
it("fails when the blindedKeyPair is not generated") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] in
|
||||
|
@ -1852,7 +1834,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(preparedRequest).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails when the sogsSignature is not generated
|
||||
// MARK: ------ fails when the sogsSignature is not generated
|
||||
it("fails when the sogsSignature is not generated") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] in
|
||||
|
@ -1887,7 +1869,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when sending
|
||||
// MARK: ---- when sending
|
||||
context("when sending") {
|
||||
beforeEach {
|
||||
mockNetwork
|
||||
|
@ -1895,7 +1877,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
.thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self))
|
||||
}
|
||||
|
||||
// MARK: -- triggers sending correctly
|
||||
// MARK: ---- triggers sending correctly
|
||||
it("triggers sending correctly") {
|
||||
var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])?
|
||||
|
||||
|
@ -1916,7 +1898,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(error).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: -- fails when not given prepared data
|
||||
// MARK: ---- fails when not given prepared data
|
||||
it("fails when not given prepared data") {
|
||||
var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])?
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,16 +8,18 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class NonceGeneratorSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a NonceGenerator16Byte
|
||||
describe("a NonceGenerator16Byte") {
|
||||
// MARK: -- has the correct number of bytes
|
||||
it("has the correct number of bytes") {
|
||||
expect(OpenGroupAPI.NonceGenerator16Byte().NonceBytes).to(equal(16))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - a NonceGenerator24Byte
|
||||
describe("a NonceGenerator24Byte") {
|
||||
// MARK: -- has the correct number of bytes
|
||||
it("has the correct number of bytes") {
|
||||
expect(OpenGroupAPI.NonceGenerator24Byte().NonceBytes).to(equal(24))
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class PersonalizationSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Personalization
|
||||
describe("a Personalization") {
|
||||
// MARK: -- generates bytes correctly
|
||||
it("generates bytes correctly") {
|
||||
expect(OpenGroupAPI.Personalization.sharedKeys.bytes)
|
||||
.to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115]))
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SOGSEndpointSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SOGSEndpoint
|
||||
describe("a SOGSEndpoint") {
|
||||
// MARK: -- generates the path value correctly
|
||||
it("generates the path value correctly") {
|
||||
// Utility
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SOGSErrorSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SOGSError
|
||||
describe("a SOGSError") {
|
||||
// MARK: -- generates the error description correctly
|
||||
it("generates the error description correctly") {
|
||||
expect(OpenGroupAPIError.decryptionFailed.errorDescription)
|
||||
.to(equal("Couldn't decrypt response."))
|
||||
|
|
|
@ -11,45 +11,35 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class MessageReceiverDecryptionSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageReceiver") {
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
],
|
||||
initialData: { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
)
|
||||
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||
initialSetup: { crypto in
|
||||
crypto
|
||||
.when { crypto in
|
||||
try crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
using: dependencies
|
||||
using: any()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.open(
|
||||
|
@ -60,13 +50,13 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
.thenReturn([UInt8](repeating: 0, count: 100))
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto
|
||||
.when { crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
using: any()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -76,34 +66,36 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
)
|
||||
)
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto
|
||||
.when { crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
using: any()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([])
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||
crypto
|
||||
.when { crypto in
|
||||
try crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: any(), using: any())
|
||||
)
|
||||
}
|
||||
.thenReturn([])
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
.thenReturn(true)
|
||||
mockCrypto
|
||||
crypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
|
@ -114,15 +106,24 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
||||
mockCrypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||
mockCrypto.when { $0.size(.publicKey) }.thenReturn(32)
|
||||
mockCrypto.when { $0.size(.signature) }.thenReturn(64)
|
||||
mockCrypto
|
||||
crypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||
crypto.when { $0.size(.publicKey) }.thenReturn(32)
|
||||
crypto.when { $0.size(.signature) }.thenReturn(64)
|
||||
crypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
// MARK: - a MessageReceiver
|
||||
describe("a MessageReceiver") {
|
||||
// MARK: -- when decrypting with the session protocol
|
||||
context("when decrypting with the session protocol") {
|
||||
// MARK: ---- successfully decrypts a message
|
||||
it("successfully decrypts a message") {
|
||||
let result = try? MessageReceiver.decryptWithSessionProtocol(
|
||||
ciphertext: Data(
|
||||
|
@ -142,6 +143,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot open the message
|
||||
it("throws an error if it cannot open the message") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -168,6 +170,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if the open message is too short
|
||||
it("throws an error if the open message is too short") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -194,6 +197,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot verify the message
|
||||
it("throws an error if it cannot verify the message") {
|
||||
mockCrypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
|
@ -212,6 +216,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot get the senders x25519 public key
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockCrypto.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }.thenReturn(nil)
|
||||
|
||||
|
@ -229,7 +234,9 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when decrypting with the blinded session protocol
|
||||
context("when decrypting with the blinded session protocol") {
|
||||
// MARK: ---- successfully decrypts a message
|
||||
it("successfully decrypts a message") {
|
||||
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||
data: Data(
|
||||
|
@ -252,6 +259,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||
}
|
||||
|
||||
// MARK: ---- successfully decrypts a mocked incoming message
|
||||
it("successfully decrypts a mocked incoming message") {
|
||||
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||
data: (
|
||||
|
@ -274,6 +282,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if the data is too short
|
||||
it("throws an error if the data is too short") {
|
||||
expect {
|
||||
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||
|
@ -291,6 +300,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot get the blinded keyPair
|
||||
it("throws an error if it cannot get the blinded keyPair") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
|
@ -324,6 +334,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot get the decryption key
|
||||
it("throws an error if it cannot get the decryption key") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
|
@ -359,6 +370,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if the data version is not 0
|
||||
it("throws an error if the data version is not 0") {
|
||||
expect {
|
||||
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||
|
@ -380,6 +392,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot decrypt the data
|
||||
it("throws an error if it cannot decrypt the data") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -413,6 +426,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if the inner bytes are too short
|
||||
it("throws an error if the inner bytes are too short") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
@ -446,6 +460,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot generate the blinding factor
|
||||
it("throws an error if it cannot generate the blinding factor") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
|
@ -473,6 +488,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot generate the combined key
|
||||
it("throws an error if it cannot generate the combined key") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
|
@ -498,6 +514,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if the combined key does not match kA
|
||||
it("throws an error if the combined key does not match kA") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
|
@ -523,6 +540,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
// MARK: ---- throws an error if it cannot get the senders x25519 public key
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
|
|
|
@ -11,41 +11,35 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class MessageSenderEncryptionSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageSender") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
],
|
||||
initialData: { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
)
|
||||
mockCrypto = MockCrypto()
|
||||
|
||||
dependencies = Dependencies(
|
||||
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||
initialSetup: { crypto in
|
||||
crypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockCrypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the session protocol
|
||||
// MARK: - a MessageSender
|
||||
describe("a MessageSender") {
|
||||
// MARK: -- when encrypting with the session protocol
|
||||
context("when encrypting with the session protocol") {
|
||||
beforeEach {
|
||||
mockCrypto
|
||||
|
@ -56,7 +50,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.thenReturn([])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
// MARK: ---- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
|
@ -72,7 +66,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(155))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
// MARK: ---- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
|
@ -86,7 +80,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.bytes).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
// MARK: ---- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
|
@ -106,7 +100,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the signature generation fails
|
||||
// MARK: ---- throws an error if the signature generation fails
|
||||
it("throws an error if the signature generation fails") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
|
@ -125,7 +119,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the encryption fails
|
||||
// MARK: ---- throws an error if the encryption fails
|
||||
it("throws an error if the encryption fails") {
|
||||
mockCrypto
|
||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
||||
|
@ -145,7 +139,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the blinded session protocol
|
||||
// MARK: -- when encrypting with the blinded session protocol
|
||||
context("when encrypting with the blinded session protocol") {
|
||||
beforeEach {
|
||||
mockCrypto
|
||||
|
@ -186,7 +180,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.thenReturn([2, 3, 4])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
// MARK: ---- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
|
@ -203,7 +197,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(84))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
// MARK: ---- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
|
@ -219,7 +213,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
|
||||
}
|
||||
|
||||
// MARK: -- includes a version at the start of the encrypted value
|
||||
// MARK: ---- includes a version at the start of the encrypted value
|
||||
it("includes a version at the start of the encrypted value") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
|
@ -234,7 +228,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.toHexString().prefix(2)).to(equal("00"))
|
||||
}
|
||||
|
||||
// MARK: -- includes the nonce at the end of the encrypted value
|
||||
// MARK: ---- includes the nonce at the end of the encrypted value
|
||||
it("includes the nonce at the end of the encrypted value") {
|
||||
let maybeResult: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
|
@ -252,7 +246,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the recipient isn't a blinded id
|
||||
// MARK: ---- throws an error if the recipient isn't a blinded id
|
||||
it("throws an error if the recipient isn't a blinded id") {
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
|
@ -268,7 +262,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
// MARK: ---- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
|
@ -289,7 +283,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate a blinded keyPair
|
||||
// MARK: ---- throws an error if it fails to generate a blinded keyPair
|
||||
it("throws an error if it fails to generate a blinded keyPair") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
|
@ -317,7 +311,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate an encryption key
|
||||
// MARK: ---- throws an error if it fails to generate an encryption key
|
||||
it("throws an error if it fails to generate an encryption key") {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
|
@ -347,7 +341,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to encrypt
|
||||
// MARK: ---- throws an error if it fails to encrypt
|
||||
it("throws an error if it fails to encrypt") {
|
||||
mockCrypto
|
||||
.when {
|
||||
|
|
|
@ -9,29 +9,12 @@ import SessionUtilitiesKit
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SessionThreadViewModelSpec: QuickSpec {
|
||||
public struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "testMessage" }
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case body
|
||||
}
|
||||
|
||||
public let body: String
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("a SessionThreadViewModel") {
|
||||
var mockStorage: Storage!
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue()
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
initialData: { db in
|
||||
try db.create(table: TestMessage.self) { t in
|
||||
t.column(.body, .text).notNull()
|
||||
}
|
||||
|
@ -43,23 +26,25 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
t.column(TestMessage.Columns.body.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// MARK: - when processing a search term
|
||||
// MARK: - a SessionThreadViewModel
|
||||
describe("a SessionThreadViewModel") {
|
||||
// MARK: -- when processing a search term
|
||||
context("when processing a search term") {
|
||||
// MARK: -- correctly generates a safe search term
|
||||
// MARK: ---- correctly generates a safe search term
|
||||
it("correctly generates a safe search term") {
|
||||
expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\""))
|
||||
}
|
||||
|
||||
// MARK: -- standardises odd quote characters
|
||||
// MARK: ---- standardises odd quote characters
|
||||
it("standardises odd quote characters") {
|
||||
expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\""))
|
||||
expect(SessionThreadViewModel.standardQuotes("”")).to(equal("\""))
|
||||
expect(SessionThreadViewModel.standardQuotes("“")).to(equal("\""))
|
||||
}
|
||||
|
||||
// MARK: -- splits on the space character
|
||||
// MARK: ---- splits on the space character
|
||||
it("splits on the space character") {
|
||||
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
||||
.to(equal([
|
||||
|
@ -68,7 +53,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- surrounds each split term with quotes
|
||||
// MARK: ---- surrounds each split term with quotes
|
||||
it("surrounds each split term with quotes") {
|
||||
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
||||
.to(equal([
|
||||
|
@ -77,7 +62,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- keeps words within quotes together
|
||||
// MARK: ---- keeps words within quotes together
|
||||
it("keeps words within quotes together") {
|
||||
expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message"))
|
||||
.to(equal([
|
||||
|
@ -113,7 +98,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- keeps words within weird quotes together
|
||||
// MARK: ---- keeps words within weird quotes together
|
||||
it("keeps words within weird quotes together") {
|
||||
expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message"))
|
||||
.to(equal([
|
||||
|
@ -123,7 +108,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- removes extra whitespace
|
||||
// MARK: ---- removes extra whitespace
|
||||
it("removes extra whitespace") {
|
||||
expect(SessionThreadViewModel.searchTermParts(" Test Message "))
|
||||
.to(equal([
|
||||
|
@ -133,7 +118,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when searching
|
||||
// MARK: -- when searching
|
||||
context("when searching") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -156,7 +141,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- returns results
|
||||
// MARK: ---- returns results
|
||||
it("returns results") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -186,7 +171,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- adds a wildcard to the final part
|
||||
// MARK: ---- adds a wildcard to the final part
|
||||
it("adds a wildcard to the final part") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -216,7 +201,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- does not add a wildcard to other parts
|
||||
// MARK: ---- does not add a wildcard to other parts
|
||||
it("does not add a wildcard to other parts") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -239,7 +224,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
.to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: -- finds similar words without the wildcard due to the porter tokenizer
|
||||
// MARK: ---- finds similar words without the wildcard due to the porter tokenizer
|
||||
it("finds similar words without the wildcard due to the porter tokenizer") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -271,7 +256,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- finds results containing the words regardless of the order
|
||||
// MARK: ---- finds results containing the words regardless of the order
|
||||
it("finds results containing the words regardless of the order") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -303,7 +288,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -- does not find quoted parts out of order
|
||||
// MARK: ---- does not find quoted parts out of order
|
||||
it("does not find quoted parts out of order") {
|
||||
let results = mockStorage.read { db in
|
||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||
|
@ -332,3 +317,16 @@ class SessionThreadViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "testMessage" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case body
|
||||
}
|
||||
|
||||
public let body: String
|
||||
}
|
||||
|
|
|
@ -10,24 +10,18 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class CryptoSMKSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var crypto: Crypto!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
crypto = Crypto()
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(crypto: crypto)
|
||||
}
|
||||
@TestState var crypto: Crypto! = Crypto()
|
||||
@TestState var mockCrypto: MockCrypto! = MockCrypto()
|
||||
@TestState var dependencies: Dependencies! = Dependencies(crypto: crypto)
|
||||
|
||||
// MARK: - Crypto for SessionMessagingKit
|
||||
describe("Crypto for SessionMessagingKit") {
|
||||
|
||||
// MARK: - when extending Sign
|
||||
// MARK: -- when extending Sign
|
||||
context("when extending Sign") {
|
||||
// MARK: -- can convert an ed25519 public key into an x25519 public key
|
||||
// MARK: ---- can convert an ed25519 public key into an x25519 public key
|
||||
it("can convert an ed25519 public key into an x25519 public key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes))
|
||||
|
||||
|
@ -35,7 +29,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
||||
}
|
||||
|
||||
// MARK: -- can convert an ed25519 private key into an x25519 private key
|
||||
// MARK: ---- can convert an ed25519 private key into an x25519 private key
|
||||
it("can convert an ed25519 private key into an x25519 private key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes))
|
||||
|
||||
|
@ -44,11 +38,11 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending Sodium
|
||||
// MARK: -- when extending Sodium
|
||||
context("when extending Sodium") {
|
||||
// MARK: -- and generating a blinding factor
|
||||
// MARK: ---- and generating a blinding factor
|
||||
context("and generating a blinding factor") {
|
||||
// MARK: --- successfully generates a blinding factor
|
||||
// MARK: ------ successfully generates a blinding factor
|
||||
it("successfully generates a blinding factor") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
|
@ -61,7 +55,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the serverPublicKey is not a hex string
|
||||
// MARK: ------ fails if the serverPublicKey is not a hex string
|
||||
it("fails if the serverPublicKey is not a hex string") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
|
@ -73,7 +67,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot hash the serverPublicKey bytes
|
||||
// MARK: ------ fails if it cannot hash the serverPublicKey bytes
|
||||
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||
dependencies = Dependencies(crypto: mockCrypto)
|
||||
mockCrypto
|
||||
|
@ -91,9 +85,9 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a blinded key pair
|
||||
// MARK: ---- and generating a blinded key pair
|
||||
context("and generating a blinded key pair") {
|
||||
// MARK: --- successfully generates a blinded key pair
|
||||
// MARK: ------ successfully generates a blinded key pair
|
||||
it("successfully generates a blinded key pair") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
|
@ -112,7 +106,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair public key length wrong
|
||||
// MARK: ------ fails if the edKeyPair public key length wrong
|
||||
it("fails if the edKeyPair public key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
|
@ -128,7 +122,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair secret key length wrong
|
||||
// MARK: ------ fails if the edKeyPair secret key length wrong
|
||||
it("fails if the edKeyPair secret key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
|
@ -144,7 +138,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot generate a blinding factor
|
||||
// MARK: ------ fails if it cannot generate a blinding factor
|
||||
it("fails if it cannot generate a blinding factor") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
|
@ -161,9 +155,9 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a sogsSignature
|
||||
// MARK: ---- and generating a sogsSignature
|
||||
context("and generating a sogsSignature") {
|
||||
// MARK: --- generates a correct signature
|
||||
// MARK: ------ generates a correct signature
|
||||
it("generates a correct signature") {
|
||||
let result = try? crypto.perform(
|
||||
.sogsSignature(
|
||||
|
@ -182,9 +176,9 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- and combining keys
|
||||
// MARK: ---- and combining keys
|
||||
context("and combining keys") {
|
||||
// MARK: --- generates a correct combined key
|
||||
// MARK: ------ generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.combineKeys(
|
||||
|
@ -198,9 +192,9 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- and creating a shared blinded encryption key
|
||||
// MARK: ---- and creating a shared blinded encryption key
|
||||
context("and creating a shared blinded encryption key") {
|
||||
// MARK: --- generates a correct combined key
|
||||
// MARK: ------ generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
|
@ -216,7 +210,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the scalar multiplication fails
|
||||
// MARK: ------ fails if the scalar multiplication fails
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
|
@ -232,9 +226,9 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- and checking if a session id matches a blinded id
|
||||
// MARK: ---- and checking if a session id matches a blinded id
|
||||
context("and checking if a session id matches a blinded id") {
|
||||
// MARK: --- returns true when they match
|
||||
// MARK: ------ returns true when they match
|
||||
it("returns true when they match") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
|
@ -248,7 +242,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid session id
|
||||
// MARK: ------ returns false if given an invalid session id
|
||||
it("returns false if given an invalid session id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
|
@ -262,7 +256,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid blinded id
|
||||
// MARK: ------ returns false if given an invalid blinded id
|
||||
it("returns false if given an invalid blinded id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
|
@ -276,7 +270,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if it fails to generate the blinding factor
|
||||
// MARK: ------ returns false if it fails to generate the blinding factor
|
||||
it("returns false if it fails to generate the blinding factor") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
|
@ -292,11 +286,11 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending GenericHash
|
||||
// MARK: -- when extending GenericHash
|
||||
describe("when extending GenericHash") {
|
||||
// MARK: -- and generating a hash with salt and personal values
|
||||
// MARK: ---- and generating a hash with salt and personal values
|
||||
context("and generating a hash with salt and personal values") {
|
||||
// MARK: --- generates a hash correctly
|
||||
// MARK: ------ generates a hash correctly
|
||||
it("generates a hash correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
|
@ -312,7 +306,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- generates a hash correctly with no key
|
||||
// MARK: ------ generates a hash correctly with no key
|
||||
it("generates a hash correctly with no key") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
|
@ -328,7 +322,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given invalid options
|
||||
// MARK: ------ fails if given invalid options
|
||||
it("fails if given invalid options") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
|
@ -345,11 +339,11 @@ class CryptoSMKSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending AeadXChaCha20Poly1305Ietf
|
||||
// MARK: -- when extending AeadXChaCha20Poly1305Ietf
|
||||
context("when extending AeadXChaCha20Poly1305Ietf") {
|
||||
// MARK: -- when encrypting
|
||||
// MARK: ---- when encrypting
|
||||
context("when encrypting") {
|
||||
// MARK: --- encrypts correctly
|
||||
// MARK: ------ encrypts correctly
|
||||
it("encrypts correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
|
@ -365,7 +359,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- encrypts correctly with additional data
|
||||
// MARK: ------ encrypts correctly with additional data
|
||||
it("encrypts correctly with additional data") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
|
@ -381,7 +375,7 @@ class CryptoSMKSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given an invalid key
|
||||
// MARK: ------ fails if given an invalid key
|
||||
it("fails if given an invalid key") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
|
|
|
@ -11,77 +11,58 @@ import SessionUtilitiesKit
|
|||
@testable import Session
|
||||
|
||||
class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||
typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesSettingsViewModel.NavButton, ThreadDisappearingMessagesSettingsViewModel.Section, ThreadDisappearingMessagesSettingsViewModel.Item>
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var cancellables: [AnyCancellable] = []
|
||||
var dependencies: Dependencies!
|
||||
var viewModel: ThreadDisappearingMessagesSettingsViewModel!
|
||||
|
||||
describe("a ThreadDisappearingMessagesSettingsViewModel") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNSnodeKit.self,
|
||||
SNMessagingKit.self,
|
||||
SNUIKit.self
|
||||
]
|
||||
)
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
scheduler: .immediate
|
||||
)
|
||||
mockStorage.write { db in
|
||||
],
|
||||
initialData: { db in
|
||||
try SessionThread(
|
||||
id: "TestId",
|
||||
variant: .contact
|
||||
).insert(db)
|
||||
}
|
||||
viewModel = ThreadDisappearingMessagesSettingsViewModel(
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
scheduler: .immediate
|
||||
)
|
||||
@TestState var viewModel: ThreadDisappearingMessagesSettingsViewModel! = ThreadDisappearingMessagesSettingsViewModel(
|
||||
threadId: "TestId",
|
||||
threadVariant: .contact,
|
||||
config: DisappearingMessagesConfiguration.defaultWith("TestId"),
|
||||
using: dependencies
|
||||
)
|
||||
cancellables.append(
|
||||
|
||||
@TestState var cancellables: [AnyCancellable]! = [
|
||||
viewModel.observableTableData
|
||||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { viewModel.updateTableData($0.0) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
cancellables.forEach { $0.cancel() }
|
||||
|
||||
mockStorage = nil
|
||||
cancellables = []
|
||||
dependencies = nil
|
||||
viewModel = nil
|
||||
}
|
||||
|
||||
// MARK: - Basic Tests
|
||||
]
|
||||
|
||||
// MARK: - a ThreadDisappearingMessagesSettingsViewModel
|
||||
describe("a ThreadDisappearingMessagesSettingsViewModel") {
|
||||
// MARK: -- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("DISAPPEARING_MESSAGES".localized()))
|
||||
}
|
||||
|
||||
// MARK: -- has the correct number of items
|
||||
it("has the correct number of items") {
|
||||
expect(viewModel.tableData.count)
|
||||
.to(equal(1))
|
||||
expect(viewModel.tableData.first?.elements.count)
|
||||
.to(equal(12))
|
||||
expect(viewModel.tableData.count).to(equal(1))
|
||||
expect(viewModel.tableData.first?.elements.count).to(equal(12))
|
||||
}
|
||||
|
||||
// MARK: -- has the correct default state
|
||||
it("has the correct default state") {
|
||||
expect(viewModel.tableData.first?.elements.first)
|
||||
.to(
|
||||
|
@ -117,6 +98,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: -- starts with the correct item active if not default
|
||||
it("starts with the correct item active if not default") {
|
||||
let config: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration
|
||||
.defaultWith("TestId")
|
||||
|
@ -176,6 +158,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: -- has no right bar button
|
||||
it("has no right bar button") {
|
||||
var items: [ParentType.NavItem]?
|
||||
|
||||
|
@ -191,8 +174,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
expect(items).to(equal([]))
|
||||
}
|
||||
|
||||
// MARK: -- when changed from the previous setting
|
||||
context("when changed from the previous setting") {
|
||||
var items: [ParentType.NavItem]?
|
||||
@TestState var items: [ParentType.NavItem]?
|
||||
|
||||
beforeEach {
|
||||
cancellables.append(
|
||||
|
@ -207,6 +191,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
viewModel.tableData.first?.elements.last?.onTap?()
|
||||
}
|
||||
|
||||
// MARK: ---- shows the save button
|
||||
it("shows the save button") {
|
||||
expect(items)
|
||||
.to(equal([
|
||||
|
@ -218,7 +203,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ---- and saving
|
||||
context("and saving") {
|
||||
// MARK: ------ dismisses the screen
|
||||
it("dismisses the screen") {
|
||||
var didDismissScreen: Bool = false
|
||||
|
||||
|
@ -236,6 +223,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
expect(didDismissScreen).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ saves the updated config
|
||||
it("saves the updated config") {
|
||||
items?.first?.action?()
|
||||
|
||||
|
@ -252,3 +240,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesSettingsViewModel.NavButton, ThreadDisappearingMessagesSettingsViewModel.Section, ThreadDisappearingMessagesSettingsViewModel.Item>
|
||||
|
|
|
@ -11,67 +11,43 @@ import SessionUtilitiesKit
|
|||
@testable import Session
|
||||
|
||||
class ThreadSettingsViewModelSpec: QuickSpec {
|
||||
typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockCaches: MockCaches!
|
||||
var mockGeneralCache: MockGeneralCache!
|
||||
var disposables: [AnyCancellable] = []
|
||||
var dependencies: Dependencies!
|
||||
var viewModel: ThreadSettingsViewModel!
|
||||
var didTriggerSearchCallbackTriggered: Bool = false
|
||||
|
||||
describe("a ThreadSettingsViewModel") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNSnodeKit.self,
|
||||
SNMessagingKit.self,
|
||||
SNUIKit.self
|
||||
]
|
||||
)
|
||||
mockCaches = MockCaches()
|
||||
mockGeneralCache = MockGeneralCache()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
caches: mockCaches,
|
||||
scheduler: .immediate
|
||||
)
|
||||
mockCaches[.general] = mockGeneralCache
|
||||
mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
|
||||
mockStorage.write { db in
|
||||
try SessionThread(
|
||||
id: "TestId",
|
||||
variant: .contact
|
||||
).insert(db)
|
||||
|
||||
],
|
||||
initialData: { db in
|
||||
try Identity(
|
||||
variant: .x25519PublicKey,
|
||||
data: Data(hex: TestConstants.publicKey)
|
||||
).insert(db)
|
||||
|
||||
try Profile(
|
||||
id: "05\(TestConstants.publicKey)",
|
||||
name: "TestMe",
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
).insert(db)
|
||||
|
||||
try Profile(
|
||||
id: "TestId",
|
||||
name: "TestUser",
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
).insert(db)
|
||||
try SessionThread(id: "TestId",variant: .contact).insert(db)
|
||||
try Profile(id: "05\(TestConstants.publicKey)", name: "TestMe").insert(db)
|
||||
try Profile(id: "TestId", name: "TestUser").insert(db)
|
||||
}
|
||||
viewModel = ThreadSettingsViewModel(
|
||||
)
|
||||
@TestState var mockGeneralCache: MockGeneralCache! = MockGeneralCache(
|
||||
initialSetup: { cache in
|
||||
cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
|
||||
}
|
||||
)
|
||||
@TestState var mockCaches: MockCaches! = MockCaches()
|
||||
.setting(cache: .general, to: mockGeneralCache)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
caches: mockCaches,
|
||||
scheduler: .immediate
|
||||
)
|
||||
@TestState var threadVariant: SessionThread.Variant! = .contact
|
||||
@TestState var didTriggerSearchCallbackTriggered: Bool! = false
|
||||
@TestState var viewModel: ThreadSettingsViewModel! = ThreadSettingsViewModel(
|
||||
threadId: "TestId",
|
||||
threadVariant: .contact,
|
||||
didTriggerSearch: {
|
||||
|
@ -79,29 +55,21 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
},
|
||||
using: dependencies
|
||||
)
|
||||
disposables.append(
|
||||
|
||||
@TestState var disposables: [AnyCancellable]! = [
|
||||
viewModel.observableTableData
|
||||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { viewModel.updateTableData($0.0) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
disposables.forEach { $0.cancel() }
|
||||
|
||||
mockStorage = nil
|
||||
disposables = []
|
||||
dependencies = nil
|
||||
viewModel = nil
|
||||
didTriggerSearchCallbackTriggered = false
|
||||
}
|
||||
|
||||
// MARK: - Basic Tests
|
||||
]
|
||||
|
||||
// MARK: - a ThreadSettingsViewModel
|
||||
describe("a ThreadSettingsViewModel") {
|
||||
// MARK: -- with any conversation type
|
||||
context("with any conversation type") {
|
||||
// MARK: ---- triggers the search callback when tapping search
|
||||
it("triggers the search callback when tapping search") {
|
||||
viewModel.tableData
|
||||
.first(where: { $0.model == .content })?
|
||||
|
@ -112,6 +80,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
expect(didTriggerSearchCallbackTriggered).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- mutes a conversation
|
||||
it("mutes a conversation") {
|
||||
viewModel.tableData
|
||||
.first(where: { $0.model == .content })?
|
||||
|
@ -127,6 +96,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
.toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- unmutes a conversation
|
||||
it("unmutes a conversation") {
|
||||
mockStorage.write { db in
|
||||
try SessionThread
|
||||
|
@ -158,6 +128,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a note-to-self conversation
|
||||
context("with a note-to-self conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -187,10 +158,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: ---- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
||||
}
|
||||
|
||||
// MARK: ---- starts in the standard nav state
|
||||
it("starts in the standard nav state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -206,6 +179,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ---- has no mute button
|
||||
it("has no mute button") {
|
||||
expect(
|
||||
viewModel.tableData
|
||||
|
@ -215,6 +189,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- when entering edit mode
|
||||
context("when entering edit mode") {
|
||||
beforeEach {
|
||||
viewModel.navState.sinkAndStore(in: &disposables)
|
||||
|
@ -222,6 +197,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
viewModel.textChanged("TestNew", for: .nickname)
|
||||
}
|
||||
|
||||
// MARK: ------ enters the editing state
|
||||
it("enters the editing state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.editing))
|
||||
|
@ -244,11 +220,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ------ when cancelling edit mode
|
||||
context("when cancelling edit mode") {
|
||||
beforeEach {
|
||||
viewModel.leftNavItems.firstValue()??.first?.action?()
|
||||
}
|
||||
|
||||
// MARK: -------- exits editing mode
|
||||
it("exits editing mode") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -264,6 +242,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- does not update the nickname for the current user
|
||||
it("does not update the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -276,11 +255,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ------ when saving edit mode
|
||||
context("when saving edit mode") {
|
||||
beforeEach {
|
||||
viewModel.rightNavItems.firstValue()??.first?.action?()
|
||||
}
|
||||
|
||||
// MARK: -------- exits editing mode
|
||||
it("exits editing mode") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -296,6 +277,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- updates the nickname for the current user
|
||||
it("updates the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -310,6 +292,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a one-to-one conversation
|
||||
context("with a one-to-one conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -322,10 +305,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
||||
}
|
||||
|
||||
// MARK: ---- starts in the standard nav state
|
||||
it("starts in the standard nav state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -341,6 +326,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ---- when entering edit mode
|
||||
context("when entering edit mode") {
|
||||
beforeEach {
|
||||
viewModel.navState.sinkAndStore(in: &disposables)
|
||||
|
@ -348,6 +334,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
viewModel.textChanged("TestUserNew", for: .nickname)
|
||||
}
|
||||
|
||||
// MARK: ------ enters the editing state
|
||||
it("enters the editing state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.editing))
|
||||
|
@ -370,11 +357,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ------ when cancelling edit mode
|
||||
context("when cancelling edit mode") {
|
||||
beforeEach {
|
||||
viewModel.leftNavItems.firstValue()??.first?.action?()
|
||||
}
|
||||
|
||||
// MARK: -------- exits editing mode
|
||||
it("exits editing mode") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -390,6 +379,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- does not update the nickname for the current user
|
||||
it("does not update the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -400,11 +390,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ------ when saving edit mode
|
||||
context("when saving edit mode") {
|
||||
beforeEach {
|
||||
viewModel.rightNavItems.firstValue()??.first?.action?()
|
||||
}
|
||||
|
||||
// MARK: -------- exits editing mode
|
||||
it("exits editing mode") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -420,6 +412,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- updates the nickname for the current user
|
||||
it("updates the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -432,6 +425,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a group conversation
|
||||
context("with a group conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -461,10 +455,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: ---- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
||||
}
|
||||
|
||||
// MARK: ---- starts in the standard nav state
|
||||
it("starts in the standard nav state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -474,6 +470,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a community conversation
|
||||
context("with a community conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -503,10 +500,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: ---- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
||||
}
|
||||
|
||||
// MARK: ---- starts in the standard nav state
|
||||
it("starts in the standard nav state") {
|
||||
expect(viewModel.navState.firstValue())
|
||||
.to(equal(.standard))
|
||||
|
@ -518,3 +517,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
|
||||
|
|
|
@ -11,19 +11,9 @@ import SessionUtilitiesKit
|
|||
@testable import Session
|
||||
|
||||
class NotificationContentViewModelSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var dataChangeCancellable: AnyCancellable?
|
||||
var dismissCancellable: AnyCancellable?
|
||||
var viewModel: NotificationContentViewModel!
|
||||
|
||||
describe("a NotificationContentViewModel") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
|
@ -32,31 +22,27 @@ class NotificationContentViewModelSpec: QuickSpec {
|
|||
SNUIKit.self
|
||||
]
|
||||
)
|
||||
viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate)
|
||||
dataChangeCancellable = viewModel.observableTableData
|
||||
@TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel(
|
||||
storage: mockStorage,
|
||||
scheduling: .immediate
|
||||
)
|
||||
|
||||
@TestState var dataChangeCancellable: AnyCancellable? = viewModel.observableTableData
|
||||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { viewModel.updateTableData($0.0) }
|
||||
)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
dataChangeCancellable?.cancel()
|
||||
dismissCancellable?.cancel()
|
||||
|
||||
mockStorage = nil
|
||||
dataChangeCancellable = nil
|
||||
dismissCancellable = nil
|
||||
viewModel = nil
|
||||
}
|
||||
|
||||
// MARK: - Basic Tests
|
||||
@TestState var dismissCancellable: AnyCancellable?
|
||||
|
||||
// MARK: - a NotificationContentViewModel
|
||||
describe("a NotificationContentViewModel") {
|
||||
// MARK: -- has the correct title
|
||||
it("has the correct title") {
|
||||
expect(viewModel.title).to(equal("NOTIFICATIONS_STYLE_CONTENT_TITLE".localized()))
|
||||
}
|
||||
|
||||
// MARK: -- has the correct number of items
|
||||
it("has the correct number of items") {
|
||||
expect(viewModel.tableData.count)
|
||||
.to(equal(1))
|
||||
|
@ -64,6 +50,7 @@ class NotificationContentViewModelSpec: QuickSpec {
|
|||
.to(equal(3))
|
||||
}
|
||||
|
||||
// MARK: -- has the correct default state
|
||||
it("has the correct default state") {
|
||||
expect(viewModel.tableData.first?.elements)
|
||||
.to(
|
||||
|
@ -96,6 +83,7 @@ class NotificationContentViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: -- starts with the correct item active if not default
|
||||
it("starts with the correct item active if not default") {
|
||||
mockStorage.write { db in
|
||||
db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview
|
||||
|
@ -139,7 +127,9 @@ class NotificationContentViewModelSpec: QuickSpec {
|
|||
)
|
||||
}
|
||||
|
||||
// MARK: -- when tapping an item
|
||||
context("when tapping an item") {
|
||||
// MARK: ---- updates the saved preference
|
||||
it("updates the saved preference") {
|
||||
viewModel.tableData.first?.elements.last?.onTap?()
|
||||
|
||||
|
@ -147,6 +137,7 @@ class NotificationContentViewModelSpec: QuickSpec {
|
|||
.to(equal(Preferences.NotificationPreviewType.noNameNoPreview))
|
||||
}
|
||||
|
||||
// MARK: ---- dismisses the screen
|
||||
it("dismisses the screen") {
|
||||
var didDismissScreen: Bool = false
|
||||
|
||||
|
|
|
@ -9,21 +9,19 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class IdentitySpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
|
||||
describe("an Identity") {
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - an Identity
|
||||
describe("an Identity") {
|
||||
// MARK: -- correctly retrieves the user user public key
|
||||
it("correctly retrieves the user user public key") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .x25519PublicKey, data: "Test1".data(using: .utf8)!).insert(db)
|
||||
|
@ -35,6 +33,7 @@ class IdentitySpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- correctly retrieves the user private key
|
||||
it("correctly retrieves the user private key") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .x25519PrivateKey, data: "Test2".data(using: .utf8)!).insert(db)
|
||||
|
@ -46,6 +45,7 @@ class IdentitySpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- correctly retrieves the user key pair
|
||||
it("correctly retrieves the user key pair") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
||||
|
@ -62,6 +62,7 @@ class IdentitySpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- correctly determines if the user exists
|
||||
it("correctly determines if the user exists") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
||||
|
@ -74,6 +75,7 @@ class IdentitySpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- correctly retrieves the user ED25519 key pair
|
||||
it("correctly retrieves the user ED25519 key pair") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: "Test5".data(using: .utf8)!).insert(db)
|
||||
|
@ -90,6 +92,7 @@ class IdentitySpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- correctly retrieves the hex encoded seed
|
||||
it("correctly retrieves the hex encoded seed") {
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .seed, data: "Test7".data(using: .utf8)!).insert(db)
|
||||
|
|
|
@ -9,113 +9,22 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||
struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "TestType" }
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case columnA
|
||||
case columnB
|
||||
}
|
||||
|
||||
public let columnA: String
|
||||
public let columnB: String?
|
||||
}
|
||||
|
||||
struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "MutableTestType" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case id
|
||||
case columnA
|
||||
case columnB
|
||||
}
|
||||
|
||||
public var id: Int64?
|
||||
public let columnA: String
|
||||
public let columnB: String?
|
||||
|
||||
init(id: Int64? = nil, columnA: String, columnB: String?) {
|
||||
self.id = id
|
||||
self.columnA = columnA
|
||||
self.columnB = columnB
|
||||
}
|
||||
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
self.id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
enum TestInsertTestTypeMigration: Migration {
|
||||
static let target: TargetMigrations.Identifier = .test
|
||||
static let identifier: String = "TestInsertTestType"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.create(table: TestType.self) { t in
|
||||
t.column(.columnA, .text).primaryKey()
|
||||
}
|
||||
|
||||
try db.create(table: MutableTestType.self) { t in
|
||||
t.column(.id, .integer).primaryKey(autoincrement: true)
|
||||
t.column(.columnA, .text).unique()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TestAddColumnMigration: Migration {
|
||||
static let target: TargetMigrations.Identifier = .test
|
||||
static let identifier: String = "TestAddColumn"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.alter(table: TestType.self) { t in
|
||||
t.add(.columnB, .text)
|
||||
}
|
||||
|
||||
try db.alter(table: MutableTestType.self) { t in
|
||||
t.add(.columnB, .text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TestTarget: MigratableTarget {
|
||||
static func migrations(_ db: Database) -> TargetMigrations {
|
||||
return TargetMigrations(
|
||||
identifier: .test,
|
||||
migrations: (0..<100)
|
||||
.map { _ in [] }
|
||||
.appending([TestInsertTestTypeMigration.self])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var customWriter: DatabaseQueue!
|
||||
var mockStorage: Storage!
|
||||
|
||||
describe("a PersistableRecord") {
|
||||
beforeEach {
|
||||
customWriter = try! DatabaseQueue()
|
||||
mockStorage = SynchronousStorage(
|
||||
@TestState var customWriter: DatabaseQueue! = try! DatabaseQueue()
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: customWriter,
|
||||
customMigrationTargets: [
|
||||
TestTarget.self
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
customWriter = nil
|
||||
mockStorage = nil
|
||||
}
|
||||
|
||||
// MARK: - a PersistableRecord
|
||||
describe("a PersistableRecord") {
|
||||
// MARK: -- before running the add column migration
|
||||
context("before running the add column migration") {
|
||||
// MARK: ---- fails when using the standard insert
|
||||
it("fails when using the standard insert") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -125,6 +34,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard inserted
|
||||
it("fails when using the standard inserted") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -134,6 +44,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard save and the item does not already exist
|
||||
it("fails when using the standard save and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -143,6 +54,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard saved and the item does not already exist
|
||||
it("fails when using the standard saved and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -152,6 +64,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard upsert and the item does not already exist
|
||||
it("fails when using the standard upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -161,6 +74,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard mutable upsert and the item does not already exist
|
||||
it("fails when using the standard mutable upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -172,6 +86,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard upsert and the item already exists
|
||||
it("fails when using the standard upsert and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -185,6 +100,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- fails when using the standard mutable upsert and the item already exists
|
||||
it("fails when using the standard mutable upsert and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -200,6 +116,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe insert
|
||||
it("succeeds when using the migration safe insert") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -214,6 +131,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe inserted
|
||||
it("succeeds when using the migration safe inserted") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -235,6 +153,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe save and the item does not already exist
|
||||
it("succeeds when using the migration safe save and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -244,6 +163,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe saved and the item does not already exist
|
||||
it("succeeds when using the migration safe saved and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -265,6 +185,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe upsert and the item does not already exist
|
||||
it("succeeds when using the migration safe upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -274,6 +195,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist
|
||||
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -290,8 +212,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method only updates existing columns so this shouldn't fail
|
||||
// MARK: ---- succeeds when using the standard save and the item already exists
|
||||
it("succeeds when using the standard save and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method only updates existing columns so this shouldn't fail
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -304,10 +227,10 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method only updates existing columns so this won't fail
|
||||
// due to the structure discrepancy but won't update the id as that only happens on
|
||||
// insert
|
||||
// MARK: ---- succeeds when using the standard saved and the item already exists
|
||||
it("succeeds when using the standard saved and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method only updates existing columns so this won't fail
|
||||
/// due to the structure discrepancy but won't update the id as that only happens on insert
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -339,6 +262,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- after running the add column migration
|
||||
context("after running the add column migration") {
|
||||
beforeEach {
|
||||
var migrator: DatabaseMigrator = DatabaseMigrator()
|
||||
|
@ -352,6 +276,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
.toNot(throwError())
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard insert
|
||||
it("succeeds when using the standard insert") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -366,6 +291,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard inserted
|
||||
it("succeeds when using the standard inserted") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -380,6 +306,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard save and the item does not already exist
|
||||
it("succeeds when using the standard save and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -389,6 +316,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard saved and the item does not already exist
|
||||
it("succeeds when using the standard saved and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -398,6 +326,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard save and the item already exists
|
||||
it("succeeds when using the standard save and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -411,9 +340,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
||||
// insert
|
||||
// MARK: ---- succeeds when using the standard saved and the item already exists
|
||||
it("succeeds when using the standard saved and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -444,6 +373,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard upsert and the item does not already exist
|
||||
it("succeeds when using the standard upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -453,6 +383,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard mutable upsert and the item does not already exist
|
||||
it("succeeds when using the standard mutable upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -464,6 +395,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the standard upsert and the item already exists
|
||||
it("succeeds when using the standard upsert and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -477,9 +409,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
||||
// insert
|
||||
// MARK: ---- succeeds when using the standard mutable upsert and the item already exists
|
||||
it("succeeds when using the standard mutable upsert and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -512,6 +444,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe insert
|
||||
it("succeeds when using the migration safe insert") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -526,6 +459,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe inserted
|
||||
it("succeeds when using the migration safe inserted") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -547,6 +481,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe save and the item does not already exist
|
||||
it("succeeds when using the migration safe save and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -556,6 +491,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe saved and the item does not already exist
|
||||
it("succeeds when using the migration safe saved and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -565,6 +501,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe save and the item already exists
|
||||
it("succeeds when using the migration safe save and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -578,9 +515,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
||||
// insert
|
||||
// MARK: ---- succeeds when using the migration safe saved and the item already exists
|
||||
it("succeeds when using the migration safe saved and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -612,6 +549,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe upsert and the item does not already exist
|
||||
it("succeeds when using the migration safe upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -621,6 +559,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist
|
||||
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -632,6 +571,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- succeeds when using the migration safe upsert and the item already exists
|
||||
it("succeeds when using the migration safe upsert and the item already exists") {
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
|
@ -645,9 +585,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
||||
// insert
|
||||
// MARK: ---- succeeds when using the migration safe mutable upsert and the item already exists
|
||||
it("succeeds when using the migration safe mutable upsert and the item already exists") {
|
||||
/// **Note:** The built-in 'update' method won't update the id as that only happens on insert
|
||||
mockStorage.write { db in
|
||||
expect {
|
||||
try db.execute(
|
||||
|
@ -683,3 +623,89 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "TestType" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case columnA
|
||||
case columnB
|
||||
}
|
||||
|
||||
public let columnA: String
|
||||
public let columnB: String?
|
||||
}
|
||||
|
||||
fileprivate struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
|
||||
public static var databaseTableName: String { "MutableTestType" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case id
|
||||
case columnA
|
||||
case columnB
|
||||
}
|
||||
|
||||
public var id: Int64?
|
||||
public let columnA: String
|
||||
public let columnB: String?
|
||||
|
||||
init(id: Int64? = nil, columnA: String, columnB: String?) {
|
||||
self.id = id
|
||||
self.columnA = columnA
|
||||
self.columnB = columnB
|
||||
}
|
||||
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
self.id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum TestInsertTestTypeMigration: Migration {
|
||||
static let target: TargetMigrations.Identifier = .test
|
||||
static let identifier: String = "TestInsertTestType"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.create(table: TestType.self) { t in
|
||||
t.column(.columnA, .text).primaryKey()
|
||||
}
|
||||
|
||||
try db.create(table: MutableTestType.self) { t in
|
||||
t.column(.id, .integer).primaryKey(autoincrement: true)
|
||||
t.column(.columnA, .text).unique()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum TestAddColumnMigration: Migration {
|
||||
static let target: TargetMigrations.Identifier = .test
|
||||
static let identifier: String = "TestAddColumn"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.alter(table: TestType.self) { t in
|
||||
t.add(.columnB, .text)
|
||||
}
|
||||
|
||||
try db.alter(table: MutableTestType.self) { t in
|
||||
t.add(.columnB, .text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct TestTarget: MigratableTarget {
|
||||
static func migrations(_ db: Database) -> TargetMigrations {
|
||||
return TargetMigrations(
|
||||
identifier: .test,
|
||||
migrations: (0..<100)
|
||||
.map { _ in [] }
|
||||
.appending([TestInsertTestTypeMigration.self])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,12 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class ArrayUtilitiesSpec: QuickSpec {
|
||||
private struct TestType: Equatable {
|
||||
let stringValue: String
|
||||
let intValue: Int
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - an Array
|
||||
describe("an Array") {
|
||||
// MARK: -- when grouping
|
||||
context("when grouping") {
|
||||
// MARK: ---- maintains the original array ordering
|
||||
it("maintains the original array ordering") {
|
||||
let data: [TestType] = [
|
||||
TestType(stringValue: "b", intValue: 5),
|
||||
|
@ -84,3 +80,10 @@ class ArrayUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Equatable {
|
||||
let stringValue: String
|
||||
let intValue: Int
|
||||
}
|
||||
|
|
|
@ -8,17 +8,16 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class DependenciesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
override func spec() {
|
||||
var dependencies: Dependencies!
|
||||
@TestState var dependencies: Dependencies! = Dependencies()
|
||||
|
||||
// MARK: - Dependencies
|
||||
describe("Dependencies") {
|
||||
beforeEach {
|
||||
dependencies = Dependencies()
|
||||
}
|
||||
|
||||
// MARK: -- when accessing dateNow
|
||||
context("when accessing dateNow") {
|
||||
// MARK: ---- creates a new date every time when not overwritten
|
||||
it("creates a new date every time when not overwritten") {
|
||||
let date1 = dependencies.dateNow
|
||||
Thread.sleep(forTimeInterval: 0.05)
|
||||
|
@ -27,6 +26,7 @@ class DependenciesSpec: QuickSpec {
|
|||
expect(date1.timeIntervalSince1970).toNot(equal(date2.timeIntervalSince1970))
|
||||
}
|
||||
|
||||
// MARK: ---- returns the same new date every time when overwritten
|
||||
it("returns the same new date every time when overwritten") {
|
||||
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class SessionIdSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SessionId
|
||||
describe("a SessionId") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- with an idString
|
||||
context("with an idString") {
|
||||
// MARK: ------ succeeds when correct
|
||||
it("succeeds when correct") {
|
||||
let sessionId: SessionId? = SessionId(from: "05\(TestConstants.publicKey)")
|
||||
|
||||
|
@ -21,16 +23,20 @@ class SessionIdSpec: QuickSpec {
|
|||
expect(sessionId?.publicKey).to(equal(TestConstants.publicKey))
|
||||
}
|
||||
|
||||
// MARK: ------ fails when too short
|
||||
it("fails when too short") {
|
||||
expect(SessionId(from: "")).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with an invalid prefix
|
||||
it("fails with an invalid prefix") {
|
||||
expect(SessionId(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- with a prefix and publicKey
|
||||
context("with a prefix and publicKey") {
|
||||
// MARK: ------ converts the bytes into a hex string
|
||||
it("converts the bytes into a hex string") {
|
||||
let sessionId: SessionId? = SessionId(.standard, publicKey: [0, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
|
||||
|
@ -40,6 +46,7 @@ class SessionIdSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- generates the correct hex string
|
||||
it("generates the correct hex string") {
|
||||
expect(SessionId(.unblinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString)
|
||||
.to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||
|
@ -52,9 +59,13 @@ class SessionIdSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - a SessionId Prefix
|
||||
describe("a SessionId Prefix") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- with just a prefix
|
||||
context("with just a prefix") {
|
||||
// MARK: ------ succeeds when valid
|
||||
it("succeeds when valid") {
|
||||
expect(SessionId.Prefix(from: "00")).to(equal(.unblinded))
|
||||
expect(SessionId.Prefix(from: "05")).to(equal(.standard))
|
||||
|
@ -62,24 +73,30 @@ class SessionIdSpec: QuickSpec {
|
|||
expect(SessionId.Prefix(from: "25")).to(equal(.blinded25))
|
||||
}
|
||||
|
||||
// MARK: ------ fails when nil
|
||||
it("fails when nil") {
|
||||
expect(SessionId.Prefix(from: nil)).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails when invalid
|
||||
it("fails when invalid") {
|
||||
expect(SessionId.Prefix(from: "AB")).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- with a longer string
|
||||
context("with a longer string") {
|
||||
// MARK: ------ fails with invalid hex
|
||||
it("fails with invalid hex") {
|
||||
expect(SessionId.Prefix(from: "Hello!!!")).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with the wrong length
|
||||
it("fails with the wrong length") {
|
||||
expect(SessionId.Prefix(from: String(TestConstants.publicKey.prefix(10)))).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with an invalid prefix
|
||||
it("fails with an invalid prefix") {
|
||||
expect(SessionId.Prefix(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||
}
|
||||
|
|
|
@ -9,120 +9,10 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class JobRunnerSpec: QuickSpec {
|
||||
struct TestDetails: Codable {
|
||||
enum ResultType: Codable {
|
||||
case success
|
||||
case failure
|
||||
case permanentFailure
|
||||
case deferred
|
||||
}
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
public let result: ResultType
|
||||
public let completeTime: Int
|
||||
public let intValue: Int64
|
||||
public let stringValue: String
|
||||
|
||||
init(
|
||||
result: ResultType = .success,
|
||||
completeTime: Int = 0,
|
||||
intValue: Int64 = 100,
|
||||
stringValue: String = "200"
|
||||
) {
|
||||
self.result = result
|
||||
self.completeTime = completeTime
|
||||
self.intValue = intValue
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
}
|
||||
|
||||
struct InvalidDetails: Codable {
|
||||
func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed }
|
||||
}
|
||||
|
||||
enum TestJob: JobExecutor {
|
||||
static let maxFailureCount: Int = 1
|
||||
static let requiresThreadId: Bool = false
|
||||
static let requiresInteractionId: Bool = false
|
||||
|
||||
static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData)
|
||||
else { return success(job, true, dependencies) }
|
||||
|
||||
let completeJob: () -> () = {
|
||||
// Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job
|
||||
// from immediately being run again or immediately completing afterwards
|
||||
let updatedJob: Job = job
|
||||
.with(nextRunTimestamp: TimeInterval(details.completeTime + 1))
|
||||
.with(
|
||||
details: TestDetails(
|
||||
result: details.result,
|
||||
completeTime: (details.completeTime + 2),
|
||||
intValue: details.intValue,
|
||||
stringValue: details.stringValue
|
||||
)
|
||||
)!
|
||||
dependencies.storage.write { db in try _ = updatedJob.saved(db) }
|
||||
|
||||
switch details.result {
|
||||
case .success: success(job, true, dependencies)
|
||||
case .failure: failure(job, nil, false, dependencies)
|
||||
case .permanentFailure: failure(job, nil, true, dependencies)
|
||||
case .deferred: deferred(updatedJob, dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
guard dependencies.fixedTime < details.completeTime else {
|
||||
return queue.async(using: dependencies) {
|
||||
completeJob()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.asyncExecutions.appendTo(details.completeTime) {
|
||||
queue.async(using: dependencies) {
|
||||
completeJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var jobRunner: JobRunnerType!
|
||||
var job1: Job!
|
||||
var job2: Job!
|
||||
var mockStorage: Storage!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a JobRunner") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self
|
||||
]
|
||||
)
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
dateNow: Date(timeIntervalSince1970: 0),
|
||||
forceSynchronous: true
|
||||
)
|
||||
|
||||
// Migrations add jobs which we don't want so delete them
|
||||
mockStorage.write { db in try Job.deleteAll(db) }
|
||||
|
||||
job1 = Job(
|
||||
@TestState var job1: Job! = Job(
|
||||
id: 100,
|
||||
failureCount: 0,
|
||||
variant: .messageSend,
|
||||
|
@ -134,7 +24,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
interactionId: nil,
|
||||
details: nil
|
||||
)
|
||||
job2 = Job(
|
||||
@TestState var job2: Job! = Job(
|
||||
id: 101,
|
||||
failureCount: 0,
|
||||
variant: .attachmentUpload,
|
||||
|
@ -146,28 +36,44 @@ class JobRunnerSpec: QuickSpec {
|
|||
interactionId: nil,
|
||||
details: nil
|
||||
)
|
||||
|
||||
jobRunner = JobRunner(isTestingJobRunner: true, using: dependencies)
|
||||
jobRunner.setExecutor(TestJob.self, for: .messageSend)
|
||||
jobRunner.setExecutor(TestJob.self, for: .attachmentUpload)
|
||||
jobRunner.setExecutor(TestJob.self, for: .messageReceive)
|
||||
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self
|
||||
],
|
||||
initialData: { db in
|
||||
// Migrations add jobs which we don't want so delete them
|
||||
try Job.deleteAll(db)
|
||||
}
|
||||
)
|
||||
@TestState var dependencies: Dependencies! = Dependencies(
|
||||
storage: mockStorage,
|
||||
dateNow: Date(timeIntervalSince1970: 0),
|
||||
forceSynchronous: true
|
||||
)
|
||||
@TestState var jobRunner: JobRunnerType! = {
|
||||
let result = JobRunner(isTestingJobRunner: true, using: dependencies)
|
||||
result.setExecutor(TestJob.self, for: .messageSend)
|
||||
result.setExecutor(TestJob.self, for: .attachmentUpload)
|
||||
result.setExecutor(TestJob.self, for: .messageReceive)
|
||||
|
||||
// Need to assign this to ensure it's used by nested dependencies
|
||||
dependencies.jobRunner = jobRunner
|
||||
}
|
||||
dependencies.jobRunner = result
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: - a JobRunner
|
||||
describe("a JobRunner") {
|
||||
afterEach {
|
||||
/// We **must** set `fixedTime` to ensure we break any loops within the `TestJob` executor
|
||||
dependencies.fixedTime = Int.max
|
||||
jobRunner.stopAndClearPendingJobs()
|
||||
jobRunner = nil
|
||||
mockStorage = nil
|
||||
dependencies = nil
|
||||
}
|
||||
|
||||
// MARK: - when configuring
|
||||
// MARK: -- when configuring
|
||||
context("when configuring") {
|
||||
// MARK: -- adds an executor correctly
|
||||
// MARK: ---- adds an executor correctly
|
||||
it("adds an executor correctly") {
|
||||
job1 = Job(
|
||||
id: 101,
|
||||
|
@ -222,28 +128,28 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when managing state
|
||||
// MARK: ---- when managing state
|
||||
context("when managing state") {
|
||||
// MARK: ---- by checking if a job is currently running
|
||||
// MARK: ------ by checking if a job is currently running
|
||||
context("by checking if a job is currently running") {
|
||||
// MARK: ------ returns false when not given a job
|
||||
// MARK: -------- returns false when not given a job
|
||||
it("returns false when not given a job") {
|
||||
expect(jobRunner.isCurrentlyRunning(nil)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns false when given a job that has not been persisted
|
||||
// MARK: -------- returns false when given a job that has not been persisted
|
||||
it("returns false when given a job that has not been persisted") {
|
||||
job1 = Job(variant: .messageSend)
|
||||
|
||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns false when given a job that is not running
|
||||
// MARK: -------- returns false when given a job that is not running
|
||||
it("returns false when given a job that is not running") {
|
||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when given a non blocking job that is running
|
||||
// MARK: -------- returns true when given a non blocking job that is running
|
||||
it("returns true when given a non blocking job that is running") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
|
@ -261,7 +167,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when given a blocking job that is running
|
||||
// MARK: -------- returns true when given a blocking job that is running
|
||||
it("returns true when given a blocking job that is running") {
|
||||
job2 = Job(
|
||||
id: 101,
|
||||
|
@ -293,14 +199,14 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by getting the details for jobs
|
||||
// MARK: ------ by getting the details for jobs
|
||||
context("by getting the details for jobs") {
|
||||
// MARK: ------ returns an empty dictionary when there are no jobs
|
||||
// MARK: -------- returns an empty dictionary when there are no jobs
|
||||
it("returns an empty dictionary when there are no jobs") {
|
||||
expect(jobRunner.allJobInfo()).to(equal([:]))
|
||||
}
|
||||
|
||||
// MARK: ------ returns an empty dictionary when there are no jobs matching the filters
|
||||
// MARK: -------- returns an empty dictionary when there are no jobs matching the filters
|
||||
it("returns an empty dictionary when there are no jobs matching the filters") {
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
jobRunner.appDidBecomeActive(using: dependencies)
|
||||
|
@ -317,7 +223,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.jobInfoFor(state: .running, variant: .messageSend)).to(equal([:]))
|
||||
}
|
||||
|
||||
// MARK: ------ can filter to specific jobs
|
||||
// MARK: -------- can filter to specific jobs
|
||||
it("can filter to specific jobs") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -386,7 +292,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ------ can filter to running jobs
|
||||
// MARK: -------- can filter to running jobs
|
||||
it("can filter to running jobs") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -450,7 +356,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
||||
}
|
||||
|
||||
// MARK: ------ can filter to pending jobs
|
||||
// MARK: -------- can filter to pending jobs
|
||||
it("can filter to pending jobs") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -514,7 +420,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
||||
}
|
||||
|
||||
// MARK: ------ can filter to specific variants
|
||||
// MARK: -------- can filter to specific variants
|
||||
it("can filter to specific variants") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
||||
|
@ -552,7 +458,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
||||
}
|
||||
|
||||
// MARK: ------ includes non blocking jobs
|
||||
// MARK: -------- includes non blocking jobs
|
||||
it("includes non blocking jobs") {
|
||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
|
@ -580,7 +486,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ------ includes blocking jobs
|
||||
// MARK: -------- includes blocking jobs
|
||||
it("includes blocking jobs") {
|
||||
job2 = Job(
|
||||
id: 101,
|
||||
|
@ -622,9 +528,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by checking for an existing job
|
||||
// MARK: ------ by checking for an existing job
|
||||
context("by checking for an existing job") {
|
||||
// MARK: ------ returns false for a queue that doesn't exist
|
||||
// MARK: -------- returns false for a queue that doesn't exist
|
||||
it("returns false for a queue that doesn't exist") {
|
||||
jobRunner = JobRunner(
|
||||
isTestingJobRunner: true,
|
||||
|
@ -636,19 +542,19 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns false when the provided details fail to decode
|
||||
// MARK: -------- returns false when the provided details fail to decode
|
||||
it("returns false when the provided details fail to decode") {
|
||||
expect(jobRunner.hasJob(of: .attachmentUpload, with: InvalidDetails()))
|
||||
.to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns false when there is not a pending or running job
|
||||
// MARK: -------- returns false when there is not a pending or running job
|
||||
it("returns false when there is not a pending or running job") {
|
||||
expect(jobRunner.hasJob(of: .attachmentUpload, with: TestDetails()))
|
||||
.to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when there is a pending job
|
||||
// MARK: -------- returns true when there is a pending job
|
||||
it("returns true when there is a pending job") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -703,7 +609,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when there is a running job
|
||||
// MARK: -------- returns true when there is a running job
|
||||
it("returns true when there is a running job") {
|
||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
|
@ -724,7 +630,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when there is a blocking job
|
||||
// MARK: -------- returns true when there is a blocking job
|
||||
it("returns true when there is a blocking job") {
|
||||
job2 = Job(
|
||||
id: 101,
|
||||
|
@ -760,7 +666,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ returns true when there is a non blocking job
|
||||
// MARK: -------- returns true when there is a non blocking job
|
||||
it("returns true when there is a non blocking job") {
|
||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
|
@ -782,9 +688,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by being notified of app launch
|
||||
// MARK: ------ by being notified of app launch
|
||||
context("by being notified of app launch") {
|
||||
// MARK: ------ does not start a job before getting the app launch call
|
||||
// MARK: -------- does not start a job before getting the app launch call
|
||||
it("does not start a job before getting the app launch call") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
|
||||
|
@ -800,7 +706,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ starts the job queues if there are no app launch jobs
|
||||
// MARK: -------- starts the job queues if there are no app launch jobs
|
||||
it("does nothing if there are no app launch jobs") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
|
||||
|
@ -818,9 +724,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by being notified of app becoming active
|
||||
// MARK: ------ by being notified of app becoming active
|
||||
context("by being notified of app becoming active") {
|
||||
// MARK: ------ does not start a job before getting the app active call
|
||||
// MARK: -------- does not start a job before getting the app active call
|
||||
it("does not start a job before getting the app active call") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
|
||||
|
@ -836,7 +742,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ does not start the job queues if there are no app active jobs and blocking jobs are running
|
||||
// MARK: -------- does not start the job queues if there are no app active jobs and blocking jobs are running
|
||||
it("does not start the job queues if there are no app active jobs and blocking jobs are running") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||
job2 = Job(
|
||||
|
@ -881,7 +787,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ does not start the job queues if there are app active jobs and blocking jobs are running
|
||||
// MARK: -------- does not start the job queues if there are app active jobs and blocking jobs are running
|
||||
it("does not start the job queues if there are app active jobs and blocking jobs are running") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -939,7 +845,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ------ starts the job queues if there are no app active jobs
|
||||
// MARK: -------- starts the job queues if there are no app active jobs
|
||||
it("starts the job queues if there are no app active jobs") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
|
@ -962,7 +868,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ starts the job queues if there are app active jobs
|
||||
// MARK: -------- starts the job queues if there are app active jobs
|
||||
it("starts the job queues if there are app active jobs") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1020,7 +926,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job2)).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ starts the job queues after completing blocking app launch jobs
|
||||
// MARK: -------- starts the job queues after completing blocking app launch jobs
|
||||
it("starts the job queues after completing blocking app launch jobs") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||
job2 = Job(
|
||||
|
@ -1074,7 +980,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ starts the job queues alongside non blocking app launch jobs
|
||||
// MARK: -------- starts the job queues alongside non blocking app launch jobs
|
||||
it("starts the job queues alongside non blocking app launch jobs") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
job2 = Job(
|
||||
|
@ -1120,9 +1026,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by checking if a job can be added to the queue
|
||||
// MARK: ------ by checking if a job can be added to the queue
|
||||
context("by checking if a job can be added to the queue") {
|
||||
// MARK: ------ does not add a general job to the queue before launch
|
||||
// MARK: -------- does not add a general job to the queue before launch
|
||||
it("does not add a general job to the queue before launch") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1151,7 +1057,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.allJobInfo()).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ adds a launch job to the queue in a pending state before launch
|
||||
// MARK: -------- adds a launch job to the queue in a pending state before launch
|
||||
it("adds a launch job to the queue in a pending state before launch") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1180,7 +1086,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: [.pending]).keys)).to(equal([100]))
|
||||
}
|
||||
|
||||
// MARK: ------ does not add a general job to the queue after launch but before becoming active
|
||||
// MARK: -------- does not add a general job to the queue after launch but before becoming active
|
||||
it("does not add a general job to the queue after launch but before becoming active") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1210,7 +1116,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.allJobInfo()).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ adds a launch job to the queue in a pending state after launch but before becoming active
|
||||
// MARK: -------- adds a launch job to the queue in a pending state after launch but before becoming active
|
||||
it("adds a launch job to the queue in a pending state after launch but before becoming active") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1240,7 +1146,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .pending).keys)).to(equal([100]))
|
||||
}
|
||||
|
||||
// MARK: ------ adds a general job to the queue after becoming active
|
||||
// MARK: -------- adds a general job to the queue after becoming active
|
||||
it("adds a general job to the queue after becoming active") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1271,7 +1177,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.allJobInfo().keys)).to(equal([100]))
|
||||
}
|
||||
|
||||
// MARK: ------ adds a launch job to the queue and starts it after becoming active
|
||||
// MARK: -------- adds a launch job to the queue and starts it after becoming active
|
||||
it("adds a launch job to the queue and starts it after becoming active") {
|
||||
job1 = Job(
|
||||
id: 100,
|
||||
|
@ -1304,16 +1210,16 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when running jobs
|
||||
// MARK: ---- when running jobs
|
||||
context("when running jobs") {
|
||||
beforeEach {
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
jobRunner.appDidBecomeActive(using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: ---- by adding
|
||||
// MARK: ------ by adding
|
||||
context("by adding") {
|
||||
// MARK: ------ does not start until after the db transaction completes
|
||||
// MARK: -------- does not start until after the db transaction completes
|
||||
it("does not start until after the db transaction completes") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
|
||||
|
@ -1327,9 +1233,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- with job dependencies
|
||||
// MARK: ------ with job dependencies
|
||||
context("with job dependencies") {
|
||||
// MARK: ------ starts dependencies first
|
||||
// MARK: -------- starts dependencies first
|
||||
it("starts dependencies first") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
||||
|
@ -1347,7 +1253,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(equal([101]))
|
||||
}
|
||||
|
||||
// MARK: ------ removes the initial job from the queue
|
||||
// MARK: -------- removes the initial job from the queue
|
||||
it("removes the initial job from the queue") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
||||
|
@ -1366,7 +1272,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(jobRunner.jobInfoFor(state: .running, variant: .messageSend).keys).toNot(contain(100))
|
||||
}
|
||||
|
||||
// MARK: ------ starts the initial job when the dependencies succeed
|
||||
// MARK: -------- starts the initial job when the dependencies succeed
|
||||
it("starts the initial job when the dependencies succeed") {
|
||||
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||
|
@ -1390,7 +1296,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
.to(equal([100]))
|
||||
}
|
||||
|
||||
// MARK: ------ does not start the initial job if the dependencies are deferred
|
||||
// MARK: -------- does not start the initial job if the dependencies are deferred
|
||||
it("does not start the initial job if the dependencies are deferred") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||
job2 = job2.with(details: TestDetails(result: .deferred, completeTime: 1))
|
||||
|
@ -1413,7 +1319,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ does not start the initial job if the dependencies fail
|
||||
// MARK: -------- does not start the initial job if the dependencies fail
|
||||
it("does not start the initial job if the dependencies fail") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||
job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1))
|
||||
|
@ -1436,7 +1342,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ does not delete the initial job if the dependencies fail
|
||||
// MARK: -------- does not delete the initial job if the dependencies fail
|
||||
it("does not delete the initial job if the dependencies fail") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||
job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1))
|
||||
|
@ -1466,7 +1372,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(mockStorage.read { db in try Job.fetchCount(db) }).to(equal(2))
|
||||
}
|
||||
|
||||
// MARK: ------ deletes the initial job if the dependencies permanently fail
|
||||
// MARK: -------- deletes the initial job if the dependencies permanently fail
|
||||
it("deletes the initial job if the dependencies permanently fail") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||
job2 = job2.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
||||
|
@ -1495,16 +1401,16 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when completing jobs
|
||||
// MARK: ---- when completing jobs
|
||||
context("when completing jobs") {
|
||||
beforeEach {
|
||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||
jobRunner.appDidBecomeActive(using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: ---- by succeeding
|
||||
// MARK: ------ by succeeding
|
||||
context("by succeeding") {
|
||||
// MARK: ------ removes the job from the queue
|
||||
// MARK: -------- removes the job from the queue
|
||||
it("removes the job from the queue") {
|
||||
job1 = job1.with(details: TestDetails(result: .success, completeTime: 1))
|
||||
|
||||
|
@ -1522,7 +1428,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ deletes the job
|
||||
// MARK: -------- deletes the job
|
||||
it("deletes the job") {
|
||||
job1 = job1.with(details: TestDetails(result: .success, completeTime: 1))
|
||||
|
||||
|
@ -1544,9 +1450,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by deferring
|
||||
// MARK: ------ by deferring
|
||||
context("by deferring") {
|
||||
// MARK: ------ reschedules the job to run again later
|
||||
// MARK: -------- reschedules the job to run again later
|
||||
it("reschedules the job to run again later") {
|
||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
||||
|
||||
|
@ -1571,7 +1477,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ------ does not delete the job
|
||||
// MARK: -------- does not delete the job
|
||||
it("does not delete the job") {
|
||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
||||
|
||||
|
@ -1592,7 +1498,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(mockStorage.read { db in try Job.fetchCount(db) }).toNot(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ------ fails the job if it is deferred too many times
|
||||
// MARK: -------- fails the job if it is deferred too many times
|
||||
it("fails the job if it is deferred too many times") {
|
||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
||||
|
||||
|
@ -1666,9 +1572,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by failing
|
||||
// MARK: ------ by failing
|
||||
context("by failing") {
|
||||
// MARK: ------ removes the job from the queue
|
||||
// MARK: -------- removes the job from the queue
|
||||
it("removes the job from the queue") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1))
|
||||
|
||||
|
@ -1686,7 +1592,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ does not delete the job
|
||||
// MARK: -------- does not delete the job
|
||||
it("does not delete the job") {
|
||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1))
|
||||
|
||||
|
@ -1708,9 +1614,9 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: ---- by permanently failing
|
||||
// MARK: ------ by permanently failing
|
||||
context("by permanently failing") {
|
||||
// MARK: ------ removes the job from the queue
|
||||
// MARK: -------- removes the job from the queue
|
||||
it("removes the job from the queue") {
|
||||
job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
||||
|
||||
|
@ -1728,7 +1634,7 @@ class JobRunnerSpec: QuickSpec {
|
|||
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||
}
|
||||
|
||||
// MARK: ------ deletes the job
|
||||
// MARK: -------- deletes the job
|
||||
it("deletes the job") {
|
||||
job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
||||
|
||||
|
@ -1753,3 +1659,90 @@ class JobRunnerSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestDetails: Codable {
|
||||
enum ResultType: Codable {
|
||||
case success
|
||||
case failure
|
||||
case permanentFailure
|
||||
case deferred
|
||||
}
|
||||
|
||||
public let result: ResultType
|
||||
public let completeTime: Int
|
||||
public let intValue: Int64
|
||||
public let stringValue: String
|
||||
|
||||
init(
|
||||
result: ResultType = .success,
|
||||
completeTime: Int = 0,
|
||||
intValue: Int64 = 100,
|
||||
stringValue: String = "200"
|
||||
) {
|
||||
self.result = result
|
||||
self.completeTime = completeTime
|
||||
self.intValue = intValue
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct InvalidDetails: Codable {
|
||||
func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed }
|
||||
}
|
||||
|
||||
fileprivate enum TestJob: JobExecutor {
|
||||
static let maxFailureCount: Int = 1
|
||||
static let requiresThreadId: Bool = false
|
||||
static let requiresInteractionId: Bool = false
|
||||
|
||||
static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData)
|
||||
else { return success(job, true, dependencies) }
|
||||
|
||||
let completeJob: () -> () = {
|
||||
// Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job
|
||||
// from immediately being run again or immediately completing afterwards
|
||||
let updatedJob: Job = job
|
||||
.with(nextRunTimestamp: TimeInterval(details.completeTime + 1))
|
||||
.with(
|
||||
details: TestDetails(
|
||||
result: details.result,
|
||||
completeTime: (details.completeTime + 2),
|
||||
intValue: details.intValue,
|
||||
stringValue: details.stringValue
|
||||
)
|
||||
)!
|
||||
dependencies.storage.write { db in try _ = updatedJob.saved(db) }
|
||||
|
||||
switch details.result {
|
||||
case .success: success(job, true, dependencies)
|
||||
case .failure: failure(job, nil, false, dependencies)
|
||||
case .permanentFailure: failure(job, nil, true, dependencies)
|
||||
case .deferred: deferred(updatedJob, dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
guard dependencies.fixedTime < details.completeTime else {
|
||||
return queue.async(using: dependencies) {
|
||||
completeJob()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.asyncExecutions.appendTo(details.completeTime) {
|
||||
queue.async(using: dependencies) {
|
||||
completeJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,145 +9,13 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class BatchResponseSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let stringValue: String
|
||||
}
|
||||
struct TestType2: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue2: String
|
||||
}
|
||||
override class func spec() {
|
||||
// MARK: Configuration
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
// MARK: - HTTP.BatchSubResponse<T>
|
||||
|
||||
describe("an HTTP.BatchSubResponse<T>") {
|
||||
context("when decoding") {
|
||||
it("decodes correctly") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": {
|
||||
"stringValue": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).toNot(beNil())
|
||||
}
|
||||
|
||||
it("decodes with invalid body data") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": "Hello!!!"
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
}
|
||||
|
||||
it("flags invalid body data as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": "Hello!!!"
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beTrue())
|
||||
}
|
||||
|
||||
it("does not flag a missing or invalid optional body as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType?>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType?>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||
}
|
||||
|
||||
it("does not flag a NoResponse body as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<NoResponse>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<NoResponse>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
// MARK: --Decodable
|
||||
|
||||
describe("a Decodable") {
|
||||
it("decodes correctly") {
|
||||
let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)!
|
||||
let result: TestType? = try? TestType.decoded(from: jsonData)
|
||||
|
||||
expect(result).to(equal(TestType(stringValue: "testValue")))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - --Combine
|
||||
|
||||
describe("a (ResponseInfoType, Data?) Publisher") {
|
||||
var responseInfo: ResponseInfoType!
|
||||
var testType: TestType!
|
||||
var testType2: TestType2!
|
||||
var data: Data!
|
||||
|
||||
beforeEach {
|
||||
responseInfo = HTTP.ResponseInfo(code: 200, headers: [:])
|
||||
testType = TestType(stringValue: "test1")
|
||||
testType2 = TestType2(intValue: 123, stringValue2: "test2")
|
||||
data = """
|
||||
@TestState var responseInfo: ResponseInfoType! = HTTP.ResponseInfo(code: 200, headers: [:])
|
||||
@TestState var testType: TestType! = TestType(stringValue: "test1")
|
||||
@TestState var testType2: TestType2! = TestType2(intValue: 123, stringValue2: "test2")
|
||||
@TestState var data: Data! = """
|
||||
[\([
|
||||
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
|
||||
HTTP.BatchSubResponse(
|
||||
|
@ -169,8 +37,129 @@ class BatchResponseSpec: QuickSpec {
|
|||
.map { String(data: $0, encoding: .utf8)! }
|
||||
.joined(separator: ","))]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
// MARK: - an HTTP.BatchSubResponse<T>
|
||||
describe("an HTTP.BatchSubResponse<T>") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- decodes correctly
|
||||
it("decodes correctly") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": {
|
||||
"stringValue": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- decodes with invalid body data
|
||||
it("decodes with invalid body data") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": "Hello!!!"
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- flags invalid body data as invalid
|
||||
it("flags invalid body data as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
},
|
||||
"body": "Hello!!!"
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- does not flag a missing or invalid optional body as invalid
|
||||
it("does not flag a missing or invalid optional body as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<TestType?>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<TestType?>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- does not flag a NoResponse body as invalid
|
||||
it("does not flag a NoResponse body as invalid") {
|
||||
let jsonString: String = """
|
||||
{
|
||||
"code": 200,
|
||||
"headers": {
|
||||
"testKey": "testValue"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let subResponse: HTTP.BatchSubResponse<NoResponse>? = try? JSONDecoder().decode(
|
||||
HTTP.BatchSubResponse<NoResponse>.self,
|
||||
from: jsonString.data(using: .utf8)!
|
||||
)
|
||||
|
||||
expect(subResponse).toNot(beNil())
|
||||
expect(subResponse?.body).to(beNil())
|
||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - a Decodable
|
||||
describe("a Decodable") {
|
||||
// MARK: -- decodes correctly
|
||||
it("decodes correctly") {
|
||||
let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)!
|
||||
let result: TestType? = try? TestType.decoded(from: jsonData)
|
||||
|
||||
expect(result).to(equal(TestType(stringValue: "testValue")))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - a (ResponseInfoType, Data?) Publisher
|
||||
describe("a (ResponseInfoType, Data?) Publisher") {
|
||||
// MARK: -- decodes valid data correctly
|
||||
it("decodes valid data correctly") {
|
||||
var result: HTTP.BatchResponse?
|
||||
Just((responseInfo, data))
|
||||
|
@ -191,6 +180,7 @@ class BatchResponseSpec: QuickSpec {
|
|||
.to(equal(testType2))
|
||||
}
|
||||
|
||||
// MARK: -- fails if there is no data
|
||||
it("fails if there is no data") {
|
||||
var error: Error?
|
||||
Just((responseInfo, nil))
|
||||
|
@ -203,6 +193,7 @@ class BatchResponseSpec: QuickSpec {
|
|||
expect(error).to(matchError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: -- fails if the data is not JSON
|
||||
it("fails if the data is not JSON") {
|
||||
var error: Error?
|
||||
Just((responseInfo, Data([1, 2, 3])))
|
||||
|
@ -215,6 +206,7 @@ class BatchResponseSpec: QuickSpec {
|
|||
expect(error).to(matchError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: -- fails if the data is not a JSON array
|
||||
it("fails if the data is not a JSON array") {
|
||||
var error: Error?
|
||||
Just((responseInfo, "{}".data(using: .utf8)))
|
||||
|
@ -227,6 +219,7 @@ class BatchResponseSpec: QuickSpec {
|
|||
expect(error).to(matchError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: -- fails if the JSON array does not have the same number of items as the expected types
|
||||
it("fails if the JSON array does not have the same number of items as the expected types") {
|
||||
var error: Error?
|
||||
Just((responseInfo, data))
|
||||
|
@ -243,6 +236,7 @@ class BatchResponseSpec: QuickSpec {
|
|||
expect(error).to(matchError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: -- fails if one of the JSON array values fails to decode
|
||||
it("fails if one of the JSON array values fails to decode") {
|
||||
data = """
|
||||
[\([
|
||||
|
@ -275,3 +269,13 @@ class BatchResponseSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Codable, Equatable {
|
||||
let stringValue: String
|
||||
}
|
||||
fileprivate struct TestType2: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue2: String
|
||||
}
|
||||
|
|
|
@ -8,42 +8,12 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class BencodeSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
struct TestType2: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
}
|
||||
|
||||
struct TestType3: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
|
||||
init(_ stringValue: String, _ boolValue: Bool) {
|
||||
self.stringValue = stringValue
|
||||
self.boolValue = boolValue
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self = TestType3(
|
||||
try container.decode(String.self, forKey: .stringValue),
|
||||
((try? container.decode(Bool.self, forKey: .boolValue)) ?? false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - Bencode
|
||||
describe("Bencode") {
|
||||
// MARK: - when decoding
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: -- should decode a basic string
|
||||
// MARK: ---- should decode a basic string
|
||||
it("should decode a basic string") {
|
||||
let basicStringData: Data = "5:howdy".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(String.self, from: basicStringData)
|
||||
|
@ -51,7 +21,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal("howdy"))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a basic integer
|
||||
// MARK: ---- should decode a basic integer
|
||||
it("should decode a basic integer") {
|
||||
let basicIntegerData: Data = "i3e".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(Int.self, from: basicIntegerData)
|
||||
|
@ -59,7 +29,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(3))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a list of integers
|
||||
// MARK: ---- should decode a list of integers
|
||||
it("should decode a list of integers") {
|
||||
let basicIntListData: Data = "li1ei2ee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([Int].self, from: basicIntListData)
|
||||
|
@ -67,7 +37,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal([1, 2]))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a basic dict
|
||||
// MARK: ---- should decode a basic dict
|
||||
it("should decode a basic dict") {
|
||||
let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([String: [String]].self, from: basicDictData)
|
||||
|
@ -75,7 +45,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(["spam": ["a", "b"]]))
|
||||
}
|
||||
|
||||
// MARK: -- decodes a decodable type
|
||||
// MARK: ---- decodes a decodable type
|
||||
it("decodes a decodable type") {
|
||||
let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)!
|
||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
||||
|
@ -83,7 +53,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(TestType(intValue: 100, stringValue: "Test")))
|
||||
}
|
||||
|
||||
// MARK: -- decodes a stringified decodable type
|
||||
// MARK: ---- decodes a stringified decodable type
|
||||
it("decodes a stringified decodable type") {
|
||||
let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)!
|
||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
||||
|
@ -92,11 +62,11 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when decoding a response
|
||||
// MARK: -- when decoding a response
|
||||
context("when decoding a response") {
|
||||
// MARK: -- with a decodable type
|
||||
// MARK: ---- with a decodable type
|
||||
context("with a decodable type") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -114,7 +84,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste"
|
||||
.data(using: .utf8)!
|
||||
|
@ -132,7 +102,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when given an invalid length
|
||||
// MARK: ------ throws a parsing error when given an invalid length
|
||||
it("throws a parsing error when given an invalid length") {
|
||||
let data: Data = "ld12:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -143,7 +113,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when given an invalid key
|
||||
// MARK: ------ throws a parsing error when given an invalid key
|
||||
it("throws a parsing error when given an invalid key") {
|
||||
let data: Data = "ld7:INVALIDi100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -154,7 +124,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes correctly when trying to decode an int to a bool with custom handling
|
||||
// MARK: ------ decodes correctly when trying to decode an int to a bool with custom handling
|
||||
it("decodes correctly when trying to decode an int to a bool with custom handling") {
|
||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||
.data(using: .utf8)!
|
||||
|
@ -165,7 +135,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.toNot(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when trying to decode an int to a bool
|
||||
// MARK: ------ throws a parsing error when trying to decode an int to a bool
|
||||
it("throws a parsing error when trying to decode an int to a bool") {
|
||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||
.data(using: .utf8)!
|
||||
|
@ -177,9 +147,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with stringified json info
|
||||
// MARK: ---- with stringified json info
|
||||
context("with stringified json info") {
|
||||
// MARK: -- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -197,7 +167,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -215,7 +185,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -227,9 +197,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a string value
|
||||
// MARK: ---- with a string value
|
||||
context("with a string value") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "l4:Test5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)!
|
||||
let result: BencodeResponse<String>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -243,7 +213,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
||||
let result: BencodeResponse<String>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -257,7 +227,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l10:Teste".data(using: .utf8)!
|
||||
|
||||
|
@ -268,9 +238,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with an int value
|
||||
// MARK: ---- with an int value
|
||||
context("with an int value") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "li100e5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)!
|
||||
let result: BencodeResponse<Int>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -284,7 +254,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "li100ee".data(using: .utf8)!
|
||||
let result: BencodeResponse<Int>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -298,7 +268,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
||||
|
||||
|
@ -312,3 +282,34 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
fileprivate struct TestType2: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
}
|
||||
|
||||
fileprivate struct TestType3: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
|
||||
init(_ stringValue: String, _ boolValue: Bool) {
|
||||
self.stringValue = stringValue
|
||||
self.boolValue = boolValue
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self = TestType3(
|
||||
try container.decode(String.self, forKey: .stringValue),
|
||||
((try? container.decode(Bool.self, forKey: .boolValue)) ?? false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class VersionSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Version
|
||||
describe("a Version") {
|
||||
// MARK: -- can be created from a string
|
||||
it("can be created from a string") {
|
||||
let version: Version = Version.from("1.20.3")
|
||||
|
||||
|
@ -20,13 +20,16 @@ class VersionSpec: QuickSpec {
|
|||
expect(version.patch).to(equal(3))
|
||||
}
|
||||
|
||||
// MARK: -- correctly exposes a string value
|
||||
it("correctly exposes a string value") {
|
||||
let version: Version = Version(major: 1, minor: 20, patch: 3)
|
||||
|
||||
expect(version.stringValue).to(equal("1.20.3"))
|
||||
}
|
||||
|
||||
// MARK: -- when checking equality
|
||||
context("when checking equality") {
|
||||
// MARK: ---- returns true if the values match
|
||||
it("returns true if the values match") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.0")
|
||||
|
@ -35,6 +38,7 @@ class VersionSpec: QuickSpec {
|
|||
.to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns false if the values do not match
|
||||
it("returns false if the values do not match") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.1")
|
||||
|
@ -44,7 +48,9 @@ class VersionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when comparing versions
|
||||
context("when comparing versions") {
|
||||
// MARK: ---- returns correctly for a simple major difference
|
||||
it("returns correctly for a simple major difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("2.0.0")
|
||||
|
@ -53,6 +59,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex major difference
|
||||
it("returns correctly for a complex major difference") {
|
||||
let version1a: Version = Version.from("2.90.90")
|
||||
let version2a: Version = Version.from("10.0.0")
|
||||
|
@ -65,6 +72,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2b > version1b).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a simple minor difference
|
||||
it("returns correctly for a simple minor difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.1.0")
|
||||
|
@ -73,6 +81,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex minor difference
|
||||
it("returns correctly for a complex minor difference") {
|
||||
let version1a: Version = Version.from("90.2.90")
|
||||
let version2a: Version = Version.from("90.10.0")
|
||||
|
@ -85,6 +94,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2b > version1b).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a simple patch difference
|
||||
it("returns correctly for a simple patch difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.1")
|
||||
|
@ -93,6 +103,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex patch difference
|
||||
it("returns correctly for a complex patch difference") {
|
||||
let version1a: Version = Version.from("90.90.2")
|
||||
let version2a: Version = Version.from("90.90.10")
|
||||
|
|
|
@ -17,9 +17,13 @@ public class Mock<T> {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
internal required init(functionHandler: MockFunctionHandler? = nil) {
|
||||
internal required init(
|
||||
functionHandler: MockFunctionHandler? = nil,
|
||||
initialSetup: ((Mock<T>) -> ())? = nil
|
||||
) {
|
||||
self.functionConsumer = FunctionConsumer()
|
||||
self.functionHandler = (functionHandler ?? self.functionConsumer)
|
||||
initialSetup?(self)
|
||||
}
|
||||
|
||||
// MARK: - MockFunctionHandler
|
||||
|
@ -103,7 +107,7 @@ internal class MockFunction {
|
|||
|
||||
internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
||||
private let callBlock: (inout T) throws -> R
|
||||
private let mockInit: (MockFunctionHandler?) -> Mock<T>
|
||||
private let mockInit: (MockFunctionHandler?, ((Mock<T>) -> ())?) -> Mock<T>
|
||||
private var functionName: String?
|
||||
private var parameterCount: Int?
|
||||
private var parameterSummary: String?
|
||||
|
@ -113,7 +117,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?) -> Mock<T>) {
|
||||
init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?, ((Mock<T>) -> ())?) -> Mock<T>) {
|
||||
self.callBlock = callBlock
|
||||
self.mockInit = mockInit
|
||||
}
|
||||
|
@ -141,7 +145,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
|||
// MARK: - Build
|
||||
|
||||
func build() throws -> MockFunction {
|
||||
var completionMock = mockInit(self) as! T
|
||||
var completionMock = mockInit(self, nil) as! T
|
||||
_ = try? callBlock(&completionMock)
|
||||
|
||||
guard let name: String = functionName, let parameterCount: Int = parameterCount, let parameterSummary: String = parameterSummary else {
|
||||
|
|
|
@ -17,6 +17,11 @@ class MockCaches: CachesType {
|
|||
set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } }
|
||||
}
|
||||
|
||||
public func setting<M, I>(cache: CacheInfo.Config<M, I>, to value: M?) -> MockCaches {
|
||||
self[cache] = value
|
||||
return self
|
||||
}
|
||||
|
||||
// MARK: - Mutable Access
|
||||
|
||||
@discardableResult public func mutate<M, I, R>(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Mocked
|
||||
|
@ -32,6 +33,20 @@ func any() -> Double { 0 }
|
|||
func any() -> String { "" }
|
||||
func any() -> Data { Data() }
|
||||
func any() -> Bool { false }
|
||||
func any() -> Dependencies {
|
||||
Dependencies(
|
||||
storage: SynchronousStorage(customWriter: try! DatabaseQueue()),
|
||||
network: MockNetwork(),
|
||||
crypto: MockCrypto(),
|
||||
standardUserDefaults: MockUserDefaults(),
|
||||
caches: MockCaches(),
|
||||
jobRunner: MockJobRunner(),
|
||||
scheduler: .immediate,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890),
|
||||
fixedTime: 0,
|
||||
forceSynchronous: true
|
||||
)
|
||||
}
|
||||
|
||||
func anyAny() -> Any { 0 } // Unique name for compilation performance reasons
|
||||
func anyArray<R>() -> [R] { [] } // Unique name for compilation performance reasons
|
||||
|
|
|
@ -29,7 +29,7 @@ public func call<M, T, R>(
|
|||
matchingParameters: Bool = false,
|
||||
exclusive: Bool = false,
|
||||
functionBlock: @escaping (inout T) throws -> R
|
||||
) -> Predicate<M> where M: Mock<T> {
|
||||
) -> Nimble.Predicate<M> where M: Mock<T> {
|
||||
return Predicate.define { actualExpression in
|
||||
let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock)
|
||||
let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls
|
||||
|
|
|
@ -6,6 +6,16 @@ import GRDB
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class SynchronousStorage: Storage {
|
||||
public init(
|
||||
customWriter: DatabaseWriter? = nil,
|
||||
customMigrationTargets: [MigratableTarget.Type]? = nil,
|
||||
initialData: ((Database) throws -> ())? = nil
|
||||
) {
|
||||
super.init(customWriter: customWriter, customMigrationTargets: customMigrationTargets)
|
||||
|
||||
write { db in try initialData?(db) }
|
||||
}
|
||||
|
||||
@discardableResult override func write<T>(
|
||||
fileName: String = #file,
|
||||
functionName: String = #function,
|
||||
|
|
Loading…
Reference in New Issue