Merge pull request #908 from mpretty-cyro/fix/xcode-15-build-issues
Started work fixing XCode 15 build issues
This commit is contained in:
commit
a6bd2676b0
33
Podfile.lock
33
Podfile.lock
|
@ -13,22 +13,25 @@ PODS:
|
|||
- DifferenceKit/Core
|
||||
- GRDB.swift/SQLCipher (6.13.0):
|
||||
- SQLCipher (>= 3.4.2)
|
||||
- libwebp (1.2.1):
|
||||
- libwebp/demux (= 1.2.1)
|
||||
- libwebp/mux (= 1.2.1)
|
||||
- libwebp/webp (= 1.2.1)
|
||||
- libwebp/demux (1.2.1):
|
||||
- libwebp (1.3.2):
|
||||
- libwebp/demux (= 1.3.2)
|
||||
- libwebp/mux (= 1.3.2)
|
||||
- libwebp/sharpyuv (= 1.3.2)
|
||||
- libwebp/webp (= 1.3.2)
|
||||
- libwebp/demux (1.3.2):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.1):
|
||||
- libwebp/mux (1.3.2):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.1)
|
||||
- Nimble (10.0.0)
|
||||
- libwebp/sharpyuv (1.3.2)
|
||||
- libwebp/webp (1.3.2):
|
||||
- libwebp/sharpyuv
|
||||
- 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):
|
||||
|
@ -134,18 +137,18 @@ SPEC REPOS:
|
|||
- CocoaLumberjack
|
||||
- DifferenceKit
|
||||
- GRDB.swift
|
||||
- libwebp
|
||||
- Nimble
|
||||
- NVActivityIndicatorView
|
||||
- OpenSSL-Universal
|
||||
- PureLayout
|
||||
- Quick
|
||||
- Reachability
|
||||
- SAMKeychain
|
||||
- SQLCipher
|
||||
- SwiftProtobuf
|
||||
- WebRTC-lib
|
||||
trunk:
|
||||
- libwebp
|
||||
- Nimble
|
||||
- Quick
|
||||
- xcbeautify
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
|
@ -186,12 +189,12 @@ SPEC CHECKSUMS:
|
|||
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
|
||||
DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca
|
||||
GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78
|
||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
||||
Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
Nimble: f8a8219d16f176429b951e8f7e72df5c23ceddc0
|
||||
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
|
||||
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
|
||||
PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88
|
||||
Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179
|
||||
Quick: d32871931c05547cb4e0bc9009d66a18b50d8558
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d
|
||||
|
|
|
@ -263,26 +263,43 @@ extension EmojiGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
static func writeStringConversionsFile(from emojiModel: EmojiModel) {
|
||||
// Inline helpers:
|
||||
var firstItem = true
|
||||
func conditionalCheckForEmojiItem(_ item: EmojiModel.EmojiDefinition.Emoji) -> String {
|
||||
let isFirst = (firstItem == true)
|
||||
firstItem = false
|
||||
|
||||
let prefix = isFirst ? "" : "} else "
|
||||
let suffix = "if rawValue == \"\(item.emojiChar)\" {"
|
||||
return prefix + suffix
|
||||
}
|
||||
func conversionForEmojiItem(_ item: EmojiModel.EmojiDefinition.Emoji, definition: EmojiModel.EmojiDefinition) -> String {
|
||||
let skinToneString: String
|
||||
if item.skintoneSequence.isEmpty {
|
||||
skinToneString = "nil"
|
||||
} else {
|
||||
skinToneString = "[\(item.skintoneSequence.map { ".\($0)" }.joined(separator: ", "))]"
|
||||
indirect enum Structure {
|
||||
enum ChunkType {
|
||||
case firstScalar
|
||||
case scalarSum
|
||||
|
||||
func chunk(_ character: Character, into size: UInt32) -> UInt32 {
|
||||
guard size > 0 else { return 0 }
|
||||
|
||||
let scalarValues: [UInt32] = character.unicodeScalars.map { $0.value }
|
||||
|
||||
switch self {
|
||||
case .firstScalar: return (scalarValues.first.map { $0 / size } ?? 0)
|
||||
case .scalarSum: return (scalarValues.reduce(0, +) / size)
|
||||
}
|
||||
}
|
||||
|
||||
func switchString(with variableName: String = "rawValue", size: UInt32) -> String {
|
||||
switch self {
|
||||
case .firstScalar: return "rawValue.unicodeScalars.map({ $0.value }).first.map({ $0 / \(size) })"
|
||||
case .scalarSum: return "(rawValue.unicodeScalars.map({ $0.value }).reduce(0, +) / \(size))"
|
||||
}
|
||||
}
|
||||
return "self.init(baseEmoji: .\(definition.enumName), skinTones: \(skinToneString))"
|
||||
}
|
||||
|
||||
case ifElse // XCode 15 taking over 10 min with M1 Pro (gave up)
|
||||
case switchStatement // XCode 15 taking over 10 min with M1 Pro (gave up)
|
||||
case directLookup // XCode 15 taking 93 sec with M1 Pro
|
||||
case chunked(UInt32, Structure, ChunkType) // XCode 15 taking <10 sec with M1 Pro (chunk by 100)
|
||||
}
|
||||
typealias ChunkedEmojiInfo = (
|
||||
variant: EmojiModel.EmojiDefinition.Emoji,
|
||||
baseName: String
|
||||
)
|
||||
|
||||
static func writeStringConversionsFile(from emojiModel: EmojiModel) {
|
||||
// This combination seems to have the smallest compile time (~2.2 sec out of all of the combinations)
|
||||
let desiredStructure: Structure = .chunked(100, .directLookup, .scalarSum)
|
||||
|
||||
// Conversion from String: Creates an initializer mapping a single character emoji string to an EmojiWithSkinTones
|
||||
// e.g.
|
||||
|
@ -291,30 +308,131 @@ extension EmojiGenerator {
|
|||
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
||||
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("init?(rawValue: String) {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }")
|
||||
|
||||
emojiModel.definitions.forEach { definition in
|
||||
definition.variants.forEach { emoji in
|
||||
fileHandle.writeLine(conditionalCheckForEmojiItem(emoji))
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine(conversionForEmojiItem(emoji, definition: definition))
|
||||
switch desiredStructure {
|
||||
case .chunked(let chunkSize, let childStructure, let chunkType):
|
||||
let chunkedEmojiInfo = emojiModel.definitions
|
||||
.reduce(into: [UInt32: [ChunkedEmojiInfo]]()) { result, next in
|
||||
next.variants.forEach { emoji in
|
||||
let chunk: UInt32 = chunkType.chunk(emoji.emojiChar, into: chunkSize)
|
||||
result[chunk] = ((result[chunk] ?? []) + [(emoji, next.enumName)])
|
||||
.sorted { lhs, rhs in lhs.variant < rhs.variant }
|
||||
}
|
||||
}
|
||||
.sorted { lhs, rhs in lhs.key < rhs.key }
|
||||
|
||||
fileHandle.writeLine("init?(rawValue: String) {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }")
|
||||
fileHandle.writeLine("switch \(chunkType.switchString(size: chunkSize)) {")
|
||||
fileHandle.indent {
|
||||
chunkedEmojiInfo.forEach { chunk, _ in
|
||||
fileHandle.writeLine("case \(chunk): self = EmojiWithSkinTones.emojiFrom\(chunk)(rawValue)")
|
||||
}
|
||||
fileHandle.writeLine("default: self = EmojiWithSkinTones(unsupportedValue: rawValue)")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
fileHandle.writeLine("} else {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("self.init(unsupportedValue: rawValue)")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("}")
|
||||
|
||||
chunkedEmojiInfo.forEach { chunk, emojiInfo in
|
||||
fileHandle.writeLine("")
|
||||
fileHandle.writeLine("private static func emojiFrom\(chunk)(_ rawValue: String) -> EmojiWithSkinTones {")
|
||||
fileHandle.indent {
|
||||
switch emojiInfo.count {
|
||||
case 0:
|
||||
fileHandle.writeLine("return EmojiWithSkinTones(unsupportedValue: rawValue)")
|
||||
|
||||
default:
|
||||
writeStructure(
|
||||
childStructure,
|
||||
for: emojiInfo,
|
||||
using: fileHandle,
|
||||
assignmentPrefix: "return "
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
|
||||
default:
|
||||
fileHandle.writeLine("init?(rawValue: String) {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }")
|
||||
writeStructure(
|
||||
desiredStructure,
|
||||
for: emojiModel.definitions
|
||||
.flatMap { definition in
|
||||
definition.variants.map { ($0, definition.enumName) }
|
||||
},
|
||||
using: fileHandle
|
||||
)
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
private static func writeStructure(
|
||||
_ structure: Structure,
|
||||
for emojiInfo: [ChunkedEmojiInfo],
|
||||
using fileHandle: WriteHandle,
|
||||
assignmentPrefix: String = "self = "
|
||||
) {
|
||||
func initItem(_ info: ChunkedEmojiInfo) -> String {
|
||||
let skinToneString: String = {
|
||||
guard !info.variant.skintoneSequence.isEmpty else { return "nil" }
|
||||
return "[\(info.variant.skintoneSequence.map { ".\($0)" }.joined(separator: ", "))]"
|
||||
}()
|
||||
|
||||
return "EmojiWithSkinTones(baseEmoji: .\(info.baseName), skinTones: \(skinToneString))"
|
||||
}
|
||||
|
||||
switch structure {
|
||||
case .ifElse:
|
||||
emojiInfo.enumerated().forEach { index, info in
|
||||
switch index {
|
||||
case 0: fileHandle.writeLine("if rawValue == \"\(info.variant.emojiChar)\" {")
|
||||
default: fileHandle.writeLine("} else if rawValue == \"\(info.variant.emojiChar)\" {")
|
||||
}
|
||||
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("\(assignmentPrefix)\(initItem(info))")
|
||||
}
|
||||
}
|
||||
|
||||
fileHandle.writeLine("} else {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("\(assignmentPrefix)EmojiWithSkinTones(unsupportedValue: rawValue)")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
|
||||
case .switchStatement:
|
||||
fileHandle.writeLine("switch rawValue {")
|
||||
fileHandle.indent {
|
||||
emojiInfo.forEach { info in
|
||||
fileHandle.writeLine("case \"\(info.variant.emojiChar)\": \(assignmentPrefix)\(initItem(info))")
|
||||
}
|
||||
fileHandle.writeLine("default: \(assignmentPrefix)EmojiWithSkinTones(unsupportedValue: rawValue)")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
|
||||
case .directLookup:
|
||||
fileHandle.writeLine("let lookup: [String: EmojiWithSkinTones] = [")
|
||||
fileHandle.indent {
|
||||
emojiInfo.enumerated().forEach { index, info in
|
||||
let isLast: Bool = (index == (emojiInfo.count - 1))
|
||||
fileHandle.writeLine("\"\(info.variant.emojiChar)\": \(initItem(info))\(isLast ? "" : ",")")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("]")
|
||||
fileHandle.writeLine("\(assignmentPrefix)(lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue))")
|
||||
|
||||
case .chunked: break // Provide one of the other types
|
||||
}
|
||||
}
|
||||
|
||||
static func writeSkinToneLookupFile(from emojiModel: EmojiModel) {
|
||||
writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in
|
||||
|
@ -514,7 +632,7 @@ extension EmojiGenerator {
|
|||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
emojiModel.definitions.forEach {
|
||||
fileHandle.writeLine("case .\($0.enumName): return \"\($0.shortNames.joined(separator:", "))\"")
|
||||
fileHandle.writeLine("case .\($0.enumName): return \"\($0.shortNames.sorted().joined(separator:", "))\"")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
|
|
|
@ -572,7 +572,7 @@ public func serializedData() throws -> Data {
|
|||
# if self.can_field_be_optional(field):
|
||||
writer.add('guard proto.%s else {' % field.has_accessor_name() )
|
||||
writer.push_indent()
|
||||
writer.add('throw %s.invalidProtobuf(description: "\(logTag) missing required field: %s")' % ( writer.invalid_protobuf_error_name, field.name_swift, ) )
|
||||
writer.add('throw %s.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: %s")' % ( writer.invalid_protobuf_error_name, field.name_swift, ) )
|
||||
writer.pop_indent()
|
||||
writer.add('}')
|
||||
|
||||
|
|
|
@ -572,7 +572,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 */; };
|
||||
|
@ -723,7 +722,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 */; };
|
||||
|
@ -745,7 +743,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 */; };
|
||||
|
@ -756,7 +753,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 */; };
|
||||
|
@ -919,6 +915,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 */; };
|
||||
|
@ -1673,7 +1674,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>"; };
|
||||
|
@ -1822,7 +1822,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>"; };
|
||||
|
@ -1881,7 +1880,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>"; };
|
||||
|
@ -1892,7 +1890,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>"; };
|
||||
|
@ -1944,6 +1941,7 @@
|
|||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
|
||||
FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = "<group>"; };
|
||||
FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = "<group>"; };
|
||||
FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Differentiable+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -2057,6 +2055,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 */
|
||||
|
@ -3626,6 +3625,7 @@
|
|||
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */,
|
||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */,
|
||||
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */,
|
||||
FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4054,6 +4054,7 @@
|
|||
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
|
@ -4105,7 +4106,6 @@
|
|||
FD8ECF802934385900C0D1BB /* LibSessionUtil */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDA1E83729A5770C00C5C3BD /* Configs */,
|
||||
FDDC08F029A300D500BF9681 /* Utilities */,
|
||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */,
|
||||
FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */,
|
||||
|
@ -4167,17 +4167,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 = (
|
||||
|
@ -4309,7 +4298,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||
);
|
||||
path = _TestUtilities;
|
||||
|
@ -4335,6 +4323,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */,
|
||||
FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */,
|
||||
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */,
|
||||
FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */,
|
||||
);
|
||||
|
@ -5981,6 +5970,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 */,
|
||||
|
@ -6239,9 +6229,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 */,
|
||||
);
|
||||
|
@ -6256,10 +6248,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 */,
|
||||
|
@ -6286,7 +6280,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 */,
|
||||
|
@ -6299,9 +6292,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 */,
|
||||
|
@ -6310,7 +6301,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 */,
|
||||
|
@ -6517,7 +6507,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6541,7 +6531,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -6589,7 +6579,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -6618,7 +6608,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -6654,7 +6644,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -6677,7 +6667,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -6728,7 +6718,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -6756,7 +6746,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -7688,7 +7678,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7726,7 +7716,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
|
@ -7759,7 +7749,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 424;
|
||||
CURRENT_PROJECT_VERSION = 425;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7797,7 +7787,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 2.4.1;
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
PRODUCT_NAME = Session;
|
||||
|
|
|
@ -105,7 +105,6 @@ final class QuoteView: UIView {
|
|||
availableWidth -= cancelButtonSize
|
||||
}
|
||||
|
||||
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
|
||||
var body: String? = quotedText
|
||||
|
||||
// Main stack view
|
||||
|
|
|
@ -112,7 +112,6 @@ public final class VoiceMessageView: UIView {
|
|||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
let toggleContainerSize = VoiceMessageView.toggleContainerSize
|
||||
let inset = VoiceMessageView.inset
|
||||
|
||||
// Width & height
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -86,6 +86,7 @@ extension Emoji {
|
|||
.grimacing,
|
||||
.faceExhaling,
|
||||
.lyingFace,
|
||||
.shakingFace,
|
||||
.relieved,
|
||||
.pensive,
|
||||
.sleepy,
|
||||
|
@ -163,7 +164,6 @@ extension Emoji {
|
|||
.seeNoEvil,
|
||||
.hearNoEvil,
|
||||
.speakNoEvil,
|
||||
.kiss,
|
||||
.loveLetter,
|
||||
.cupid,
|
||||
.giftHeart,
|
||||
|
@ -178,14 +178,18 @@ extension Emoji {
|
|||
.heartOnFire,
|
||||
.mendingHeart,
|
||||
.heart,
|
||||
.pinkHeart,
|
||||
.orangeHeart,
|
||||
.yellowHeart,
|
||||
.greenHeart,
|
||||
.blueHeart,
|
||||
.lightBlueHeart,
|
||||
.purpleHeart,
|
||||
.brownHeart,
|
||||
.blackHeart,
|
||||
.greyHeart,
|
||||
.whiteHeart,
|
||||
.kiss,
|
||||
.oneHundred,
|
||||
.anger,
|
||||
.boom,
|
||||
|
@ -193,7 +197,6 @@ extension Emoji {
|
|||
.sweatDrops,
|
||||
.dash,
|
||||
.hole,
|
||||
.bomb,
|
||||
.speechBalloon,
|
||||
.eyeInSpeechBubble,
|
||||
.leftSpeechBubble,
|
||||
|
@ -209,6 +212,8 @@ extension Emoji {
|
|||
.leftwardsHand,
|
||||
.palmDownHand,
|
||||
.palmUpHand,
|
||||
.leftwardsPushingHand,
|
||||
.rightwardsPushingHand,
|
||||
.okHand,
|
||||
.pinchedFingers,
|
||||
.pinchingHand,
|
||||
|
@ -584,6 +589,8 @@ extension Emoji {
|
|||
.tiger2,
|
||||
.leopard,
|
||||
.horse,
|
||||
.moose,
|
||||
.donkey,
|
||||
.racehorse,
|
||||
.unicornFace,
|
||||
.zebraFace,
|
||||
|
@ -646,6 +653,9 @@ extension Emoji {
|
|||
.flamingo,
|
||||
.peacock,
|
||||
.parrot,
|
||||
.wing,
|
||||
.blackBird,
|
||||
.goose,
|
||||
.frog,
|
||||
.crocodile,
|
||||
.turtle,
|
||||
|
@ -666,6 +676,7 @@ extension Emoji {
|
|||
.octopus,
|
||||
.shell,
|
||||
.coral,
|
||||
.jellyfish,
|
||||
.snail,
|
||||
.butterfly,
|
||||
.bug,
|
||||
|
@ -693,6 +704,7 @@ extension Emoji {
|
|||
.sunflower,
|
||||
.blossom,
|
||||
.tulip,
|
||||
.hyacinth,
|
||||
.seedling,
|
||||
.pottedPlant,
|
||||
.evergreenTree,
|
||||
|
@ -708,6 +720,7 @@ extension Emoji {
|
|||
.leaves,
|
||||
.emptyNest,
|
||||
.nestWithEggs,
|
||||
.mushroom,
|
||||
]
|
||||
case .food:
|
||||
return [
|
||||
|
@ -742,10 +755,11 @@ extension Emoji {
|
|||
.broccoli,
|
||||
.garlic,
|
||||
.onion,
|
||||
.mushroom,
|
||||
.peanuts,
|
||||
.beans,
|
||||
.chestnut,
|
||||
.gingerRoot,
|
||||
.peaPod,
|
||||
.bread,
|
||||
.croissant,
|
||||
.baguetteBread,
|
||||
|
@ -903,11 +917,10 @@ extension Emoji {
|
|||
.dart,
|
||||
.yoYo,
|
||||
.kite,
|
||||
.gun,
|
||||
.eightBall,
|
||||
.crystalBall,
|
||||
.magicWand,
|
||||
.nazarAmulet,
|
||||
.hamsa,
|
||||
.videoGame,
|
||||
.joystick,
|
||||
.slotMachine,
|
||||
|
@ -1176,6 +1189,7 @@ extension Emoji {
|
|||
.shorts,
|
||||
.bikini,
|
||||
.womansClothes,
|
||||
.foldingHandFan,
|
||||
.purse,
|
||||
.handbag,
|
||||
.pouch,
|
||||
|
@ -1190,6 +1204,7 @@ extension Emoji {
|
|||
.sandal,
|
||||
.balletShoes,
|
||||
.boot,
|
||||
.hairPick,
|
||||
.crown,
|
||||
.womansHat,
|
||||
.tophat,
|
||||
|
@ -1228,6 +1243,8 @@ extension Emoji {
|
|||
.banjo,
|
||||
.drumWithDrumsticks,
|
||||
.longDrum,
|
||||
.maracas,
|
||||
.flute,
|
||||
.iphone,
|
||||
.calling,
|
||||
.phone,
|
||||
|
@ -1347,7 +1364,7 @@ extension Emoji {
|
|||
.hammerAndWrench,
|
||||
.daggerKnife,
|
||||
.crossedSwords,
|
||||
.gun,
|
||||
.bomb,
|
||||
.boomerang,
|
||||
.bowAndArrow,
|
||||
.shield,
|
||||
|
@ -1408,6 +1425,8 @@ extension Emoji {
|
|||
.coffin,
|
||||
.headstone,
|
||||
.funeralUrn,
|
||||
.nazarAmulet,
|
||||
.hamsa,
|
||||
.moyai,
|
||||
.placard,
|
||||
.identificationCard,
|
||||
|
@ -1473,6 +1492,7 @@ extension Emoji {
|
|||
.peaceSymbol,
|
||||
.menorahWithNineBranches,
|
||||
.sixPointedStar,
|
||||
.khanda,
|
||||
.aries,
|
||||
.taurus,
|
||||
.gemini,
|
||||
|
@ -1508,6 +1528,7 @@ extension Emoji {
|
|||
.lowBrightness,
|
||||
.highBrightness,
|
||||
.signalStrength,
|
||||
.wireless,
|
||||
.vibrationMode,
|
||||
.mobilePhoneOff,
|
||||
.femaleSign,
|
||||
|
@ -1962,6 +1983,7 @@ extension Emoji {
|
|||
case .grimacing: return .smileysAndPeople
|
||||
case .faceExhaling: return .smileysAndPeople
|
||||
case .lyingFace: return .smileysAndPeople
|
||||
case .shakingFace: return .smileysAndPeople
|
||||
case .relieved: return .smileysAndPeople
|
||||
case .pensive: return .smileysAndPeople
|
||||
case .sleepy: return .smileysAndPeople
|
||||
|
@ -2039,7 +2061,6 @@ extension Emoji {
|
|||
case .seeNoEvil: return .smileysAndPeople
|
||||
case .hearNoEvil: return .smileysAndPeople
|
||||
case .speakNoEvil: return .smileysAndPeople
|
||||
case .kiss: return .smileysAndPeople
|
||||
case .loveLetter: return .smileysAndPeople
|
||||
case .cupid: return .smileysAndPeople
|
||||
case .giftHeart: return .smileysAndPeople
|
||||
|
@ -2054,14 +2075,18 @@ extension Emoji {
|
|||
case .heartOnFire: return .smileysAndPeople
|
||||
case .mendingHeart: return .smileysAndPeople
|
||||
case .heart: return .smileysAndPeople
|
||||
case .pinkHeart: return .smileysAndPeople
|
||||
case .orangeHeart: return .smileysAndPeople
|
||||
case .yellowHeart: return .smileysAndPeople
|
||||
case .greenHeart: return .smileysAndPeople
|
||||
case .blueHeart: return .smileysAndPeople
|
||||
case .lightBlueHeart: return .smileysAndPeople
|
||||
case .purpleHeart: return .smileysAndPeople
|
||||
case .brownHeart: return .smileysAndPeople
|
||||
case .blackHeart: return .smileysAndPeople
|
||||
case .greyHeart: return .smileysAndPeople
|
||||
case .whiteHeart: return .smileysAndPeople
|
||||
case .kiss: return .smileysAndPeople
|
||||
case .oneHundred: return .smileysAndPeople
|
||||
case .anger: return .smileysAndPeople
|
||||
case .boom: return .smileysAndPeople
|
||||
|
@ -2069,7 +2094,6 @@ extension Emoji {
|
|||
case .sweatDrops: return .smileysAndPeople
|
||||
case .dash: return .smileysAndPeople
|
||||
case .hole: return .smileysAndPeople
|
||||
case .bomb: return .smileysAndPeople
|
||||
case .speechBalloon: return .smileysAndPeople
|
||||
case .eyeInSpeechBubble: return .smileysAndPeople
|
||||
case .leftSpeechBubble: return .smileysAndPeople
|
||||
|
@ -2085,6 +2109,8 @@ extension Emoji {
|
|||
case .leftwardsHand: return .smileysAndPeople
|
||||
case .palmDownHand: return .smileysAndPeople
|
||||
case .palmUpHand: return .smileysAndPeople
|
||||
case .leftwardsPushingHand: return .smileysAndPeople
|
||||
case .rightwardsPushingHand: return .smileysAndPeople
|
||||
case .okHand: return .smileysAndPeople
|
||||
case .pinchedFingers: return .smileysAndPeople
|
||||
case .pinchingHand: return .smileysAndPeople
|
||||
|
@ -2457,6 +2483,8 @@ extension Emoji {
|
|||
case .tiger2: return .animals
|
||||
case .leopard: return .animals
|
||||
case .horse: return .animals
|
||||
case .moose: return .animals
|
||||
case .donkey: return .animals
|
||||
case .racehorse: return .animals
|
||||
case .unicornFace: return .animals
|
||||
case .zebraFace: return .animals
|
||||
|
@ -2519,6 +2547,9 @@ extension Emoji {
|
|||
case .flamingo: return .animals
|
||||
case .peacock: return .animals
|
||||
case .parrot: return .animals
|
||||
case .wing: return .animals
|
||||
case .blackBird: return .animals
|
||||
case .goose: return .animals
|
||||
case .frog: return .animals
|
||||
case .crocodile: return .animals
|
||||
case .turtle: return .animals
|
||||
|
@ -2539,6 +2570,7 @@ extension Emoji {
|
|||
case .octopus: return .animals
|
||||
case .shell: return .animals
|
||||
case .coral: return .animals
|
||||
case .jellyfish: return .animals
|
||||
case .snail: return .animals
|
||||
case .butterfly: return .animals
|
||||
case .bug: return .animals
|
||||
|
@ -2566,6 +2598,7 @@ extension Emoji {
|
|||
case .sunflower: return .animals
|
||||
case .blossom: return .animals
|
||||
case .tulip: return .animals
|
||||
case .hyacinth: return .animals
|
||||
case .seedling: return .animals
|
||||
case .pottedPlant: return .animals
|
||||
case .evergreenTree: return .animals
|
||||
|
@ -2581,6 +2614,7 @@ extension Emoji {
|
|||
case .leaves: return .animals
|
||||
case .emptyNest: return .animals
|
||||
case .nestWithEggs: return .animals
|
||||
case .mushroom: return .animals
|
||||
case .grapes: return .food
|
||||
case .melon: return .food
|
||||
case .watermelon: return .food
|
||||
|
@ -2612,10 +2646,11 @@ extension Emoji {
|
|||
case .broccoli: return .food
|
||||
case .garlic: return .food
|
||||
case .onion: return .food
|
||||
case .mushroom: return .food
|
||||
case .peanuts: return .food
|
||||
case .beans: return .food
|
||||
case .chestnut: return .food
|
||||
case .gingerRoot: return .food
|
||||
case .peaPod: return .food
|
||||
case .bread: return .food
|
||||
case .croissant: return .food
|
||||
case .baguetteBread: return .food
|
||||
|
@ -2988,11 +3023,10 @@ extension Emoji {
|
|||
case .dart: return .activities
|
||||
case .yoYo: return .activities
|
||||
case .kite: return .activities
|
||||
case .gun: return .activities
|
||||
case .eightBall: return .activities
|
||||
case .crystalBall: return .activities
|
||||
case .magicWand: return .activities
|
||||
case .nazarAmulet: return .activities
|
||||
case .hamsa: return .activities
|
||||
case .videoGame: return .activities
|
||||
case .joystick: return .activities
|
||||
case .slotMachine: return .activities
|
||||
|
@ -3037,6 +3071,7 @@ extension Emoji {
|
|||
case .shorts: return .objects
|
||||
case .bikini: return .objects
|
||||
case .womansClothes: return .objects
|
||||
case .foldingHandFan: return .objects
|
||||
case .purse: return .objects
|
||||
case .handbag: return .objects
|
||||
case .pouch: return .objects
|
||||
|
@ -3051,6 +3086,7 @@ extension Emoji {
|
|||
case .sandal: return .objects
|
||||
case .balletShoes: return .objects
|
||||
case .boot: return .objects
|
||||
case .hairPick: return .objects
|
||||
case .crown: return .objects
|
||||
case .womansHat: return .objects
|
||||
case .tophat: return .objects
|
||||
|
@ -3089,6 +3125,8 @@ extension Emoji {
|
|||
case .banjo: return .objects
|
||||
case .drumWithDrumsticks: return .objects
|
||||
case .longDrum: return .objects
|
||||
case .maracas: return .objects
|
||||
case .flute: return .objects
|
||||
case .iphone: return .objects
|
||||
case .calling: return .objects
|
||||
case .phone: return .objects
|
||||
|
@ -3208,7 +3246,7 @@ extension Emoji {
|
|||
case .hammerAndWrench: return .objects
|
||||
case .daggerKnife: return .objects
|
||||
case .crossedSwords: return .objects
|
||||
case .gun: return .objects
|
||||
case .bomb: return .objects
|
||||
case .boomerang: return .objects
|
||||
case .bowAndArrow: return .objects
|
||||
case .shield: return .objects
|
||||
|
@ -3269,6 +3307,8 @@ extension Emoji {
|
|||
case .coffin: return .objects
|
||||
case .headstone: return .objects
|
||||
case .funeralUrn: return .objects
|
||||
case .nazarAmulet: return .objects
|
||||
case .hamsa: return .objects
|
||||
case .moyai: return .objects
|
||||
case .placard: return .objects
|
||||
case .identificationCard: return .objects
|
||||
|
@ -3331,6 +3371,7 @@ extension Emoji {
|
|||
case .peaceSymbol: return .symbols
|
||||
case .menorahWithNineBranches: return .symbols
|
||||
case .sixPointedStar: return .symbols
|
||||
case .khanda: return .symbols
|
||||
case .aries: return .symbols
|
||||
case .taurus: return .symbols
|
||||
case .gemini: return .symbols
|
||||
|
@ -3366,6 +3407,7 @@ extension Emoji {
|
|||
case .lowBrightness: return .symbols
|
||||
case .highBrightness: return .symbols
|
||||
case .signalStrength: return .symbols
|
||||
case .wireless: return .symbols
|
||||
case .vibrationMode: return .symbols
|
||||
case .mobilePhoneOff: return .symbols
|
||||
case .femaleSign: return .symbols
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -106,6 +106,22 @@ extension Emoji {
|
|||
[.mediumDark]: "🫴🏾",
|
||||
[.dark]: "🫴🏿",
|
||||
]
|
||||
case .leftwardsPushingHand:
|
||||
return [
|
||||
[.light]: "🫷🏻",
|
||||
[.mediumLight]: "🫷🏼",
|
||||
[.medium]: "🫷🏽",
|
||||
[.mediumDark]: "🫷🏾",
|
||||
[.dark]: "🫷🏿",
|
||||
]
|
||||
case .rightwardsPushingHand:
|
||||
return [
|
||||
[.light]: "🫸🏻",
|
||||
[.mediumLight]: "🫸🏼",
|
||||
[.medium]: "🫸🏽",
|
||||
[.mediumDark]: "🫸🏾",
|
||||
[.dark]: "🫸🏿",
|
||||
]
|
||||
case .okHand:
|
||||
return [
|
||||
[.light]: "👌🏻",
|
||||
|
|
|
@ -54,6 +54,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case grimacing = "😬"
|
||||
case faceExhaling = "😮💨"
|
||||
case lyingFace = "🤥"
|
||||
case shakingFace = "🫨"
|
||||
case relieved = "😌"
|
||||
case pensive = "😔"
|
||||
case sleepy = "😪"
|
||||
|
@ -131,7 +132,6 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case seeNoEvil = "🙈"
|
||||
case hearNoEvil = "🙉"
|
||||
case speakNoEvil = "🙊"
|
||||
case kiss = "💋"
|
||||
case loveLetter = "💌"
|
||||
case cupid = "💘"
|
||||
case giftHeart = "💝"
|
||||
|
@ -146,14 +146,18 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case heartOnFire = "❤️🔥"
|
||||
case mendingHeart = "❤️🩹"
|
||||
case heart = "❤️"
|
||||
case pinkHeart = "🩷"
|
||||
case orangeHeart = "🧡"
|
||||
case yellowHeart = "💛"
|
||||
case greenHeart = "💚"
|
||||
case blueHeart = "💙"
|
||||
case lightBlueHeart = "🩵"
|
||||
case purpleHeart = "💜"
|
||||
case brownHeart = "🤎"
|
||||
case blackHeart = "🖤"
|
||||
case greyHeart = "🩶"
|
||||
case whiteHeart = "🤍"
|
||||
case kiss = "💋"
|
||||
case oneHundred = "💯"
|
||||
case anger = "💢"
|
||||
case boom = "💥"
|
||||
|
@ -161,7 +165,6 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case sweatDrops = "💦"
|
||||
case dash = "💨"
|
||||
case hole = "🕳️"
|
||||
case bomb = "💣"
|
||||
case speechBalloon = "💬"
|
||||
case eyeInSpeechBubble = "👁️🗨️"
|
||||
case leftSpeechBubble = "🗨️"
|
||||
|
@ -177,6 +180,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case leftwardsHand = "🫲"
|
||||
case palmDownHand = "🫳"
|
||||
case palmUpHand = "🫴"
|
||||
case leftwardsPushingHand = "🫷"
|
||||
case rightwardsPushingHand = "🫸"
|
||||
case okHand = "👌"
|
||||
case pinchedFingers = "🤌"
|
||||
case pinchingHand = "🤏"
|
||||
|
@ -554,6 +559,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case tiger2 = "🐅"
|
||||
case leopard = "🐆"
|
||||
case horse = "🐴"
|
||||
case moose = "🫎"
|
||||
case donkey = "🫏"
|
||||
case racehorse = "🐎"
|
||||
case unicornFace = "🦄"
|
||||
case zebraFace = "🦓"
|
||||
|
@ -616,6 +623,9 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case flamingo = "🦩"
|
||||
case peacock = "🦚"
|
||||
case parrot = "🦜"
|
||||
case wing = "🪽"
|
||||
case blackBird = "🐦⬛"
|
||||
case goose = "🪿"
|
||||
case frog = "🐸"
|
||||
case crocodile = "🐊"
|
||||
case turtle = "🐢"
|
||||
|
@ -636,6 +646,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case octopus = "🐙"
|
||||
case shell = "🐚"
|
||||
case coral = "🪸"
|
||||
case jellyfish = "🪼"
|
||||
case snail = "🐌"
|
||||
case butterfly = "🦋"
|
||||
case bug = "🐛"
|
||||
|
@ -663,6 +674,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case sunflower = "🌻"
|
||||
case blossom = "🌼"
|
||||
case tulip = "🌷"
|
||||
case hyacinth = "🪻"
|
||||
case seedling = "🌱"
|
||||
case pottedPlant = "🪴"
|
||||
case evergreenTree = "🌲"
|
||||
|
@ -678,6 +690,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case leaves = "🍃"
|
||||
case emptyNest = "🪹"
|
||||
case nestWithEggs = "🪺"
|
||||
case mushroom = "🍄"
|
||||
case grapes = "🍇"
|
||||
case melon = "🍈"
|
||||
case watermelon = "🍉"
|
||||
|
@ -709,10 +722,11 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case broccoli = "🥦"
|
||||
case garlic = "🧄"
|
||||
case onion = "🧅"
|
||||
case mushroom = "🍄"
|
||||
case peanuts = "🥜"
|
||||
case beans = "🫘"
|
||||
case chestnut = "🌰"
|
||||
case gingerRoot = "🫚"
|
||||
case peaPod = "🫛"
|
||||
case bread = "🍞"
|
||||
case croissant = "🥐"
|
||||
case baguetteBread = "🥖"
|
||||
|
@ -1085,11 +1099,10 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case dart = "🎯"
|
||||
case yoYo = "🪀"
|
||||
case kite = "🪁"
|
||||
case gun = "🔫"
|
||||
case eightBall = "🎱"
|
||||
case crystalBall = "🔮"
|
||||
case magicWand = "🪄"
|
||||
case nazarAmulet = "🧿"
|
||||
case hamsa = "🪬"
|
||||
case videoGame = "🎮"
|
||||
case joystick = "🕹️"
|
||||
case slotMachine = "🎰"
|
||||
|
@ -1134,6 +1147,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case shorts = "🩳"
|
||||
case bikini = "👙"
|
||||
case womansClothes = "👚"
|
||||
case foldingHandFan = "🪭"
|
||||
case purse = "👛"
|
||||
case handbag = "👜"
|
||||
case pouch = "👝"
|
||||
|
@ -1148,6 +1162,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case sandal = "👡"
|
||||
case balletShoes = "🩰"
|
||||
case boot = "👢"
|
||||
case hairPick = "🪮"
|
||||
case crown = "👑"
|
||||
case womansHat = "👒"
|
||||
case tophat = "🎩"
|
||||
|
@ -1186,6 +1201,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case banjo = "🪕"
|
||||
case drumWithDrumsticks = "🥁"
|
||||
case longDrum = "🪘"
|
||||
case maracas = "🪇"
|
||||
case flute = "🪈"
|
||||
case iphone = "📱"
|
||||
case calling = "📲"
|
||||
case phone = "☎️"
|
||||
|
@ -1305,7 +1322,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case hammerAndWrench = "🛠️"
|
||||
case daggerKnife = "🗡️"
|
||||
case crossedSwords = "⚔️"
|
||||
case gun = "🔫"
|
||||
case bomb = "💣"
|
||||
case boomerang = "🪃"
|
||||
case bowAndArrow = "🏹"
|
||||
case shield = "🛡️"
|
||||
|
@ -1366,6 +1383,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case coffin = "⚰️"
|
||||
case headstone = "🪦"
|
||||
case funeralUrn = "⚱️"
|
||||
case nazarAmulet = "🧿"
|
||||
case hamsa = "🪬"
|
||||
case moyai = "🗿"
|
||||
case placard = "🪧"
|
||||
case identificationCard = "🪪"
|
||||
|
@ -1428,6 +1447,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case peaceSymbol = "☮️"
|
||||
case menorahWithNineBranches = "🕎"
|
||||
case sixPointedStar = "🔯"
|
||||
case khanda = "🪯"
|
||||
case aries = "♈"
|
||||
case taurus = "♉"
|
||||
case gemini = "♊"
|
||||
|
@ -1463,6 +1483,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
|||
case lowBrightness = "🔅"
|
||||
case highBrightness = "🔆"
|
||||
case signalStrength = "📶"
|
||||
case wireless = "🛜"
|
||||
case vibrationMode = "📳"
|
||||
case mobilePhoneOff = "📴"
|
||||
case femaleSign = "♀️"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -275,14 +275,11 @@ class PhotoCaptureViewController: OWSViewController {
|
|||
|
||||
let transformFromOrientation: CGAffineTransform
|
||||
switch captureOrientation {
|
||||
case .portrait:
|
||||
transformFromOrientation = .identity
|
||||
case .portraitUpsideDown:
|
||||
transformFromOrientation = CGAffineTransform(rotationAngle: .pi)
|
||||
case .landscapeLeft:
|
||||
transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi)
|
||||
case .landscapeRight:
|
||||
transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi)
|
||||
case .portrait: transformFromOrientation = .identity
|
||||
case .portraitUpsideDown: transformFromOrientation = CGAffineTransform(rotationAngle: .pi)
|
||||
case .landscapeLeft: transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi)
|
||||
case .landscapeRight: transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi)
|
||||
@unknown default: transformFromOrientation = .identity
|
||||
}
|
||||
|
||||
// Don't "unrotate" the switch camera icon if the front facing camera had been selected.
|
||||
|
|
|
@ -82,7 +82,7 @@ public struct SessionApp {
|
|||
)
|
||||
}
|
||||
|
||||
/// The thread should generally exist at the time of calling this method, but on the off change it doesn't then we need to `fetchOrCreate` it and
|
||||
/// The thread should generally exist at the time of calling this method, but on the off chance it doesn't then we need to `fetchOrCreate` it and
|
||||
/// should do it on a background thread just in case something is keeping the DBWrite thread busy as in the past this could cause the app to hang
|
||||
guard threadInfo?.threadExists == true else {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
|
|
|
@ -635,8 +635,6 @@
|
|||
"message_info_title" = "Message Info";
|
||||
"mute_button_text" = "Mute";
|
||||
"unmute_button_text" = "Unmute";
|
||||
"mark_read_button_text" = "Mark read";
|
||||
"mark_unread_button_text" = "Mark unread";
|
||||
"leave_group_confirmation_alert_title" = "Leave Group";
|
||||
"leave_community_confirmation_alert_title" = "Leave Community";
|
||||
"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?";
|
||||
|
|
|
@ -59,8 +59,7 @@ public enum PushRegistrationError: Error {
|
|||
.tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
|
||||
#if targetEnvironment(simulator)
|
||||
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
|
||||
#endif
|
||||
|
||||
#else
|
||||
return self.registerForVanillaPushToken()
|
||||
.flatMap { vanillaPushToken -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
|
||||
self.registerForVoipPushToken()
|
||||
|
@ -68,6 +67,7 @@ public enum PushRegistrationError: Error {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
#endif
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -98,10 +98,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -180,10 +177,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -311,10 +305,7 @@ enum MockDataGenerator {
|
|||
id: randomSessionId,
|
||||
name: (0..<contactNameLength)
|
||||
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
.joined()
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
|
|
@ -160,9 +160,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
return Deferred {
|
||||
Future<Void, Error> { [weak self] resolver in
|
||||
self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in
|
||||
if let error = error {
|
||||
return
|
||||
}
|
||||
guard error == nil else { return }
|
||||
|
||||
guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else {
|
||||
preconditionFailure()
|
||||
|
|
|
@ -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
|
||||
]
|
||||
]
|
||||
)
|
||||
|
|
|
@ -65,7 +65,7 @@ enum _001_InitialSetupMigration: Migration {
|
|||
t.column(.variant, .integer).notNull()
|
||||
t.column(.creationDateTimestamp, .double).notNull()
|
||||
t.column(.shouldBeVisible, .boolean).notNull()
|
||||
t.column(.isPinned, .boolean).notNull()
|
||||
t.deprecatedColumn(name: "isPinned", .boolean).notNull()
|
||||
t.column(.messageDraft, .text)
|
||||
t.column(.notificationSound, .integer)
|
||||
t.column(.mutedUntilTimestamp, .double)
|
||||
|
|
|
@ -417,13 +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,
|
||||
lastBlocksCommunityMessageRequests: 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
|
||||
|
@ -644,10 +641,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
// constraint violation
|
||||
try? Profile(
|
||||
id: profileId,
|
||||
name: profileId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
name: profileId
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
@ -1059,10 +1053,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
// constraint violation
|
||||
try Profile(
|
||||
id: quotedMessage.authorId,
|
||||
name: quotedMessage.authorId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
name: quotedMessage.authorId
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,12 +22,8 @@ enum _013_SessionUtilChanges: Migration {
|
|||
|
||||
// Add `lastNameUpdate` and `lastProfilePictureUpdate` columns to the profile table
|
||||
try db.alter(table: Profile.self) { t in
|
||||
t.add(.lastNameUpdate, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastProfilePictureUpdate, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastNameUpdate, .integer).defaults(to: 0)
|
||||
t.add(.lastProfilePictureUpdate, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// SQLite doesn't support adding a new primary key after creation so we need to create a new table with
|
||||
|
@ -180,7 +176,7 @@ enum _013_SessionUtilChanges: Migration {
|
|||
|
||||
// Migrate the 'isPinned' value to 'pinnedPriority'
|
||||
try SessionThread
|
||||
.filter(SessionThread.Columns.isPinned == true)
|
||||
.filter(sql: "isPinned = true")
|
||||
.updateAll(
|
||||
db,
|
||||
SessionThread.Columns.pinnedPriority.set(to: 1)
|
||||
|
|
|
@ -16,9 +16,7 @@ enum _015_BlockCommunityMessageRequests: Migration {
|
|||
// Add the new 'Profile' properties
|
||||
try db.alter(table: Profile.self) { t in
|
||||
t.add(.blocksCommunityMessageRequests, .boolean)
|
||||
t.add(.lastBlocksCommunityMessageRequests, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
t.add(.lastBlocksCommunityMessageRequests, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// If the user exists and the 'checkForCommunityMessageRequests' hasn't already been set then default it to "false"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// This migration updates the tiemstamps added to the `Profile` in earlier migrations to be nullable (having it not null
|
||||
/// results in migration issues when a user jumps between multiple versions)
|
||||
enum _016_MakeBrokenProfileTimestampsNullable: Migration {
|
||||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "MakeBrokenProfileTimestampsNullable"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
/// SQLite doesn't support altering columns after creation so we need to create a new table with the setup we
|
||||
/// want, copy data from the old table over, drop the old table and rename the new table
|
||||
struct TmpProfile: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible {
|
||||
static var databaseTableName: String { "tmpProfile" }
|
||||
|
||||
public typealias Columns = CodingKeys
|
||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case id
|
||||
|
||||
case name
|
||||
case lastNameUpdate
|
||||
case nickname
|
||||
|
||||
case profilePictureUrl
|
||||
case profilePictureFileName
|
||||
case profileEncryptionKey
|
||||
case lastProfilePictureUpdate
|
||||
|
||||
case blocksCommunityMessageRequests
|
||||
case lastBlocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let lastNameUpdate: TimeInterval?
|
||||
public let nickname: String?
|
||||
public let profilePictureUrl: String?
|
||||
public let profilePictureFileName: String?
|
||||
public let profileEncryptionKey: Data?
|
||||
public let lastProfilePictureUpdate: TimeInterval?
|
||||
public let blocksCommunityMessageRequests: Bool?
|
||||
public let lastBlocksCommunityMessageRequests: TimeInterval?
|
||||
}
|
||||
|
||||
try db.create(table: TmpProfile.self) { t in
|
||||
t.column(.id, .text)
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
t.column(.name, .text).notNull()
|
||||
t.column(.nickname, .text)
|
||||
t.column(.profilePictureUrl, .text)
|
||||
t.column(.profilePictureFileName, .text)
|
||||
t.column(.profileEncryptionKey, .blob)
|
||||
t.column(.lastNameUpdate, .integer).defaults(to: 0)
|
||||
t.column(.lastProfilePictureUpdate, .integer).defaults(to: 0)
|
||||
t.column(.blocksCommunityMessageRequests, .boolean)
|
||||
t.column(.lastBlocksCommunityMessageRequests, .integer).defaults(to: 0)
|
||||
}
|
||||
|
||||
// Insert into the new table, drop the old table and rename the new table to be the old one
|
||||
try db.execute(sql: """
|
||||
INSERT INTO \(TmpProfile.databaseTableName)
|
||||
SELECT \(Profile.databaseTableName).*
|
||||
FROM \(Profile.databaseTableName)
|
||||
""")
|
||||
|
||||
try db.drop(table: Profile.self)
|
||||
try db.rename(table: TmpProfile.databaseTableName, to: Profile.databaseTableName)
|
||||
|
||||
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
public let name: String
|
||||
|
||||
/// The timestamp (in seconds since epoch) that the name was last updated
|
||||
public let lastNameUpdate: TimeInterval
|
||||
public let lastNameUpdate: TimeInterval?
|
||||
|
||||
/// A custom name for the profile set by the current user
|
||||
public let nickname: String?
|
||||
|
@ -54,27 +54,27 @@ 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?
|
||||
|
||||
/// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated
|
||||
public let lastBlocksCommunityMessageRequests: TimeInterval
|
||||
public let lastBlocksCommunityMessageRequests: TimeInterval?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
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
|
||||
lastBlocksCommunityMessageRequests: TimeInterval? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
@ -122,14 +122,14 @@ 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)
|
||||
lastBlocksCommunityMessageRequests: try? container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ public extension Profile {
|
|||
try container.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey)
|
||||
try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate)
|
||||
try container.encodeIfPresent(blocksCommunityMessageRequests, forKey: .blocksCommunityMessageRequests)
|
||||
try container.encode(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests)
|
||||
try container.encodeIfPresent(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,14 +256,14 @@ 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: 0
|
||||
lastBlocksCommunityMessageRequests: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
|
|||
case variant
|
||||
case creationDateTimestamp
|
||||
case shouldBeVisible
|
||||
case isPinned
|
||||
@available(*, deprecated, message: "use 'pinnedPriority > 0' instead") case isPinned
|
||||
case messageDraft
|
||||
case notificationSound
|
||||
case mutedUntilTimestamp
|
||||
|
@ -61,8 +61,8 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
|
|||
public let shouldBeVisible: Bool
|
||||
|
||||
/// A flag indicating whether the thread is pinned
|
||||
@available(*, unavailable, message: "use 'pinnedPriority' instead")
|
||||
public let isPinned: Bool = false
|
||||
@available(*, deprecated, message: "use 'pinnedPriority > 0' instead")
|
||||
private let isPinned: Bool = false
|
||||
|
||||
/// The value the user started entering into the input field before they left the conversation screen
|
||||
public let messageDraft: String?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -165,12 +165,12 @@ public enum SNProtoError: Error {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_Envelope) throws -> SNProtoEnvelope {
|
||||
guard proto.hasType else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = SNProtoEnvelopeTypeWrap(proto.type)
|
||||
|
||||
guard proto.hasTimestamp else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp")
|
||||
}
|
||||
let timestamp = proto.timestamp
|
||||
|
||||
|
@ -298,12 +298,12 @@ extension SNProtoEnvelope.SNProtoEnvelopeBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_TypingMessage) throws -> SNProtoTypingMessage {
|
||||
guard proto.hasTimestamp else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp")
|
||||
}
|
||||
let timestamp = proto.timestamp
|
||||
|
||||
guard proto.hasAction else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: action")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: action")
|
||||
}
|
||||
let action = SNProtoTypingMessageActionWrap(proto.action)
|
||||
|
||||
|
@ -410,12 +410,12 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_UnsendRequest) throws -> SNProtoUnsendRequest {
|
||||
guard proto.hasTimestamp else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp")
|
||||
}
|
||||
let timestamp = proto.timestamp
|
||||
|
||||
guard proto.hasAuthor else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author")
|
||||
}
|
||||
let author = proto.author
|
||||
|
||||
|
@ -541,7 +541,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_MessageRequestResponse) throws -> SNProtoMessageRequestResponse {
|
||||
guard proto.hasIsApproved else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: isApproved")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: isApproved")
|
||||
}
|
||||
let isApproved = proto.isApproved
|
||||
|
||||
|
@ -961,12 +961,12 @@ extension SNProtoContent.SNProtoContentBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_CallMessage) throws -> SNProtoCallMessage {
|
||||
guard proto.hasType else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = SNProtoCallMessageTypeWrap(proto.type)
|
||||
|
||||
guard proto.hasUuid else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: uuid")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: uuid")
|
||||
}
|
||||
let uuid = proto.uuid
|
||||
|
||||
|
@ -1073,12 +1073,12 @@ extension SNProtoCallMessage.SNProtoCallMessageBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_KeyPair) throws -> SNProtoKeyPair {
|
||||
guard proto.hasPublicKey else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey")
|
||||
}
|
||||
let publicKey = proto.publicKey
|
||||
|
||||
guard proto.hasPrivateKey else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: privateKey")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: privateKey")
|
||||
}
|
||||
let privateKey = proto.privateKey
|
||||
|
||||
|
@ -1211,7 +1211,7 @@ extension SNProtoKeyPair.SNProtoKeyPairBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataExtractionNotification) throws -> SNProtoDataExtractionNotification {
|
||||
guard proto.hasType else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = SNProtoDataExtractionNotificationTypeWrap(proto.type)
|
||||
|
||||
|
@ -1620,12 +1620,12 @@ extension SNProtoDataMessageQuoteQuotedAttachment.SNProtoDataMessageQuoteQuotedA
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Quote) throws -> SNProtoDataMessageQuote {
|
||||
guard proto.hasID else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id")
|
||||
}
|
||||
let id = proto.id
|
||||
|
||||
guard proto.hasAuthor else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author")
|
||||
}
|
||||
let author = proto.author
|
||||
|
||||
|
@ -1755,7 +1755,7 @@ extension SNProtoDataMessageQuote.SNProtoDataMessageQuoteBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Preview) throws -> SNProtoDataMessagePreview {
|
||||
guard proto.hasURL else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: url")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: url")
|
||||
}
|
||||
let url = proto.url
|
||||
|
||||
|
@ -1914,17 +1914,17 @@ extension SNProtoDataMessagePreview.SNProtoDataMessagePreviewBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Reaction) throws -> SNProtoDataMessageReaction {
|
||||
guard proto.hasID else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id")
|
||||
}
|
||||
let id = proto.id
|
||||
|
||||
guard proto.hasAuthor else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author")
|
||||
}
|
||||
let author = proto.author
|
||||
|
||||
guard proto.hasAction else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: action")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: action")
|
||||
}
|
||||
let action = SNProtoDataMessageReactionActionWrap(proto.action)
|
||||
|
||||
|
@ -2032,12 +2032,12 @@ extension SNProtoDataMessageReaction.SNProtoDataMessageReactionBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.OpenGroupInvitation) throws -> SNProtoDataMessageOpenGroupInvitation {
|
||||
guard proto.hasURL else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: url")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: url")
|
||||
}
|
||||
let url = proto.url
|
||||
|
||||
guard proto.hasName else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: name")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: name")
|
||||
}
|
||||
let name = proto.name
|
||||
|
||||
|
@ -2144,12 +2144,12 @@ extension SNProtoDataMessageOpenGroupInvitation.SNProtoDataMessageOpenGroupInvit
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper) throws -> SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper {
|
||||
guard proto.hasPublicKey else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey")
|
||||
}
|
||||
let publicKey = proto.publicKey
|
||||
|
||||
guard proto.hasEncryptedKeyPair else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: encryptedKeyPair")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: encryptedKeyPair")
|
||||
}
|
||||
let encryptedKeyPair = proto.encryptedKeyPair
|
||||
|
||||
|
@ -2387,7 +2387,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage) throws -> SNProtoDataMessageClosedGroupControlMessage {
|
||||
guard proto.hasType else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = SNProtoDataMessageClosedGroupControlMessageTypeWrap(proto.type)
|
||||
|
||||
|
@ -3076,12 +3076,12 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_ConfigurationMessage.Contact) throws -> SNProtoConfigurationMessageContact {
|
||||
guard proto.hasPublicKey else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey")
|
||||
}
|
||||
let publicKey = proto.publicKey
|
||||
|
||||
guard proto.hasName else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: name")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: name")
|
||||
}
|
||||
let name = proto.name
|
||||
|
||||
|
@ -3396,7 +3396,7 @@ extension SNProtoConfigurationMessage.SNProtoConfigurationMessageBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_ReceiptMessage) throws -> SNProtoReceiptMessage {
|
||||
guard proto.hasType else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = SNProtoReceiptMessageTypeWrap(proto.type)
|
||||
|
||||
|
@ -3686,7 +3686,7 @@ extension SNProtoReceiptMessage.SNProtoReceiptMessageBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_AttachmentPointer) throws -> SNProtoAttachmentPointer {
|
||||
guard proto.hasID else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id")
|
||||
}
|
||||
let id = proto.id
|
||||
|
||||
|
@ -3828,17 +3828,17 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder {
|
|||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_SharedConfigMessage) throws -> SNProtoSharedConfigMessage {
|
||||
guard proto.hasKind else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: kind")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: kind")
|
||||
}
|
||||
let kind = SNProtoSharedConfigMessageKindWrap(proto.kind)
|
||||
|
||||
guard proto.hasSeqno else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: seqno")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: seqno")
|
||||
}
|
||||
let seqno = proto.seqno
|
||||
|
||||
guard proto.hasData else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: data")
|
||||
throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: data")
|
||||
}
|
||||
let data = proto.data
|
||||
|
||||
|
|
|
@ -123,17 +123,17 @@ public enum WebSocketProtoError: Error {
|
|||
|
||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage {
|
||||
guard proto.hasVerb else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: verb")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: verb")
|
||||
}
|
||||
let verb = proto.verb
|
||||
|
||||
guard proto.hasPath else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: path")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: path")
|
||||
}
|
||||
let path = proto.path
|
||||
|
||||
guard proto.hasRequestID else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: requestID")
|
||||
}
|
||||
let requestID = proto.requestID
|
||||
|
||||
|
@ -290,12 +290,12 @@ extension WebSocketProtoWebSocketRequestMessage.WebSocketProtoWebSocketRequestMe
|
|||
|
||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage {
|
||||
guard proto.hasRequestID else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: requestID")
|
||||
}
|
||||
let requestID = proto.requestID
|
||||
|
||||
guard proto.hasStatus else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: status")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: status")
|
||||
}
|
||||
let status = proto.status
|
||||
|
||||
|
@ -439,7 +439,7 @@ extension WebSocketProtoWebSocketResponseMessage.WebSocketProtoWebSocketResponse
|
|||
|
||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage {
|
||||
guard proto.hasType else {
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: type")
|
||||
throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type")
|
||||
}
|
||||
let type = WebSocketProtoWebSocketMessageTypeWrap(proto.type)
|
||||
|
||||
|
|
|
@ -88,11 +88,9 @@ extension MessageReceiver {
|
|||
// Need to check if the blinded id matches for open groups
|
||||
switch senderSessionId.prefix {
|
||||
case .blinded15, .blinded25:
|
||||
let sodium: Sodium = Sodium()
|
||||
|
||||
guard
|
||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public extension Notification.Name {
|
||||
static let missedCall = Notification.Name("missedCall")
|
||||
|
|
|
@ -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
|
||||
|
@ -564,8 +564,7 @@ private extension SessionUtil {
|
|||
count: ProfileManager.avatarAES256KeyByteLength
|
||||
)
|
||||
),
|
||||
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000)
|
||||
)
|
||||
|
||||
result[contactId] = ContactData(
|
||||
|
|
|
@ -510,14 +510,14 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks community message requets flag
|
||||
if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > profile.lastBlocksCommunityMessageRequests {
|
||||
if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > (profile.lastBlocksCommunityMessageRequests ?? 0) {
|
||||
profileChanges.append(Profile.Columns.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests))
|
||||
profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp))
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ public struct ProfileManager {
|
|||
var avatarNeedsDownload: Bool = false
|
||||
var targetAvatarUrl: String? = nil
|
||||
|
||||
if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) {
|
||||
if sentTimestamp > (profile.lastProfilePictureUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) {
|
||||
switch avatarUpdate {
|
||||
case .none: break
|
||||
case .uploadImageData: preconditionFailure("Invalid options for this function")
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class FileUploadResponseSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a FileUploadResponse
|
||||
describe("a FileUploadResponse") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- handles a string id value
|
||||
it("handles a string id value") {
|
||||
let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)!
|
||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||
|
@ -20,6 +21,7 @@ class FileUploadResponseSpec: QuickSpec {
|
|||
expect(response?.id).to(equal("123"))
|
||||
}
|
||||
|
||||
// MARK: ---- handles an int id value
|
||||
it("handles an int id value") {
|
||||
let jsonData: Data = "{\"id\":124}".data(using: .utf8)!
|
||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class BlindedIdLookupSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a BlindedIdLookup
|
||||
describe("a BlindedIdLookup") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- sets the values correctly
|
||||
it("sets the values correctly") {
|
||||
let lookup: BlindedIdLookup = BlindedIdLookup(
|
||||
blindedId: "testBlindedId",
|
||||
|
|
|
@ -10,49 +10,37 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class MessageSendJobSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override 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,
|
||||
|
|
|
@ -1,547 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigContactsSpec {
|
||||
enum ContactProperty: CaseIterable {
|
||||
case name
|
||||
case nickname
|
||||
case approved
|
||||
case approved_me
|
||||
case blocked
|
||||
case profile_pic
|
||||
case created
|
||||
case notifications
|
||||
case mute_until
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("CONTACTS") {
|
||||
// MARK: - when checking error catching
|
||||
context("when checking error catching") {
|
||||
var seed: Data!
|
||||
var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)!
|
||||
var edSK: [UInt8]!
|
||||
var error: UnsafeMutablePointer<CChar>?
|
||||
var conf: UnsafeMutablePointer<config_object>?
|
||||
|
||||
beforeEach {
|
||||
seed = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
identity = try! Identity.generate(from: seed)
|
||||
edSK = identity.ed25519KeyPair.secretKey
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
error = nil
|
||||
conf = nil
|
||||
_ = contacts_init(&conf, &edSK, nil, 0, error)
|
||||
error?.deallocate()
|
||||
}
|
||||
|
||||
// MARK: -- it can catch size limit errors thrown when pushing
|
||||
it("can catch size limit errors thrown when pushing") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
try (0..<10000).forEach { index in
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: .allProperties
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
}
|
||||
|
||||
expect(contacts_size(conf)).to(equal(10000))
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
expect {
|
||||
try CExceptionHelper.performSafely { config_push(conf).deallocate() }
|
||||
}
|
||||
.to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"])))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when checking size limits
|
||||
context("when checking size limits") {
|
||||
var numRecords: Int!
|
||||
var seed: Data!
|
||||
var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)!
|
||||
var edSK: [UInt8]!
|
||||
var error: UnsafeMutablePointer<CChar>?
|
||||
var conf: UnsafeMutablePointer<config_object>?
|
||||
|
||||
beforeEach {
|
||||
numRecords = 0
|
||||
seed = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
identity = try! Identity.generate(from: seed)
|
||||
edSK = identity.ed25519KeyPair.secretKey
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
error = nil
|
||||
conf = nil
|
||||
_ = contacts_init(&conf, &edSK, nil, 0, error)
|
||||
error?.deallocate()
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max empty records
|
||||
it("has not changed the max empty records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(2370))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max name only records
|
||||
it("has not changed the max name only records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: [.name]
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(796))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max name and profile pic only records
|
||||
it("has not changed the max name and profile pic only records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: [.name, .profile_pic]
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(290))
|
||||
}
|
||||
|
||||
// MARK: -- has not changed the max filled records
|
||||
it("has not changed the max filled records") {
|
||||
var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000)
|
||||
|
||||
for index in (0..<100000) {
|
||||
var contact: contacts_contact = try createContact(
|
||||
for: index,
|
||||
in: conf,
|
||||
rand: &randomGenerator,
|
||||
maxing: .allProperties
|
||||
)
|
||||
contacts_set(conf, &contact)
|
||||
|
||||
do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } }
|
||||
catch { break }
|
||||
|
||||
// We successfully inserted a contact and didn't hit the limit so increment the counter
|
||||
numRecords += 1
|
||||
}
|
||||
|
||||
// Check that the record count matches the maximum when we last checked
|
||||
expect(numRecords).to(equal(236))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - generates config correctly
|
||||
|
||||
it("generates config correctly") {
|
||||
let createdTs: Int64 = 1680064059
|
||||
let nowTs: Int64 = Int64(Date().timeIntervalSince1970)
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
let contactPtr: UnsafeMutablePointer<contacts_contact>? = nil
|
||||
expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse())
|
||||
|
||||
expect(contacts_size(conf)).to(equal(0))
|
||||
|
||||
var contact2: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact2.name)).to(beEmpty())
|
||||
expect(String(libSessionVal: contact2.nickname)).to(beEmpty())
|
||||
expect(contact2.approved).to(beFalse())
|
||||
expect(contact2.approved_me).to(beFalse())
|
||||
expect(contact2.blocked).to(beFalse())
|
||||
expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty())
|
||||
expect(contact2.created).to(equal(0))
|
||||
expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT))
|
||||
expect(contact2.mute_until).to(equal(0))
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
pushData1.deallocate()
|
||||
|
||||
// Update the contact data
|
||||
contact2.name = "Joe".toLibSession()
|
||||
contact2.nickname = "Joey".toLibSession()
|
||||
contact2.approved = true
|
||||
contact2.approved_me = true
|
||||
contact2.created = createdTs
|
||||
contact2.notifications = CONVO_NOTIFY_ALL
|
||||
contact2.mute_until = nowTs + 1800
|
||||
|
||||
// Update the contact
|
||||
contacts_set(conf, &contact2)
|
||||
|
||||
// Ensure the contact details were updated
|
||||
var contact3: contacts_contact = contacts_contact()
|
||||
expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact3.name)).to(equal("Joe"))
|
||||
expect(String(libSessionVal: contact3.nickname)).to(equal("Joey"))
|
||||
expect(contact3.approved).to(beTrue())
|
||||
expect(contact3.approved_me).to(beTrue())
|
||||
expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty())
|
||||
expect(contact3.blocked).to(beFalse())
|
||||
expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId))
|
||||
expect(contact3.created).to(equal(createdTs))
|
||||
expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL))
|
||||
expect(contact2.mute_until).to(equal(nowTs + 1800))
|
||||
|
||||
|
||||
// Since we've made changes, we should need to push new config to the swarm, *and* should need
|
||||
// to dump the updated state:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed multiple fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed multiple fields here).
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
pushData2.deallocate()
|
||||
|
||||
// NB: Not going to check encrypted data and decryption here because that's general (not
|
||||
// specific to contacts) and is covered already in the user profile tests.
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData3.pointee.seqno).to(equal(1))
|
||||
pushData3.deallocate()
|
||||
|
||||
// Because we just called dump() above, to load up contacts2
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Ensure the contact details were updated
|
||||
var contact4: contacts_contact = contacts_contact()
|
||||
expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact4.name)).to(equal("Joe"))
|
||||
expect(String(libSessionVal: contact4.nickname)).to(equal("Joey"))
|
||||
expect(contact4.approved).to(beTrue())
|
||||
expect(contact4.approved_me).to(beTrue())
|
||||
expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty())
|
||||
expect(contact4.blocked).to(beFalse())
|
||||
expect(contact4.created).to(equal(createdTs))
|
||||
|
||||
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
|
||||
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
|
||||
var contact5: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue())
|
||||
expect(String(libSessionVal: contact5.name)).to(beEmpty())
|
||||
expect(String(libSessionVal: contact5.nickname)).to(beEmpty())
|
||||
expect(contact5.approved).to(beFalse())
|
||||
expect(contact5.approved_me).to(beFalse())
|
||||
expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently
|
||||
expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty())
|
||||
expect(contact5.blocked).to(beFalse())
|
||||
|
||||
// We're not setting any fields, but we should still keep a record of the session id
|
||||
contacts_set(conf2, &contact5)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(2))
|
||||
|
||||
// Check the merging
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
|
||||
var mergeSize: [Int] = [pushData4.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2)
|
||||
mergeHashes.forEach { $0?.deallocate() }
|
||||
pushData4.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData5.pointee.seqno).to(equal(2))
|
||||
pushData5.deallocate()
|
||||
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var sessionIds: [String] = []
|
||||
var nicknames: [String] = []
|
||||
expect(contacts_size(conf)).to(equal(2))
|
||||
|
||||
var contact6: contacts_contact = contacts_contact()
|
||||
let contactIterator: UnsafeMutablePointer<contacts_iterator> = contacts_iterator_new(conf)
|
||||
while !contacts_iterator_done(contactIterator, &contact6) {
|
||||
sessionIds.append(String(libSessionVal: contact6.session_id))
|
||||
nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)")
|
||||
contacts_iterator_advance(contactIterator)
|
||||
}
|
||||
contacts_iterator_free(contactIterator) // Need to free the iterator
|
||||
|
||||
expect(sessionIds.count).to(equal(2))
|
||||
expect(sessionIds.count).to(equal(contacts_size(conf)))
|
||||
expect(sessionIds.first).to(equal(definitelyRealId))
|
||||
expect(sessionIds.last).to(equal(anotherId))
|
||||
expect(nicknames.first).to(equal("Joey"))
|
||||
expect(nicknames.last).to(equal("(N/A)"))
|
||||
|
||||
// Conflict! Oh no!
|
||||
|
||||
// On client 1 delete a contact:
|
||||
contacts_erase(conf, definitelyRealId)
|
||||
|
||||
// Client 2 adds a new friend:
|
||||
let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222"
|
||||
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
|
||||
var contact7: contacts_contact = contacts_contact()
|
||||
expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue())
|
||||
contact7.nickname = "Nickname 3".toLibSession()
|
||||
contact7.approved = true
|
||||
contact7.approved_me = true
|
||||
contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession()
|
||||
contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
contacts_set(conf2, &contact7)
|
||||
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData6.pointee.seqno).to(equal(3))
|
||||
|
||||
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData7.pointee.seqno).to(equal(3))
|
||||
|
||||
let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)!
|
||||
let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)!
|
||||
expect(pushData6Str).toNot(equal(pushData7Str))
|
||||
expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
|
||||
let fakeHash3a: String = "fakehash3a"
|
||||
var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated()
|
||||
let fakeHash3b: String = "fakehash3b"
|
||||
var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a)
|
||||
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b)
|
||||
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3b].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData7.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData7.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3a].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData6.pointee.config)]
|
||||
var mergeSize3: [Int] = [pushData6.pointee.config_len]
|
||||
expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1))
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
mergeHashes2.forEach { $0?.deallocate() }
|
||||
mergeHashes3.forEach { $0?.deallocate() }
|
||||
pushData6.deallocate()
|
||||
pushData7.deallocate()
|
||||
|
||||
let pushData8: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData8.pointee.seqno).to(equal(4))
|
||||
|
||||
let pushData9: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno))
|
||||
|
||||
let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)!
|
||||
let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)!
|
||||
expect(pushData8Str).to(equal(pushData9Str))
|
||||
expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3b, fakeHash3a]))
|
||||
expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3a, fakeHash3b]))
|
||||
|
||||
let fakeHash4: String = "fakeHash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4)
|
||||
config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4)
|
||||
pushData8.deallocate()
|
||||
pushData9.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Validate the changes
|
||||
var sessionIds2: [String] = []
|
||||
var nicknames2: [String] = []
|
||||
expect(contacts_size(conf)).to(equal(2))
|
||||
|
||||
var contact8: contacts_contact = contacts_contact()
|
||||
let contactIterator2: UnsafeMutablePointer<contacts_iterator> = contacts_iterator_new(conf)
|
||||
while !contacts_iterator_done(contactIterator2, &contact8) {
|
||||
sessionIds2.append(String(libSessionVal: contact8.session_id))
|
||||
nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)")
|
||||
contacts_iterator_advance(contactIterator2)
|
||||
}
|
||||
contacts_iterator_free(contactIterator2) // Need to free the iterator
|
||||
|
||||
expect(sessionIds2.count).to(equal(2))
|
||||
expect(sessionIds2.first).to(equal(anotherId))
|
||||
expect(sessionIds2.last).to(equal(thirdId))
|
||||
expect(nicknames2.first).to(equal("(N/A)"))
|
||||
expect(nicknames2.last).to(equal("Nickname 3"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
private static func createContact(
|
||||
for index: Int,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
rand: inout ARC4RandomNumberGenerator,
|
||||
maxing properties: [ContactProperty] = []
|
||||
) throws -> contacts_contact {
|
||||
let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())"
|
||||
let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count))
|
||||
var cSessionId: [CChar] = sessionId.cArray.nullTerminated()
|
||||
var contact: contacts_contact = contacts_contact()
|
||||
|
||||
guard contacts_get_or_construct(conf, &contact, &cSessionId) else {
|
||||
throw SessionUtilError.getOrConstructFailedUnexpectedly
|
||||
}
|
||||
|
||||
// Set the values to the maximum data that can fit
|
||||
properties.forEach { property in
|
||||
switch property {
|
||||
case .approved: contact.approved = true
|
||||
case .approved_me: contact.approved_me = true
|
||||
case .blocked: contact.blocked = true
|
||||
case .created: contact.created = Int64.max
|
||||
case .notifications: contact.notifications = CONVO_NOTIFY_MENTIONS_ONLY
|
||||
case .mute_until: contact.mute_until = Int64.max
|
||||
|
||||
case .name:
|
||||
contact.name = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength)
|
||||
.toHexString()
|
||||
.toLibSession()
|
||||
|
||||
case .nickname:
|
||||
contact.nickname = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength)
|
||||
.toHexString()
|
||||
.toLibSession()
|
||||
|
||||
case .profile_pic:
|
||||
contact.profile_pic = user_profile_pic(
|
||||
url: rand.nextBytes(count: SessionUtil.libSessionMaxProfileUrlByteLength)
|
||||
.toHexString()
|
||||
.toLibSession(),
|
||||
key: Data(rand.nextBytes(count: 32))
|
||||
.toLibSession()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension Array where Element == ConfigContactsSpec.ContactProperty {
|
||||
static var allProperties: [ConfigContactsSpec.ContactProperty] = ConfigContactsSpec.ContactProperty.allCases
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigConvoInfoVolatileSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("CONVO_INFO_VOLATILE") {
|
||||
it("generates config correctly") {
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse())
|
||||
expect(convo_info_volatile_size(conf)).to(equal(0))
|
||||
|
||||
var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId))
|
||||
expect(oneToOne2.last_read).to(equal(0))
|
||||
expect(oneToOne2.unread).to(beFalse())
|
||||
|
||||
// No need to sync a conversation with a default state
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Update the last read
|
||||
let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000))
|
||||
oneToOne2.last_read = nowTimestampMs
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
convo_info_volatile_set_1to1(conf, &oneToOne2)
|
||||
|
||||
var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId))
|
||||
.to(beFalse())
|
||||
expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue())
|
||||
expect(oneToOne3.last_read).to(equal(nowTimestampMs))
|
||||
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
let openGroupBaseUrl: String = "http://Example.ORG:5678"
|
||||
var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated()
|
||||
let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased()
|
||||
// ("http://Example.ORG:5678"
|
||||
// .lowercased()
|
||||
// .cArray +
|
||||
// [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count))
|
||||
// )
|
||||
let openGroupRoom: String = "SudokuRoom"
|
||||
var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated()
|
||||
let openGroupRoomResult: String = openGroupRoom.lowercased()
|
||||
// ("SudokuRoom"
|
||||
// .lowercased()
|
||||
// .cArray +
|
||||
// [CChar](repeating: 0, count: (65 - openGroupRoom.count))
|
||||
// )
|
||||
var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
.bytes
|
||||
var community1: convo_info_volatile_community = convo_info_volatile_community()
|
||||
expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue())
|
||||
expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult))
|
||||
expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult))
|
||||
expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community1.unread = true
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
convo_info_volatile_set_community(conf, &community1);
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(1))
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
pushData1.deallocate()
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true))
|
||||
expect(oneToOne4.last_read).to(equal(nowTimestampMs))
|
||||
expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId))
|
||||
expect(oneToOne4.unread).to(beFalse())
|
||||
|
||||
var community2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue())
|
||||
expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult))
|
||||
expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult))
|
||||
expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community2.unread = true
|
||||
|
||||
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
|
||||
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
|
||||
var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue())
|
||||
oneToOne5.unread = true
|
||||
convo_info_volatile_set_1to1(conf2, &oneToOne5)
|
||||
|
||||
let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
|
||||
var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue())
|
||||
legacyGroup2.last_read = (nowTimestampMs - 50)
|
||||
convo_info_volatile_set_legacy_group(conf2, &legacyGroup2)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData2.pointee.seqno).to(equal(2))
|
||||
|
||||
// Check the merging
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData2.pointee.config)]
|
||||
var mergeSize: [Int] = [pushData2.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2)
|
||||
pushData2.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
expect(convo_info_volatile_size(conf)).to(equal(4))
|
||||
expect(convo_info_volatile_size_1to1(conf)).to(equal(2))
|
||||
expect(convo_info_volatile_size_communities(conf)).to(equal(1))
|
||||
expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
var c2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it) {
|
||||
if convo_info_volatile_it_is_1to1(it, &c1) {
|
||||
seen.append("1-to-1: \(String(libSessionVal: c1.session_id))")
|
||||
}
|
||||
else if convo_info_volatile_it_is_community(it, &c2) {
|
||||
seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else if convo_info_volatile_it_is_legacy_group(it, &c3) {
|
||||
seen.append("cl: \(String(libSessionVal: c3.group_id))")
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_advance(it)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"1-to-1: 051111111111111111111111111111111111111111111111111111111111111111",
|
||||
"1-to-1: 055000000000000000000000000000000000000000000000000000000000000000",
|
||||
"og: http://example.org:5678/r/sudokuroom",
|
||||
"cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
]))
|
||||
}
|
||||
|
||||
let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cFourthId: [CChar] = fourthId.cArray.nullTerminated()
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
convo_info_volatile_erase_1to1(conf, &cFourthId)
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId)
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(convo_info_volatile_size(conf)).to(equal(3))
|
||||
expect(convo_info_volatile_size_1to1(conf)).to(equal(1))
|
||||
|
||||
// Check the single-type iterators:
|
||||
var seen1: [String?] = []
|
||||
var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
|
||||
let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it1) {
|
||||
expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue())
|
||||
|
||||
seen1.append(String(libSessionVal: c1.session_id))
|
||||
convo_info_volatile_iterator_advance(it1)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it1)
|
||||
expect(seen1).to(equal([
|
||||
"051111111111111111111111111111111111111111111111111111111111111111"
|
||||
]))
|
||||
|
||||
var seen2: [String?] = []
|
||||
var c2: convo_info_volatile_community = convo_info_volatile_community()
|
||||
let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it2) {
|
||||
expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue())
|
||||
|
||||
seen2.append(String(libSessionVal: c2.base_url))
|
||||
convo_info_volatile_iterator_advance(it2)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it2)
|
||||
expect(seen2).to(equal([
|
||||
"http://example.org:5678"
|
||||
]))
|
||||
|
||||
var seen3: [String?] = []
|
||||
var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
|
||||
let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf)
|
||||
|
||||
while !convo_info_volatile_iterator_done(it3) {
|
||||
expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue())
|
||||
|
||||
seen3.append(String(libSessionVal: c3.group_id))
|
||||
convo_info_volatile_iterator_advance(it3)
|
||||
}
|
||||
|
||||
convo_info_volatile_iterator_free(it3)
|
||||
expect(seen3).to(equal([
|
||||
"05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,589 +0,0 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigUserGroupsSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
it("parses community URLs correctly") {
|
||||
let result1 = SessionUtil.parseCommunity(url: [
|
||||
"https://example.com/",
|
||||
"SomeRoom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
].joined())
|
||||
let result2 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.COM/",
|
||||
"sOMErOOM?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result3 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.COM/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result4 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result5 = SessionUtil.parseCommunity(url: [
|
||||
"HTTPS://EXAMPLE.com:443/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result6 = SessionUtil.parseCommunity(url: [
|
||||
"HTTP://EXAMPLE.com:80/r/",
|
||||
"someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF"
|
||||
].joined())
|
||||
let result7 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com:80/r/",
|
||||
"someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8"
|
||||
].joined())
|
||||
let result8 = SessionUtil.parseCommunity(url: [
|
||||
"http://example.com:80/r/",
|
||||
"someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo"
|
||||
].joined())
|
||||
|
||||
expect(result1?.server).to(equal("https://example.com"))
|
||||
expect(result1?.server).to(equal(result2?.server))
|
||||
expect(result1?.server).to(equal(result3?.server))
|
||||
expect(result1?.server).toNot(equal(result4?.server))
|
||||
expect(result4?.server).to(equal("http://example.com"))
|
||||
expect(result1?.server).to(equal(result5?.server))
|
||||
expect(result4?.server).to(equal(result6?.server))
|
||||
expect(result4?.server).to(equal(result7?.server))
|
||||
expect(result4?.server).to(equal(result8?.server))
|
||||
expect(result1?.room).to(equal("SomeRoom"))
|
||||
expect(result2?.room).to(equal("sOMErOOM"))
|
||||
expect(result3?.room).to(equal("someroom"))
|
||||
expect(result4?.room).to(equal("someroom"))
|
||||
expect(result5?.room).to(equal("someroom"))
|
||||
expect(result6?.room).to(equal("someroom"))
|
||||
expect(result7?.room).to(equal("someroom"))
|
||||
expect(result8?.room).to(equal("someroom"))
|
||||
expect(result1?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result2?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result3?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result4?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result5?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result6?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result7?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(result8?.publicKey)
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
}
|
||||
|
||||
context("USER_GROUPS") {
|
||||
it("generates config correctly") {
|
||||
let createdTs: Int64 = 1680064059
|
||||
let nowTs: Int64 = Int64(Date().timeIntervalSince1970)
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// Empty contacts shouldn't have an existing contact
|
||||
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
|
||||
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
|
||||
let legacyGroup1: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup1?.pointee).to(beNil())
|
||||
expect(user_groups_size(conf)).to(equal(0))
|
||||
|
||||
let legacyGroup2: UnsafeMutablePointer<ugroups_legacy_group_info> = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup2.pointee).toNot(beNil())
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.session_id))
|
||||
.to(equal(definitelyRealId))
|
||||
expect(legacyGroup2.pointee.disappearing_timer).to(equal(0))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal(""))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal(""))
|
||||
expect(legacyGroup2.pointee.priority).to(equal(0))
|
||||
expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal(""))
|
||||
expect(legacyGroup2.pointee.joined_at).to(equal(0))
|
||||
expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT))
|
||||
expect(legacyGroup2.pointee.mute_until).to(equal(0))
|
||||
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var membersSeen1: [String: Bool] = [:]
|
||||
var memberSessionId1: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin1: Bool = false
|
||||
let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) {
|
||||
membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt1)
|
||||
|
||||
expect(membersSeen1).to(beEmpty())
|
||||
|
||||
// No need to sync a conversation with a default state
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
expect(pushData1.pointee.config_len).to(equal(256))
|
||||
pushData1.deallocate()
|
||||
|
||||
let users: [String] = [
|
||||
"050000000000000000000000000000000000000000000000000000000000000000",
|
||||
"051111111111111111111111111111111111111111111111111111111111111111",
|
||||
"052222222222222222222222222222222222222222222222222222222222222222",
|
||||
"053333333333333333333333333333333333333333333333333333333333333333",
|
||||
"054444444444444444444444444444444444444444444444444444444444444444",
|
||||
"055555555555555555555555555555555555555555555555555555555555555555",
|
||||
"056666666666666666666666666666666666666666666666666666666666666666"
|
||||
]
|
||||
var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() }
|
||||
legacyGroup2.pointee.name = "Englishmen".toLibSession()
|
||||
legacyGroup2.pointee.disappearing_timer = 60
|
||||
legacyGroup2.pointee.joined_at = createdTs
|
||||
legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL
|
||||
legacyGroup2.pointee.mute_until = (nowTs + 3600)
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse())
|
||||
|
||||
// Flip to and from admin
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue())
|
||||
|
||||
expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue())
|
||||
expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue())
|
||||
|
||||
var membersSeen2: [String: Bool] = [:]
|
||||
var memberSessionId2: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin2: Bool = false
|
||||
let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) {
|
||||
membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt2)
|
||||
|
||||
expect(membersSeen2).to(equal([
|
||||
"050000000000000000000000000000000000000000000000000000000000000000": false,
|
||||
"051111111111111111111111111111111111111111111111111111111111111111": false,
|
||||
"052222222222222222222222222222222222222222222222222222222222222222": true
|
||||
]))
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
|
||||
let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)!
|
||||
let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)!
|
||||
|
||||
// Note: this isn't exactly what Session actually does here for legacy closed
|
||||
// groups (rather it uses X25519 keys) but for this test the distinction doesn't matter.
|
||||
legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession()
|
||||
legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession()
|
||||
legacyGroup2.pointee.priority = 3
|
||||
|
||||
expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString())
|
||||
.to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"))
|
||||
expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString())
|
||||
.to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"))
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
user_groups_set_free_legacy_group(conf, legacyGroup2)
|
||||
|
||||
let legacyGroup3: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cDefinitelyRealId)
|
||||
expect(legacyGroup3?.pointee).toNot(beNil())
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
ugroups_legacy_group_free(legacyGroup3)
|
||||
|
||||
let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray
|
||||
var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated()
|
||||
var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated()
|
||||
var community1: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey))
|
||||
.to(beTrue())
|
||||
|
||||
expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case
|
||||
expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving
|
||||
expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
community1.priority = 14
|
||||
|
||||
// The new data doesn't get stored until we call this:
|
||||
user_groups_set_community(conf, &community1)
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed two fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
|
||||
// Pretend we uploaded it
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0))
|
||||
error2?.deallocate()
|
||||
dump1?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData3.pointee.seqno).to(equal(1))
|
||||
expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
pushData3.deallocate()
|
||||
|
||||
let currentHashes1: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf)
|
||||
expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len))
|
||||
.to(equal(["fakehash1"]))
|
||||
currentHashes1?.deallocate()
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len))
|
||||
.to(beEmpty())
|
||||
pushData4.deallocate()
|
||||
|
||||
let currentHashes2: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len))
|
||||
.to(equal(["fakehash1"]))
|
||||
currentHashes2?.deallocate()
|
||||
|
||||
expect(user_groups_size(conf2)).to(equal(2))
|
||||
expect(user_groups_size_communities(conf2)).to(equal(1))
|
||||
expect(user_groups_size_legacy_groups(conf2)).to(equal(1))
|
||||
|
||||
let legacyGroup4: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId)
|
||||
expect(legacyGroup4?.pointee).toNot(beNil())
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal(""))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal(""))
|
||||
expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId))
|
||||
expect(legacyGroup4?.pointee.priority).to(equal(3))
|
||||
expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen"))
|
||||
expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs))
|
||||
expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL))
|
||||
expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600))
|
||||
|
||||
var membersSeen3: [String: Bool] = [:]
|
||||
var memberSessionId3: UnsafePointer<CChar>? = nil
|
||||
var memberAdmin3: Bool = false
|
||||
let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4)
|
||||
|
||||
while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) {
|
||||
membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3
|
||||
}
|
||||
|
||||
ugroups_legacy_members_free(membersIt3)
|
||||
ugroups_legacy_group_free(legacyGroup4)
|
||||
|
||||
expect(membersSeen3).to(equal([
|
||||
"050000000000000000000000000000000000000000000000000000000000000000": false,
|
||||
"051111111111111111111111111111111111111111111111111111111111111111": false,
|
||||
"052222222222222222222222222222222222222222222222222222222222222222": true
|
||||
]))
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData5.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData5.deallocate()
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
|
||||
var c1: ugroups_legacy_group_info = ugroups_legacy_group_info()
|
||||
var c2: ugroups_community_info = ugroups_community_info()
|
||||
let it: OpaquePointer = user_groups_iterator_new(targetConf)
|
||||
|
||||
while !user_groups_iterator_done(it) {
|
||||
if user_groups_it_is_legacy_group(it, &c1) {
|
||||
var memberCount: Int = 0
|
||||
var adminCount: Int = 0
|
||||
ugroups_legacy_members_count(&c1, &memberCount, &adminCount)
|
||||
seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members")
|
||||
}
|
||||
else if user_groups_it_is_community(it, &c2) {
|
||||
seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else {
|
||||
seen.append("unknown")
|
||||
}
|
||||
|
||||
user_groups_iterator_advance(it)
|
||||
}
|
||||
|
||||
user_groups_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"community: http://example.org:5678/r/SudokuRoom",
|
||||
"legacy: Englishmen, 1 admins, 2 members"
|
||||
]))
|
||||
}
|
||||
|
||||
var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
|
||||
var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated()
|
||||
var community2: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678"))
|
||||
expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value
|
||||
expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString())
|
||||
.to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
expect(community2.priority).to(equal(14))
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData6.pointee.seqno).to(equal(1))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData6.deallocate()
|
||||
|
||||
community2.room = "sudokuRoom".toLibSession() // Change capitalization
|
||||
user_groups_set_community(conf2, &community2)
|
||||
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData7.pointee.seqno).to(equal(2))
|
||||
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2)
|
||||
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
|
||||
.to(equal([fakeHash1]))
|
||||
|
||||
let currentHashes3: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len))
|
||||
.to(equal([fakeHash2]))
|
||||
currentHashes3?.deallocate()
|
||||
|
||||
var dump2: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump2Len: Int = 0
|
||||
config_dump(conf2, &dump2, &dump2Len)
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData8: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData8.pointee.seqno).to(equal(2))
|
||||
config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2)
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
var mergeHashes1: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData1: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData8.pointee.config)]
|
||||
var mergeSize1: [Int] = [pushData8.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1))
|
||||
pushData8.deallocate()
|
||||
|
||||
var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
|
||||
var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated()
|
||||
var community3: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room))
|
||||
.to(beTrue())
|
||||
expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(2))
|
||||
expect(user_groups_size_communities(conf)).to(equal(1))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
let legacyGroup5: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId)
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue())
|
||||
expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue())
|
||||
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
let pushData9: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData9.pointee.seqno).to(equal(2))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
pushData9.deallocate()
|
||||
|
||||
user_groups_set_free_legacy_group(conf2, legacyGroup5)
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
|
||||
var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated()
|
||||
var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated()
|
||||
user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room)
|
||||
|
||||
let fakeHash3: String = "fakehash3"
|
||||
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
|
||||
let pushData10: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3)
|
||||
|
||||
expect(pushData10.pointee.seqno).to(equal(3))
|
||||
expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len))
|
||||
.to(equal([fakeHash2]))
|
||||
|
||||
let currentHashes4: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len))
|
||||
.to(equal([fakeHash3]))
|
||||
currentHashes4?.deallocate()
|
||||
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData10.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData10.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(1))
|
||||
expect(user_groups_size_communities(conf)).to(equal(0))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
var prio: Int32 = 0
|
||||
var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated()
|
||||
var cBeanstalkPubkey: [UInt8] = Data(
|
||||
hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
|
||||
).cArray
|
||||
|
||||
["fee", "fi", "fo", "fum"].forEach { room in
|
||||
var cRoom: [CChar] = room.cArray.nullTerminated()
|
||||
prio += 1
|
||||
|
||||
var community4: ugroups_community_info = ugroups_community_info()
|
||||
expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey))
|
||||
.to(beTrue())
|
||||
community4.priority = prio
|
||||
user_groups_set_community(conf, &community4)
|
||||
}
|
||||
|
||||
expect(user_groups_size(conf)).to(equal(5))
|
||||
expect(user_groups_size_communities(conf)).to(equal(4))
|
||||
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
|
||||
|
||||
let fakeHash4: String = "fakehash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
let pushData11: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4)
|
||||
expect(pushData11.pointee.seqno).to(equal(4))
|
||||
expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len))
|
||||
.to(equal([fakeHash3, fakeHash2, fakeHash1]))
|
||||
|
||||
// Load some obsolete ones in just to check that they get immediately obsoleted
|
||||
let fakeHash10: String = "fakehash10"
|
||||
let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated()
|
||||
let fakeHash11: String = "fakehash11"
|
||||
let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated()
|
||||
let fakeHash12: String = "fakehash12"
|
||||
let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated()
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [
|
||||
UnsafePointer(pushData10.pointee.config),
|
||||
UnsafePointer(pushData2.pointee.config),
|
||||
UnsafePointer(pushData7.pointee.config),
|
||||
UnsafePointer(pushData11.pointee.config)
|
||||
]
|
||||
var mergeSize3: [Int] = [
|
||||
pushData10.pointee.config_len,
|
||||
pushData2.pointee.config_len,
|
||||
pushData7.pointee.config_len,
|
||||
pushData11.pointee.config_len
|
||||
]
|
||||
expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4))
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
pushData2.deallocate()
|
||||
pushData7.deallocate()
|
||||
pushData10.deallocate()
|
||||
pushData11.deallocate()
|
||||
|
||||
let currentHashes5: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
|
||||
expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len))
|
||||
.to(equal([fakeHash4]))
|
||||
currentHashes5?.deallocate()
|
||||
|
||||
let pushData12: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData12.pointee.seqno).to(equal(4))
|
||||
expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len))
|
||||
.to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3]))
|
||||
pushData12.deallocate()
|
||||
|
||||
for targetConf in [conf, conf2] {
|
||||
// Iterate through and make sure we got everything we expected
|
||||
var seen: [String] = []
|
||||
|
||||
var c1: ugroups_legacy_group_info = ugroups_legacy_group_info()
|
||||
var c2: ugroups_community_info = ugroups_community_info()
|
||||
let it: OpaquePointer = user_groups_iterator_new(targetConf)
|
||||
|
||||
while !user_groups_iterator_done(it) {
|
||||
if user_groups_it_is_legacy_group(it, &c1) {
|
||||
var memberCount: Int = 0
|
||||
var adminCount: Int = 0
|
||||
ugroups_legacy_members_count(&c1, &memberCount, &adminCount)
|
||||
|
||||
seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members")
|
||||
}
|
||||
else if user_groups_it_is_community(it, &c2) {
|
||||
seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))")
|
||||
}
|
||||
else {
|
||||
seen.append("unknown")
|
||||
}
|
||||
|
||||
user_groups_iterator_advance(it)
|
||||
}
|
||||
|
||||
user_groups_iterator_free(it)
|
||||
|
||||
expect(seen).to(equal([
|
||||
"community: http://jacksbeanstalk.org/r/fee",
|
||||
"community: http://jacksbeanstalk.org/r/fi",
|
||||
"community: http://jacksbeanstalk.org/r/fo",
|
||||
"community: http://jacksbeanstalk.org/r/fum",
|
||||
"legacy: Englishmen, 3 admins, 2 members"
|
||||
]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,414 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtil
|
||||
import SessionUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches
|
||||
class ConfigUserProfileSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
static func spec() {
|
||||
context("USER_PROFILE") {
|
||||
it("generates config correctly") {
|
||||
let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
|
||||
|
||||
// FIXME: Would be good to move these into the libSession-util instead of using Sodium separately
|
||||
let identity = try! Identity.generate(from: seed)
|
||||
var edSK: [UInt8] = identity.ed25519KeyPair.secretKey
|
||||
expect(edSK.toHexString().suffix(64))
|
||||
.to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"))
|
||||
expect(identity.x25519KeyPair.publicKey.toHexString())
|
||||
.to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"))
|
||||
expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString()))
|
||||
|
||||
// Initialize a brand new, empty config because we have no dump data to deal with.
|
||||
let error: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0))
|
||||
error?.deallocate()
|
||||
|
||||
// We don't need to push anything, since this is an empty config
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
// And we haven't changed anything so don't need to dump to db
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Since it's empty there shouldn't be a name.
|
||||
let namePtr: UnsafePointer<CChar>? = user_profile_get_name(conf)
|
||||
expect(namePtr).to(beNil())
|
||||
|
||||
// We don't need to push since we haven't changed anything, so this call is mainly just for
|
||||
// testing:
|
||||
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData1.pointee).toNot(beNil())
|
||||
expect(pushData1.pointee.seqno).to(equal(0))
|
||||
expect(pushData1.pointee.config_len).to(equal(256))
|
||||
|
||||
let encDomain: [CChar] = "UserProfile"
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile"))
|
||||
|
||||
var toPushDecSize: Int = 0
|
||||
let toPushDecrypted: UnsafeMutablePointer<UInt8>? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize)
|
||||
let prefixPadding: String = (0..<193)
|
||||
.map { _ in "\0" }
|
||||
.joined()
|
||||
expect(toPushDecrypted).toNot(beNil())
|
||||
expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead
|
||||
expect(String(pointer: toPushDecrypted, length: toPushDecSize))
|
||||
.to(equal("\(prefixPadding)d1:#i0e1:&de1:<le1:=dee"))
|
||||
pushData1.deallocate()
|
||||
toPushDecrypted?.deallocate()
|
||||
|
||||
// This should also be unset:
|
||||
let pic: user_profile_pic = user_profile_get_pic(conf)
|
||||
expect(String(libSessionVal: pic.url)).to(beEmpty())
|
||||
|
||||
// Now let's go set a profile name and picture:
|
||||
expect(user_profile_set_name(conf, "Kallie")).to(equal(0))
|
||||
let p: user_profile_pic = user_profile_pic(
|
||||
url: "http://example.org/omg-pic-123.bmp".toLibSession(),
|
||||
key: "secret78901234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
)
|
||||
expect(user_profile_set_pic(conf, p)).to(equal(0))
|
||||
user_profile_set_nts_priority(conf, 9)
|
||||
|
||||
// Retrieve them just to make sure they set properly:
|
||||
let namePtr2: UnsafePointer<CChar>? = user_profile_get_name(conf)
|
||||
expect(namePtr2).toNot(beNil())
|
||||
expect(String(cString: namePtr2!)).to(equal("Kallie"))
|
||||
|
||||
let pic2: user_profile_pic = user_profile_get_pic(conf);
|
||||
expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp"))
|
||||
expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength))
|
||||
.to(equal("secret78901234567890123456789012".data(using: .utf8)))
|
||||
expect(user_profile_get_nts_priority(conf)).to(equal(9))
|
||||
|
||||
// Since we've made changes, we should need to push new config to the swarm, *and* should need
|
||||
// to dump the updated state:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
|
||||
// incremented since we made changes (this only increments once between
|
||||
// dumps; even though we changed two fields here).
|
||||
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData2.pointee.seqno).to(equal(1))
|
||||
|
||||
// Note: This hex value differs from the value in the library tests because
|
||||
// it looks like the library has an "end of cell mark" character added at the
|
||||
// end (0x07 or '0007') so we need to manually add it to work
|
||||
let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965")
|
||||
.bytes
|
||||
// The data to be actually pushed, expanded like this to make it somewhat human-readable:
|
||||
let expPush1Decrypted: [UInt8] = ["""
|
||||
d
|
||||
1:#i1e
|
||||
1:& d
|
||||
1:+ i9e
|
||||
1:n 6:Kallie
|
||||
1:p 34:http://example.org/omg-pic-123.bmp
|
||||
1:q 32:secret78901234567890123456789012
|
||||
e
|
||||
1:< l
|
||||
l i0e 32:
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability
|
||||
.bytes,
|
||||
expHash0,
|
||||
"""
|
||||
de e
|
||||
e
|
||||
1:= d
|
||||
1:+ 0:
|
||||
1:n 0:
|
||||
1:p 0:
|
||||
1:q 0:
|
||||
e
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability
|
||||
.bytes
|
||||
].flatMap { $0 }
|
||||
let expPush1Encrypted: [UInt8] = Data(hex: [
|
||||
"9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f",
|
||||
"1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84",
|
||||
"aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868",
|
||||
"2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d",
|
||||
"e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656",
|
||||
"056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea",
|
||||
"49bf122762d7bc1d6d9c02f6d54f8384"
|
||||
].joined()).bytes
|
||||
|
||||
let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)!
|
||||
let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)!
|
||||
expect(pushData2Str).to(equal(expPush1EncryptedStr))
|
||||
|
||||
// Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data)
|
||||
var pushData2DecSize: Int = 0
|
||||
let pushData2Decrypted: UnsafeMutablePointer<UInt8>? = config_decrypt(
|
||||
pushData2.pointee.config,
|
||||
pushData2.pointee.config_len,
|
||||
edSK,
|
||||
encDomain,
|
||||
&pushData2DecSize
|
||||
)
|
||||
let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count))
|
||||
.map { _ in "\0" }
|
||||
.joined()
|
||||
expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead
|
||||
|
||||
let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)!
|
||||
let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii)
|
||||
.map { "\(prefixPadding2)\($0)" }!
|
||||
expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr))
|
||||
pushData2Decrypted?.deallocate()
|
||||
|
||||
// We haven't dumped, so still need to dump:
|
||||
expect(config_needs_dump(conf)).to(beTrue())
|
||||
// We did call push, but we haven't confirmed it as stored yet, so this will still return true:
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
|
||||
var dump1: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump1Len: Int = 0
|
||||
|
||||
config_dump(conf, &dump1, &dump1Len)
|
||||
// (in a real client we'd now store this to disk)
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
let expDump1: [CChar] = [
|
||||
"""
|
||||
d
|
||||
1:! i2e
|
||||
1:$ \(expPush1Decrypted.count):
|
||||
"""
|
||||
.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) },
|
||||
expPush1Decrypted
|
||||
.map { CChar(bitPattern: $0) },
|
||||
"""
|
||||
1:(0:
|
||||
1:)le
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
].flatMap { $0 }
|
||||
expect(String(pointer: dump1, length: dump1Len, encoding: .ascii))
|
||||
.to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii)))
|
||||
dump1?.deallocate()
|
||||
|
||||
// So now imagine we got back confirmation from the swarm that the push has been stored:
|
||||
let fakeHash1: String = "fakehash1"
|
||||
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
|
||||
pushData2.deallocate()
|
||||
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump
|
||||
|
||||
var dump2: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump2Len: Int = 0
|
||||
config_dump(conf, &dump2, &dump2Len)
|
||||
|
||||
let expDump2: [CChar] = [
|
||||
"""
|
||||
d
|
||||
1:! i0e
|
||||
1:$ \(expPush1Decrypted.count):
|
||||
"""
|
||||
.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) },
|
||||
expPush1Decrypted
|
||||
.map { CChar(bitPattern: $0) },
|
||||
"""
|
||||
1:(9:fakehash1
|
||||
1:)le
|
||||
e
|
||||
""".removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines)
|
||||
.bytes
|
||||
.map { CChar(bitPattern: $0) }
|
||||
].flatMap { $0 }
|
||||
expect(String(pointer: dump2, length: dump2Len, encoding: .ascii))
|
||||
.to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii)))
|
||||
dump2?.deallocate()
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
|
||||
// Now we're going to set up a second, competing config object (in the real world this would be
|
||||
// another Session client somewhere).
|
||||
|
||||
// Start with an empty config, as above:
|
||||
let error2: UnsafeMutablePointer<CChar>? = nil
|
||||
var conf2: UnsafeMutablePointer<config_object>? = nil
|
||||
expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0))
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
error2?.deallocate()
|
||||
|
||||
// Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into
|
||||
// conf2:
|
||||
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash1].unsafeCopy()
|
||||
var mergeData: [UnsafePointer<UInt8>?] = [expPush1Encrypted].unsafeCopy()
|
||||
var mergeSize: [Int] = [expPush1Encrypted.count]
|
||||
expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1))
|
||||
mergeHashes.forEach { $0?.deallocate() }
|
||||
mergeData.forEach { $0?.deallocate() }
|
||||
|
||||
// Our state has changed, so we need to dump:
|
||||
expect(config_needs_dump(conf2)).to(beTrue())
|
||||
var dump3: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump3Len: Int = 0
|
||||
config_dump(conf2, &dump3, &dump3Len)
|
||||
// (store in db)
|
||||
dump3?.deallocate()
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
|
||||
// We *don't* need to push: even though we updated, all we did is update to the merged data (and
|
||||
// didn't have any sort of merge conflict needed):
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Now let's create a conflicting update:
|
||||
|
||||
// Change the name on both clients:
|
||||
user_profile_set_name(conf, "Nibbler")
|
||||
user_profile_set_name(conf2, "Raz")
|
||||
|
||||
// And, on conf2, we're also going to change the profile pic:
|
||||
let p2: user_profile_pic = user_profile_pic(
|
||||
url: "http://new.example.com/pic".toLibSession(),
|
||||
key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession()
|
||||
)
|
||||
user_profile_set_pic(conf2, p2)
|
||||
|
||||
user_profile_set_nts_expiry(conf2, 86400)
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 0)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0))
|
||||
user_profile_set_blinded_msgreqs(conf2, -1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
// Both have changes, so push need a push
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
||||
let fakeHash2: String = "fakehash2"
|
||||
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
|
||||
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change
|
||||
config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2)
|
||||
|
||||
let fakeHash3: String = "fakehash3"
|
||||
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
|
||||
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change
|
||||
config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3)
|
||||
|
||||
var dump4: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump4Len: Int = 0
|
||||
config_dump(conf, &dump4, &dump4Len);
|
||||
var dump5: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump5Len: Int = 0
|
||||
config_dump(conf2, &dump5, &dump5Len);
|
||||
// (store in db)
|
||||
dump4?.deallocate()
|
||||
dump5?.deallocate()
|
||||
|
||||
// Since we set different things, we're going to get back different serialized data to be
|
||||
// pushed:
|
||||
let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii)
|
||||
let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii)
|
||||
expect(pushData3Str).toNot(equal(pushData4Str))
|
||||
|
||||
// Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client
|
||||
// also fetches new messages and pulls down the other client's `seqno=2` value.
|
||||
|
||||
// Feed the new config into each other. (This array could hold multiple configs if we pulled
|
||||
// down more than one).
|
||||
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
|
||||
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData3.pointee.config)]
|
||||
var mergeSize2: [Int] = [pushData3.pointee.config_len]
|
||||
expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1))
|
||||
pushData3.deallocate()
|
||||
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
|
||||
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
|
||||
var mergeSize3: [Int] = [pushData4.pointee.config_len]
|
||||
expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1))
|
||||
pushData4.deallocate()
|
||||
|
||||
// Now after the merge we *will* want to push from both client, since both will have generated a
|
||||
// merge conflict update (with seqno = 3).
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
let pushData5: UnsafeMutablePointer<config_push_data> = config_push(conf)
|
||||
let pushData6: UnsafeMutablePointer<config_push_data> = config_push(conf2)
|
||||
expect(pushData5.pointee.seqno).to(equal(3))
|
||||
expect(pushData6.pointee.seqno).to(equal(3))
|
||||
|
||||
// They should have resolved the conflict to the same thing:
|
||||
expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler"))
|
||||
expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler"))
|
||||
// (Note that they could have also both resolved to "Raz" here, but the hash of the serialized
|
||||
// message just happens to have a higher hash -- and thus gets priority -- for this particular
|
||||
// test).
|
||||
|
||||
// Since only one of them set a profile pic there should be no conflict there:
|
||||
let pic3: user_profile_pic = user_profile_get_pic(conf)
|
||||
expect(pic3.url).toNot(beNil())
|
||||
expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic"))
|
||||
expect(pic3.key).toNot(beNil())
|
||||
expect(Data(libSessionVal: pic3.key, count: 32).toHexString())
|
||||
.to(equal("7177657274007975696f31323334353637383930313233343536373839303132"))
|
||||
let pic4: user_profile_pic = user_profile_get_pic(conf2)
|
||||
expect(pic4.url).toNot(beNil())
|
||||
expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic"))
|
||||
expect(pic4.key).toNot(beNil())
|
||||
expect(Data(libSessionVal: pic4.key, count: 32).toHexString())
|
||||
.to(equal("7177657274007975696f31323334353637383930313233343536373839303132"))
|
||||
expect(user_profile_get_nts_priority(conf)).to(equal(9))
|
||||
expect(user_profile_get_nts_priority(conf2)).to(equal(9))
|
||||
expect(user_profile_get_nts_expiry(conf)).to(equal(86400))
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1))
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
let fakeHash4: String = "fakehash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
let fakeHash5: String = "fakehash5"
|
||||
var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated()
|
||||
config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4)
|
||||
config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5)
|
||||
pushData5.deallocate()
|
||||
pushData6.deallocate()
|
||||
|
||||
var dump6: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump6Len: Int = 0
|
||||
config_dump(conf, &dump6, &dump6Len);
|
||||
var dump7: UnsafeMutablePointer<UInt8>? = nil
|
||||
var dump7Len: Int = 0
|
||||
config_dump(conf2, &dump7, &dump7Len);
|
||||
// (store in db)
|
||||
dump6?.deallocate()
|
||||
dump7?.deallocate()
|
||||
|
||||
expect(config_needs_dump(conf)).to(beFalse())
|
||||
expect(config_needs_dump(conf2)).to(beFalse())
|
||||
expect(config_needs_push(conf)).to(beFalse())
|
||||
expect(config_needs_push(conf2)).to(beFalse())
|
||||
|
||||
// Wouldn't do this in a normal session but doing it here to properly clean up
|
||||
// after the test
|
||||
conf?.deallocate()
|
||||
conf2?.deallocate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -12,13 +12,12 @@ import Quick
|
|||
import Nimble
|
||||
|
||||
class SessionUtilSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - SessionUtil
|
||||
describe("SessionUtil") {
|
||||
// MARK: - Parsing URLs
|
||||
|
||||
// MARK: -- when parsing a community url
|
||||
context("when parsing a community url") {
|
||||
// MARK: ---- handles the example urls correctly
|
||||
it("handles the example urls correctly") {
|
||||
let validUrls: [String] = [
|
||||
[
|
||||
|
@ -82,6 +81,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(processedPublicKeys).to(equal(expectedPublicKeys))
|
||||
}
|
||||
|
||||
// MARK: ---- handles the r prefix if present
|
||||
it("handles the r prefix if present") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -95,6 +95,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
|
||||
}
|
||||
|
||||
// MARK: ---- fails if no scheme is provided
|
||||
it("fails if no scheme is provided") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -108,6 +109,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if there is no room
|
||||
it("fails if there is no room") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -121,6 +123,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if there is no public key parameter
|
||||
it("fails if there is no public key parameter") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: "https://sessionopengroup.co/r/main"
|
||||
|
@ -131,6 +134,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if the public key parameter is not 64 characters
|
||||
it("fails if the public key parameter is not 64 characters") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -144,6 +148,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- fails if the public key parameter is not a hex string
|
||||
it("fails if the public key parameter is not a hex string") {
|
||||
let info = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -157,6 +162,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(info?.publicKey).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the same TLS
|
||||
it("maintains the same TLS") {
|
||||
let server1 = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -175,6 +181,7 @@ class SessionUtilSpec: QuickSpec {
|
|||
expect(server2).to(equal("https://sessionopengroup.co"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the same port
|
||||
it("maintains the same port") {
|
||||
let server1 = SessionUtil.parseCommunity(
|
||||
url: [
|
||||
|
@ -194,14 +201,15 @@ class SessionUtilSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Generating URLs
|
||||
|
||||
// MARK: -- when generating a url
|
||||
context("when generating a url") {
|
||||
// MARK: ---- generates the url correctly
|
||||
it("generates the url correctly") {
|
||||
expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
.to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the casing provided
|
||||
it("maintains the casing provided") {
|
||||
expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
.to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||
|
|
|
@ -9,16 +9,15 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
// MARK: - String
|
||||
|
||||
override class func spec() {
|
||||
// MARK: - a String
|
||||
describe("a String") {
|
||||
// MARK: -- can convert to a cArray
|
||||
it("can convert to a cArray") {
|
||||
expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51]))
|
||||
}
|
||||
|
||||
// MARK: -- can contain emoji
|
||||
it("can contain emoji") {
|
||||
let original: String = "Hi 👋"
|
||||
let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession()
|
||||
|
@ -27,7 +26,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(original))
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a pointer and length
|
||||
context("when initialised with a pointer and length") {
|
||||
// MARK: ---- returns null when given a null pointer
|
||||
it("returns null when given a null pointer") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -37,6 +38,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns a truncated string when given an incorrect length
|
||||
it("returns a truncated string when given an incorrect length") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -46,6 +48,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns a string when valid
|
||||
it("returns a string when valid") {
|
||||
let test: [CChar] = [84, 101, 115, 116]
|
||||
let result = test.withUnsafeBufferPointer { ptr in
|
||||
|
@ -56,7 +59,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a libSession value
|
||||
context("when initialised with a libSession value") {
|
||||
// MARK: ---- returns a string when valid and has no fixed length
|
||||
it("returns a string when valid and has no fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -64,6 +69,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Test"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns a string when valid and has a fixed length
|
||||
it("returns a string when valid and has a fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||
let result = String(libSessionVal: value, fixedLength: 5)
|
||||
|
@ -71,6 +77,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te\0st"))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates at the first null termination character when fixed length is none
|
||||
it("truncates at the first null termination character when fixed length is none") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -78,6 +85,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- parses successfully if there is no null termination character and there is no fixed length
|
||||
it("parses successfully if there is no null termination character and there is no fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84)
|
||||
let result = String(libSessionVal: value, fixedLength: .none)
|
||||
|
@ -85,6 +93,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("TestT"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty string when given a value only containing null termination characters with a fixed length
|
||||
it("returns an empty string when given a value only containing null termination characters with a fixed length") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, fixedLength: 5)
|
||||
|
@ -92,6 +101,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(""))
|
||||
}
|
||||
|
||||
// MARK: ---- defaults the fixed length value to none
|
||||
it("defaults the fixed length value to none") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0)
|
||||
let result = String(libSessionVal: value)
|
||||
|
@ -99,6 +109,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal("Te"))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty string when null and not set to return null
|
||||
it("returns an empty string when null and not set to return null") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, nullIfEmpty: false)
|
||||
|
@ -106,6 +117,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(""))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when specified and empty
|
||||
it("returns null when specified and empty") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value, nullIfEmpty: true)
|
||||
|
@ -113,6 +125,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- defaults the null if empty flag to false
|
||||
it("defaults the null if empty flag to false") {
|
||||
let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||
let result = String(libSessionVal: value)
|
||||
|
@ -121,7 +134,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when converting to a libSession value
|
||||
context("when converting to a libSession value") {
|
||||
// MARK: ---- succeeeds with a valid value
|
||||
it("succeeeds with a valid value") {
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession()
|
||||
expect(result.0).to(equal(84))
|
||||
|
@ -131,6 +146,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates when too long
|
||||
it("truncates when too long") {
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession()
|
||||
expect(result.0).to(equal(84))
|
||||
|
@ -140,7 +156,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(84))
|
||||
}
|
||||
|
||||
// MARK: ---- when optional
|
||||
context("when optional") {
|
||||
// MARK: ------ returns empty when null
|
||||
context("returns empty when null") {
|
||||
let value: String? = nil
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||
|
@ -152,6 +170,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ------ returns a libSession value when not null
|
||||
context("returns a libSession value when not null") {
|
||||
let value: String? = "Test"
|
||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||
|
@ -167,13 +186,15 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
describe("Data") {
|
||||
// MARK: -- can convert to a cArray
|
||||
it("can convert to a cArray") {
|
||||
expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: -- when initialised with a libSession value
|
||||
context("when initialised with a libSession value") {
|
||||
// MARK: ---- returns truncated data when given the wrong length
|
||||
it("returns truncated data when given the wrong length") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||
let result = Data(libSessionVal: value, count: 2)
|
||||
|
@ -181,6 +202,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([1, 2])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns data when valid
|
||||
it("returns data when valid") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||
let result = Data(libSessionVal: value, count: 5)
|
||||
|
@ -188,6 +210,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([1, 2, 3, 4, 5])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns data when all bytes are zero and nullIfEmpty is false
|
||||
it("returns data when all bytes are zero and nullIfEmpty is false") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false)
|
||||
|
@ -195,6 +218,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(Data([0, 0, 0, 0, 0])))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when all bytes are zero and nullIfEmpty is true
|
||||
it("returns null when all bytes are zero and nullIfEmpty is true") {
|
||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true)
|
||||
|
@ -203,7 +227,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when converting to a libSession value
|
||||
context("when converting to a libSession value") {
|
||||
// MARK: ---- succeeeds with a valid value
|
||||
it("succeeeds with a valid value") {
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession()
|
||||
expect(result.0).to(equal(1))
|
||||
|
@ -213,6 +239,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(5))
|
||||
}
|
||||
|
||||
// MARK: ---- truncates when too long
|
||||
it("truncates when too long") {
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession()
|
||||
expect(result.0).to(equal(1))
|
||||
|
@ -222,6 +249,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(1))
|
||||
}
|
||||
|
||||
// MARK: ---- fills with empty data when too short
|
||||
context("fills with empty data when too short") {
|
||||
let value: Data? = Data([1, 2, 3])
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -233,7 +261,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ---- when optional
|
||||
context("when optional") {
|
||||
// MARK: ------ returns null when null
|
||||
context("returns null when null") {
|
||||
let value: Data? = nil
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -245,6 +275,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result.4).to(equal(0))
|
||||
}
|
||||
|
||||
// MARK: ------ returns a libSession value when not null
|
||||
context("returns a libSession value when not null") {
|
||||
let value: Data? = Data([1, 2, 3, 4, 5])
|
||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||
|
@ -259,10 +290,11 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Array
|
||||
|
||||
// MARK: - an Array
|
||||
describe("an Array") {
|
||||
// MARK: -- when initialised with a 2D C array
|
||||
context("when initialised with a 2D C array") {
|
||||
// MARK: ---- returns the correct array
|
||||
it("returns the correct array") {
|
||||
var test: [CChar] = (
|
||||
"Test1".cArray.nullTerminated() +
|
||||
|
@ -278,6 +310,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"]))
|
||||
}
|
||||
|
||||
// MARK: ---- returns an empty array if given one
|
||||
it("returns an empty array if given one") {
|
||||
var test = [CChar]()
|
||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||
|
@ -289,6 +322,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal([]))
|
||||
}
|
||||
|
||||
// MARK: ---- handles empty strings without issues
|
||||
it("handles empty strings without issues") {
|
||||
var test: [CChar] = (
|
||||
"Test1".cArray.nullTerminated() +
|
||||
|
@ -304,10 +338,12 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(equal(["Test1", "", "Test2"]))
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when given a null pointer
|
||||
it("returns null when given a null pointer") {
|
||||
expect([String](pointer: nil, count: 5)).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns null when given a null count
|
||||
it("returns null when given a null count") {
|
||||
var test: [CChar] = "Test1".cArray.nullTerminated()
|
||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||
|
@ -319,19 +355,23 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
|||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- returns the default value if given null values
|
||||
it("returns the default value if given null values") {
|
||||
expect([String](pointer: nil, count: 5, defaultValue: ["Test"]))
|
||||
.to(equal(["Test"]))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- when adding a null terminated character
|
||||
context("when adding a null terminated character") {
|
||||
// MARK: ---- adds a null termination character when not present
|
||||
it("adds a null termination character when not present") {
|
||||
let value: [CChar] = [1, 2, 3, 4, 5]
|
||||
|
||||
expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0]))
|
||||
}
|
||||
|
||||
// MARK: ---- adds nothing when already present
|
||||
it("adds nothing when already present") {
|
||||
let value: [CChar] = [1, 2, 3, 4, 0]
|
||||
|
||||
|
|
|
@ -9,22 +9,19 @@ import Quick
|
|||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
import AVFoundation
|
||||
|
||||
class BatchRequestInfoSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class CapabilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - Capabilities
|
||||
describe("Capabilities") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- assigns values correctly
|
||||
it("assigns values correctly") {
|
||||
let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(
|
||||
capabilities: [.sogs],
|
||||
|
@ -34,8 +35,11 @@ class CapabilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - a Capability
|
||||
describe("a Capability") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- succeeeds with a valid case
|
||||
it("succeeeds with a valid case") {
|
||||
let capability: Capability.Variant = Capability.Variant(
|
||||
from: "sogs"
|
||||
|
@ -44,6 +48,7 @@ class CapabilitiesSpec: QuickSpec {
|
|||
expect(capability).to(equal(.sogs))
|
||||
}
|
||||
|
||||
// MARK: ---- wraps an unknown value in the unsupported case
|
||||
it("wraps an unknown value in the unsupported case") {
|
||||
let capability: Capability.Variant = Capability.Variant(
|
||||
from: "test"
|
||||
|
@ -53,18 +58,23 @@ class CapabilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when accessing the rawValue
|
||||
context("when accessing the rawValue") {
|
||||
// MARK: ---- provides known cases exactly
|
||||
it("provides known cases exactly") {
|
||||
expect(Capability.Variant.sogs.rawValue).to(equal("sogs"))
|
||||
expect(Capability.Variant.blind.rawValue).to(equal("blind"))
|
||||
}
|
||||
|
||||
// MARK: ---- provides the wrapped value for unsupported cases
|
||||
it("provides the wrapped value for unsupported cases") {
|
||||
expect(Capability.Variant.unsupported("test").rawValue).to(equal("test"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- when Decoding
|
||||
context("when Decoding") {
|
||||
// MARK: ---- decodes known cases exactly
|
||||
it("decodes known cases exactly") {
|
||||
expect(
|
||||
try? JSONDecoder().decode(
|
||||
|
@ -82,6 +92,7 @@ class CapabilitiesSpec: QuickSpec {
|
|||
.to(equal(.blind))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes unknown cases into the unsupported case
|
||||
it("decodes unknown cases into the unsupported case") {
|
||||
expect(
|
||||
try? JSONDecoder().decode(
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class OpenGroupSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - an Open Group
|
||||
describe("an Open Group") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- generates the id
|
||||
it("generates the id") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -34,7 +35,9 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when describing
|
||||
context("when describing") {
|
||||
// MARK: ---- includes relevant information
|
||||
it("includes relevant information") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -57,7 +60,9 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when describing in debug
|
||||
context("when describing in debug") {
|
||||
// MARK: ---- includes relevant information
|
||||
it("includes relevant information") {
|
||||
let openGroup: OpenGroup = OpenGroup(
|
||||
server: "server",
|
||||
|
@ -80,15 +85,19 @@ class OpenGroupSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when generating an id
|
||||
context("when generating an id") {
|
||||
// MARK: ---- generates correctly
|
||||
it("generates correctly") {
|
||||
expect(OpenGroup.idFor(roomToken: "room", server: "server")).to(equal("server.room"))
|
||||
}
|
||||
|
||||
// MARK: ---- converts the server to lowercase
|
||||
it("converts the server to lowercase") {
|
||||
expect(OpenGroup.idFor(roomToken: "room", server: "SeRVeR")).to(equal("server.room"))
|
||||
}
|
||||
|
||||
// MARK: ---- maintains the casing of the roomToken
|
||||
it("maintains the casing of the roomToken") {
|
||||
expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM"))
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class RoomPollInfoSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a RoomPollInfo
|
||||
describe("a RoomPollInfo") {
|
||||
// MARK: -- when initializing with a room
|
||||
context("when initializing with a room") {
|
||||
// MARK: ---- copies all the relevant values across
|
||||
it("copies all the relevant values across") {
|
||||
let room: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
||||
token: "testToken",
|
||||
|
@ -60,7 +61,9 @@ class RoomPollInfoSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- defaults admin and moderator values to false if omitted
|
||||
it("defaults admin and moderator values to false if omitted") {
|
||||
let roomPollInfoJson: String = """
|
||||
{
|
||||
|
@ -87,6 +90,7 @@ class RoomPollInfoSpec: QuickSpec {
|
|||
expect(result.globalModerator).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- sets the admin and moderator values when provided
|
||||
it("sets the admin and moderator values when provided") {
|
||||
let roomPollInfoJson: String = """
|
||||
{
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class RoomSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Room
|
||||
describe("a Room") {
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: ---- defaults admin and moderator values to false if omitted
|
||||
it("defaults admin and moderator values to false if omitted") {
|
||||
let roomJson: String = """
|
||||
{
|
||||
|
@ -52,6 +53,7 @@ class RoomSpec: QuickSpec {
|
|||
expect(result.globalModerator).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: ---- sets the admin and moderator values when provided
|
||||
it("sets the admin and moderator values when provided") {
|
||||
let roomJson: String = """
|
||||
{
|
||||
|
|
|
@ -9,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())) }
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SendDirectMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SendDirectMessageRequest
|
||||
describe("a SendDirectMessageRequest") {
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest(
|
||||
message: "TestData".data(using: .utf8)!
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SendMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SendMessageRequest
|
||||
describe("a SendMessageRequest") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- defaults the optional values to nil
|
||||
it("defaults the optional values to nil") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -25,7 +26,9 @@ class SendMessageRequestSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -41,6 +44,7 @@ class SendMessageRequestSpec: QuickSpec {
|
|||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||
}
|
||||
|
||||
// MARK: ---- encodes the signature as a base64 string
|
||||
it("encodes the signature as a base64 string") {
|
||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class UpdateMessageRequestSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("a UpdateMessageRequest") {
|
||||
override class func spec() {
|
||||
// MARK: - an UpdateMessageRequest
|
||||
describe("an UpdateMessageRequest") {
|
||||
// MARK: -- when encoding
|
||||
context("when encoding") {
|
||||
// MARK: ---- encodes the data as a base64 string
|
||||
it("encodes the data as a base64 string") {
|
||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
@ -26,6 +27,7 @@ class UpdateMessageRequestSpec: QuickSpec {
|
|||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||
}
|
||||
|
||||
// MARK: ---- encodes the signature as a base64 string
|
||||
it("encodes the signature as a base64 string") {
|
||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||
data: "TestData".data(using: .utf8)!,
|
||||
|
|
|
@ -13,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])?
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,16 +8,18 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class NonceGeneratorSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a NonceGenerator16Byte
|
||||
describe("a NonceGenerator16Byte") {
|
||||
// MARK: -- has the correct number of bytes
|
||||
it("has the correct number of bytes") {
|
||||
expect(OpenGroupAPI.NonceGenerator16Byte().NonceBytes).to(equal(16))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - a NonceGenerator24Byte
|
||||
describe("a NonceGenerator24Byte") {
|
||||
// MARK: -- has the correct number of bytes
|
||||
it("has the correct number of bytes") {
|
||||
expect(OpenGroupAPI.NonceGenerator24Byte().NonceBytes).to(equal(24))
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class PersonalizationSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Personalization
|
||||
describe("a Personalization") {
|
||||
// MARK: -- generates bytes correctly
|
||||
it("generates bytes correctly") {
|
||||
expect(OpenGroupAPI.Personalization.sharedKeys.bytes)
|
||||
.to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115]))
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SOGSEndpointSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SOGSEndpoint
|
||||
describe("a SOGSEndpoint") {
|
||||
// MARK: -- generates the path value correctly
|
||||
it("generates the path value correctly") {
|
||||
// Utility
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class SOGSErrorSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SOGSError
|
||||
describe("a SOGSError") {
|
||||
// MARK: -- generates the error description correctly
|
||||
it("generates the error description correctly") {
|
||||
expect(OpenGroupAPIError.decryptionFailed.errorDescription)
|
||||
.to(equal("Couldn't decrypt response."))
|
||||
|
|
|
@ -11,45 +11,35 @@ import Nimble
|
|||
@testable import SessionMessagingKit
|
||||
|
||||
class MessageReceiverDecryptionSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override 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())) }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -11,99 +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,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
).insert(db)
|
||||
|
||||
try Profile(
|
||||
id: "TestId",
|
||||
name: "TestUser",
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 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 })?
|
||||
|
@ -114,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 })?
|
||||
|
@ -129,6 +96,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
.toNot(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- unmutes a conversation
|
||||
it("unmutes a conversation") {
|
||||
mockStorage.write { db in
|
||||
try SessionThread
|
||||
|
@ -160,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
|
||||
|
@ -189,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))
|
||||
|
@ -208,6 +179,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ---- has no mute button
|
||||
it("has no mute button") {
|
||||
expect(
|
||||
viewModel.tableData
|
||||
|
@ -217,6 +189,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ---- when entering edit mode
|
||||
context("when entering edit mode") {
|
||||
beforeEach {
|
||||
viewModel.navState.sinkAndStore(in: &disposables)
|
||||
|
@ -224,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))
|
||||
|
@ -246,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))
|
||||
|
@ -266,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
|
||||
|
@ -278,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))
|
||||
|
@ -298,6 +277,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- updates the nickname for the current user
|
||||
it("updates the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -312,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
|
||||
|
@ -324,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))
|
||||
|
@ -343,6 +326,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: ---- when entering edit mode
|
||||
context("when entering edit mode") {
|
||||
beforeEach {
|
||||
viewModel.navState.sinkAndStore(in: &disposables)
|
||||
|
@ -350,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))
|
||||
|
@ -372,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))
|
||||
|
@ -392,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
|
||||
|
@ -402,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))
|
||||
|
@ -422,6 +412,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
]))
|
||||
}
|
||||
|
||||
// MARK: -------- updates the nickname for the current user
|
||||
it("updates the nickname for the current user") {
|
||||
expect(
|
||||
mockStorage
|
||||
|
@ -434,6 +425,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a group conversation
|
||||
context("with a group conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -463,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))
|
||||
|
@ -476,6 +470,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a community conversation
|
||||
context("with a community conversation") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
|
@ -505,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))
|
||||
|
@ -520,3 +517,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -82,11 +82,11 @@ internal enum Theme_ClassicDark: ThemeColors {
|
|||
.alert_buttonBackground: .classicDark1,
|
||||
|
||||
// ConversationButton
|
||||
.conversationButton_background: .classicDark1,
|
||||
.conversationButton_unreadBackground: .classicDark2,
|
||||
.conversationButton_background: .classicDark0,
|
||||
.conversationButton_unreadBackground: .classicDark1,
|
||||
.conversationButton_unreadStripBackground: .primary,
|
||||
.conversationButton_unreadBubbleBackground: .classicDark3,
|
||||
.conversationButton_unreadBubbleText: .classicDark6,
|
||||
.conversationButton_unreadBubbleBackground: .primary,
|
||||
.conversationButton_unreadBubbleText: .classicDark0,
|
||||
.conversationButton_swipeDestructive: .dangerDark,
|
||||
.conversationButton_swipeSecondary: .classicDark2,
|
||||
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color,
|
||||
|
|
|
@ -85,7 +85,7 @@ internal enum Theme_ClassicLight: ThemeColors {
|
|||
.conversationButton_background: .classicLight6,
|
||||
.conversationButton_unreadBackground: .classicLight6,
|
||||
.conversationButton_unreadStripBackground: .primary,
|
||||
.conversationButton_unreadBubbleBackground: .classicLight3,
|
||||
.conversationButton_unreadBubbleBackground: .primary,
|
||||
.conversationButton_unreadBubbleText: .classicLight0,
|
||||
.conversationButton_swipeDestructive: .dangerLight,
|
||||
.conversationButton_swipeSecondary: .classicLight1,
|
||||
|
|
|
@ -82,8 +82,8 @@ internal enum Theme_OceanDark: ThemeColors {
|
|||
.alert_buttonBackground: .oceanDark3,
|
||||
|
||||
// ConversationButton
|
||||
.conversationButton_background: .oceanDark3,
|
||||
.conversationButton_unreadBackground: .oceanDark4,
|
||||
.conversationButton_background: .oceanDark2,
|
||||
.conversationButton_unreadBackground: .oceanDark3,
|
||||
.conversationButton_unreadStripBackground: .primary,
|
||||
.conversationButton_unreadBubbleBackground: .primary,
|
||||
.conversationButton_unreadBubbleText: .oceanDark0,
|
||||
|
|
|
@ -16,6 +16,10 @@ public class TypedTableDefinition<T> where T: TableRecord, T: ColumnExpressible
|
|||
return definition.column(key.name, type)
|
||||
}
|
||||
|
||||
@discardableResult public func deprecatedColumn(name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition {
|
||||
return definition.column(name, type)
|
||||
}
|
||||
|
||||
public func primaryKey(_ columns: [T.Columns], onConflict: Database.ConflictResolution? = nil) {
|
||||
definition.primaryKey(columns.map { $0.name }, onConflict: onConflict)
|
||||
}
|
||||
|
|
|
@ -81,11 +81,14 @@ extension Atomic where Value: CustomDebugStringConvertible {
|
|||
// MARK: - ReadWriteLock
|
||||
|
||||
private class ReadWriteLock {
|
||||
private var rwlock: pthread_rwlock_t = {
|
||||
var rwlock = pthread_rwlock_t()
|
||||
private var rwlock: pthread_rwlock_t
|
||||
|
||||
// Need to do this in a proper init function instead of a lazy variable or it can indefinitely
|
||||
// hang on XCode 15 when trying to retrieve a lock (potentially due to optimisations?)
|
||||
init() {
|
||||
rwlock = pthread_rwlock_t()
|
||||
pthread_rwlock_init(&rwlock, nil)
|
||||
return rwlock
|
||||
}()
|
||||
}
|
||||
|
||||
func writeLock() {
|
||||
pthread_rwlock_wrlock(&rwlock)
|
||||
|
|
|
@ -4,6 +4,11 @@ import Foundation
|
|||
import Combine
|
||||
|
||||
public enum HTTP {
|
||||
private struct Certificates {
|
||||
let isValid: Bool
|
||||
let certificates: [SecCertificate]
|
||||
}
|
||||
|
||||
private static let seedNodeURLSession = URLSession(configuration: .ephemeral, delegate: seedNodeURLSessionDelegate, delegateQueue: nil)
|
||||
private static let seedNodeURLSessionDelegate = SeedNodeURLSessionDelegateImplementation()
|
||||
private static let snodeURLSession = URLSession(configuration: .ephemeral, delegate: snodeURLSessionDelegate, delegateQueue: nil)
|
||||
|
@ -14,22 +19,21 @@ public enum HTTP {
|
|||
/// **Note:** These certificates will need to be regenerated and replaced at the start of April 2025, iOS has a restriction after iOS 13
|
||||
/// where certificates can have a maximum lifetime of 825 days (https://support.apple.com/en-au/HT210176) as a result we
|
||||
/// can't use the 10 year certificates that the other platforms use
|
||||
private static let storageSeed1Cert: SecCertificate = {
|
||||
let path = Bundle.main.path(forResource: "seed1-2023-2y", ofType: "der")!
|
||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
||||
return SecCertificateCreateWithData(nil, data as CFData)!
|
||||
}()
|
||||
|
||||
private static let storageSeed2Cert: SecCertificate = {
|
||||
let path = Bundle.main.path(forResource: "seed2-2023-2y", ofType: "der")!
|
||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
||||
return SecCertificateCreateWithData(nil, data as CFData)!
|
||||
}()
|
||||
|
||||
private static let storageSeed3Cert: SecCertificate = {
|
||||
let path = Bundle.main.path(forResource: "seed3-2023-2y", ofType: "der")!
|
||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
||||
return SecCertificateCreateWithData(nil, data as CFData)!
|
||||
private static let storageSeedCertificates: Atomic<Certificates> = {
|
||||
let certFileNames: [String] = [
|
||||
"seed1-2023-2y",
|
||||
"seed2-2023-2y",
|
||||
"seed3-2023-2y"
|
||||
]
|
||||
let paths: [String] = certFileNames.compactMap { Bundle.main.path(forResource: $0, ofType: "der") }
|
||||
let certData: [Data] = paths.compactMap { try? Data(contentsOf: URL(fileURLWithPath: $0)) }
|
||||
let certificates: [SecCertificate] = certData.compactMap { SecCertificateCreateWithData(nil, $0 as CFData) }
|
||||
|
||||
guard certificates.count == certFileNames.count else {
|
||||
return Atomic(Certificates(isValid: false, certificates: []))
|
||||
}
|
||||
|
||||
return Atomic(Certificates(isValid: true, certificates: certificates))
|
||||
}()
|
||||
|
||||
// MARK: - Settings
|
||||
|
@ -41,12 +45,15 @@ public enum HTTP {
|
|||
private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate {
|
||||
|
||||
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
||||
guard HTTP.storageSeedCertificates.wrappedValue.isValid else {
|
||||
SNLog("Failed to set load seed node certificates.")
|
||||
return completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
guard let trust = challenge.protectionSpace.serverTrust else {
|
||||
return completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
// Mark the seed node certificates as trusted
|
||||
let certificates = [ storageSeed1Cert, storageSeed2Cert, storageSeed3Cert ]
|
||||
guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else {
|
||||
guard SecTrustSetAnchorCertificates(trust, HTTP.storageSeedCertificates.wrappedValue.certificates as CFArray) == errSecSuccess else {
|
||||
SNLog("Failed to set seed node certificates.")
|
||||
return completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,12 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class ArrayUtilitiesSpec: QuickSpec {
|
||||
private struct TestType: Equatable {
|
||||
let stringValue: String
|
||||
let intValue: Int
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - an Array
|
||||
describe("an Array") {
|
||||
// MARK: -- when grouping
|
||||
context("when grouping") {
|
||||
// MARK: ---- maintains the original array ordering
|
||||
it("maintains the original array ordering") {
|
||||
let data: [TestType] = [
|
||||
TestType(stringValue: "b", intValue: 5),
|
||||
|
@ -84,3 +80,10 @@ class ArrayUtilitiesSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Equatable {
|
||||
let stringValue: String
|
||||
let intValue: Int
|
||||
}
|
||||
|
|
|
@ -8,17 +8,16 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class DependenciesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override 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)
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class SessionIdSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a SessionId
|
||||
describe("a SessionId") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- with an idString
|
||||
context("with an idString") {
|
||||
// MARK: ------ succeeds when correct
|
||||
it("succeeds when correct") {
|
||||
let sessionId: SessionId? = SessionId(from: "05\(TestConstants.publicKey)")
|
||||
|
||||
|
@ -21,16 +23,20 @@ class SessionIdSpec: QuickSpec {
|
|||
expect(sessionId?.publicKey).to(equal(TestConstants.publicKey))
|
||||
}
|
||||
|
||||
// MARK: ------ fails when too short
|
||||
it("fails when too short") {
|
||||
expect(SessionId(from: "")).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with an invalid prefix
|
||||
it("fails with an invalid prefix") {
|
||||
expect(SessionId(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- with a prefix and publicKey
|
||||
context("with a prefix and publicKey") {
|
||||
// MARK: ------ converts the bytes into a hex string
|
||||
it("converts the bytes into a hex string") {
|
||||
let sessionId: SessionId? = SessionId(.standard, publicKey: [0, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
|
||||
|
@ -40,6 +46,7 @@ class SessionIdSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- generates the correct hex string
|
||||
it("generates the correct hex string") {
|
||||
expect(SessionId(.unblinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString)
|
||||
.to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||
|
@ -52,9 +59,13 @@ class SessionIdSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - a SessionId Prefix
|
||||
describe("a SessionId Prefix") {
|
||||
// MARK: -- when initializing
|
||||
context("when initializing") {
|
||||
// MARK: ---- with just a prefix
|
||||
context("with just a prefix") {
|
||||
// MARK: ------ succeeds when valid
|
||||
it("succeeds when valid") {
|
||||
expect(SessionId.Prefix(from: "00")).to(equal(.unblinded))
|
||||
expect(SessionId.Prefix(from: "05")).to(equal(.standard))
|
||||
|
@ -62,24 +73,30 @@ class SessionIdSpec: QuickSpec {
|
|||
expect(SessionId.Prefix(from: "25")).to(equal(.blinded25))
|
||||
}
|
||||
|
||||
// MARK: ------ fails when nil
|
||||
it("fails when nil") {
|
||||
expect(SessionId.Prefix(from: nil)).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails when invalid
|
||||
it("fails when invalid") {
|
||||
expect(SessionId.Prefix(from: "AB")).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- with a longer string
|
||||
context("with a longer string") {
|
||||
// MARK: ------ fails with invalid hex
|
||||
it("fails with invalid hex") {
|
||||
expect(SessionId.Prefix(from: "Hello!!!")).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with the wrong length
|
||||
it("fails with the wrong length") {
|
||||
expect(SessionId.Prefix(from: String(TestConstants.publicKey.prefix(10)))).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: ------ fails with an invalid prefix
|
||||
it("fails with an invalid prefix") {
|
||||
expect(SessionId.Prefix(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||
}
|
||||
|
|
|
@ -9,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -8,42 +8,12 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class BencodeSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
struct TestType2: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
}
|
||||
|
||||
struct TestType3: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
|
||||
init(_ stringValue: String, _ boolValue: Bool) {
|
||||
self.stringValue = stringValue
|
||||
self.boolValue = boolValue
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self = TestType3(
|
||||
try container.decode(String.self, forKey: .stringValue),
|
||||
((try? container.decode(Bool.self, forKey: .boolValue)) ?? false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - Bencode
|
||||
describe("Bencode") {
|
||||
// MARK: - when decoding
|
||||
// MARK: -- when decoding
|
||||
context("when decoding") {
|
||||
// MARK: -- should decode a basic string
|
||||
// MARK: ---- should decode a basic string
|
||||
it("should decode a basic string") {
|
||||
let basicStringData: Data = "5:howdy".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(String.self, from: basicStringData)
|
||||
|
@ -51,7 +21,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal("howdy"))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a basic integer
|
||||
// MARK: ---- should decode a basic integer
|
||||
it("should decode a basic integer") {
|
||||
let basicIntegerData: Data = "i3e".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(Int.self, from: basicIntegerData)
|
||||
|
@ -59,7 +29,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(3))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a list of integers
|
||||
// MARK: ---- should decode a list of integers
|
||||
it("should decode a list of integers") {
|
||||
let basicIntListData: Data = "li1ei2ee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([Int].self, from: basicIntListData)
|
||||
|
@ -67,7 +37,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal([1, 2]))
|
||||
}
|
||||
|
||||
// MARK: -- should decode a basic dict
|
||||
// MARK: ---- should decode a basic dict
|
||||
it("should decode a basic dict") {
|
||||
let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([String: [String]].self, from: basicDictData)
|
||||
|
@ -75,7 +45,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(["spam": ["a", "b"]]))
|
||||
}
|
||||
|
||||
// MARK: -- decodes a decodable type
|
||||
// MARK: ---- decodes a decodable type
|
||||
it("decodes a decodable type") {
|
||||
let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)!
|
||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
||||
|
@ -83,7 +53,7 @@ class BencodeSpec: QuickSpec {
|
|||
expect(result).to(equal(TestType(intValue: 100, stringValue: "Test")))
|
||||
}
|
||||
|
||||
// MARK: -- decodes a stringified decodable type
|
||||
// MARK: ---- decodes a stringified decodable type
|
||||
it("decodes a stringified decodable type") {
|
||||
let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)!
|
||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
||||
|
@ -92,11 +62,11 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when decoding a response
|
||||
// MARK: -- when decoding a response
|
||||
context("when decoding a response") {
|
||||
// MARK: -- with a decodable type
|
||||
// MARK: ---- with a decodable type
|
||||
context("with a decodable type") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -114,7 +84,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste"
|
||||
.data(using: .utf8)!
|
||||
|
@ -132,7 +102,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when given an invalid length
|
||||
// MARK: ------ throws a parsing error when given an invalid length
|
||||
it("throws a parsing error when given an invalid length") {
|
||||
let data: Data = "ld12:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -143,7 +113,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when given an invalid key
|
||||
// MARK: ------ throws a parsing error when given an invalid key
|
||||
it("throws a parsing error when given an invalid key") {
|
||||
let data: Data = "ld7:INVALIDi100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -154,7 +124,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes correctly when trying to decode an int to a bool with custom handling
|
||||
// MARK: ------ decodes correctly when trying to decode an int to a bool with custom handling
|
||||
it("decodes correctly when trying to decode an int to a bool with custom handling") {
|
||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||
.data(using: .utf8)!
|
||||
|
@ -165,7 +135,7 @@ class BencodeSpec: QuickSpec {
|
|||
}.toNot(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when trying to decode an int to a bool
|
||||
// MARK: ------ throws a parsing error when trying to decode an int to a bool
|
||||
it("throws a parsing error when trying to decode an int to a bool") {
|
||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||
.data(using: .utf8)!
|
||||
|
@ -177,9 +147,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with stringified json info
|
||||
// MARK: ---- with stringified json info
|
||||
context("with stringified json info") {
|
||||
// MARK: -- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -197,7 +167,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -215,7 +185,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: -- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
@ -227,9 +197,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with a string value
|
||||
// MARK: ---- with a string value
|
||||
context("with a string value") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "l4:Test5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)!
|
||||
let result: BencodeResponse<String>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -243,7 +213,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
||||
let result: BencodeResponse<String>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -257,7 +227,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l10:Teste".data(using: .utf8)!
|
||||
|
||||
|
@ -268,9 +238,9 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- with an int value
|
||||
// MARK: ---- with an int value
|
||||
context("with an int value") {
|
||||
// MARK: ---- decodes successfully
|
||||
// MARK: ------ decodes successfully
|
||||
it("decodes successfully") {
|
||||
let data: Data = "li100e5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)!
|
||||
let result: BencodeResponse<Int>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -284,7 +254,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- decodes successfully with no body
|
||||
// MARK: ------ decodes successfully with no body
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "li100ee".data(using: .utf8)!
|
||||
let result: BencodeResponse<Int>? = try? Bencode.decodeResponse(from: data)
|
||||
|
@ -298,7 +268,7 @@ class BencodeSpec: QuickSpec {
|
|||
))
|
||||
}
|
||||
|
||||
// MARK: ---- throws a parsing error when invalid
|
||||
// MARK: ------ throws a parsing error when invalid
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
||||
|
||||
|
@ -312,3 +282,34 @@ class BencodeSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
|
||||
fileprivate struct TestType: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
fileprivate struct TestType2: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
}
|
||||
|
||||
fileprivate struct TestType3: Codable, Equatable {
|
||||
let stringValue: String
|
||||
let boolValue: Bool
|
||||
|
||||
init(_ stringValue: String, _ boolValue: Bool) {
|
||||
self.stringValue = stringValue
|
||||
self.boolValue = boolValue
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self = TestType3(
|
||||
try container.decode(String.self, forKey: .stringValue),
|
||||
((try? container.decode(Bool.self, forKey: .boolValue)) ?? false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class VersionSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
override class func spec() {
|
||||
// MARK: - a Version
|
||||
describe("a Version") {
|
||||
// MARK: -- can be created from a string
|
||||
it("can be created from a string") {
|
||||
let version: Version = Version.from("1.20.3")
|
||||
|
||||
|
@ -20,13 +20,16 @@ class VersionSpec: QuickSpec {
|
|||
expect(version.patch).to(equal(3))
|
||||
}
|
||||
|
||||
// MARK: -- correctly exposes a string value
|
||||
it("correctly exposes a string value") {
|
||||
let version: Version = Version(major: 1, minor: 20, patch: 3)
|
||||
|
||||
expect(version.stringValue).to(equal("1.20.3"))
|
||||
}
|
||||
|
||||
// MARK: -- when checking equality
|
||||
context("when checking equality") {
|
||||
// MARK: ---- returns true if the values match
|
||||
it("returns true if the values match") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.0")
|
||||
|
@ -35,6 +38,7 @@ class VersionSpec: QuickSpec {
|
|||
.to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns false if the values do not match
|
||||
it("returns false if the values do not match") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.1")
|
||||
|
@ -44,7 +48,9 @@ class VersionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- when comparing versions
|
||||
context("when comparing versions") {
|
||||
// MARK: ---- returns correctly for a simple major difference
|
||||
it("returns correctly for a simple major difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("2.0.0")
|
||||
|
@ -53,6 +59,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex major difference
|
||||
it("returns correctly for a complex major difference") {
|
||||
let version1a: Version = Version.from("2.90.90")
|
||||
let version2a: Version = Version.from("10.0.0")
|
||||
|
@ -65,6 +72,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2b > version1b).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a simple minor difference
|
||||
it("returns correctly for a simple minor difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.1.0")
|
||||
|
@ -73,6 +81,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex minor difference
|
||||
it("returns correctly for a complex minor difference") {
|
||||
let version1a: Version = Version.from("90.2.90")
|
||||
let version2a: Version = Version.from("90.10.0")
|
||||
|
@ -85,6 +94,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2b > version1b).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a simple patch difference
|
||||
it("returns correctly for a simple patch difference") {
|
||||
let version1: Version = Version.from("1.0.0")
|
||||
let version2: Version = Version.from("1.0.1")
|
||||
|
@ -93,6 +103,7 @@ class VersionSpec: QuickSpec {
|
|||
expect(version2 > version1).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- returns correctly for a complex patch difference
|
||||
it("returns correctly for a complex patch difference") {
|
||||
let version1a: Version = Version.from("90.90.2")
|
||||
let version2a: Version = Version.from("90.90.10")
|
||||
|
|
|
@ -17,9 +17,13 @@ public class Mock<T> {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
internal required init(functionHandler: MockFunctionHandler? = nil) {
|
||||
internal required init(
|
||||
functionHandler: MockFunctionHandler? = nil,
|
||||
initialSetup: ((Mock<T>) -> ())? = nil
|
||||
) {
|
||||
self.functionConsumer = FunctionConsumer()
|
||||
self.functionHandler = (functionHandler ?? self.functionConsumer)
|
||||
initialSetup?(self)
|
||||
}
|
||||
|
||||
// MARK: - MockFunctionHandler
|
||||
|
@ -103,7 +107,7 @@ internal class MockFunction {
|
|||
|
||||
internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
||||
private let callBlock: (inout T) throws -> R
|
||||
private let mockInit: (MockFunctionHandler?) -> Mock<T>
|
||||
private let mockInit: (MockFunctionHandler?, ((Mock<T>) -> ())?) -> Mock<T>
|
||||
private var functionName: String?
|
||||
private var parameterCount: Int?
|
||||
private var parameterSummary: String?
|
||||
|
@ -113,7 +117,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?) -> Mock<T>) {
|
||||
init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?, ((Mock<T>) -> ())?) -> Mock<T>) {
|
||||
self.callBlock = callBlock
|
||||
self.mockInit = mockInit
|
||||
}
|
||||
|
@ -141,7 +145,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
|||
// MARK: - Build
|
||||
|
||||
func build() throws -> MockFunction {
|
||||
var completionMock = mockInit(self) as! T
|
||||
var completionMock = mockInit(self, nil) as! T
|
||||
_ = try? callBlock(&completionMock)
|
||||
|
||||
guard let name: String = functionName, let parameterCount: Int = parameterCount, let parameterSummary: String = parameterSummary else {
|
||||
|
|
|
@ -17,6 +17,11 @@ class MockCaches: CachesType {
|
|||
set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } }
|
||||
}
|
||||
|
||||
public func setting<M, I>(cache: CacheInfo.Config<M, I>, to value: M?) -> MockCaches {
|
||||
self[cache] = value
|
||||
return self
|
||||
}
|
||||
|
||||
// MARK: - Mutable Access
|
||||
|
||||
@discardableResult public func mutate<M, I, R>(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Mocked
|
||||
|
@ -32,6 +33,20 @@ func any() -> Double { 0 }
|
|||
func any() -> String { "" }
|
||||
func any() -> Data { Data() }
|
||||
func any() -> Bool { false }
|
||||
func any() -> Dependencies {
|
||||
Dependencies(
|
||||
storage: SynchronousStorage(customWriter: try! DatabaseQueue()),
|
||||
network: MockNetwork(),
|
||||
crypto: MockCrypto(),
|
||||
standardUserDefaults: MockUserDefaults(),
|
||||
caches: MockCaches(),
|
||||
jobRunner: MockJobRunner(),
|
||||
scheduler: .immediate,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890),
|
||||
fixedTime: 0,
|
||||
forceSynchronous: true
|
||||
)
|
||||
}
|
||||
|
||||
func anyAny() -> Any { 0 } // Unique name for compilation performance reasons
|
||||
func anyArray<R>() -> [R] { [] } // Unique name for compilation performance reasons
|
||||
|
|
|
@ -29,7 +29,7 @@ public func call<M, T, R>(
|
|||
matchingParameters: Bool = false,
|
||||
exclusive: Bool = false,
|
||||
functionBlock: @escaping (inout T) throws -> R
|
||||
) -> Predicate<M> where M: Mock<T> {
|
||||
) -> Nimble.Predicate<M> where M: Mock<T> {
|
||||
return Predicate.define { actualExpression in
|
||||
let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock)
|
||||
let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls
|
||||
|
|
|
@ -6,6 +6,16 @@ import GRDB
|
|||
@testable import SessionUtilitiesKit
|
||||
|
||||
class SynchronousStorage: Storage {
|
||||
public init(
|
||||
customWriter: DatabaseWriter? = nil,
|
||||
customMigrationTargets: [MigratableTarget.Type]? = nil,
|
||||
initialData: ((Database) throws -> ())? = nil
|
||||
) {
|
||||
super.init(customWriter: customWriter, customMigrationTargets: customMigrationTargets)
|
||||
|
||||
write { db in try initialData?(db) }
|
||||
}
|
||||
|
||||
@discardableResult override func write<T>(
|
||||
fileName: String = #file,
|
||||
functionName: String = #function,
|
||||
|
|
Loading…
Reference in New Issue