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:
Morgan Pretty 2023-09-22 14:49:39 +10:00
parent 20ce1deb23
commit c4aadaff1c
60 changed files with 3579 additions and 3441 deletions

View File

@ -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

View File

@ -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 */,

View File

@ -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(

View File

@ -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)

View File

@ -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
]
]
)

View File

@ -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)
}

View File

@ -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

View File

@ -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"

View File

@ -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
}
}

View File

@ -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
)

View File

@ -14,7 +14,7 @@ public final class OpenGroupManager {
// MARK: - Variables
public static let shared: OpenGroupManager = OpenGroupManager()
public static let shared: OpenGroupManager = OpenGroupManager()
// MARK: - Polling
@ -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

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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",

View File

@ -10,49 +10,37 @@ import Nimble
@testable import SessionUtilitiesKit
class MessageSendJobSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var job: Job!
var interaction: Interaction!
var attachment: Attachment!
var interactionAttachment: InteractionAttachment!
var mockStorage: Storage!
var mockJobRunner: MockJobRunner!
var dependencies: Dependencies!
override class func spec() {
// MARK: Configuration
// MARK: - JobRunner
describe("a MessageSendJob") {
// MARK: - Configuration
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
]
@TestState var job: Job!
@TestState var interaction: Interaction!
@TestState var attachment: Attachment! = Attachment(
id: "200",
variant: .standard,
state: .failedDownload,
contentType: "text/plain",
byteCount: 200
)
@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 = MockJobRunner()
dependencies = Dependencies(
storage: mockStorage,
jobRunner: mockJobRunner,
dateNow: Date(timeIntervalSince1970: 1234567890)
)
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)
}
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)))
}
afterEach {
job = nil
mockStorage = nil
dependencies = nil
}
// MARK: - fails when not given any details
)
@TestState var dependencies: Dependencies! = Dependencies(
storage: mockStorage,
jobRunner: mockJobRunner,
dateNow: Date(timeIntervalSince1970: 1234567890)
)
// 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,

View File

@ -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
}

View File

@ -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"
]))
}
}
}
}

View File

@ -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"
]))
}
}
}
}
}

View File

@ -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

View File

@ -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"))

View File

@ -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]

View File

@ -9,22 +9,19 @@ import Quick
import Nimble
@testable import SessionMessagingKit
import AVFoundation
class BatchRequestInfoSpec: QuickSpec {
struct TestType: Codable, Equatable {
let stringValue: String
}
// MARK: - Spec
override func spec() {
// MARK: - BatchRequest.Child
override class func spec() {
// MARK: Configuration
@TestState var request: OpenGroupAPI.BatchRequest!
// 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
}

View File

@ -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(

View File

@ -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"))
}

View File

@ -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 = """
{

View File

@ -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 = """
{

View File

@ -9,44 +9,38 @@ import SessionUtilitiesKit
@testable import SessionMessagingKit
class SOGSMessageSpec: QuickSpec {
// MARK: - Spec
override func spec() {
override class func spec() {
// MARK: Configuration
@TestState var messageJson: String! = """
{
"id": 123,
"session_id": "05\(TestConstants.publicKey)",
"posted": 234,
"seqno": 345,
"whisper": false,
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
@TestState var messageData: Data! = messageJson.data(using: .utf8)!
@TestState var mockCrypto: MockCrypto! = MockCrypto()
@TestState var dependencies: Dependencies! = Dependencies(
crypto: mockCrypto
)
@TestState var decoder: JSONDecoder! = {
let result = JSONDecoder()
result.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
return result
}()
// MARK: - a SOGSMessage
describe("a SOGSMessage") {
var messageJson: String!
var messageData: Data!
var decoder: JSONDecoder!
var mockCrypto: MockCrypto!
var dependencies: Dependencies!
beforeEach {
messageJson = """
{
"id": 123,
"session_id": "05\(TestConstants.publicKey)",
"posted": 234,
"seqno": 345,
"whisper": false,
"whisper_mods": false,
"data": "VGVzdERhdGE=",
"signature": "VGVzdFNpZ25hdHVyZQ=="
}
"""
messageData = messageJson.data(using: .utf8)!
mockCrypto = MockCrypto()
dependencies = Dependencies(
crypto: mockCrypto
)
decoder = JSONDecoder()
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
}
afterEach {
mockCrypto = nil
}
// 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())) }

View File

@ -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)!

View File

@ -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)!,

View File

@ -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)!,

View File

@ -13,66 +13,49 @@ import Nimble
@testable import SessionMessagingKit
class OpenGroupAPISpec: QuickSpec {
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var mockNetwork: MockNetwork!
var mockCrypto: MockCrypto!
var dependencies: Dependencies!
var disposables: [AnyCancellable] = []
override class func spec() {
// MARK: Configuration
var error: Error?
describe("an OpenGroupAPI") {
// MARK: - Configuration
beforeEach {
mockStorage = 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)
)
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
],
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)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db)
mockStorage.write { 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)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db)
try OpenGroup(
server: "testServer",
roomToken: "testRoom",
publicKey: TestConstants.publicKey,
isActive: true,
name: "Test",
roomDescription: nil,
imageId: nil,
userCount: 0,
infoUpdates: 0,
sequenceNumber: 0,
inboxLatestMessageId: 0,
outboxLatestMessageId: 0
).insert(db)
try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db)
}
mockCrypto
try OpenGroup(
server: "testServer",
roomToken: "testRoom",
publicKey: TestConstants.publicKey,
isActive: true,
name: "Test",
roomDescription: nil,
imageId: nil,
userCount: 0,
infoUpdates: 0,
sequenceNumber: 0,
inboxLatestMessageId: 0,
outboxLatestMessageId: 0
).insert(db)
try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db)
}
)
@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)
}
afterEach {
disposables.forEach { $0.cancel() }
mockStorage = nil
mockNetwork = nil
mockCrypto = nil
dependencies = nil
disposables = []
error = nil
}
// MARK: - when preparing a poll request
)
@TestState var dependencies: Dependencies! = Dependencies(
storage: mockStorage,
network: mockNetwork,
crypto: mockCrypto,
dateNow: Date(timeIntervalSince1970: 1234567890)
)
@TestState var disposables: [AnyCancellable]! = []
@TestState var error: Error?
// 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])?

View File

@ -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))
}

View File

@ -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]))

View File

@ -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

View File

@ -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."))

View File

@ -11,45 +11,35 @@ import Nimble
@testable import SessionMessagingKit
class MessageReceiverDecryptionSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var mockCrypto: MockCrypto!
var dependencies: Dependencies!
override class func spec() {
// MARK: Configuration
describe("a MessageReceiver") {
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
]
)
mockCrypto = MockCrypto()
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 { [dependencies = dependencies!] crypto in
@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)
}
)
@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())) }

View File

@ -11,41 +11,35 @@ import Nimble
@testable import SessionMessagingKit
class MessageSenderEncryptionSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var mockCrypto: MockCrypto!
var dependencies: Dependencies!
override class func spec() {
// MARK: Configuration
describe("a MessageSender") {
// MARK: - Configuration
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
]
)
mockCrypto = MockCrypto()
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
@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)
}
)
@TestState var mockCrypto: MockCrypto! = MockCrypto(
initialSetup: { crypto in
crypto
.when { try $0.perform(.generateNonce24()) }
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
}
// MARK: - when encrypting with the session protocol
)
@TestState var dependencies: Dependencies! = Dependencies(
storage: mockStorage,
crypto: mockCrypto
)
// 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 {

View File

@ -9,57 +9,42 @@ 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()
)
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
initialData: { db in
try db.create(table: TestMessage.self) { t in
t.column(.body, .text).notNull()
}
mockStorage.write { db in
try db.create(table: TestMessage.self) { t in
t.column(.body, .text).notNull()
}
try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in
t.synchronize(withTable: TestMessage.databaseTableName)
t.tokenizer = .porter(wrapping: .unicode61())
try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in
t.synchronize(withTable: TestMessage.databaseTableName)
t.tokenizer = .porter(wrapping: .unicode61())
t.column(TestMessage.Columns.body.name)
}
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
}

View File

@ -10,24 +10,18 @@ import Nimble
@testable import SessionMessagingKit
class CryptoSMKSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var crypto: Crypto!
var mockCrypto: MockCrypto!
var dependencies: Dependencies!
override class func spec() {
// MARK: Configuration
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(

View File

@ -11,77 +11,58 @@ import SessionUtilitiesKit
@testable import Session
class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesSettingsViewModel.NavButton, ThreadDisappearingMessagesSettingsViewModel.Section, ThreadDisappearingMessagesSettingsViewModel.Item>
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var cancellables: [AnyCancellable] = []
var dependencies: Dependencies!
var viewModel: ThreadDisappearingMessagesSettingsViewModel!
override class func spec() {
// MARK: Configuration
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNSnodeKit.self,
SNMessagingKit.self,
SNUIKit.self
],
initialData: { db in
try SessionThread(
id: "TestId",
variant: .contact
).insert(db)
}
)
@TestState var dependencies: Dependencies! = Dependencies(
storage: mockStorage,
scheduler: .immediate
)
@TestState var viewModel: ThreadDisappearingMessagesSettingsViewModel! = ThreadDisappearingMessagesSettingsViewModel(
threadId: "TestId",
threadVariant: .contact,
config: DisappearingMessagesConfiguration.defaultWith("TestId"),
using: dependencies
)
@TestState var cancellables: [AnyCancellable]! = [
viewModel.observableTableData
.receive(on: ImmediateScheduler.shared)
.sink(
receiveCompletion: { _ in },
receiveValue: { viewModel.updateTableData($0.0) }
)
]
// MARK: - a ThreadDisappearingMessagesSettingsViewModel
describe("a ThreadDisappearingMessagesSettingsViewModel") {
// MARK: - Configuration
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNSnodeKit.self,
SNMessagingKit.self,
SNUIKit.self
]
)
dependencies = Dependencies(
storage: mockStorage,
scheduler: .immediate
)
mockStorage.write { db in
try SessionThread(
id: "TestId",
variant: .contact
).insert(db)
}
viewModel = ThreadDisappearingMessagesSettingsViewModel(
threadId: "TestId",
threadVariant: .contact,
config: DisappearingMessagesConfiguration.defaultWith("TestId"),
using: dependencies
)
cancellables.append(
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: -- 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>

View File

@ -11,97 +11,65 @@ import SessionUtilitiesKit
@testable import Session
class ThreadSettingsViewModelSpec: QuickSpec {
typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
// 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
override class func spec() {
// MARK: Configuration
describe("a ThreadSettingsViewModel") {
// MARK: - Configuration
beforeEach {
mockStorage = 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)
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)
}
viewModel = ThreadSettingsViewModel(
threadId: "TestId",
threadVariant: .contact,
didTriggerSearch: {
didTriggerSearchCallbackTriggered = true
},
using: dependencies
)
disposables.append(
viewModel.observableTableData
.receive(on: ImmediateScheduler.shared)
.sink(
receiveCompletion: { _ in },
receiveValue: { viewModel.updateTableData($0.0) }
)
)
}
afterEach {
disposables.forEach { $0.cancel() }
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNSnodeKit.self,
SNMessagingKit.self,
SNUIKit.self
],
initialData: { db in
try Identity(
variant: .x25519PublicKey,
data: Data(hex: TestConstants.publicKey)
).insert(db)
mockStorage = nil
disposables = []
dependencies = nil
viewModel = nil
didTriggerSearchCallbackTriggered = false
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)
}
// MARK: - Basic Tests
)
@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: {
didTriggerSearchCallbackTriggered = true
},
using: dependencies
)
@TestState var disposables: [AnyCancellable]! = [
viewModel.observableTableData
.receive(on: ImmediateScheduler.shared)
.sink(
receiveCompletion: { _ in },
receiveValue: { viewModel.updateTableData($0.0) }
)
]
// 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>

View File

@ -11,52 +11,38 @@ 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!
override class func spec() {
// MARK: Configuration
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNSnodeKit.self,
SNMessagingKit.self,
SNUIKit.self
]
)
@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) }
)
@TestState var dismissCancellable: AnyCancellable?
// MARK: - a NotificationContentViewModel
describe("a NotificationContentViewModel") {
// MARK: - Configuration
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNSnodeKit.self,
SNMessagingKit.self,
SNUIKit.self
]
)
viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate)
dataChangeCancellable = 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
// 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

View File

@ -9,21 +9,19 @@ import Nimble
@testable import SessionUtilitiesKit
class IdentitySpec: QuickSpec {
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
override class func spec() {
// MARK: Configuration
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self
]
)
// MARK: - an Identity
describe("an Identity") {
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self
]
)
}
// 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)

View File

@ -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!
@TestState var customWriter: DatabaseQueue! = try! DatabaseQueue()
@TestState var mockStorage: Storage! = SynchronousStorage(
customWriter: customWriter,
customMigrationTargets: [
TestTarget.self
]
)
// MARK: - a PersistableRecord
describe("a PersistableRecord") {
beforeEach {
customWriter = try! DatabaseQueue()
mockStorage = SynchronousStorage(
customWriter: customWriter,
customMigrationTargets: [
TestTarget.self
]
)
}
afterEach {
customWriter = nil
mockStorage = nil
}
// 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])
)
}
}

View File

@ -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
}

View File

@ -8,17 +8,16 @@ import Nimble
@testable import SessionUtilitiesKit
class DependenciesSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var dependencies: Dependencies!
override class func spec() {
// MARK: Configuration
@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)

View File

@ -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())
}

View File

@ -9,165 +9,71 @@ 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
)
@TestState var job1: Job! = Job(
id: 100,
failureCount: 0,
variant: .messageSend,
behaviour: .runOnce,
shouldBlock: false,
shouldSkipLaunchBecomeActive: false,
nextRunTimestamp: 0,
threadId: nil,
interactionId: nil,
details: nil
)
@TestState var job2: Job! = Job(
id: 101,
failureCount: 0,
variant: .attachmentUpload,
behaviour: .runOnce,
shouldBlock: false,
shouldSkipLaunchBecomeActive: false,
nextRunTimestamp: 0,
threadId: nil,
interactionId: nil,
details: nil
)
@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
mockStorage.write { db in try Job.deleteAll(db) }
job1 = Job(
id: 100,
failureCount: 0,
variant: .messageSend,
behaviour: .runOnce,
shouldBlock: false,
shouldSkipLaunchBecomeActive: false,
nextRunTimestamp: 0,
threadId: nil,
interactionId: nil,
details: nil
)
job2 = Job(
id: 101,
failureCount: 0,
variant: .attachmentUpload,
behaviour: .runOnce,
shouldBlock: false,
shouldSkipLaunchBecomeActive: false,
nextRunTimestamp: 0,
threadId: nil,
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)
// Need to assign this to ensure it's used by nested dependencies
dependencies.jobRunner = jobRunner
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 = 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()
}
}
}
}

View File

@ -9,21 +9,40 @@ 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
}
// MARK: - Spec
override func spec() {
// MARK: - HTTP.BatchSubResponse<T>
override class func spec() {
// MARK: Configuration
@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(
code: 200,
headers: [:],
body: testType,
failedToParseBody: false
)
),
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
HTTP.BatchSubResponse(
code: 200,
headers: [:],
body: testType2,
failedToParseBody: false
)
)
]
.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 = """
{
@ -45,6 +64,7 @@ class BatchResponseSpec: QuickSpec {
expect(subResponse?.body).toNot(beNil())
}
// MARK: ---- decodes with invalid body data
it("decodes with invalid body data") {
let jsonString: String = """
{
@ -63,6 +83,7 @@ class BatchResponseSpec: QuickSpec {
expect(subResponse).toNot(beNil())
}
// MARK: ---- flags invalid body data as invalid
it("flags invalid body data as invalid") {
let jsonString: String = """
{
@ -83,6 +104,7 @@ class BatchResponseSpec: QuickSpec {
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 = """
{
@ -102,6 +124,7 @@ class BatchResponseSpec: QuickSpec {
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 = """
{
@ -123,10 +146,9 @@ class BatchResponseSpec: QuickSpec {
}
}
// MARK: - Convenience
// MARK: --Decodable
// 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)
@ -135,42 +157,9 @@ class BatchResponseSpec: QuickSpec {
}
}
// MARK: - --Combine
// MARK: - a (ResponseInfoType, Data?) Publisher
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 = """
[\([
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
HTTP.BatchSubResponse(
code: 200,
headers: [:],
body: testType,
failedToParseBody: false
)
),
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
HTTP.BatchSubResponse(
code: 200,
headers: [:],
body: testType2,
failedToParseBody: false
)
)
]
.map { String(data: $0, encoding: .utf8)! }
.joined(separator: ","))]
""".data(using: .utf8)!
}
// 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
}

View File

@ -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)
)
}
}

View File

@ -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")

View File

@ -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 {

View File

@ -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>(

View File

@ -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

View File

@ -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

View File

@ -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,