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
|
- DifferenceKit/Core
|
||||||
- GRDB.swift/SQLCipher (6.13.0):
|
- GRDB.swift/SQLCipher (6.13.0):
|
||||||
- SQLCipher (>= 3.4.2)
|
- SQLCipher (>= 3.4.2)
|
||||||
- libwebp (1.2.1):
|
- libwebp (1.3.2):
|
||||||
- libwebp/demux (= 1.2.1)
|
- libwebp/demux (= 1.3.2)
|
||||||
- libwebp/mux (= 1.2.1)
|
- libwebp/mux (= 1.3.2)
|
||||||
- libwebp/webp (= 1.2.1)
|
- libwebp/sharpyuv (= 1.3.2)
|
||||||
- libwebp/demux (1.2.1):
|
- libwebp/webp (= 1.3.2)
|
||||||
|
- libwebp/demux (1.3.2):
|
||||||
- libwebp/webp
|
- libwebp/webp
|
||||||
- libwebp/mux (1.2.1):
|
- libwebp/mux (1.3.2):
|
||||||
- libwebp/demux
|
- libwebp/demux
|
||||||
- libwebp/webp (1.2.1)
|
- libwebp/sharpyuv (1.3.2)
|
||||||
- Nimble (10.0.0)
|
- libwebp/webp (1.3.2):
|
||||||
|
- libwebp/sharpyuv
|
||||||
|
- Nimble (12.3.0)
|
||||||
- NVActivityIndicatorView (5.1.1):
|
- NVActivityIndicatorView (5.1.1):
|
||||||
- NVActivityIndicatorView/Base (= 5.1.1)
|
- NVActivityIndicatorView/Base (= 5.1.1)
|
||||||
- NVActivityIndicatorView/Base (5.1.1)
|
- NVActivityIndicatorView/Base (5.1.1)
|
||||||
- OpenSSL-Universal (1.1.1300)
|
- OpenSSL-Universal (1.1.1300)
|
||||||
- PureLayout (3.1.9)
|
- PureLayout (3.1.9)
|
||||||
- Quick (5.0.1)
|
- Quick (7.3.0)
|
||||||
- Reachability (3.2)
|
- Reachability (3.2)
|
||||||
- SAMKeychain (1.5.3)
|
- SAMKeychain (1.5.3)
|
||||||
- SignalCoreKit (1.0.0):
|
- SignalCoreKit (1.0.0):
|
||||||
|
@ -134,18 +137,18 @@ SPEC REPOS:
|
||||||
- CocoaLumberjack
|
- CocoaLumberjack
|
||||||
- DifferenceKit
|
- DifferenceKit
|
||||||
- GRDB.swift
|
- GRDB.swift
|
||||||
- libwebp
|
|
||||||
- Nimble
|
|
||||||
- NVActivityIndicatorView
|
- NVActivityIndicatorView
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
- PureLayout
|
- PureLayout
|
||||||
- Quick
|
|
||||||
- Reachability
|
- Reachability
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- SQLCipher
|
- SQLCipher
|
||||||
- SwiftProtobuf
|
- SwiftProtobuf
|
||||||
- WebRTC-lib
|
- WebRTC-lib
|
||||||
trunk:
|
trunk:
|
||||||
|
- libwebp
|
||||||
|
- Nimble
|
||||||
|
- Quick
|
||||||
- xcbeautify
|
- xcbeautify
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
@ -186,12 +189,12 @@ SPEC CHECKSUMS:
|
||||||
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
|
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
|
||||||
DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca
|
DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca
|
||||||
GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78
|
GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78
|
||||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||||
Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84
|
Nimble: f8a8219d16f176429b951e8f7e72df5c23ceddc0
|
||||||
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
|
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
|
||||||
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
|
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
|
||||||
PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88
|
PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88
|
||||||
Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179
|
Quick: d32871931c05547cb4e0bc9009d66a18b50d8558
|
||||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d
|
SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d
|
||||||
|
|
|
@ -263,26 +263,43 @@ extension EmojiGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func writeStringConversionsFile(from emojiModel: EmojiModel) {
|
indirect enum Structure {
|
||||||
// Inline helpers:
|
enum ChunkType {
|
||||||
var firstItem = true
|
case firstScalar
|
||||||
func conditionalCheckForEmojiItem(_ item: EmojiModel.EmojiDefinition.Emoji) -> String {
|
case scalarSum
|
||||||
let isFirst = (firstItem == true)
|
|
||||||
firstItem = false
|
|
||||||
|
|
||||||
let prefix = isFirst ? "" : "} else "
|
func chunk(_ character: Character, into size: UInt32) -> UInt32 {
|
||||||
let suffix = "if rawValue == \"\(item.emojiChar)\" {"
|
guard size > 0 else { return 0 }
|
||||||
return prefix + suffix
|
|
||||||
|
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 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: ", "))]"
|
|
||||||
}
|
}
|
||||||
return "self.init(baseEmoji: .\(definition.enumName), skinTones: \(skinToneString))"
|
|
||||||
|
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))"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Conversion from String: Creates an initializer mapping a single character emoji string to an EmojiWithSkinTones
|
||||||
// e.g.
|
// e.g.
|
||||||
|
@ -291,28 +308,129 @@ extension EmojiGenerator {
|
||||||
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
||||||
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
|
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.writeLine("init?(rawValue: String) {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }")
|
fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }")
|
||||||
|
fileHandle.writeLine("switch \(chunkType.switchString(size: chunkSize)) {")
|
||||||
emojiModel.definitions.forEach { definition in
|
|
||||||
definition.variants.forEach { emoji in
|
|
||||||
fileHandle.writeLine(conditionalCheckForEmojiItem(emoji))
|
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
fileHandle.writeLine(conversionForEmojiItem(emoji, definition: definition))
|
chunkedEmojiInfo.forEach { chunk, _ in
|
||||||
|
fileHandle.writeLine("case \(chunk): self = EmojiWithSkinTones.emojiFrom\(chunk)(rawValue)")
|
||||||
}
|
}
|
||||||
|
fileHandle.writeLine("default: self = EmojiWithSkinTones(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("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.writeLine("} else {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
fileHandle.writeLine("self.init(unsupportedValue: rawValue)")
|
fileHandle.writeLine("\(assignmentPrefix)EmojiWithSkinTones(unsupportedValue: rawValue)")
|
||||||
}
|
}
|
||||||
fileHandle.writeLine("}")
|
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("}")
|
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("]")
|
||||||
|
fileHandle.writeLine("\(assignmentPrefix)(lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue))")
|
||||||
|
|
||||||
|
case .chunked: break // Provide one of the other types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +632,7 @@ extension EmojiGenerator {
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
fileHandle.writeLine("switch self {")
|
fileHandle.writeLine("switch self {")
|
||||||
emojiModel.definitions.forEach {
|
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("}")
|
fileHandle.writeLine("}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -572,7 +572,7 @@ public func serializedData() throws -> Data {
|
||||||
# if self.can_field_be_optional(field):
|
# if self.can_field_be_optional(field):
|
||||||
writer.add('guard proto.%s else {' % field.has_accessor_name() )
|
writer.add('guard proto.%s else {' % field.has_accessor_name() )
|
||||||
writer.push_indent()
|
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.pop_indent()
|
||||||
writer.add('}')
|
writer.add('}')
|
||||||
|
|
||||||
|
|
|
@ -572,7 +572,6 @@
|
||||||
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; };
|
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; };
|
||||||
FD2AAAF128ED57B500A49611 /* 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 */; };
|
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 */; };
|
FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */; };
|
||||||
FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.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 */; };
|
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 */; };
|
FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; };
|
||||||
FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.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 */; };
|
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 */; };
|
FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; };
|
||||||
FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.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 */; };
|
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 */; };
|
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||||
FD9DD2722A72516D00ECB68E /* 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 */; };
|
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 */; };
|
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; };
|
||||||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.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 */; };
|
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 */; };
|
FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; };
|
||||||
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.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 */; };
|
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 */; };
|
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 */; };
|
FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; };
|
||||||
FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.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 */; };
|
FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; };
|
||||||
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
||||||
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.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 */; };
|
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 */; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
@ -3626,6 +3625,7 @@
|
||||||
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */,
|
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */,
|
||||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */,
|
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */,
|
||||||
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */,
|
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */,
|
||||||
|
FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */,
|
||||||
);
|
);
|
||||||
path = Migrations;
|
path = Migrations;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4054,6 +4054,7 @@
|
||||||
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||||
|
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||||
|
@ -4105,7 +4106,6 @@
|
||||||
FD8ECF802934385900C0D1BB /* LibSessionUtil */ = {
|
FD8ECF802934385900C0D1BB /* LibSessionUtil */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FDA1E83729A5770C00C5C3BD /* Configs */,
|
|
||||||
FDDC08F029A300D500BF9681 /* Utilities */,
|
FDDC08F029A300D500BF9681 /* Utilities */,
|
||||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */,
|
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */,
|
||||||
FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */,
|
FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */,
|
||||||
|
@ -4167,17 +4167,6 @@
|
||||||
path = Networking;
|
path = Networking;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
FDC13D4E2A16EE41007267C7 /* Types */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -4309,7 +4298,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
||||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
|
||||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||||
);
|
);
|
||||||
path = _TestUtilities;
|
path = _TestUtilities;
|
||||||
|
@ -4335,6 +4323,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */,
|
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */,
|
||||||
|
FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */,
|
||||||
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */,
|
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */,
|
||||||
FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */,
|
FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */,
|
||||||
);
|
);
|
||||||
|
@ -5981,6 +5970,7 @@
|
||||||
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
|
C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */,
|
||||||
FD245C642850664F00B966DD /* Threading.swift in Sources */,
|
FD245C642850664F00B966DD /* Threading.swift in Sources */,
|
||||||
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */,
|
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */,
|
||||||
|
FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */,
|
||||||
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */,
|
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */,
|
||||||
FD09799B27FFC82D00936362 /* Quote.swift in Sources */,
|
FD09799B27FFC82D00936362 /* Quote.swift in Sources */,
|
||||||
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */,
|
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */,
|
||||||
|
@ -6239,9 +6229,11 @@
|
||||||
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
||||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
||||||
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
||||||
|
FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */,
|
||||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
||||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
||||||
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||||
|
FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */,
|
||||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -6256,10 +6248,12 @@
|
||||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||||
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */,
|
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */,
|
||||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
|
FDFE75B22ABD469500655640 /* MockJobRunner.swift in Sources */,
|
||||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
||||||
|
FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */,
|
||||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
||||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
||||||
|
@ -6286,7 +6280,6 @@
|
||||||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
||||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
||||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
|
||||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
||||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||||
|
@ -6299,9 +6292,7 @@
|
||||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
||||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
|
||||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
||||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
|
||||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
||||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||||
|
@ -6310,7 +6301,6 @@
|
||||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
|
||||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
||||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
||||||
|
@ -6517,7 +6507,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6541,7 +6531,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -6589,7 +6579,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6618,7 +6608,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -6654,7 +6644,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6677,7 +6667,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -6728,7 +6718,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6756,7 +6746,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||||
|
@ -7688,7 +7678,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7726,7 +7716,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
|
@ -7759,7 +7749,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 424;
|
CURRENT_PROJECT_VERSION = 425;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7797,7 +7787,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.4.1;
|
MARKETING_VERSION = 2.4.2;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
PRODUCT_NAME = Session;
|
PRODUCT_NAME = Session;
|
||||||
|
|
|
@ -105,7 +105,6 @@ final class QuoteView: UIView {
|
||||||
availableWidth -= cancelButtonSize
|
availableWidth -= cancelButtonSize
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
|
|
||||||
var body: String? = quotedText
|
var body: String? = quotedText
|
||||||
|
|
||||||
// Main stack view
|
// Main stack view
|
||||||
|
|
|
@ -112,7 +112,6 @@ public final class VoiceMessageView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setUpViewHierarchy() {
|
private func setUpViewHierarchy() {
|
||||||
let toggleContainerSize = VoiceMessageView.toggleContainerSize
|
|
||||||
let inset = VoiceMessageView.inset
|
let inset = VoiceMessageView.inset
|
||||||
|
|
||||||
// Width & height
|
// Width & height
|
||||||
|
|
|
@ -151,7 +151,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
self?.oldDisplayName = (updatedNickname.isEmpty ? nil : editedDisplayName)
|
self?.oldDisplayName = (updatedNickname.isEmpty ? nil : editedDisplayName)
|
||||||
|
|
||||||
dependencies.storage.writeAsync { db in
|
dependencies.storage.writeAsync(using: dependencies) { db in
|
||||||
try Profile
|
try Profile
|
||||||
.filter(id: threadId)
|
.filter(id: threadId)
|
||||||
.updateAllAndConfig(
|
.updateAllAndConfig(
|
||||||
|
|
|
@ -86,6 +86,7 @@ extension Emoji {
|
||||||
.grimacing,
|
.grimacing,
|
||||||
.faceExhaling,
|
.faceExhaling,
|
||||||
.lyingFace,
|
.lyingFace,
|
||||||
|
.shakingFace,
|
||||||
.relieved,
|
.relieved,
|
||||||
.pensive,
|
.pensive,
|
||||||
.sleepy,
|
.sleepy,
|
||||||
|
@ -163,7 +164,6 @@ extension Emoji {
|
||||||
.seeNoEvil,
|
.seeNoEvil,
|
||||||
.hearNoEvil,
|
.hearNoEvil,
|
||||||
.speakNoEvil,
|
.speakNoEvil,
|
||||||
.kiss,
|
|
||||||
.loveLetter,
|
.loveLetter,
|
||||||
.cupid,
|
.cupid,
|
||||||
.giftHeart,
|
.giftHeart,
|
||||||
|
@ -178,14 +178,18 @@ extension Emoji {
|
||||||
.heartOnFire,
|
.heartOnFire,
|
||||||
.mendingHeart,
|
.mendingHeart,
|
||||||
.heart,
|
.heart,
|
||||||
|
.pinkHeart,
|
||||||
.orangeHeart,
|
.orangeHeart,
|
||||||
.yellowHeart,
|
.yellowHeart,
|
||||||
.greenHeart,
|
.greenHeart,
|
||||||
.blueHeart,
|
.blueHeart,
|
||||||
|
.lightBlueHeart,
|
||||||
.purpleHeart,
|
.purpleHeart,
|
||||||
.brownHeart,
|
.brownHeart,
|
||||||
.blackHeart,
|
.blackHeart,
|
||||||
|
.greyHeart,
|
||||||
.whiteHeart,
|
.whiteHeart,
|
||||||
|
.kiss,
|
||||||
.oneHundred,
|
.oneHundred,
|
||||||
.anger,
|
.anger,
|
||||||
.boom,
|
.boom,
|
||||||
|
@ -193,7 +197,6 @@ extension Emoji {
|
||||||
.sweatDrops,
|
.sweatDrops,
|
||||||
.dash,
|
.dash,
|
||||||
.hole,
|
.hole,
|
||||||
.bomb,
|
|
||||||
.speechBalloon,
|
.speechBalloon,
|
||||||
.eyeInSpeechBubble,
|
.eyeInSpeechBubble,
|
||||||
.leftSpeechBubble,
|
.leftSpeechBubble,
|
||||||
|
@ -209,6 +212,8 @@ extension Emoji {
|
||||||
.leftwardsHand,
|
.leftwardsHand,
|
||||||
.palmDownHand,
|
.palmDownHand,
|
||||||
.palmUpHand,
|
.palmUpHand,
|
||||||
|
.leftwardsPushingHand,
|
||||||
|
.rightwardsPushingHand,
|
||||||
.okHand,
|
.okHand,
|
||||||
.pinchedFingers,
|
.pinchedFingers,
|
||||||
.pinchingHand,
|
.pinchingHand,
|
||||||
|
@ -584,6 +589,8 @@ extension Emoji {
|
||||||
.tiger2,
|
.tiger2,
|
||||||
.leopard,
|
.leopard,
|
||||||
.horse,
|
.horse,
|
||||||
|
.moose,
|
||||||
|
.donkey,
|
||||||
.racehorse,
|
.racehorse,
|
||||||
.unicornFace,
|
.unicornFace,
|
||||||
.zebraFace,
|
.zebraFace,
|
||||||
|
@ -646,6 +653,9 @@ extension Emoji {
|
||||||
.flamingo,
|
.flamingo,
|
||||||
.peacock,
|
.peacock,
|
||||||
.parrot,
|
.parrot,
|
||||||
|
.wing,
|
||||||
|
.blackBird,
|
||||||
|
.goose,
|
||||||
.frog,
|
.frog,
|
||||||
.crocodile,
|
.crocodile,
|
||||||
.turtle,
|
.turtle,
|
||||||
|
@ -666,6 +676,7 @@ extension Emoji {
|
||||||
.octopus,
|
.octopus,
|
||||||
.shell,
|
.shell,
|
||||||
.coral,
|
.coral,
|
||||||
|
.jellyfish,
|
||||||
.snail,
|
.snail,
|
||||||
.butterfly,
|
.butterfly,
|
||||||
.bug,
|
.bug,
|
||||||
|
@ -693,6 +704,7 @@ extension Emoji {
|
||||||
.sunflower,
|
.sunflower,
|
||||||
.blossom,
|
.blossom,
|
||||||
.tulip,
|
.tulip,
|
||||||
|
.hyacinth,
|
||||||
.seedling,
|
.seedling,
|
||||||
.pottedPlant,
|
.pottedPlant,
|
||||||
.evergreenTree,
|
.evergreenTree,
|
||||||
|
@ -708,6 +720,7 @@ extension Emoji {
|
||||||
.leaves,
|
.leaves,
|
||||||
.emptyNest,
|
.emptyNest,
|
||||||
.nestWithEggs,
|
.nestWithEggs,
|
||||||
|
.mushroom,
|
||||||
]
|
]
|
||||||
case .food:
|
case .food:
|
||||||
return [
|
return [
|
||||||
|
@ -742,10 +755,11 @@ extension Emoji {
|
||||||
.broccoli,
|
.broccoli,
|
||||||
.garlic,
|
.garlic,
|
||||||
.onion,
|
.onion,
|
||||||
.mushroom,
|
|
||||||
.peanuts,
|
.peanuts,
|
||||||
.beans,
|
.beans,
|
||||||
.chestnut,
|
.chestnut,
|
||||||
|
.gingerRoot,
|
||||||
|
.peaPod,
|
||||||
.bread,
|
.bread,
|
||||||
.croissant,
|
.croissant,
|
||||||
.baguetteBread,
|
.baguetteBread,
|
||||||
|
@ -903,11 +917,10 @@ extension Emoji {
|
||||||
.dart,
|
.dart,
|
||||||
.yoYo,
|
.yoYo,
|
||||||
.kite,
|
.kite,
|
||||||
|
.gun,
|
||||||
.eightBall,
|
.eightBall,
|
||||||
.crystalBall,
|
.crystalBall,
|
||||||
.magicWand,
|
.magicWand,
|
||||||
.nazarAmulet,
|
|
||||||
.hamsa,
|
|
||||||
.videoGame,
|
.videoGame,
|
||||||
.joystick,
|
.joystick,
|
||||||
.slotMachine,
|
.slotMachine,
|
||||||
|
@ -1176,6 +1189,7 @@ extension Emoji {
|
||||||
.shorts,
|
.shorts,
|
||||||
.bikini,
|
.bikini,
|
||||||
.womansClothes,
|
.womansClothes,
|
||||||
|
.foldingHandFan,
|
||||||
.purse,
|
.purse,
|
||||||
.handbag,
|
.handbag,
|
||||||
.pouch,
|
.pouch,
|
||||||
|
@ -1190,6 +1204,7 @@ extension Emoji {
|
||||||
.sandal,
|
.sandal,
|
||||||
.balletShoes,
|
.balletShoes,
|
||||||
.boot,
|
.boot,
|
||||||
|
.hairPick,
|
||||||
.crown,
|
.crown,
|
||||||
.womansHat,
|
.womansHat,
|
||||||
.tophat,
|
.tophat,
|
||||||
|
@ -1228,6 +1243,8 @@ extension Emoji {
|
||||||
.banjo,
|
.banjo,
|
||||||
.drumWithDrumsticks,
|
.drumWithDrumsticks,
|
||||||
.longDrum,
|
.longDrum,
|
||||||
|
.maracas,
|
||||||
|
.flute,
|
||||||
.iphone,
|
.iphone,
|
||||||
.calling,
|
.calling,
|
||||||
.phone,
|
.phone,
|
||||||
|
@ -1347,7 +1364,7 @@ extension Emoji {
|
||||||
.hammerAndWrench,
|
.hammerAndWrench,
|
||||||
.daggerKnife,
|
.daggerKnife,
|
||||||
.crossedSwords,
|
.crossedSwords,
|
||||||
.gun,
|
.bomb,
|
||||||
.boomerang,
|
.boomerang,
|
||||||
.bowAndArrow,
|
.bowAndArrow,
|
||||||
.shield,
|
.shield,
|
||||||
|
@ -1408,6 +1425,8 @@ extension Emoji {
|
||||||
.coffin,
|
.coffin,
|
||||||
.headstone,
|
.headstone,
|
||||||
.funeralUrn,
|
.funeralUrn,
|
||||||
|
.nazarAmulet,
|
||||||
|
.hamsa,
|
||||||
.moyai,
|
.moyai,
|
||||||
.placard,
|
.placard,
|
||||||
.identificationCard,
|
.identificationCard,
|
||||||
|
@ -1473,6 +1492,7 @@ extension Emoji {
|
||||||
.peaceSymbol,
|
.peaceSymbol,
|
||||||
.menorahWithNineBranches,
|
.menorahWithNineBranches,
|
||||||
.sixPointedStar,
|
.sixPointedStar,
|
||||||
|
.khanda,
|
||||||
.aries,
|
.aries,
|
||||||
.taurus,
|
.taurus,
|
||||||
.gemini,
|
.gemini,
|
||||||
|
@ -1508,6 +1528,7 @@ extension Emoji {
|
||||||
.lowBrightness,
|
.lowBrightness,
|
||||||
.highBrightness,
|
.highBrightness,
|
||||||
.signalStrength,
|
.signalStrength,
|
||||||
|
.wireless,
|
||||||
.vibrationMode,
|
.vibrationMode,
|
||||||
.mobilePhoneOff,
|
.mobilePhoneOff,
|
||||||
.femaleSign,
|
.femaleSign,
|
||||||
|
@ -1962,6 +1983,7 @@ extension Emoji {
|
||||||
case .grimacing: return .smileysAndPeople
|
case .grimacing: return .smileysAndPeople
|
||||||
case .faceExhaling: return .smileysAndPeople
|
case .faceExhaling: return .smileysAndPeople
|
||||||
case .lyingFace: return .smileysAndPeople
|
case .lyingFace: return .smileysAndPeople
|
||||||
|
case .shakingFace: return .smileysAndPeople
|
||||||
case .relieved: return .smileysAndPeople
|
case .relieved: return .smileysAndPeople
|
||||||
case .pensive: return .smileysAndPeople
|
case .pensive: return .smileysAndPeople
|
||||||
case .sleepy: return .smileysAndPeople
|
case .sleepy: return .smileysAndPeople
|
||||||
|
@ -2039,7 +2061,6 @@ extension Emoji {
|
||||||
case .seeNoEvil: return .smileysAndPeople
|
case .seeNoEvil: return .smileysAndPeople
|
||||||
case .hearNoEvil: return .smileysAndPeople
|
case .hearNoEvil: return .smileysAndPeople
|
||||||
case .speakNoEvil: return .smileysAndPeople
|
case .speakNoEvil: return .smileysAndPeople
|
||||||
case .kiss: return .smileysAndPeople
|
|
||||||
case .loveLetter: return .smileysAndPeople
|
case .loveLetter: return .smileysAndPeople
|
||||||
case .cupid: return .smileysAndPeople
|
case .cupid: return .smileysAndPeople
|
||||||
case .giftHeart: return .smileysAndPeople
|
case .giftHeart: return .smileysAndPeople
|
||||||
|
@ -2054,14 +2075,18 @@ extension Emoji {
|
||||||
case .heartOnFire: return .smileysAndPeople
|
case .heartOnFire: return .smileysAndPeople
|
||||||
case .mendingHeart: return .smileysAndPeople
|
case .mendingHeart: return .smileysAndPeople
|
||||||
case .heart: return .smileysAndPeople
|
case .heart: return .smileysAndPeople
|
||||||
|
case .pinkHeart: return .smileysAndPeople
|
||||||
case .orangeHeart: return .smileysAndPeople
|
case .orangeHeart: return .smileysAndPeople
|
||||||
case .yellowHeart: return .smileysAndPeople
|
case .yellowHeart: return .smileysAndPeople
|
||||||
case .greenHeart: return .smileysAndPeople
|
case .greenHeart: return .smileysAndPeople
|
||||||
case .blueHeart: return .smileysAndPeople
|
case .blueHeart: return .smileysAndPeople
|
||||||
|
case .lightBlueHeart: return .smileysAndPeople
|
||||||
case .purpleHeart: return .smileysAndPeople
|
case .purpleHeart: return .smileysAndPeople
|
||||||
case .brownHeart: return .smileysAndPeople
|
case .brownHeart: return .smileysAndPeople
|
||||||
case .blackHeart: return .smileysAndPeople
|
case .blackHeart: return .smileysAndPeople
|
||||||
|
case .greyHeart: return .smileysAndPeople
|
||||||
case .whiteHeart: return .smileysAndPeople
|
case .whiteHeart: return .smileysAndPeople
|
||||||
|
case .kiss: return .smileysAndPeople
|
||||||
case .oneHundred: return .smileysAndPeople
|
case .oneHundred: return .smileysAndPeople
|
||||||
case .anger: return .smileysAndPeople
|
case .anger: return .smileysAndPeople
|
||||||
case .boom: return .smileysAndPeople
|
case .boom: return .smileysAndPeople
|
||||||
|
@ -2069,7 +2094,6 @@ extension Emoji {
|
||||||
case .sweatDrops: return .smileysAndPeople
|
case .sweatDrops: return .smileysAndPeople
|
||||||
case .dash: return .smileysAndPeople
|
case .dash: return .smileysAndPeople
|
||||||
case .hole: return .smileysAndPeople
|
case .hole: return .smileysAndPeople
|
||||||
case .bomb: return .smileysAndPeople
|
|
||||||
case .speechBalloon: return .smileysAndPeople
|
case .speechBalloon: return .smileysAndPeople
|
||||||
case .eyeInSpeechBubble: return .smileysAndPeople
|
case .eyeInSpeechBubble: return .smileysAndPeople
|
||||||
case .leftSpeechBubble: return .smileysAndPeople
|
case .leftSpeechBubble: return .smileysAndPeople
|
||||||
|
@ -2085,6 +2109,8 @@ extension Emoji {
|
||||||
case .leftwardsHand: return .smileysAndPeople
|
case .leftwardsHand: return .smileysAndPeople
|
||||||
case .palmDownHand: return .smileysAndPeople
|
case .palmDownHand: return .smileysAndPeople
|
||||||
case .palmUpHand: return .smileysAndPeople
|
case .palmUpHand: return .smileysAndPeople
|
||||||
|
case .leftwardsPushingHand: return .smileysAndPeople
|
||||||
|
case .rightwardsPushingHand: return .smileysAndPeople
|
||||||
case .okHand: return .smileysAndPeople
|
case .okHand: return .smileysAndPeople
|
||||||
case .pinchedFingers: return .smileysAndPeople
|
case .pinchedFingers: return .smileysAndPeople
|
||||||
case .pinchingHand: return .smileysAndPeople
|
case .pinchingHand: return .smileysAndPeople
|
||||||
|
@ -2457,6 +2483,8 @@ extension Emoji {
|
||||||
case .tiger2: return .animals
|
case .tiger2: return .animals
|
||||||
case .leopard: return .animals
|
case .leopard: return .animals
|
||||||
case .horse: return .animals
|
case .horse: return .animals
|
||||||
|
case .moose: return .animals
|
||||||
|
case .donkey: return .animals
|
||||||
case .racehorse: return .animals
|
case .racehorse: return .animals
|
||||||
case .unicornFace: return .animals
|
case .unicornFace: return .animals
|
||||||
case .zebraFace: return .animals
|
case .zebraFace: return .animals
|
||||||
|
@ -2519,6 +2547,9 @@ extension Emoji {
|
||||||
case .flamingo: return .animals
|
case .flamingo: return .animals
|
||||||
case .peacock: return .animals
|
case .peacock: return .animals
|
||||||
case .parrot: return .animals
|
case .parrot: return .animals
|
||||||
|
case .wing: return .animals
|
||||||
|
case .blackBird: return .animals
|
||||||
|
case .goose: return .animals
|
||||||
case .frog: return .animals
|
case .frog: return .animals
|
||||||
case .crocodile: return .animals
|
case .crocodile: return .animals
|
||||||
case .turtle: return .animals
|
case .turtle: return .animals
|
||||||
|
@ -2539,6 +2570,7 @@ extension Emoji {
|
||||||
case .octopus: return .animals
|
case .octopus: return .animals
|
||||||
case .shell: return .animals
|
case .shell: return .animals
|
||||||
case .coral: return .animals
|
case .coral: return .animals
|
||||||
|
case .jellyfish: return .animals
|
||||||
case .snail: return .animals
|
case .snail: return .animals
|
||||||
case .butterfly: return .animals
|
case .butterfly: return .animals
|
||||||
case .bug: return .animals
|
case .bug: return .animals
|
||||||
|
@ -2566,6 +2598,7 @@ extension Emoji {
|
||||||
case .sunflower: return .animals
|
case .sunflower: return .animals
|
||||||
case .blossom: return .animals
|
case .blossom: return .animals
|
||||||
case .tulip: return .animals
|
case .tulip: return .animals
|
||||||
|
case .hyacinth: return .animals
|
||||||
case .seedling: return .animals
|
case .seedling: return .animals
|
||||||
case .pottedPlant: return .animals
|
case .pottedPlant: return .animals
|
||||||
case .evergreenTree: return .animals
|
case .evergreenTree: return .animals
|
||||||
|
@ -2581,6 +2614,7 @@ extension Emoji {
|
||||||
case .leaves: return .animals
|
case .leaves: return .animals
|
||||||
case .emptyNest: return .animals
|
case .emptyNest: return .animals
|
||||||
case .nestWithEggs: return .animals
|
case .nestWithEggs: return .animals
|
||||||
|
case .mushroom: return .animals
|
||||||
case .grapes: return .food
|
case .grapes: return .food
|
||||||
case .melon: return .food
|
case .melon: return .food
|
||||||
case .watermelon: return .food
|
case .watermelon: return .food
|
||||||
|
@ -2612,10 +2646,11 @@ extension Emoji {
|
||||||
case .broccoli: return .food
|
case .broccoli: return .food
|
||||||
case .garlic: return .food
|
case .garlic: return .food
|
||||||
case .onion: return .food
|
case .onion: return .food
|
||||||
case .mushroom: return .food
|
|
||||||
case .peanuts: return .food
|
case .peanuts: return .food
|
||||||
case .beans: return .food
|
case .beans: return .food
|
||||||
case .chestnut: return .food
|
case .chestnut: return .food
|
||||||
|
case .gingerRoot: return .food
|
||||||
|
case .peaPod: return .food
|
||||||
case .bread: return .food
|
case .bread: return .food
|
||||||
case .croissant: return .food
|
case .croissant: return .food
|
||||||
case .baguetteBread: return .food
|
case .baguetteBread: return .food
|
||||||
|
@ -2988,11 +3023,10 @@ extension Emoji {
|
||||||
case .dart: return .activities
|
case .dart: return .activities
|
||||||
case .yoYo: return .activities
|
case .yoYo: return .activities
|
||||||
case .kite: return .activities
|
case .kite: return .activities
|
||||||
|
case .gun: return .activities
|
||||||
case .eightBall: return .activities
|
case .eightBall: return .activities
|
||||||
case .crystalBall: return .activities
|
case .crystalBall: return .activities
|
||||||
case .magicWand: return .activities
|
case .magicWand: return .activities
|
||||||
case .nazarAmulet: return .activities
|
|
||||||
case .hamsa: return .activities
|
|
||||||
case .videoGame: return .activities
|
case .videoGame: return .activities
|
||||||
case .joystick: return .activities
|
case .joystick: return .activities
|
||||||
case .slotMachine: return .activities
|
case .slotMachine: return .activities
|
||||||
|
@ -3037,6 +3071,7 @@ extension Emoji {
|
||||||
case .shorts: return .objects
|
case .shorts: return .objects
|
||||||
case .bikini: return .objects
|
case .bikini: return .objects
|
||||||
case .womansClothes: return .objects
|
case .womansClothes: return .objects
|
||||||
|
case .foldingHandFan: return .objects
|
||||||
case .purse: return .objects
|
case .purse: return .objects
|
||||||
case .handbag: return .objects
|
case .handbag: return .objects
|
||||||
case .pouch: return .objects
|
case .pouch: return .objects
|
||||||
|
@ -3051,6 +3086,7 @@ extension Emoji {
|
||||||
case .sandal: return .objects
|
case .sandal: return .objects
|
||||||
case .balletShoes: return .objects
|
case .balletShoes: return .objects
|
||||||
case .boot: return .objects
|
case .boot: return .objects
|
||||||
|
case .hairPick: return .objects
|
||||||
case .crown: return .objects
|
case .crown: return .objects
|
||||||
case .womansHat: return .objects
|
case .womansHat: return .objects
|
||||||
case .tophat: return .objects
|
case .tophat: return .objects
|
||||||
|
@ -3089,6 +3125,8 @@ extension Emoji {
|
||||||
case .banjo: return .objects
|
case .banjo: return .objects
|
||||||
case .drumWithDrumsticks: return .objects
|
case .drumWithDrumsticks: return .objects
|
||||||
case .longDrum: return .objects
|
case .longDrum: return .objects
|
||||||
|
case .maracas: return .objects
|
||||||
|
case .flute: return .objects
|
||||||
case .iphone: return .objects
|
case .iphone: return .objects
|
||||||
case .calling: return .objects
|
case .calling: return .objects
|
||||||
case .phone: return .objects
|
case .phone: return .objects
|
||||||
|
@ -3208,7 +3246,7 @@ extension Emoji {
|
||||||
case .hammerAndWrench: return .objects
|
case .hammerAndWrench: return .objects
|
||||||
case .daggerKnife: return .objects
|
case .daggerKnife: return .objects
|
||||||
case .crossedSwords: return .objects
|
case .crossedSwords: return .objects
|
||||||
case .gun: return .objects
|
case .bomb: return .objects
|
||||||
case .boomerang: return .objects
|
case .boomerang: return .objects
|
||||||
case .bowAndArrow: return .objects
|
case .bowAndArrow: return .objects
|
||||||
case .shield: return .objects
|
case .shield: return .objects
|
||||||
|
@ -3269,6 +3307,8 @@ extension Emoji {
|
||||||
case .coffin: return .objects
|
case .coffin: return .objects
|
||||||
case .headstone: return .objects
|
case .headstone: return .objects
|
||||||
case .funeralUrn: return .objects
|
case .funeralUrn: return .objects
|
||||||
|
case .nazarAmulet: return .objects
|
||||||
|
case .hamsa: return .objects
|
||||||
case .moyai: return .objects
|
case .moyai: return .objects
|
||||||
case .placard: return .objects
|
case .placard: return .objects
|
||||||
case .identificationCard: return .objects
|
case .identificationCard: return .objects
|
||||||
|
@ -3331,6 +3371,7 @@ extension Emoji {
|
||||||
case .peaceSymbol: return .symbols
|
case .peaceSymbol: return .symbols
|
||||||
case .menorahWithNineBranches: return .symbols
|
case .menorahWithNineBranches: return .symbols
|
||||||
case .sixPointedStar: return .symbols
|
case .sixPointedStar: return .symbols
|
||||||
|
case .khanda: return .symbols
|
||||||
case .aries: return .symbols
|
case .aries: return .symbols
|
||||||
case .taurus: return .symbols
|
case .taurus: return .symbols
|
||||||
case .gemini: return .symbols
|
case .gemini: return .symbols
|
||||||
|
@ -3366,6 +3407,7 @@ extension Emoji {
|
||||||
case .lowBrightness: return .symbols
|
case .lowBrightness: return .symbols
|
||||||
case .highBrightness: return .symbols
|
case .highBrightness: return .symbols
|
||||||
case .signalStrength: return .symbols
|
case .signalStrength: return .symbols
|
||||||
|
case .wireless: return .symbols
|
||||||
case .vibrationMode: return .symbols
|
case .vibrationMode: return .symbols
|
||||||
case .mobilePhoneOff: return .symbols
|
case .mobilePhoneOff: return .symbols
|
||||||
case .femaleSign: return .symbols
|
case .femaleSign: return .symbols
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -106,6 +106,22 @@ extension Emoji {
|
||||||
[.mediumDark]: "🫴🏾",
|
[.mediumDark]: "🫴🏾",
|
||||||
[.dark]: "🫴🏿",
|
[.dark]: "🫴🏿",
|
||||||
]
|
]
|
||||||
|
case .leftwardsPushingHand:
|
||||||
|
return [
|
||||||
|
[.light]: "🫷🏻",
|
||||||
|
[.mediumLight]: "🫷🏼",
|
||||||
|
[.medium]: "🫷🏽",
|
||||||
|
[.mediumDark]: "🫷🏾",
|
||||||
|
[.dark]: "🫷🏿",
|
||||||
|
]
|
||||||
|
case .rightwardsPushingHand:
|
||||||
|
return [
|
||||||
|
[.light]: "🫸🏻",
|
||||||
|
[.mediumLight]: "🫸🏼",
|
||||||
|
[.medium]: "🫸🏽",
|
||||||
|
[.mediumDark]: "🫸🏾",
|
||||||
|
[.dark]: "🫸🏿",
|
||||||
|
]
|
||||||
case .okHand:
|
case .okHand:
|
||||||
return [
|
return [
|
||||||
[.light]: "👌🏻",
|
[.light]: "👌🏻",
|
||||||
|
|
|
@ -54,6 +54,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case grimacing = "😬"
|
case grimacing = "😬"
|
||||||
case faceExhaling = "😮💨"
|
case faceExhaling = "😮💨"
|
||||||
case lyingFace = "🤥"
|
case lyingFace = "🤥"
|
||||||
|
case shakingFace = "🫨"
|
||||||
case relieved = "😌"
|
case relieved = "😌"
|
||||||
case pensive = "😔"
|
case pensive = "😔"
|
||||||
case sleepy = "😪"
|
case sleepy = "😪"
|
||||||
|
@ -131,7 +132,6 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case seeNoEvil = "🙈"
|
case seeNoEvil = "🙈"
|
||||||
case hearNoEvil = "🙉"
|
case hearNoEvil = "🙉"
|
||||||
case speakNoEvil = "🙊"
|
case speakNoEvil = "🙊"
|
||||||
case kiss = "💋"
|
|
||||||
case loveLetter = "💌"
|
case loveLetter = "💌"
|
||||||
case cupid = "💘"
|
case cupid = "💘"
|
||||||
case giftHeart = "💝"
|
case giftHeart = "💝"
|
||||||
|
@ -146,14 +146,18 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case heartOnFire = "❤️🔥"
|
case heartOnFire = "❤️🔥"
|
||||||
case mendingHeart = "❤️🩹"
|
case mendingHeart = "❤️🩹"
|
||||||
case heart = "❤️"
|
case heart = "❤️"
|
||||||
|
case pinkHeart = "🩷"
|
||||||
case orangeHeart = "🧡"
|
case orangeHeart = "🧡"
|
||||||
case yellowHeart = "💛"
|
case yellowHeart = "💛"
|
||||||
case greenHeart = "💚"
|
case greenHeart = "💚"
|
||||||
case blueHeart = "💙"
|
case blueHeart = "💙"
|
||||||
|
case lightBlueHeart = "🩵"
|
||||||
case purpleHeart = "💜"
|
case purpleHeart = "💜"
|
||||||
case brownHeart = "🤎"
|
case brownHeart = "🤎"
|
||||||
case blackHeart = "🖤"
|
case blackHeart = "🖤"
|
||||||
|
case greyHeart = "🩶"
|
||||||
case whiteHeart = "🤍"
|
case whiteHeart = "🤍"
|
||||||
|
case kiss = "💋"
|
||||||
case oneHundred = "💯"
|
case oneHundred = "💯"
|
||||||
case anger = "💢"
|
case anger = "💢"
|
||||||
case boom = "💥"
|
case boom = "💥"
|
||||||
|
@ -161,7 +165,6 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case sweatDrops = "💦"
|
case sweatDrops = "💦"
|
||||||
case dash = "💨"
|
case dash = "💨"
|
||||||
case hole = "🕳️"
|
case hole = "🕳️"
|
||||||
case bomb = "💣"
|
|
||||||
case speechBalloon = "💬"
|
case speechBalloon = "💬"
|
||||||
case eyeInSpeechBubble = "👁️🗨️"
|
case eyeInSpeechBubble = "👁️🗨️"
|
||||||
case leftSpeechBubble = "🗨️"
|
case leftSpeechBubble = "🗨️"
|
||||||
|
@ -177,6 +180,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case leftwardsHand = "🫲"
|
case leftwardsHand = "🫲"
|
||||||
case palmDownHand = "🫳"
|
case palmDownHand = "🫳"
|
||||||
case palmUpHand = "🫴"
|
case palmUpHand = "🫴"
|
||||||
|
case leftwardsPushingHand = "🫷"
|
||||||
|
case rightwardsPushingHand = "🫸"
|
||||||
case okHand = "👌"
|
case okHand = "👌"
|
||||||
case pinchedFingers = "🤌"
|
case pinchedFingers = "🤌"
|
||||||
case pinchingHand = "🤏"
|
case pinchingHand = "🤏"
|
||||||
|
@ -554,6 +559,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case tiger2 = "🐅"
|
case tiger2 = "🐅"
|
||||||
case leopard = "🐆"
|
case leopard = "🐆"
|
||||||
case horse = "🐴"
|
case horse = "🐴"
|
||||||
|
case moose = "🫎"
|
||||||
|
case donkey = "🫏"
|
||||||
case racehorse = "🐎"
|
case racehorse = "🐎"
|
||||||
case unicornFace = "🦄"
|
case unicornFace = "🦄"
|
||||||
case zebraFace = "🦓"
|
case zebraFace = "🦓"
|
||||||
|
@ -616,6 +623,9 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case flamingo = "🦩"
|
case flamingo = "🦩"
|
||||||
case peacock = "🦚"
|
case peacock = "🦚"
|
||||||
case parrot = "🦜"
|
case parrot = "🦜"
|
||||||
|
case wing = "🪽"
|
||||||
|
case blackBird = "🐦⬛"
|
||||||
|
case goose = "🪿"
|
||||||
case frog = "🐸"
|
case frog = "🐸"
|
||||||
case crocodile = "🐊"
|
case crocodile = "🐊"
|
||||||
case turtle = "🐢"
|
case turtle = "🐢"
|
||||||
|
@ -636,6 +646,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case octopus = "🐙"
|
case octopus = "🐙"
|
||||||
case shell = "🐚"
|
case shell = "🐚"
|
||||||
case coral = "🪸"
|
case coral = "🪸"
|
||||||
|
case jellyfish = "🪼"
|
||||||
case snail = "🐌"
|
case snail = "🐌"
|
||||||
case butterfly = "🦋"
|
case butterfly = "🦋"
|
||||||
case bug = "🐛"
|
case bug = "🐛"
|
||||||
|
@ -663,6 +674,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case sunflower = "🌻"
|
case sunflower = "🌻"
|
||||||
case blossom = "🌼"
|
case blossom = "🌼"
|
||||||
case tulip = "🌷"
|
case tulip = "🌷"
|
||||||
|
case hyacinth = "🪻"
|
||||||
case seedling = "🌱"
|
case seedling = "🌱"
|
||||||
case pottedPlant = "🪴"
|
case pottedPlant = "🪴"
|
||||||
case evergreenTree = "🌲"
|
case evergreenTree = "🌲"
|
||||||
|
@ -678,6 +690,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case leaves = "🍃"
|
case leaves = "🍃"
|
||||||
case emptyNest = "🪹"
|
case emptyNest = "🪹"
|
||||||
case nestWithEggs = "🪺"
|
case nestWithEggs = "🪺"
|
||||||
|
case mushroom = "🍄"
|
||||||
case grapes = "🍇"
|
case grapes = "🍇"
|
||||||
case melon = "🍈"
|
case melon = "🍈"
|
||||||
case watermelon = "🍉"
|
case watermelon = "🍉"
|
||||||
|
@ -709,10 +722,11 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case broccoli = "🥦"
|
case broccoli = "🥦"
|
||||||
case garlic = "🧄"
|
case garlic = "🧄"
|
||||||
case onion = "🧅"
|
case onion = "🧅"
|
||||||
case mushroom = "🍄"
|
|
||||||
case peanuts = "🥜"
|
case peanuts = "🥜"
|
||||||
case beans = "🫘"
|
case beans = "🫘"
|
||||||
case chestnut = "🌰"
|
case chestnut = "🌰"
|
||||||
|
case gingerRoot = "🫚"
|
||||||
|
case peaPod = "🫛"
|
||||||
case bread = "🍞"
|
case bread = "🍞"
|
||||||
case croissant = "🥐"
|
case croissant = "🥐"
|
||||||
case baguetteBread = "🥖"
|
case baguetteBread = "🥖"
|
||||||
|
@ -1085,11 +1099,10 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case dart = "🎯"
|
case dart = "🎯"
|
||||||
case yoYo = "🪀"
|
case yoYo = "🪀"
|
||||||
case kite = "🪁"
|
case kite = "🪁"
|
||||||
|
case gun = "🔫"
|
||||||
case eightBall = "🎱"
|
case eightBall = "🎱"
|
||||||
case crystalBall = "🔮"
|
case crystalBall = "🔮"
|
||||||
case magicWand = "🪄"
|
case magicWand = "🪄"
|
||||||
case nazarAmulet = "🧿"
|
|
||||||
case hamsa = "🪬"
|
|
||||||
case videoGame = "🎮"
|
case videoGame = "🎮"
|
||||||
case joystick = "🕹️"
|
case joystick = "🕹️"
|
||||||
case slotMachine = "🎰"
|
case slotMachine = "🎰"
|
||||||
|
@ -1134,6 +1147,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case shorts = "🩳"
|
case shorts = "🩳"
|
||||||
case bikini = "👙"
|
case bikini = "👙"
|
||||||
case womansClothes = "👚"
|
case womansClothes = "👚"
|
||||||
|
case foldingHandFan = "🪭"
|
||||||
case purse = "👛"
|
case purse = "👛"
|
||||||
case handbag = "👜"
|
case handbag = "👜"
|
||||||
case pouch = "👝"
|
case pouch = "👝"
|
||||||
|
@ -1148,6 +1162,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case sandal = "👡"
|
case sandal = "👡"
|
||||||
case balletShoes = "🩰"
|
case balletShoes = "🩰"
|
||||||
case boot = "👢"
|
case boot = "👢"
|
||||||
|
case hairPick = "🪮"
|
||||||
case crown = "👑"
|
case crown = "👑"
|
||||||
case womansHat = "👒"
|
case womansHat = "👒"
|
||||||
case tophat = "🎩"
|
case tophat = "🎩"
|
||||||
|
@ -1186,6 +1201,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case banjo = "🪕"
|
case banjo = "🪕"
|
||||||
case drumWithDrumsticks = "🥁"
|
case drumWithDrumsticks = "🥁"
|
||||||
case longDrum = "🪘"
|
case longDrum = "🪘"
|
||||||
|
case maracas = "🪇"
|
||||||
|
case flute = "🪈"
|
||||||
case iphone = "📱"
|
case iphone = "📱"
|
||||||
case calling = "📲"
|
case calling = "📲"
|
||||||
case phone = "☎️"
|
case phone = "☎️"
|
||||||
|
@ -1305,7 +1322,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case hammerAndWrench = "🛠️"
|
case hammerAndWrench = "🛠️"
|
||||||
case daggerKnife = "🗡️"
|
case daggerKnife = "🗡️"
|
||||||
case crossedSwords = "⚔️"
|
case crossedSwords = "⚔️"
|
||||||
case gun = "🔫"
|
case bomb = "💣"
|
||||||
case boomerang = "🪃"
|
case boomerang = "🪃"
|
||||||
case bowAndArrow = "🏹"
|
case bowAndArrow = "🏹"
|
||||||
case shield = "🛡️"
|
case shield = "🛡️"
|
||||||
|
@ -1366,6 +1383,8 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case coffin = "⚰️"
|
case coffin = "⚰️"
|
||||||
case headstone = "🪦"
|
case headstone = "🪦"
|
||||||
case funeralUrn = "⚱️"
|
case funeralUrn = "⚱️"
|
||||||
|
case nazarAmulet = "🧿"
|
||||||
|
case hamsa = "🪬"
|
||||||
case moyai = "🗿"
|
case moyai = "🗿"
|
||||||
case placard = "🪧"
|
case placard = "🪧"
|
||||||
case identificationCard = "🪪"
|
case identificationCard = "🪪"
|
||||||
|
@ -1428,6 +1447,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case peaceSymbol = "☮️"
|
case peaceSymbol = "☮️"
|
||||||
case menorahWithNineBranches = "🕎"
|
case menorahWithNineBranches = "🕎"
|
||||||
case sixPointedStar = "🔯"
|
case sixPointedStar = "🔯"
|
||||||
|
case khanda = "🪯"
|
||||||
case aries = "♈"
|
case aries = "♈"
|
||||||
case taurus = "♉"
|
case taurus = "♉"
|
||||||
case gemini = "♊"
|
case gemini = "♊"
|
||||||
|
@ -1463,6 +1483,7 @@ enum Emoji: String, CaseIterable, Equatable {
|
||||||
case lowBrightness = "🔅"
|
case lowBrightness = "🔅"
|
||||||
case highBrightness = "🔆"
|
case highBrightness = "🔆"
|
||||||
case signalStrength = "📶"
|
case signalStrength = "📶"
|
||||||
|
case wireless = "🛜"
|
||||||
case vibrationMode = "📳"
|
case vibrationMode = "📳"
|
||||||
case mobilePhoneOff = "📴"
|
case mobilePhoneOff = "📴"
|
||||||
case femaleSign = "♀️"
|
case femaleSign = "♀️"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -275,14 +275,11 @@ class PhotoCaptureViewController: OWSViewController {
|
||||||
|
|
||||||
let transformFromOrientation: CGAffineTransform
|
let transformFromOrientation: CGAffineTransform
|
||||||
switch captureOrientation {
|
switch captureOrientation {
|
||||||
case .portrait:
|
case .portrait: transformFromOrientation = .identity
|
||||||
transformFromOrientation = .identity
|
case .portraitUpsideDown: transformFromOrientation = CGAffineTransform(rotationAngle: .pi)
|
||||||
case .portraitUpsideDown:
|
case .landscapeLeft: transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi)
|
||||||
transformFromOrientation = CGAffineTransform(rotationAngle: .pi)
|
case .landscapeRight: transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi)
|
||||||
case .landscapeLeft:
|
@unknown default: transformFromOrientation = .identity
|
||||||
transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi)
|
|
||||||
case .landscapeRight:
|
|
||||||
transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't "unrotate" the switch camera icon if the front facing camera had been selected.
|
// 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
|
/// 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 {
|
guard threadInfo?.threadExists == true else {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
|
|
@ -635,8 +635,6 @@
|
||||||
"message_info_title" = "Message Info";
|
"message_info_title" = "Message Info";
|
||||||
"mute_button_text" = "Mute";
|
"mute_button_text" = "Mute";
|
||||||
"unmute_button_text" = "Unmute";
|
"unmute_button_text" = "Unmute";
|
||||||
"mark_read_button_text" = "Mark read";
|
|
||||||
"mark_unread_button_text" = "Mark unread";
|
|
||||||
"leave_group_confirmation_alert_title" = "Leave Group";
|
"leave_group_confirmation_alert_title" = "Leave Group";
|
||||||
"leave_community_confirmation_alert_title" = "Leave Community";
|
"leave_community_confirmation_alert_title" = "Leave Community";
|
||||||
"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?";
|
"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
|
.tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
|
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
|
||||||
#endif
|
#else
|
||||||
|
|
||||||
return self.registerForVanillaPushToken()
|
return self.registerForVanillaPushToken()
|
||||||
.flatMap { vanillaPushToken -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
|
.flatMap { vanillaPushToken -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
|
||||||
self.registerForVoipPushToken()
|
self.registerForVoipPushToken()
|
||||||
|
@ -68,6 +67,7 @@ public enum PushRegistrationError: Error {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,10 +98,7 @@ enum MockDataGenerator {
|
||||||
id: randomSessionId,
|
id: randomSessionId,
|
||||||
name: (0..<contactNameLength)
|
name: (0..<contactNameLength)
|
||||||
.compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) }
|
.compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) }
|
||||||
.joined(),
|
.joined()
|
||||||
lastNameUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
)
|
)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
|
|
||||||
|
@ -180,10 +177,7 @@ enum MockDataGenerator {
|
||||||
id: randomSessionId,
|
id: randomSessionId,
|
||||||
name: (0..<contactNameLength)
|
name: (0..<contactNameLength)
|
||||||
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
||||||
.joined(),
|
.joined()
|
||||||
lastNameUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
)
|
)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
|
|
||||||
|
@ -311,10 +305,7 @@ enum MockDataGenerator {
|
||||||
id: randomSessionId,
|
id: randomSessionId,
|
||||||
name: (0..<contactNameLength)
|
name: (0..<contactNameLength)
|
||||||
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
|
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
|
||||||
.joined(),
|
.joined()
|
||||||
lastNameUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
)
|
)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
|
|
||||||
|
|
|
@ -160,9 +160,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
return Deferred {
|
return Deferred {
|
||||||
Future<Void, Error> { [weak self] resolver in
|
Future<Void, Error> { [weak self] resolver in
|
||||||
self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in
|
self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in
|
||||||
if let error = error {
|
guard error == nil else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else {
|
guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
|
|
|
@ -32,7 +32,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
|
||||||
_012_AddFTSIfNeeded.self,
|
_012_AddFTSIfNeeded.self,
|
||||||
_013_SessionUtilChanges.self,
|
_013_SessionUtilChanges.self,
|
||||||
_014_GenerateInitialUserConfigDumps.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(.variant, .integer).notNull()
|
||||||
t.column(.creationDateTimestamp, .double).notNull()
|
t.column(.creationDateTimestamp, .double).notNull()
|
||||||
t.column(.shouldBeVisible, .boolean).notNull()
|
t.column(.shouldBeVisible, .boolean).notNull()
|
||||||
t.column(.isPinned, .boolean).notNull()
|
t.deprecatedColumn(name: "isPinned", .boolean).notNull()
|
||||||
t.column(.messageDraft, .text)
|
t.column(.messageDraft, .text)
|
||||||
t.column(.notificationSound, .integer)
|
t.column(.notificationSound, .integer)
|
||||||
t.column(.mutedUntilTimestamp, .double)
|
t.column(.mutedUntilTimestamp, .double)
|
||||||
|
|
|
@ -417,13 +417,10 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
try Profile(
|
try Profile(
|
||||||
id: legacyContact.sessionID,
|
id: legacyContact.sessionID,
|
||||||
name: (legacyContact.name ?? legacyContact.sessionID),
|
name: (legacyContact.name ?? legacyContact.sessionID),
|
||||||
lastNameUpdate: 0,
|
|
||||||
nickname: legacyContact.nickname,
|
nickname: legacyContact.nickname,
|
||||||
profilePictureUrl: legacyContact.profilePictureURL,
|
profilePictureUrl: legacyContact.profilePictureURL,
|
||||||
profilePictureFileName: legacyContact.profilePictureFileName,
|
profilePictureFileName: legacyContact.profilePictureFileName,
|
||||||
profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData,
|
profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData
|
||||||
lastProfilePictureUpdate: 0,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
).migrationSafeInsert(db)
|
).migrationSafeInsert(db)
|
||||||
|
|
||||||
/// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they
|
/// **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
|
// constraint violation
|
||||||
try? Profile(
|
try? Profile(
|
||||||
id: profileId,
|
id: profileId,
|
||||||
name: profileId,
|
name: profileId
|
||||||
lastNameUpdate: 0,
|
|
||||||
lastProfilePictureUpdate: 0,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
).migrationSafeSave(db)
|
).migrationSafeSave(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1059,10 +1053,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
// constraint violation
|
// constraint violation
|
||||||
try Profile(
|
try Profile(
|
||||||
id: quotedMessage.authorId,
|
id: quotedMessage.authorId,
|
||||||
name: quotedMessage.authorId,
|
name: quotedMessage.authorId
|
||||||
lastNameUpdate: 0,
|
|
||||||
lastProfilePictureUpdate: 0,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
).migrationSafeSave(db)
|
).migrationSafeSave(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,8 @@ enum _013_SessionUtilChanges: Migration {
|
||||||
|
|
||||||
// Add `lastNameUpdate` and `lastProfilePictureUpdate` columns to the profile table
|
// Add `lastNameUpdate` and `lastProfilePictureUpdate` columns to the profile table
|
||||||
try db.alter(table: Profile.self) { t in
|
try db.alter(table: Profile.self) { t in
|
||||||
t.add(.lastNameUpdate, .integer)
|
t.add(.lastNameUpdate, .integer).defaults(to: 0)
|
||||||
.notNull()
|
t.add(.lastProfilePictureUpdate, .integer).defaults(to: 0)
|
||||||
.defaults(to: 0)
|
|
||||||
t.add(.lastProfilePictureUpdate, .integer)
|
|
||||||
.notNull()
|
|
||||||
.defaults(to: 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQLite doesn't support adding a new primary key after creation so we need to create a new table with
|
// 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'
|
// Migrate the 'isPinned' value to 'pinnedPriority'
|
||||||
try SessionThread
|
try SessionThread
|
||||||
.filter(SessionThread.Columns.isPinned == true)
|
.filter(sql: "isPinned = true")
|
||||||
.updateAll(
|
.updateAll(
|
||||||
db,
|
db,
|
||||||
SessionThread.Columns.pinnedPriority.set(to: 1)
|
SessionThread.Columns.pinnedPriority.set(to: 1)
|
||||||
|
|
|
@ -16,9 +16,7 @@ enum _015_BlockCommunityMessageRequests: Migration {
|
||||||
// Add the new 'Profile' properties
|
// Add the new 'Profile' properties
|
||||||
try db.alter(table: Profile.self) { t in
|
try db.alter(table: Profile.self) { t in
|
||||||
t.add(.blocksCommunityMessageRequests, .boolean)
|
t.add(.blocksCommunityMessageRequests, .boolean)
|
||||||
t.add(.lastBlocksCommunityMessageRequests, .integer)
|
t.add(.lastBlocksCommunityMessageRequests, .integer).defaults(to: 0)
|
||||||
.notNull()
|
|
||||||
.defaults(to: 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user exists and the 'checkForCommunityMessageRequests' hasn't already been set then default it to "false"
|
// 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
|
public let name: String
|
||||||
|
|
||||||
/// The timestamp (in seconds since epoch) that the name was last updated
|
/// 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
|
/// A custom name for the profile set by the current user
|
||||||
public let nickname: String?
|
public let nickname: String?
|
||||||
|
@ -54,27 +54,27 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
||||||
public let profileEncryptionKey: Data?
|
public let profileEncryptionKey: Data?
|
||||||
|
|
||||||
/// The timestamp (in seconds since epoch) that the profile picture was last updated
|
/// 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
|
/// A flag indicating whether this profile has reported that it blocks community message requests
|
||||||
public let blocksCommunityMessageRequests: Bool?
|
public let blocksCommunityMessageRequests: Bool?
|
||||||
|
|
||||||
/// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated
|
/// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated
|
||||||
public let lastBlocksCommunityMessageRequests: TimeInterval
|
public let lastBlocksCommunityMessageRequests: TimeInterval?
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
lastNameUpdate: TimeInterval,
|
lastNameUpdate: TimeInterval? = nil,
|
||||||
nickname: String? = nil,
|
nickname: String? = nil,
|
||||||
profilePictureUrl: String? = nil,
|
profilePictureUrl: String? = nil,
|
||||||
profilePictureFileName: String? = nil,
|
profilePictureFileName: String? = nil,
|
||||||
profileEncryptionKey: Data? = nil,
|
profileEncryptionKey: Data? = nil,
|
||||||
lastProfilePictureUpdate: TimeInterval,
|
lastProfilePictureUpdate: TimeInterval? = nil,
|
||||||
blocksCommunityMessageRequests: Bool? = nil,
|
blocksCommunityMessageRequests: Bool? = nil,
|
||||||
lastBlocksCommunityMessageRequests: TimeInterval
|
lastBlocksCommunityMessageRequests: TimeInterval? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -122,14 +122,14 @@ public extension Profile {
|
||||||
self = Profile(
|
self = Profile(
|
||||||
id: try container.decode(String.self, forKey: .id),
|
id: try container.decode(String.self, forKey: .id),
|
||||||
name: try container.decode(String.self, forKey: .name),
|
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),
|
nickname: try? container.decode(String.self, forKey: .nickname),
|
||||||
profilePictureUrl: profilePictureUrl,
|
profilePictureUrl: profilePictureUrl,
|
||||||
profilePictureFileName: try? container.decode(String.self, forKey: .profilePictureFileName),
|
profilePictureFileName: try? container.decode(String.self, forKey: .profilePictureFileName),
|
||||||
profileEncryptionKey: profileKey,
|
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),
|
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.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey)
|
||||||
try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate)
|
try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate)
|
||||||
try container.encodeIfPresent(blocksCommunityMessageRequests, forKey: .blocksCommunityMessageRequests)
|
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(
|
return Profile(
|
||||||
id: id,
|
id: id,
|
||||||
name: "",
|
name: "",
|
||||||
lastNameUpdate: 0,
|
lastNameUpdate: nil,
|
||||||
nickname: nil,
|
nickname: nil,
|
||||||
profilePictureUrl: nil,
|
profilePictureUrl: nil,
|
||||||
profilePictureFileName: nil,
|
profilePictureFileName: nil,
|
||||||
profileEncryptionKey: nil,
|
profileEncryptionKey: nil,
|
||||||
lastProfilePictureUpdate: 0,
|
lastProfilePictureUpdate: nil,
|
||||||
blocksCommunityMessageRequests: nil,
|
blocksCommunityMessageRequests: nil,
|
||||||
lastBlocksCommunityMessageRequests: 0
|
lastBlocksCommunityMessageRequests: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
|
||||||
case variant
|
case variant
|
||||||
case creationDateTimestamp
|
case creationDateTimestamp
|
||||||
case shouldBeVisible
|
case shouldBeVisible
|
||||||
case isPinned
|
@available(*, deprecated, message: "use 'pinnedPriority > 0' instead") case isPinned
|
||||||
case messageDraft
|
case messageDraft
|
||||||
case notificationSound
|
case notificationSound
|
||||||
case mutedUntilTimestamp
|
case mutedUntilTimestamp
|
||||||
|
@ -61,8 +61,8 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
|
||||||
public let shouldBeVisible: Bool
|
public let shouldBeVisible: Bool
|
||||||
|
|
||||||
/// A flag indicating whether the thread is pinned
|
/// A flag indicating whether the thread is pinned
|
||||||
@available(*, unavailable, message: "use 'pinnedPriority' instead")
|
@available(*, deprecated, message: "use 'pinnedPriority > 0' instead")
|
||||||
public let isPinned: Bool = false
|
private let isPinned: Bool = false
|
||||||
|
|
||||||
/// The value the user started entering into the input field before they left the conversation screen
|
/// The value the user started entering into the input field before they left the conversation screen
|
||||||
public let messageDraft: String?
|
public let messageDraft: String?
|
||||||
|
|
|
@ -83,7 +83,9 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func isSessionRunOpenGroup(server: String) -> Bool {
|
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 serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
||||||
let serverHost: String = serverUrl.host
|
let serverHost: String = serverUrl.host
|
||||||
|
|
|
@ -165,12 +165,12 @@ public enum SNProtoError: Error {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_Envelope) throws -> SNProtoEnvelope {
|
fileprivate class func parseProto(_ proto: SessionProtos_Envelope) throws -> SNProtoEnvelope {
|
||||||
guard proto.hasType else {
|
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)
|
let type = SNProtoEnvelopeTypeWrap(proto.type)
|
||||||
|
|
||||||
guard proto.hasTimestamp else {
|
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
|
let timestamp = proto.timestamp
|
||||||
|
|
||||||
|
@ -298,12 +298,12 @@ extension SNProtoEnvelope.SNProtoEnvelopeBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_TypingMessage) throws -> SNProtoTypingMessage {
|
fileprivate class func parseProto(_ proto: SessionProtos_TypingMessage) throws -> SNProtoTypingMessage {
|
||||||
guard proto.hasTimestamp else {
|
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
|
let timestamp = proto.timestamp
|
||||||
|
|
||||||
guard proto.hasAction else {
|
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)
|
let action = SNProtoTypingMessageActionWrap(proto.action)
|
||||||
|
|
||||||
|
@ -410,12 +410,12 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_UnsendRequest) throws -> SNProtoUnsendRequest {
|
fileprivate class func parseProto(_ proto: SessionProtos_UnsendRequest) throws -> SNProtoUnsendRequest {
|
||||||
guard proto.hasTimestamp else {
|
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
|
let timestamp = proto.timestamp
|
||||||
|
|
||||||
guard proto.hasAuthor else {
|
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
|
let author = proto.author
|
||||||
|
|
||||||
|
@ -541,7 +541,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_MessageRequestResponse) throws -> SNProtoMessageRequestResponse {
|
fileprivate class func parseProto(_ proto: SessionProtos_MessageRequestResponse) throws -> SNProtoMessageRequestResponse {
|
||||||
guard proto.hasIsApproved else {
|
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
|
let isApproved = proto.isApproved
|
||||||
|
|
||||||
|
@ -961,12 +961,12 @@ extension SNProtoContent.SNProtoContentBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_CallMessage) throws -> SNProtoCallMessage {
|
fileprivate class func parseProto(_ proto: SessionProtos_CallMessage) throws -> SNProtoCallMessage {
|
||||||
guard proto.hasType else {
|
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)
|
let type = SNProtoCallMessageTypeWrap(proto.type)
|
||||||
|
|
||||||
guard proto.hasUuid else {
|
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
|
let uuid = proto.uuid
|
||||||
|
|
||||||
|
@ -1073,12 +1073,12 @@ extension SNProtoCallMessage.SNProtoCallMessageBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_KeyPair) throws -> SNProtoKeyPair {
|
fileprivate class func parseProto(_ proto: SessionProtos_KeyPair) throws -> SNProtoKeyPair {
|
||||||
guard proto.hasPublicKey else {
|
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
|
let publicKey = proto.publicKey
|
||||||
|
|
||||||
guard proto.hasPrivateKey else {
|
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
|
let privateKey = proto.privateKey
|
||||||
|
|
||||||
|
@ -1211,7 +1211,7 @@ extension SNProtoKeyPair.SNProtoKeyPairBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataExtractionNotification) throws -> SNProtoDataExtractionNotification {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataExtractionNotification) throws -> SNProtoDataExtractionNotification {
|
||||||
guard proto.hasType else {
|
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)
|
let type = SNProtoDataExtractionNotificationTypeWrap(proto.type)
|
||||||
|
|
||||||
|
@ -1620,12 +1620,12 @@ extension SNProtoDataMessageQuoteQuotedAttachment.SNProtoDataMessageQuoteQuotedA
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Quote) throws -> SNProtoDataMessageQuote {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Quote) throws -> SNProtoDataMessageQuote {
|
||||||
guard proto.hasID else {
|
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
|
let id = proto.id
|
||||||
|
|
||||||
guard proto.hasAuthor else {
|
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
|
let author = proto.author
|
||||||
|
|
||||||
|
@ -1755,7 +1755,7 @@ extension SNProtoDataMessageQuote.SNProtoDataMessageQuoteBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Preview) throws -> SNProtoDataMessagePreview {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Preview) throws -> SNProtoDataMessagePreview {
|
||||||
guard proto.hasURL else {
|
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
|
let url = proto.url
|
||||||
|
|
||||||
|
@ -1914,17 +1914,17 @@ extension SNProtoDataMessagePreview.SNProtoDataMessagePreviewBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Reaction) throws -> SNProtoDataMessageReaction {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Reaction) throws -> SNProtoDataMessageReaction {
|
||||||
guard proto.hasID else {
|
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
|
let id = proto.id
|
||||||
|
|
||||||
guard proto.hasAuthor else {
|
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
|
let author = proto.author
|
||||||
|
|
||||||
guard proto.hasAction else {
|
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)
|
let action = SNProtoDataMessageReactionActionWrap(proto.action)
|
||||||
|
|
||||||
|
@ -2032,12 +2032,12 @@ extension SNProtoDataMessageReaction.SNProtoDataMessageReactionBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.OpenGroupInvitation) throws -> SNProtoDataMessageOpenGroupInvitation {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.OpenGroupInvitation) throws -> SNProtoDataMessageOpenGroupInvitation {
|
||||||
guard proto.hasURL else {
|
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
|
let url = proto.url
|
||||||
|
|
||||||
guard proto.hasName else {
|
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
|
let name = proto.name
|
||||||
|
|
||||||
|
@ -2144,12 +2144,12 @@ extension SNProtoDataMessageOpenGroupInvitation.SNProtoDataMessageOpenGroupInvit
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper) throws -> SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper) throws -> SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper {
|
||||||
guard proto.hasPublicKey else {
|
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
|
let publicKey = proto.publicKey
|
||||||
|
|
||||||
guard proto.hasEncryptedKeyPair else {
|
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
|
let encryptedKeyPair = proto.encryptedKeyPair
|
||||||
|
|
||||||
|
@ -2387,7 +2387,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage) throws -> SNProtoDataMessageClosedGroupControlMessage {
|
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage) throws -> SNProtoDataMessageClosedGroupControlMessage {
|
||||||
guard proto.hasType else {
|
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)
|
let type = SNProtoDataMessageClosedGroupControlMessageTypeWrap(proto.type)
|
||||||
|
|
||||||
|
@ -3076,12 +3076,12 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_ConfigurationMessage.Contact) throws -> SNProtoConfigurationMessageContact {
|
fileprivate class func parseProto(_ proto: SessionProtos_ConfigurationMessage.Contact) throws -> SNProtoConfigurationMessageContact {
|
||||||
guard proto.hasPublicKey else {
|
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
|
let publicKey = proto.publicKey
|
||||||
|
|
||||||
guard proto.hasName else {
|
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
|
let name = proto.name
|
||||||
|
|
||||||
|
@ -3396,7 +3396,7 @@ extension SNProtoConfigurationMessage.SNProtoConfigurationMessageBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_ReceiptMessage) throws -> SNProtoReceiptMessage {
|
fileprivate class func parseProto(_ proto: SessionProtos_ReceiptMessage) throws -> SNProtoReceiptMessage {
|
||||||
guard proto.hasType else {
|
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)
|
let type = SNProtoReceiptMessageTypeWrap(proto.type)
|
||||||
|
|
||||||
|
@ -3686,7 +3686,7 @@ extension SNProtoReceiptMessage.SNProtoReceiptMessageBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_AttachmentPointer) throws -> SNProtoAttachmentPointer {
|
fileprivate class func parseProto(_ proto: SessionProtos_AttachmentPointer) throws -> SNProtoAttachmentPointer {
|
||||||
guard proto.hasID else {
|
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
|
let id = proto.id
|
||||||
|
|
||||||
|
@ -3828,17 +3828,17 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: SessionProtos_SharedConfigMessage) throws -> SNProtoSharedConfigMessage {
|
fileprivate class func parseProto(_ proto: SessionProtos_SharedConfigMessage) throws -> SNProtoSharedConfigMessage {
|
||||||
guard proto.hasKind else {
|
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)
|
let kind = SNProtoSharedConfigMessageKindWrap(proto.kind)
|
||||||
|
|
||||||
guard proto.hasSeqno else {
|
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
|
let seqno = proto.seqno
|
||||||
|
|
||||||
guard proto.hasData else {
|
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
|
let data = proto.data
|
||||||
|
|
||||||
|
|
|
@ -123,17 +123,17 @@ public enum WebSocketProtoError: Error {
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage {
|
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage {
|
||||||
guard proto.hasVerb else {
|
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
|
let verb = proto.verb
|
||||||
|
|
||||||
guard proto.hasPath else {
|
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
|
let path = proto.path
|
||||||
|
|
||||||
guard proto.hasRequestID else {
|
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
|
let requestID = proto.requestID
|
||||||
|
|
||||||
|
@ -290,12 +290,12 @@ extension WebSocketProtoWebSocketRequestMessage.WebSocketProtoWebSocketRequestMe
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage {
|
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage {
|
||||||
guard proto.hasRequestID else {
|
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
|
let requestID = proto.requestID
|
||||||
|
|
||||||
guard proto.hasStatus else {
|
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
|
let status = proto.status
|
||||||
|
|
||||||
|
@ -439,7 +439,7 @@ extension WebSocketProtoWebSocketResponseMessage.WebSocketProtoWebSocketResponse
|
||||||
|
|
||||||
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage {
|
fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage {
|
||||||
guard proto.hasType else {
|
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)
|
let type = WebSocketProtoWebSocketMessageTypeWrap(proto.type)
|
||||||
|
|
||||||
|
|
|
@ -88,11 +88,9 @@ extension MessageReceiver {
|
||||||
// Need to check if the blinded id matches for open groups
|
// Need to check if the blinded id matches for open groups
|
||||||
switch senderSessionId.prefix {
|
switch senderSessionId.prefix {
|
||||||
case .blinded15, .blinded25:
|
case .blinded15, .blinded25:
|
||||||
let sodium: Sodium = Sodium()
|
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||||
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
|
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
serverPublicKey: openGroup.publicKey,
|
serverPublicKey: openGroup.publicKey,
|
||||||
edKeyPair: userEdKeyPair,
|
edKeyPair: userEdKeyPair,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public extension Notification.Name {
|
public extension Notification.Name {
|
||||||
static let missedCall = Notification.Name("missedCall")
|
static let missedCall = Notification.Name("missedCall")
|
||||||
|
|
|
@ -58,14 +58,14 @@ internal extension SessionUtil {
|
||||||
let profileNameShouldBeUpdated: Bool = (
|
let profileNameShouldBeUpdated: Bool = (
|
||||||
!data.profile.name.isEmpty &&
|
!data.profile.name.isEmpty &&
|
||||||
profile.name != data.profile.name &&
|
profile.name != data.profile.name &&
|
||||||
profile.lastNameUpdate < data.profile.lastNameUpdate
|
(profile.lastNameUpdate ?? 0) < (data.profile.lastNameUpdate ?? 0)
|
||||||
)
|
)
|
||||||
let profilePictureShouldBeUpdated: Bool = (
|
let profilePictureShouldBeUpdated: Bool = (
|
||||||
(
|
(
|
||||||
profile.profilePictureUrl != data.profile.profilePictureUrl ||
|
profile.profilePictureUrl != data.profile.profilePictureUrl ||
|
||||||
profile.profileEncryptionKey != data.profile.profileEncryptionKey
|
profile.profileEncryptionKey != data.profile.profileEncryptionKey
|
||||||
) &&
|
) &&
|
||||||
profile.lastProfilePictureUpdate < data.profile.lastProfilePictureUpdate
|
(profile.lastProfilePictureUpdate ?? 0) < (data.profile.lastProfilePictureUpdate ?? 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
if
|
if
|
||||||
|
@ -564,8 +564,7 @@ private extension SessionUtil {
|
||||||
count: ProfileManager.avatarAES256KeyByteLength
|
count: ProfileManager.avatarAES256KeyByteLength
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000)
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result[contactId] = ContactData(
|
result[contactId] = ContactData(
|
||||||
|
|
|
@ -510,14 +510,14 @@ public struct ProfileManager {
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
if let name: String = name, !name.isEmpty, name != profile.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.name.set(to: name))
|
||||||
profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp))
|
profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks community message requets flag
|
// 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.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests))
|
||||||
profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp))
|
profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp))
|
||||||
}
|
}
|
||||||
|
@ -526,7 +526,7 @@ public struct ProfileManager {
|
||||||
var avatarNeedsDownload: Bool = false
|
var avatarNeedsDownload: Bool = false
|
||||||
var targetAvatarUrl: String? = nil
|
var targetAvatarUrl: String? = nil
|
||||||
|
|
||||||
if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) {
|
if sentTimestamp > (profile.lastProfilePictureUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) {
|
||||||
switch avatarUpdate {
|
switch avatarUpdate {
|
||||||
case .none: break
|
case .none: break
|
||||||
case .uploadImageData: preconditionFailure("Invalid options for this function")
|
case .uploadImageData: preconditionFailure("Invalid options for this function")
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class FileUploadResponseSpec: QuickSpec {
|
class FileUploadResponseSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a FileUploadResponse
|
||||||
override func spec() {
|
|
||||||
describe("a FileUploadResponse") {
|
describe("a FileUploadResponse") {
|
||||||
|
// MARK: -- when decoding
|
||||||
context("when decoding") {
|
context("when decoding") {
|
||||||
|
// MARK: ---- handles a string id value
|
||||||
it("handles a string id value") {
|
it("handles a string id value") {
|
||||||
let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)!
|
let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)!
|
||||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||||
|
@ -20,6 +21,7 @@ class FileUploadResponseSpec: QuickSpec {
|
||||||
expect(response?.id).to(equal("123"))
|
expect(response?.id).to(equal("123"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- handles an int id value
|
||||||
it("handles an int id value") {
|
it("handles an int id value") {
|
||||||
let jsonData: Data = "{\"id\":124}".data(using: .utf8)!
|
let jsonData: Data = "{\"id\":124}".data(using: .utf8)!
|
||||||
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class BlindedIdLookupSpec: QuickSpec {
|
class BlindedIdLookupSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a BlindedIdLookup
|
||||||
override func spec() {
|
|
||||||
describe("a BlindedIdLookup") {
|
describe("a BlindedIdLookup") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- sets the values correctly
|
||||||
it("sets the values correctly") {
|
it("sets the values correctly") {
|
||||||
let lookup: BlindedIdLookup = BlindedIdLookup(
|
let lookup: BlindedIdLookup = BlindedIdLookup(
|
||||||
blindedId: "testBlindedId",
|
blindedId: "testBlindedId",
|
||||||
|
|
|
@ -10,49 +10,37 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class MessageSendJobSpec: QuickSpec {
|
class MessageSendJobSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var job: Job!
|
||||||
var job: Job!
|
@TestState var interaction: Interaction!
|
||||||
var interaction: Interaction!
|
@TestState var attachment: Attachment! = Attachment(
|
||||||
var attachment: Attachment!
|
|
||||||
var interactionAttachment: InteractionAttachment!
|
|
||||||
var mockStorage: Storage!
|
|
||||||
var mockJobRunner: MockJobRunner!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
// MARK: - JobRunner
|
|
||||||
|
|
||||||
describe("a MessageSendJob") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
|
||||||
customMigrationTargets: [
|
|
||||||
SNUtilitiesKit.self,
|
|
||||||
SNMessagingKit.self
|
|
||||||
]
|
|
||||||
)
|
|
||||||
mockJobRunner = MockJobRunner()
|
|
||||||
dependencies = Dependencies(
|
|
||||||
storage: mockStorage,
|
|
||||||
jobRunner: mockJobRunner,
|
|
||||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
|
||||||
)
|
|
||||||
attachment = Attachment(
|
|
||||||
id: "200",
|
id: "200",
|
||||||
variant: .standard,
|
variant: .standard,
|
||||||
state: .failedDownload,
|
state: .failedDownload,
|
||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
byteCount: 200
|
byteCount: 200
|
||||||
)
|
)
|
||||||
|
@TestState var interactionAttachment: InteractionAttachment!
|
||||||
mockStorage.write { db in
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
try SessionThread.fetchOrCreate(db, id: "Test1", variant: .contact, shouldBeVisible: true)
|
customWriter: try! DatabaseQueue(),
|
||||||
|
customMigrationTargets: [
|
||||||
|
SNUtilitiesKit.self,
|
||||||
|
SNMessagingKit.self
|
||||||
|
],
|
||||||
|
initialData: { db in
|
||||||
|
try SessionThread.fetchOrCreate(
|
||||||
|
db,
|
||||||
|
id: "Test1",
|
||||||
|
variant: .contact,
|
||||||
|
shouldBeVisible: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
mockJobRunner
|
@TestState var mockJobRunner: MockJobRunner! = MockJobRunner(
|
||||||
|
initialSetup: { jobRunner in
|
||||||
|
jobRunner
|
||||||
.when {
|
.when {
|
||||||
$0.jobInfoFor(
|
$0.jobInfoFor(
|
||||||
jobs: nil,
|
jobs: nil,
|
||||||
|
@ -61,7 +49,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([:])
|
.thenReturn([:])
|
||||||
mockJobRunner
|
jobRunner
|
||||||
.when { $0.insert(any(), job: any(), before: any()) }
|
.when { $0.insert(any(), job: any(), before: any()) }
|
||||||
.then { args in
|
.then { args in
|
||||||
let db: Database = args[0] as! Database
|
let db: Database = args[0] as! Database
|
||||||
|
@ -72,14 +60,16 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
.thenReturn((1000, Job(variant: .messageSend)))
|
.thenReturn((1000, Job(variant: .messageSend)))
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
jobRunner: mockJobRunner,
|
||||||
|
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||||
|
)
|
||||||
|
|
||||||
afterEach {
|
// MARK: - a MessageSendJob
|
||||||
job = nil
|
describe("a MessageSendJob") {
|
||||||
mockStorage = nil
|
// MARK: -- fails when not given any details
|
||||||
dependencies = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - fails when not given any details
|
|
||||||
it("fails when not given any details") {
|
it("fails when not given any details") {
|
||||||
job = Job(variant: .messageSend)
|
job = Job(variant: .messageSend)
|
||||||
|
|
||||||
|
@ -102,7 +92,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - fails when given incorrect details
|
// MARK: -- fails when given incorrect details
|
||||||
it("fails when given incorrect details") {
|
it("fails when given incorrect details") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -128,7 +118,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - of VisibleMessage
|
// MARK: -- of VisibleMessage
|
||||||
context("of VisibleMessage") {
|
context("of VisibleMessage") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
interaction = Interaction(
|
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") {
|
it("fails when there is no job id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -199,7 +189,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
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") {
|
it("fails when there is no interaction id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -230,7 +220,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
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") {
|
it("fails when there is no interaction for the provided interaction id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -263,7 +253,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- with an attachment
|
// MARK: ---- with an attachment
|
||||||
context("with an attachment") {
|
context("with an attachment") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
interactionAttachment = InteractionAttachment(
|
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") {
|
it("it fails when trying to send with an attachment which previously failed to download") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try attachment.with(state: .failedDownload).save(db)
|
try attachment.with(state: .failedDownload).save(db)
|
||||||
|
@ -303,7 +293,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ---- with a pending upload
|
// MARK: ------ with a pending upload
|
||||||
context("with a pending upload") {
|
context("with a pending upload") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("it defers when trying to send with an attachment which is still pending upload") {
|
||||||
var didDefer: Bool = false
|
var didDefer: Bool = false
|
||||||
|
|
||||||
|
@ -331,7 +321,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(didDefer).to(beTrue())
|
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") {
|
it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") {
|
||||||
var didDefer: Bool = false
|
var didDefer: Bool = false
|
||||||
|
|
||||||
|
@ -356,7 +346,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
expect(didDefer).to(beTrue())
|
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") {
|
it("inserts an attachment upload job before the message send job") {
|
||||||
mockJobRunner
|
mockJobRunner
|
||||||
.when {
|
.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") {
|
it("creates a dependency between the new job and the existing one") {
|
||||||
MessageSendJob.run(
|
MessageSendJob.run(
|
||||||
job,
|
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
|
import Nimble
|
||||||
|
|
||||||
class SessionUtilSpec: QuickSpec {
|
class SessionUtilSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - SessionUtil
|
||||||
override func spec() {
|
|
||||||
describe("SessionUtil") {
|
describe("SessionUtil") {
|
||||||
// MARK: - Parsing URLs
|
// MARK: -- when parsing a community url
|
||||||
|
|
||||||
context("when parsing a community url") {
|
context("when parsing a community url") {
|
||||||
|
// MARK: ---- handles the example urls correctly
|
||||||
it("handles the example urls correctly") {
|
it("handles the example urls correctly") {
|
||||||
let validUrls: [String] = [
|
let validUrls: [String] = [
|
||||||
[
|
[
|
||||||
|
@ -82,6 +81,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(processedPublicKeys).to(equal(expectedPublicKeys))
|
expect(processedPublicKeys).to(equal(expectedPublicKeys))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- handles the r prefix if present
|
||||||
it("handles the r prefix if present") {
|
it("handles the r prefix if present") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -95,6 +95,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
|
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- fails if no scheme is provided
|
||||||
it("fails if no scheme is provided") {
|
it("fails if no scheme is provided") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -108,6 +109,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(beNil())
|
expect(info?.publicKey).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- fails if there is no room
|
||||||
it("fails if there is no room") {
|
it("fails if there is no room") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -121,6 +123,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(beNil())
|
expect(info?.publicKey).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- fails if there is no public key parameter
|
||||||
it("fails if there is no public key parameter") {
|
it("fails if there is no public key parameter") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: "https://sessionopengroup.co/r/main"
|
url: "https://sessionopengroup.co/r/main"
|
||||||
|
@ -131,6 +134,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(beNil())
|
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") {
|
it("fails if the public key parameter is not 64 characters") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -144,6 +148,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(beNil())
|
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") {
|
it("fails if the public key parameter is not a hex string") {
|
||||||
let info = SessionUtil.parseCommunity(
|
let info = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -157,6 +162,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(info?.publicKey).to(beNil())
|
expect(info?.publicKey).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- maintains the same TLS
|
||||||
it("maintains the same TLS") {
|
it("maintains the same TLS") {
|
||||||
let server1 = SessionUtil.parseCommunity(
|
let server1 = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -175,6 +181,7 @@ class SessionUtilSpec: QuickSpec {
|
||||||
expect(server2).to(equal("https://sessionopengroup.co"))
|
expect(server2).to(equal("https://sessionopengroup.co"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- maintains the same port
|
||||||
it("maintains the same port") {
|
it("maintains the same port") {
|
||||||
let server1 = SessionUtil.parseCommunity(
|
let server1 = SessionUtil.parseCommunity(
|
||||||
url: [
|
url: [
|
||||||
|
@ -194,14 +201,15 @@ class SessionUtilSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Generating URLs
|
// MARK: -- when generating a url
|
||||||
|
|
||||||
context("when generating a url") {
|
context("when generating a url") {
|
||||||
|
// MARK: ---- generates the url correctly
|
||||||
it("generates the url correctly") {
|
it("generates the url correctly") {
|
||||||
expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||||
.to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
.to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- maintains the casing provided
|
||||||
it("maintains the casing provided") {
|
it("maintains the casing provided") {
|
||||||
expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||||
.to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
.to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
|
||||||
|
|
|
@ -9,16 +9,15 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a String
|
||||||
override func spec() {
|
|
||||||
// MARK: - String
|
|
||||||
|
|
||||||
describe("a String") {
|
describe("a String") {
|
||||||
|
// MARK: -- can convert to a cArray
|
||||||
it("can convert to a cArray") {
|
it("can convert to a cArray") {
|
||||||
expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51]))
|
expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- can contain emoji
|
||||||
it("can contain emoji") {
|
it("can contain emoji") {
|
||||||
let original: String = "Hi 👋"
|
let original: String = "Hi 👋"
|
||||||
let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession()
|
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))
|
expect(result).to(equal(original))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when initialised with a pointer and length
|
||||||
context("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") {
|
it("returns null when given a null pointer") {
|
||||||
let test: [CChar] = [84, 101, 115, 116]
|
let test: [CChar] = [84, 101, 115, 116]
|
||||||
let result = test.withUnsafeBufferPointer { ptr in
|
let result = test.withUnsafeBufferPointer { ptr in
|
||||||
|
@ -37,6 +38,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
expect(result).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns a truncated string when given an incorrect length
|
||||||
it("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 test: [CChar] = [84, 101, 115, 116]
|
||||||
let result = test.withUnsafeBufferPointer { ptr in
|
let result = test.withUnsafeBufferPointer { ptr in
|
||||||
|
@ -46,6 +48,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("Te"))
|
expect(result).to(equal("Te"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns a string when valid
|
||||||
it("returns a string when valid") {
|
it("returns a string when valid") {
|
||||||
let test: [CChar] = [84, 101, 115, 116]
|
let test: [CChar] = [84, 101, 115, 116]
|
||||||
let result = test.withUnsafeBufferPointer { ptr in
|
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") {
|
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") {
|
it("returns a string when valid and has no fixed length") {
|
||||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0)
|
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0)
|
||||||
let result = String(libSessionVal: value, fixedLength: .none)
|
let result = String(libSessionVal: value, fixedLength: .none)
|
||||||
|
@ -64,6 +69,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("Test"))
|
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") {
|
it("returns a string when valid and has a fixed length") {
|
||||||
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||||
let result = String(libSessionVal: value, fixedLength: 5)
|
let result = String(libSessionVal: value, fixedLength: 5)
|
||||||
|
@ -71,6 +77,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("Te\0st"))
|
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") {
|
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 value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116)
|
||||||
let result = String(libSessionVal: value, fixedLength: .none)
|
let result = String(libSessionVal: value, fixedLength: .none)
|
||||||
|
@ -78,6 +85,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("Te"))
|
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") {
|
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 value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84)
|
||||||
let result = String(libSessionVal: value, fixedLength: .none)
|
let result = String(libSessionVal: value, fixedLength: .none)
|
||||||
|
@ -85,6 +93,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("TestT"))
|
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") {
|
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 value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||||
let result = String(libSessionVal: value, fixedLength: 5)
|
let result = String(libSessionVal: value, fixedLength: 5)
|
||||||
|
@ -92,6 +101,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal(""))
|
expect(result).to(equal(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- defaults the fixed length value to none
|
||||||
it("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 value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0)
|
||||||
let result = String(libSessionVal: value)
|
let result = String(libSessionVal: value)
|
||||||
|
@ -99,6 +109,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal("Te"))
|
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") {
|
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 value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||||
let result = String(libSessionVal: value, nullIfEmpty: false)
|
let result = String(libSessionVal: value, nullIfEmpty: false)
|
||||||
|
@ -106,6 +117,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal(""))
|
expect(result).to(equal(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns null when specified and empty
|
||||||
it("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 value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||||
let result = String(libSessionVal: value, nullIfEmpty: true)
|
let result = String(libSessionVal: value, nullIfEmpty: true)
|
||||||
|
@ -113,6 +125,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
expect(result).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- defaults the null if empty flag to false
|
||||||
it("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 value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0)
|
||||||
let result = String(libSessionVal: value)
|
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") {
|
context("when converting to a libSession value") {
|
||||||
|
// MARK: ---- succeeeds with a valid value
|
||||||
it("succeeeds with a valid value") {
|
it("succeeeds with a valid value") {
|
||||||
let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession()
|
let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession()
|
||||||
expect(result.0).to(equal(84))
|
expect(result.0).to(equal(84))
|
||||||
|
@ -131,6 +146,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(0))
|
expect(result.4).to(equal(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- truncates when too long
|
||||||
it("truncates when too long") {
|
it("truncates when too long") {
|
||||||
let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession()
|
let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession()
|
||||||
expect(result.0).to(equal(84))
|
expect(result.0).to(equal(84))
|
||||||
|
@ -140,7 +156,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(84))
|
expect(result.4).to(equal(84))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- when optional
|
||||||
context("when optional") {
|
context("when optional") {
|
||||||
|
// MARK: ------ returns empty when null
|
||||||
context("returns empty when null") {
|
context("returns empty when null") {
|
||||||
let value: String? = nil
|
let value: String? = nil
|
||||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||||
|
@ -152,6 +170,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(0))
|
expect(result.4).to(equal(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ returns a libSession value when not null
|
||||||
context("returns a libSession value when not null") {
|
context("returns a libSession value when not null") {
|
||||||
let value: String? = "Test"
|
let value: String? = "Test"
|
||||||
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession()
|
||||||
|
@ -167,13 +186,15 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Data
|
// MARK: - Data
|
||||||
|
|
||||||
describe("Data") {
|
describe("Data") {
|
||||||
|
// MARK: -- can convert to a cArray
|
||||||
it("can convert to a cArray") {
|
it("can convert to a cArray") {
|
||||||
expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3]))
|
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") {
|
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") {
|
it("returns truncated data when given the wrong length") {
|
||||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||||
let result = Data(libSessionVal: value, count: 2)
|
let result = Data(libSessionVal: value, count: 2)
|
||||||
|
@ -181,6 +202,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal(Data([1, 2])))
|
expect(result).to(equal(Data([1, 2])))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns data when valid
|
||||||
it("returns data when valid") {
|
it("returns data when valid") {
|
||||||
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5)
|
||||||
let result = Data(libSessionVal: value, count: 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])))
|
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") {
|
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 value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false)
|
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])))
|
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") {
|
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 value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0)
|
||||||
let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true)
|
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") {
|
context("when converting to a libSession value") {
|
||||||
|
// MARK: ---- succeeeds with a valid value
|
||||||
it("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()
|
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession()
|
||||||
expect(result.0).to(equal(1))
|
expect(result.0).to(equal(1))
|
||||||
|
@ -213,6 +239,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(5))
|
expect(result.4).to(equal(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- truncates when too long
|
||||||
it("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()
|
let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession()
|
||||||
expect(result.0).to(equal(1))
|
expect(result.0).to(equal(1))
|
||||||
|
@ -222,6 +249,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(1))
|
expect(result.4).to(equal(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- fills with empty data when too short
|
||||||
context("fills with empty data when too short") {
|
context("fills with empty data when too short") {
|
||||||
let value: Data? = Data([1, 2, 3])
|
let value: Data? = Data([1, 2, 3])
|
||||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||||
|
@ -233,7 +261,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(0))
|
expect(result.4).to(equal(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- when optional
|
||||||
context("when optional") {
|
context("when optional") {
|
||||||
|
// MARK: ------ returns null when null
|
||||||
context("returns null when null") {
|
context("returns null when null") {
|
||||||
let value: Data? = nil
|
let value: Data? = nil
|
||||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||||
|
@ -245,6 +275,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result.4).to(equal(0))
|
expect(result.4).to(equal(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ returns a libSession value when not null
|
||||||
context("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 value: Data? = Data([1, 2, 3, 4, 5])
|
||||||
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession()
|
||||||
|
@ -259,10 +290,11 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Array
|
// MARK: - an Array
|
||||||
|
|
||||||
describe("an Array") {
|
describe("an Array") {
|
||||||
|
// MARK: -- when initialised with a 2D C array
|
||||||
context("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") {
|
it("returns the correct array") {
|
||||||
var test: [CChar] = (
|
var test: [CChar] = (
|
||||||
"Test1".cArray.nullTerminated() +
|
"Test1".cArray.nullTerminated() +
|
||||||
|
@ -278,6 +310,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"]))
|
expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns an empty array if given one
|
||||||
it("returns an empty array if given one") {
|
it("returns an empty array if given one") {
|
||||||
var test = [CChar]()
|
var test = [CChar]()
|
||||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||||
|
@ -289,6 +322,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal([]))
|
expect(result).to(equal([]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- handles empty strings without issues
|
||||||
it("handles empty strings without issues") {
|
it("handles empty strings without issues") {
|
||||||
var test: [CChar] = (
|
var test: [CChar] = (
|
||||||
"Test1".cArray.nullTerminated() +
|
"Test1".cArray.nullTerminated() +
|
||||||
|
@ -304,10 +338,12 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(equal(["Test1", "", "Test2"]))
|
expect(result).to(equal(["Test1", "", "Test2"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns null when given a null pointer
|
||||||
it("returns null when given a null pointer") {
|
it("returns null when given a null pointer") {
|
||||||
expect([String](pointer: nil, count: 5)).to(beNil())
|
expect([String](pointer: nil, count: 5)).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns null when given a null count
|
||||||
it("returns null when given a null count") {
|
it("returns null when given a null count") {
|
||||||
var test: [CChar] = "Test1".cArray.nullTerminated()
|
var test: [CChar] = "Test1".cArray.nullTerminated()
|
||||||
let result = test.withUnsafeMutableBufferPointer { ptr in
|
let result = test.withUnsafeMutableBufferPointer { ptr in
|
||||||
|
@ -319,19 +355,23 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
expect(result).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns the default value if given null values
|
||||||
it("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"]))
|
expect([String](pointer: nil, count: 5, defaultValue: ["Test"]))
|
||||||
.to(equal(["Test"]))
|
.to(equal(["Test"]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when adding a null terminated character
|
||||||
context("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") {
|
it("adds a null termination character when not present") {
|
||||||
let value: [CChar] = [1, 2, 3, 4, 5]
|
let value: [CChar] = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0]))
|
expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- adds nothing when already present
|
||||||
it("adds nothing when already present") {
|
it("adds nothing when already present") {
|
||||||
let value: [CChar] = [1, 2, 3, 4, 0]
|
let value: [CChar] = [1, 2, 3, 4, 0]
|
||||||
|
|
||||||
|
|
|
@ -9,22 +9,19 @@ import Quick
|
||||||
import Nimble
|
import Nimble
|
||||||
|
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
class BatchRequestInfoSpec: QuickSpec {
|
class BatchRequestInfoSpec: QuickSpec {
|
||||||
struct TestType: Codable, Equatable {
|
override class func spec() {
|
||||||
let stringValue: String
|
// MARK: Configuration
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
@TestState var request: OpenGroupAPI.BatchRequest!
|
||||||
|
|
||||||
override func spec() {
|
// MARK: - a BatchRequest.Child
|
||||||
// MARK: - BatchRequest.Child
|
|
||||||
|
|
||||||
describe("a BatchRequest.Child") {
|
describe("a BatchRequest.Child") {
|
||||||
var request: OpenGroupAPI.BatchRequest!
|
// MARK: -- when encoding
|
||||||
|
|
||||||
context("when encoding") {
|
context("when encoding") {
|
||||||
|
// MARK: ---- successfully encodes a string body
|
||||||
it("successfully encodes a string body") {
|
it("successfully encodes a string body") {
|
||||||
request = OpenGroupAPI.BatchRequest(
|
request = OpenGroupAPI.BatchRequest(
|
||||||
requests: [
|
requests: [
|
||||||
|
@ -53,6 +50,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
||||||
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))
|
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- successfully encodes a byte body
|
||||||
it("successfully encodes a byte body") {
|
it("successfully encodes a byte body") {
|
||||||
request = OpenGroupAPI.BatchRequest(
|
request = OpenGroupAPI.BatchRequest(
|
||||||
requests: [
|
requests: [
|
||||||
|
@ -81,6 +79,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
||||||
expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3]))
|
expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- successfully encodes a JSON body
|
||||||
it("successfully encodes a JSON body") {
|
it("successfully encodes a JSON body") {
|
||||||
request = OpenGroupAPI.BatchRequest(
|
request = OpenGroupAPI.BatchRequest(
|
||||||
requests: [
|
requests: [
|
||||||
|
@ -109,6 +108,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
||||||
expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"]))
|
expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- strips authentication headers
|
||||||
it("strips authentication headers") {
|
it("strips authentication headers") {
|
||||||
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
||||||
method: .get,
|
method: .get,
|
||||||
|
@ -149,6 +149,7 @@ class BatchRequestInfoSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- does not strip non authentication headers
|
||||||
it("does not strip non authentication headers") {
|
it("does not strip non authentication headers") {
|
||||||
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
let httpRequest: Request<NoBody, OpenGroupAPI.Endpoint> = Request<NoBody, OpenGroupAPI.Endpoint>(
|
||||||
method: .get,
|
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
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class CapabilitiesSpec: QuickSpec {
|
class CapabilitiesSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - Capabilities
|
||||||
override func spec() {
|
|
||||||
describe("Capabilities") {
|
describe("Capabilities") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- assigns values correctly
|
||||||
it("assigns values correctly") {
|
it("assigns values correctly") {
|
||||||
let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(
|
let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(
|
||||||
capabilities: [.sogs],
|
capabilities: [.sogs],
|
||||||
|
@ -34,8 +35,11 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - a Capability
|
||||||
describe("a Capability") {
|
describe("a Capability") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- succeeeds with a valid case
|
||||||
it("succeeeds with a valid case") {
|
it("succeeeds with a valid case") {
|
||||||
let capability: Capability.Variant = Capability.Variant(
|
let capability: Capability.Variant = Capability.Variant(
|
||||||
from: "sogs"
|
from: "sogs"
|
||||||
|
@ -44,6 +48,7 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
expect(capability).to(equal(.sogs))
|
expect(capability).to(equal(.sogs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- wraps an unknown value in the unsupported case
|
||||||
it("wraps an unknown value in the unsupported case") {
|
it("wraps an unknown value in the unsupported case") {
|
||||||
let capability: Capability.Variant = Capability.Variant(
|
let capability: Capability.Variant = Capability.Variant(
|
||||||
from: "test"
|
from: "test"
|
||||||
|
@ -53,18 +58,23 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when accessing the rawValue
|
||||||
context("when accessing the rawValue") {
|
context("when accessing the rawValue") {
|
||||||
|
// MARK: ---- provides known cases exactly
|
||||||
it("provides known cases exactly") {
|
it("provides known cases exactly") {
|
||||||
expect(Capability.Variant.sogs.rawValue).to(equal("sogs"))
|
expect(Capability.Variant.sogs.rawValue).to(equal("sogs"))
|
||||||
expect(Capability.Variant.blind.rawValue).to(equal("blind"))
|
expect(Capability.Variant.blind.rawValue).to(equal("blind"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- provides the wrapped value for unsupported cases
|
||||||
it("provides the wrapped value for unsupported cases") {
|
it("provides the wrapped value for unsupported cases") {
|
||||||
expect(Capability.Variant.unsupported("test").rawValue).to(equal("test"))
|
expect(Capability.Variant.unsupported("test").rawValue).to(equal("test"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when Decoding
|
||||||
context("when Decoding") {
|
context("when Decoding") {
|
||||||
|
// MARK: ---- decodes known cases exactly
|
||||||
it("decodes known cases exactly") {
|
it("decodes known cases exactly") {
|
||||||
expect(
|
expect(
|
||||||
try? JSONDecoder().decode(
|
try? JSONDecoder().decode(
|
||||||
|
@ -82,6 +92,7 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
.to(equal(.blind))
|
.to(equal(.blind))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- decodes unknown cases into the unsupported case
|
||||||
it("decodes unknown cases into the unsupported case") {
|
it("decodes unknown cases into the unsupported case") {
|
||||||
expect(
|
expect(
|
||||||
try? JSONDecoder().decode(
|
try? JSONDecoder().decode(
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class OpenGroupSpec: QuickSpec {
|
class OpenGroupSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - an Open Group
|
||||||
override func spec() {
|
|
||||||
describe("an Open Group") {
|
describe("an Open Group") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- generates the id
|
||||||
it("generates the id") {
|
it("generates the id") {
|
||||||
let openGroup: OpenGroup = OpenGroup(
|
let openGroup: OpenGroup = OpenGroup(
|
||||||
server: "server",
|
server: "server",
|
||||||
|
@ -34,7 +35,9 @@ class OpenGroupSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when describing
|
||||||
context("when describing") {
|
context("when describing") {
|
||||||
|
// MARK: ---- includes relevant information
|
||||||
it("includes relevant information") {
|
it("includes relevant information") {
|
||||||
let openGroup: OpenGroup = OpenGroup(
|
let openGroup: OpenGroup = OpenGroup(
|
||||||
server: "server",
|
server: "server",
|
||||||
|
@ -57,7 +60,9 @@ class OpenGroupSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when describing in debug
|
||||||
context("when describing in debug") {
|
context("when describing in debug") {
|
||||||
|
// MARK: ---- includes relevant information
|
||||||
it("includes relevant information") {
|
it("includes relevant information") {
|
||||||
let openGroup: OpenGroup = OpenGroup(
|
let openGroup: OpenGroup = OpenGroup(
|
||||||
server: "server",
|
server: "server",
|
||||||
|
@ -80,15 +85,19 @@ class OpenGroupSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when generating an id
|
||||||
context("when generating an id") {
|
context("when generating an id") {
|
||||||
|
// MARK: ---- generates correctly
|
||||||
it("generates correctly") {
|
it("generates correctly") {
|
||||||
expect(OpenGroup.idFor(roomToken: "room", server: "server")).to(equal("server.room"))
|
expect(OpenGroup.idFor(roomToken: "room", server: "server")).to(equal("server.room"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- converts the server to lowercase
|
||||||
it("converts the server to lowercase") {
|
it("converts the server to lowercase") {
|
||||||
expect(OpenGroup.idFor(roomToken: "room", server: "SeRVeR")).to(equal("server.room"))
|
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") {
|
it("maintains the casing of the roomToken") {
|
||||||
expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM"))
|
expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class RoomPollInfoSpec: QuickSpec {
|
class RoomPollInfoSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a RoomPollInfo
|
||||||
override func spec() {
|
|
||||||
describe("a RoomPollInfo") {
|
describe("a RoomPollInfo") {
|
||||||
|
// MARK: -- when initializing with a room
|
||||||
context("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") {
|
it("copies all the relevant values across") {
|
||||||
let room: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
let room: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
||||||
token: "testToken",
|
token: "testToken",
|
||||||
|
@ -60,7 +61,9 @@ class RoomPollInfoSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when decoding
|
||||||
context("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") {
|
it("defaults admin and moderator values to false if omitted") {
|
||||||
let roomPollInfoJson: String = """
|
let roomPollInfoJson: String = """
|
||||||
{
|
{
|
||||||
|
@ -87,6 +90,7 @@ class RoomPollInfoSpec: QuickSpec {
|
||||||
expect(result.globalModerator).to(beFalse())
|
expect(result.globalModerator).to(beFalse())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- sets the admin and moderator values when provided
|
||||||
it("sets the admin and moderator values when provided") {
|
it("sets the admin and moderator values when provided") {
|
||||||
let roomPollInfoJson: String = """
|
let roomPollInfoJson: String = """
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class RoomSpec: QuickSpec {
|
class RoomSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a Room
|
||||||
override func spec() {
|
|
||||||
describe("a Room") {
|
describe("a Room") {
|
||||||
|
// MARK: -- when decoding
|
||||||
context("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") {
|
it("defaults admin and moderator values to false if omitted") {
|
||||||
let roomJson: String = """
|
let roomJson: String = """
|
||||||
{
|
{
|
||||||
|
@ -52,6 +53,7 @@ class RoomSpec: QuickSpec {
|
||||||
expect(result.globalModerator).to(beFalse())
|
expect(result.globalModerator).to(beFalse())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- sets the admin and moderator values when provided
|
||||||
it("sets the admin and moderator values when provided") {
|
it("sets the admin and moderator values when provided") {
|
||||||
let roomJson: String = """
|
let roomJson: String = """
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,18 +9,10 @@ import SessionUtilitiesKit
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SOGSMessageSpec: QuickSpec {
|
class SOGSMessageSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var messageJson: String! = """
|
||||||
describe("a SOGSMessage") {
|
|
||||||
var messageJson: String!
|
|
||||||
var messageData: Data!
|
|
||||||
var decoder: JSONDecoder!
|
|
||||||
var mockCrypto: MockCrypto!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
messageJson = """
|
|
||||||
{
|
{
|
||||||
"id": 123,
|
"id": 123,
|
||||||
"session_id": "05\(TestConstants.publicKey)",
|
"session_id": "05\(TestConstants.publicKey)",
|
||||||
|
@ -33,20 +25,22 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
"signature": "VGVzdFNpZ25hdHVyZQ=="
|
"signature": "VGVzdFNpZ25hdHVyZQ=="
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
messageData = messageJson.data(using: .utf8)!
|
@TestState var messageData: Data! = messageJson.data(using: .utf8)!
|
||||||
mockCrypto = MockCrypto()
|
@TestState var mockCrypto: MockCrypto! = MockCrypto()
|
||||||
dependencies = Dependencies(
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
crypto: mockCrypto
|
crypto: mockCrypto
|
||||||
)
|
)
|
||||||
decoder = JSONDecoder()
|
@TestState var decoder: JSONDecoder! = {
|
||||||
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
let result = JSONDecoder()
|
||||||
}
|
result.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||||
|
return result
|
||||||
afterEach {
|
}()
|
||||||
mockCrypto = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: - a SOGSMessage
|
||||||
|
describe("a SOGSMessage") {
|
||||||
|
// MARK: -- when decoding
|
||||||
context("when decoding") {
|
context("when decoding") {
|
||||||
|
// MARK: ---- defaults the whisper values to false
|
||||||
it("defaults the whisper values to false") {
|
it("defaults the whisper values to false") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -63,7 +57,9 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
expect(result?.whisperMods).to(beFalse())
|
expect(result?.whisperMods).to(beFalse())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- and there is no content
|
||||||
context("and there is no content") {
|
context("and there is no content") {
|
||||||
|
// MARK: ------ does not need a sender
|
||||||
it("does not need a sender") {
|
it("does not need a sender") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -84,7 +80,9 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- and there is content
|
||||||
context("and there is content") {
|
context("and there is content") {
|
||||||
|
// MARK: ------ errors if there is no sender
|
||||||
it("errors if there is no sender") {
|
it("errors if there is no sender") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -106,6 +104,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.to(throwError(HTTPError.parsingFailed))
|
.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") {
|
it("errors if the data is not a base64 encoded string") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -128,6 +127,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.to(throwError(HTTPError.parsingFailed))
|
.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") {
|
it("errors if the signature is not a base64 encoded string") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -150,6 +150,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.to(throwError(HTTPError.parsingFailed))
|
.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") {
|
it("errors if the dependencies are not provided to the JSONDecoder") {
|
||||||
decoder = JSONDecoder()
|
decoder = JSONDecoder()
|
||||||
|
|
||||||
|
@ -159,6 +160,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.to(throwError(HTTPError.parsingFailed))
|
.to(throwError(HTTPError.parsingFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ errors if the session_id value is not valid
|
||||||
it("errors if the session_id value is not valid") {
|
it("errors if the session_id value is not valid") {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
{
|
{
|
||||||
|
@ -181,7 +183,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.to(throwError(HTTPError.parsingFailed))
|
.to(throwError(HTTPError.parsingFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ that is blinded
|
||||||
context("that is blinded") {
|
context("that is blinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
|
@ -200,6 +202,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
messageData = messageJson.data(using: .utf8)!
|
messageData = messageJson.data(using: .utf8)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- succeeds if it succeeds verification
|
||||||
it("succeeds if it succeeds verification") {
|
it("succeeds if it succeeds verification") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -213,6 +216,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.toNot(beNil())
|
.toNot(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- provides the correct values as parameters
|
||||||
it("provides the correct values as parameters") {
|
it("provides the correct values as parameters") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -234,6 +238,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- throws if it fails verification
|
||||||
it("throws if it fails verification") {
|
it("throws if it fails verification") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -248,7 +253,9 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ that is unblinded
|
||||||
context("that is unblinded") {
|
context("that is unblinded") {
|
||||||
|
// MARK: -------- succeeds if it succeeds verification
|
||||||
it("succeeds if it succeeds verification") {
|
it("succeeds if it succeeds verification") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||||
|
@ -260,6 +267,7 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
.toNot(beNil())
|
.toNot(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- provides the correct values as parameters
|
||||||
it("provides the correct values as parameters") {
|
it("provides the correct values as parameters") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
.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") {
|
it("throws if it fails verification") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SendDirectMessageRequestSpec: QuickSpec {
|
class SendDirectMessageRequestSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a SendDirectMessageRequest
|
||||||
override func spec() {
|
|
||||||
describe("a SendDirectMessageRequest") {
|
describe("a SendDirectMessageRequest") {
|
||||||
|
// MARK: -- when encoding
|
||||||
context("when encoding") {
|
context("when encoding") {
|
||||||
|
// MARK: ---- encodes the data as a base64 string
|
||||||
it("encodes the data as a base64 string") {
|
it("encodes the data as a base64 string") {
|
||||||
let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest(
|
let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest(
|
||||||
message: "TestData".data(using: .utf8)!
|
message: "TestData".data(using: .utf8)!
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SendMessageRequestSpec: QuickSpec {
|
class SendMessageRequestSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a SendMessageRequest
|
||||||
override func spec() {
|
|
||||||
describe("a SendMessageRequest") {
|
describe("a SendMessageRequest") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- defaults the optional values to nil
|
||||||
it("defaults the optional values to nil") {
|
it("defaults the optional values to nil") {
|
||||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||||
data: "TestData".data(using: .utf8)!,
|
data: "TestData".data(using: .utf8)!,
|
||||||
|
@ -25,7 +26,9 @@ class SendMessageRequestSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when encoding
|
||||||
context("when encoding") {
|
context("when encoding") {
|
||||||
|
// MARK: ---- encodes the data as a base64 string
|
||||||
it("encodes the data as a base64 string") {
|
it("encodes the data as a base64 string") {
|
||||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||||
data: "TestData".data(using: .utf8)!,
|
data: "TestData".data(using: .utf8)!,
|
||||||
|
@ -41,6 +44,7 @@ class SendMessageRequestSpec: QuickSpec {
|
||||||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- encodes the signature as a base64 string
|
||||||
it("encodes the signature as a base64 string") {
|
it("encodes the signature as a base64 string") {
|
||||||
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest(
|
||||||
data: "TestData".data(using: .utf8)!,
|
data: "TestData".data(using: .utf8)!,
|
||||||
|
|
|
@ -8,11 +8,12 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class UpdateMessageRequestSpec: QuickSpec {
|
class UpdateMessageRequestSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - an UpdateMessageRequest
|
||||||
override func spec() {
|
describe("an UpdateMessageRequest") {
|
||||||
describe("a UpdateMessageRequest") {
|
// MARK: -- when encoding
|
||||||
context("when encoding") {
|
context("when encoding") {
|
||||||
|
// MARK: ---- encodes the data as a base64 string
|
||||||
it("encodes the data as a base64 string") {
|
it("encodes the data as a base64 string") {
|
||||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||||
data: "TestData".data(using: .utf8)!,
|
data: "TestData".data(using: .utf8)!,
|
||||||
|
@ -26,6 +27,7 @@ class UpdateMessageRequestSpec: QuickSpec {
|
||||||
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
expect(requestDataString).to(contain("VGVzdERhdGE="))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- encodes the signature as a base64 string
|
||||||
it("encodes the signature as a base64 string") {
|
it("encodes the signature as a base64 string") {
|
||||||
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest(
|
||||||
data: "TestData".data(using: .utf8)!,
|
data: "TestData".data(using: .utf8)!,
|
||||||
|
|
|
@ -13,38 +13,16 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class OpenGroupAPISpec: QuickSpec {
|
class OpenGroupAPISpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
var mockStorage: Storage!
|
|
||||||
var mockNetwork: MockNetwork!
|
|
||||||
var mockCrypto: MockCrypto!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
var disposables: [AnyCancellable] = []
|
|
||||||
|
|
||||||
var error: Error?
|
|
||||||
|
|
||||||
describe("an OpenGroupAPI") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNMessagingKit.self
|
SNMessagingKit.self
|
||||||
]
|
],
|
||||||
)
|
initialData: { db in
|
||||||
mockNetwork = MockNetwork()
|
|
||||||
mockCrypto = MockCrypto()
|
|
||||||
dependencies = Dependencies(
|
|
||||||
storage: mockStorage,
|
|
||||||
network: mockNetwork,
|
|
||||||
crypto: mockCrypto,
|
|
||||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
|
||||||
)
|
|
||||||
|
|
||||||
mockStorage.write { db in
|
|
||||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db)
|
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: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db)
|
||||||
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
|
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
|
||||||
|
@ -66,13 +44,18 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
).insert(db)
|
).insert(db)
|
||||||
try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db)
|
try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
mockCrypto
|
@TestState var mockNetwork: MockNetwork! = MockNetwork()
|
||||||
|
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||||
|
initialSetup: { crypto in
|
||||||
|
crypto
|
||||||
.when { try $0.perform(.hash(message: anyArray(), outputLength: any())) }
|
.when { try $0.perform(.hash(message: anyArray(), outputLength: any())) }
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
mockCrypto
|
crypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { crypto in
|
||||||
crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies))
|
crypto.generate(
|
||||||
|
.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: any())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
|
@ -80,7 +63,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mockCrypto
|
crypto
|
||||||
.when {
|
.when {
|
||||||
try $0.perform(
|
try $0.perform(
|
||||||
.sogsSignature(
|
.sogsSignature(
|
||||||
|
@ -92,35 +75,34 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn("TestSogsSignature".bytes)
|
.thenReturn("TestSogsSignature".bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||||
.thenReturn("TestSignature".bytes)
|
.thenReturn("TestSignature".bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.signEd25519(data: anyArray(), keyPair: any())) }
|
.when { try $0.perform(.signEd25519(data: anyArray(), keyPair: any())) }
|
||||||
.thenReturn("TestStandardSignature".bytes)
|
.thenReturn("TestStandardSignature".bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.generateNonce16()) }
|
.when { try $0.perform(.generateNonce16()) }
|
||||||
.thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes)
|
.thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.generateNonce24()) }
|
.when { try $0.perform(.generateNonce24()) }
|
||||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
network: mockNetwork,
|
||||||
|
crypto: mockCrypto,
|
||||||
|
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||||
|
)
|
||||||
|
@TestState var disposables: [AnyCancellable]! = []
|
||||||
|
@TestState var error: Error?
|
||||||
|
|
||||||
afterEach {
|
// MARK: - an OpenGroupAPI
|
||||||
disposables.forEach { $0.cancel() }
|
describe("an OpenGroupAPI") {
|
||||||
|
// MARK: -- when preparing a poll request
|
||||||
mockStorage = nil
|
|
||||||
mockNetwork = nil
|
|
||||||
mockCrypto = nil
|
|
||||||
dependencies = nil
|
|
||||||
disposables = []
|
|
||||||
|
|
||||||
error = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - when preparing a poll request
|
|
||||||
context("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") {
|
it("generates the correct request") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -140,7 +122,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
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") {
|
it("retrieves recent messages if there was no last message") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -155,7 +137,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
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") {
|
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
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
try OpenGroup
|
||||||
|
@ -175,7 +157,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom")))
|
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") {
|
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
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
try OpenGroup
|
||||||
|
@ -196,7 +178,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.to(equal(.roomMessagesSince("testRoom", seqNo: 122)))
|
.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") {
|
it("retrieves recent messages if there was a last message and there has already been a poll this session") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
try OpenGroup
|
||||||
|
@ -217,7 +199,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.to(equal(.roomMessagesSince("testRoom", seqNo: 123)))
|
.to(equal(.roomMessagesSince("testRoom", seqNo: 123)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when unblinded
|
// MARK: ---- when unblinded
|
||||||
context("when unblinded") {
|
context("when unblinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("does not call the inbox and outbox endpoints") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
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") {
|
context("when blinded and checking for message requests") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("includes the inbox and outbox endpoints") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -271,7 +253,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).to(contain(.outbox))
|
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") {
|
it("retrieves recent inbox messages if there was no last message") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -286,7 +268,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).to(contain(.inbox))
|
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") {
|
it("retrieves inbox messages since the last message if there was one") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
try OpenGroup
|
||||||
|
@ -306,7 +288,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).to(contain(.inboxSince(id: 124)))
|
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") {
|
it("retrieves recent outbox messages if there was no last message") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -321,7 +303,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).to(contain(.outbox))
|
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") {
|
it("retrieves outbox messages since the last message if there was one") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
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") {
|
context("when blinded and not checking for message requests") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("does not include the inbox endpoint") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -369,7 +351,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox))
|
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") {
|
it("does not retrieve recent inbox messages if there was no last message") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPoll(
|
try OpenGroupAPI.preparedPoll(
|
||||||
|
@ -384,7 +366,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox))
|
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") {
|
it("does not retrieve inbox messages since the last message if there was one") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try OpenGroup
|
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") {
|
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") {
|
it("generates the request and handles the response correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Capabilities>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Capabilities>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedCapabilities(
|
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") {
|
context("when preparing a rooms request") {
|
||||||
|
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedRooms(
|
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") {
|
context("when preparing a capabilitiesAndRoom request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedCapabilitiesAndRoom(
|
try OpenGroupAPI.preparedCapabilitiesAndRoom(
|
||||||
|
@ -462,7 +444,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("processes a valid response correctly") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||||
|
@ -488,10 +470,10 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(error).to(beNil())
|
expect(error).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- and given an invalid response
|
// MARK: ---- and given an invalid response
|
||||||
|
|
||||||
context("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") {
|
it("errors when not given a room response") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||||
|
@ -517,7 +499,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(response).to(beNil())
|
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") {
|
it("errors when not given a capabilities response") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.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") {
|
context("when preparing a capabilitiesAndRooms request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
||||||
|
@ -565,7 +547,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("processes a valid response correctly") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||||
|
@ -590,10 +572,10 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(error).to(beNil())
|
expect(error).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- and given an invalid response
|
// MARK: ---- and given an invalid response
|
||||||
|
|
||||||
context("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") {
|
it("errors when not given a room response") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
||||||
|
@ -618,7 +600,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(response).to(beNil())
|
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") {
|
it("errors when not given a capabilities response") {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
.when { $0.send(.onionRequest(any(), to: any(), with: any())) }
|
.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") {
|
context("when preparing a send message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedSend(
|
try OpenGroupAPI.preparedSend(
|
||||||
|
@ -666,7 +648,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when unblinded
|
// MARK: ---- when unblinded
|
||||||
context("when unblinded") {
|
context("when unblinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("signs the message correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedSend(
|
try OpenGroupAPI.preparedSend(
|
||||||
|
@ -696,7 +678,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8)))
|
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") {
|
it("fails to sign if there is no open group") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.deleteAll(db)
|
_ = try OpenGroup.deleteAll(db)
|
||||||
|
@ -726,7 +708,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if there is no user key pair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
||||||
|
@ -757,7 +739,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if no signature is generated") {
|
||||||
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
||||||
mockCrypto
|
mockCrypto
|
||||||
|
@ -789,7 +771,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when blinded
|
// MARK: ---- when blinded
|
||||||
context("when blinded") {
|
context("when blinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("signs the message correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedSend(
|
try OpenGroupAPI.preparedSend(
|
||||||
|
@ -820,7 +802,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8)))
|
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") {
|
it("fails to sign if there is no open group") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.deleteAll(db)
|
_ = try OpenGroup.deleteAll(db)
|
||||||
|
@ -850,7 +832,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if there is no ed key pair key") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||||
|
@ -881,7 +863,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if no signature is generated") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.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") {
|
context("when preparing an individual message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.Message>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessage(
|
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") {
|
context("when preparing an update message request") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessageUpdate(
|
try OpenGroupAPI.preparedMessageUpdate(
|
||||||
|
@ -972,7 +954,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("PUT"))
|
expect(preparedRequest?.request.httpMethod).to(equal("PUT"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when unblinded
|
// MARK: ---- when unblinded
|
||||||
context("when unblinded") {
|
context("when unblinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("signs the message correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessageUpdate(
|
try OpenGroupAPI.preparedMessageUpdate(
|
||||||
|
@ -1001,7 +983,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8)))
|
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") {
|
it("fails to sign if there is no open group") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.deleteAll(db)
|
_ = try OpenGroup.deleteAll(db)
|
||||||
|
@ -1030,7 +1012,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if there is no user key pair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .x25519PublicKey).deleteAll(db)
|
||||||
|
@ -1060,7 +1042,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if no signature is generated") {
|
||||||
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset
|
||||||
mockCrypto
|
mockCrypto
|
||||||
|
@ -1091,7 +1073,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when blinded
|
// MARK: ---- when blinded
|
||||||
context("when blinded") {
|
context("when blinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("signs the message correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessageUpdate(
|
try OpenGroupAPI.preparedMessageUpdate(
|
||||||
|
@ -1121,7 +1103,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8)))
|
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") {
|
it("fails to sign if there is no open group") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.deleteAll(db)
|
_ = try OpenGroup.deleteAll(db)
|
||||||
|
@ -1150,7 +1132,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if there is no ed key pair key") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||||
|
@ -1180,7 +1162,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails to sign if no signature is generated") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.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") {
|
context("when preparing a delete message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessageDelete(
|
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") {
|
context("when preparing a delete all messages request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedMessagesDeleteAll(
|
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") {
|
context("when preparing a pin message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedPinMessage(
|
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") {
|
context("when preparing an unpin message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUnpinMessage(
|
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") {
|
context("when preparing an unpin all request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUnpinAll(
|
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") {
|
context("when preparing an upload file request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUploadFile(
|
try OpenGroupAPI.preparedUploadFile(
|
||||||
|
@ -1332,7 +1314,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("doesn't add a fileName to the content-disposition header when not provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUploadFile(
|
try OpenGroupAPI.preparedUploadFile(
|
||||||
|
@ -1348,7 +1330,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.toNot(contain("filename"))
|
.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") {
|
it("adds the fileName to the content-disposition header when provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<FileUploadResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUploadFile(
|
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") {
|
context("when preparing a download file request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<Data>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<Data>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedDownloadFile(
|
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") {
|
context("when preparing a send direct message request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.SendDirectMessageResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.SendDirectMessageResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedSend(
|
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") {
|
context("when preparing a ban user request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserBan(
|
try OpenGroupAPI.preparedUserBan(
|
||||||
|
@ -1423,7 +1405,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("does a global ban if no room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserBan(
|
try OpenGroupAPI.preparedUserBan(
|
||||||
|
@ -1442,7 +1424,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.rooms).to(beNil())
|
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") {
|
it("does room specific bans if room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserBan(
|
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") {
|
context("when preparing an unban user request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserUnban(
|
try OpenGroupAPI.preparedUserUnban(
|
||||||
|
@ -1480,7 +1462,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("does a global unban if no room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserUnban(
|
try OpenGroupAPI.preparedUserUnban(
|
||||||
|
@ -1498,7 +1480,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.rooms).to(beNil())
|
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") {
|
it("does room specific unbans if room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserUnban(
|
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") {
|
context("when preparing a user permissions request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||||
|
@ -1538,7 +1520,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest?.request.httpMethod).to(equal("POST"))
|
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") {
|
it("does a global update if no room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||||
|
@ -1559,7 +1541,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.rooms).to(beNil())
|
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") {
|
it("does room specific updates if room tokens are provided") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserModeratorUpdate(
|
try OpenGroupAPI.preparedUserModeratorUpdate(
|
||||||
|
@ -1580,7 +1562,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(requestBody?.rooms).to(equal(["testRoom"]))
|
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") {
|
it("fails if neither moderator or admin are set") {
|
||||||
var preparationError: Error?
|
var preparationError: Error?
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<NoResponse>? = mockStorage.read { db in
|
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") {
|
context("when preparing a ban and delete all request") {
|
||||||
// MARK: -- generates the request correctly
|
// MARK: ---- generates the request correctly
|
||||||
it("generates the request correctly") {
|
it("generates the request correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
||||||
|
@ -1629,7 +1611,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.to(equal(.roomDeleteMessages("testRoom", sessionId: "testUserId")))
|
.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") {
|
// it("bans the user from the specified room rather than globally") {
|
||||||
// let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
// let preparedRequest: OpenGroupAPI.PreparedSendData<OpenGroupAPI.BatchResponse>? = mockStorage.read { db in
|
||||||
// try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
// try OpenGroupAPI.preparedUserBanAndDeleteAllMessages(
|
||||||
|
@ -1648,9 +1630,9 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - when signing
|
// MARK: -- when signing
|
||||||
context("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") {
|
it("fails when there is no serverPublicKey") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.deleteAll(db)
|
_ = try OpenGroup.deleteAll(db)
|
||||||
|
@ -1675,7 +1657,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails when there is no userEdKeyPair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||||
|
@ -1701,7 +1683,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails when the serverPublicKey is not a hex string") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try OpenGroup.updateAll(db, OpenGroup.Columns.publicKey.set(to: "TestString!!!"))
|
_ = try OpenGroup.updateAll(db, OpenGroup.Columns.publicKey.set(to: "TestString!!!"))
|
||||||
|
@ -1726,7 +1708,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
expect(preparedRequest).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when unblinded
|
// MARK: ---- when unblinded
|
||||||
context("when unblinded") {
|
context("when unblinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -1735,7 +1717,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ---- signs correctly
|
// MARK: ------ signs correctly
|
||||||
it("signs correctly") {
|
it("signs correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedRooms(
|
try OpenGroupAPI.preparedRooms(
|
||||||
|
@ -1759,7 +1741,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.to(equal("TestSignature".bytes.toBase64()))
|
.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") {
|
it("fails when the signature is not generated") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
.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") {
|
context("when blinded") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -1795,7 +1777,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ---- signs correctly
|
// MARK: ------ signs correctly
|
||||||
it("signs correctly") {
|
it("signs correctly") {
|
||||||
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in
|
||||||
try OpenGroupAPI.preparedRooms(
|
try OpenGroupAPI.preparedRooms(
|
||||||
|
@ -1819,7 +1801,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.to(equal("TestSogsSignature".bytes.toBase64()))
|
.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") {
|
it("fails when the blindedKeyPair is not generated") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] in
|
.when { [dependencies = dependencies!] in
|
||||||
|
@ -1852,7 +1834,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(preparedRequest).to(beNil())
|
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") {
|
it("fails when the sogsSignature is not generated") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] in
|
.when { [dependencies = dependencies!] in
|
||||||
|
@ -1887,7 +1869,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when sending
|
// MARK: ---- when sending
|
||||||
context("when sending") {
|
context("when sending") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockNetwork
|
mockNetwork
|
||||||
|
@ -1895,7 +1877,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
.thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self))
|
.thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- triggers sending correctly
|
// MARK: ---- triggers sending correctly
|
||||||
it("triggers sending correctly") {
|
it("triggers sending correctly") {
|
||||||
var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])?
|
var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])?
|
||||||
|
|
||||||
|
@ -1916,7 +1898,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
expect(error).to(beNil())
|
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") {
|
it("fails when not given prepared data") {
|
||||||
var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])?
|
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
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class NonceGeneratorSpec: QuickSpec {
|
class NonceGeneratorSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a NonceGenerator16Byte
|
||||||
override func spec() {
|
|
||||||
describe("a NonceGenerator16Byte") {
|
describe("a NonceGenerator16Byte") {
|
||||||
|
// MARK: -- has the correct number of bytes
|
||||||
it("has the correct number of bytes") {
|
it("has the correct number of bytes") {
|
||||||
expect(OpenGroupAPI.NonceGenerator16Byte().NonceBytes).to(equal(16))
|
expect(OpenGroupAPI.NonceGenerator16Byte().NonceBytes).to(equal(16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - a NonceGenerator24Byte
|
||||||
describe("a NonceGenerator24Byte") {
|
describe("a NonceGenerator24Byte") {
|
||||||
|
// MARK: -- has the correct number of bytes
|
||||||
it("has the correct number of bytes") {
|
it("has the correct number of bytes") {
|
||||||
expect(OpenGroupAPI.NonceGenerator24Byte().NonceBytes).to(equal(24))
|
expect(OpenGroupAPI.NonceGenerator24Byte().NonceBytes).to(equal(24))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class PersonalizationSpec: QuickSpec {
|
class PersonalizationSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a Personalization
|
||||||
override func spec() {
|
|
||||||
describe("a Personalization") {
|
describe("a Personalization") {
|
||||||
|
// MARK: -- generates bytes correctly
|
||||||
it("generates bytes correctly") {
|
it("generates bytes correctly") {
|
||||||
expect(OpenGroupAPI.Personalization.sharedKeys.bytes)
|
expect(OpenGroupAPI.Personalization.sharedKeys.bytes)
|
||||||
.to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115]))
|
.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
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SOGSEndpointSpec: QuickSpec {
|
class SOGSEndpointSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a SOGSEndpoint
|
||||||
override func spec() {
|
|
||||||
describe("a SOGSEndpoint") {
|
describe("a SOGSEndpoint") {
|
||||||
|
// MARK: -- generates the path value correctly
|
||||||
it("generates the path value correctly") {
|
it("generates the path value correctly") {
|
||||||
// Utility
|
// Utility
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SOGSErrorSpec: QuickSpec {
|
class SOGSErrorSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a SOGSError
|
||||||
override func spec() {
|
|
||||||
describe("a SOGSError") {
|
describe("a SOGSError") {
|
||||||
|
// MARK: -- generates the error description correctly
|
||||||
it("generates the error description correctly") {
|
it("generates the error description correctly") {
|
||||||
expect(OpenGroupAPIError.decryptionFailed.errorDescription)
|
expect(OpenGroupAPIError.decryptionFailed.errorDescription)
|
||||||
.to(equal("Couldn't decrypt response."))
|
.to(equal("Couldn't decrypt response."))
|
||||||
|
|
|
@ -11,45 +11,35 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class MessageReceiverDecryptionSpec: QuickSpec {
|
class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
var mockStorage: Storage!
|
|
||||||
var mockCrypto: MockCrypto!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
describe("a MessageReceiver") {
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNMessagingKit.self
|
SNMessagingKit.self
|
||||||
]
|
],
|
||||||
)
|
initialData: { db in
|
||||||
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: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||||
}
|
}
|
||||||
mockCrypto
|
)
|
||||||
.when { [dependencies = dependencies!] crypto in
|
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||||
|
initialSetup: { crypto in
|
||||||
|
crypto
|
||||||
|
.when { crypto in
|
||||||
try crypto.perform(
|
try crypto.perform(
|
||||||
.encryptAeadXChaCha20(
|
.encryptAeadXChaCha20(
|
||||||
message: anyArray(),
|
message: anyArray(),
|
||||||
secretKey: anyArray(),
|
secretKey: anyArray(),
|
||||||
nonce: anyArray(),
|
nonce: anyArray(),
|
||||||
using: dependencies
|
using: any()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
mockCrypto
|
crypto
|
||||||
.when {
|
.when {
|
||||||
try $0.perform(
|
try $0.perform(
|
||||||
.open(
|
.open(
|
||||||
|
@ -60,13 +50,13 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([UInt8](repeating: 0, count: 100))
|
.thenReturn([UInt8](repeating: 0, count: 100))
|
||||||
mockCrypto
|
crypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { crypto in
|
||||||
crypto.generate(
|
crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
serverPublicKey: any(),
|
serverPublicKey: any(),
|
||||||
edKeyPair: any(),
|
edKeyPair: any(),
|
||||||
using: dependencies
|
using: any()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -76,34 +66,36 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { crypto in
|
||||||
try crypto.perform(
|
try crypto.perform(
|
||||||
.sharedBlindedEncryptionKey(
|
.sharedBlindedEncryptionKey(
|
||||||
secretKey: anyArray(),
|
secretKey: anyArray(),
|
||||||
otherBlindedPublicKey: anyArray(),
|
otherBlindedPublicKey: anyArray(),
|
||||||
fromBlindedPublicKey: anyArray(),
|
fromBlindedPublicKey: anyArray(),
|
||||||
toBlindedPublicKey: anyArray(),
|
toBlindedPublicKey: anyArray(),
|
||||||
using: dependencies
|
using: any()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
mockCrypto
|
crypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { crypto in
|
||||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
try crypto.perform(
|
||||||
|
.generateBlindingFactor(serverPublicKey: any(), using: any())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
mockCrypto
|
crypto
|
||||||
.when {
|
.when {
|
||||||
try $0.perform(
|
try $0.perform(
|
||||||
.decryptAeadXChaCha20(
|
.decryptAeadXChaCha20(
|
||||||
|
@ -114,15 +106,24 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
||||||
mockCrypto.when { $0.size(.nonce24) }.thenReturn(24)
|
crypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||||
mockCrypto.when { $0.size(.publicKey) }.thenReturn(32)
|
crypto.when { $0.size(.publicKey) }.thenReturn(32)
|
||||||
mockCrypto.when { $0.size(.signature) }.thenReturn(64)
|
crypto.when { $0.size(.signature) }.thenReturn(64)
|
||||||
mockCrypto
|
crypto
|
||||||
.when { try $0.perform(.generateNonce24()) }
|
.when { try $0.perform(.generateNonce24()) }
|
||||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
.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") {
|
context("when decrypting with the session protocol") {
|
||||||
|
// MARK: ---- successfully decrypts a message
|
||||||
it("successfully decrypts a message") {
|
it("successfully decrypts a message") {
|
||||||
let result = try? MessageReceiver.decryptWithSessionProtocol(
|
let result = try? MessageReceiver.decryptWithSessionProtocol(
|
||||||
ciphertext: Data(
|
ciphertext: Data(
|
||||||
|
@ -142,6 +143,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- throws an error if it cannot open the message
|
||||||
it("throws an error if it cannot open the message") {
|
it("throws an error if it cannot open the message") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -168,6 +170,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if the open message is too short") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -194,6 +197,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- throws an error if it cannot verify the message
|
||||||
it("throws an error if it cannot verify the message") {
|
it("throws an error if it cannot verify the message") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||||
|
@ -212,6 +216,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.invalidSignature))
|
.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") {
|
it("throws an error if it cannot get the senders x25519 public key") {
|
||||||
mockCrypto.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }.thenReturn(nil)
|
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") {
|
context("when decrypting with the blinded session protocol") {
|
||||||
|
// MARK: ---- successfully decrypts a message
|
||||||
it("successfully decrypts a message") {
|
it("successfully decrypts a message") {
|
||||||
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||||
data: Data(
|
data: Data(
|
||||||
|
@ -252,6 +259,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- successfully decrypts a mocked incoming message
|
||||||
it("successfully decrypts a mocked incoming message") {
|
it("successfully decrypts a mocked incoming message") {
|
||||||
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
let result = try? MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||||
data: (
|
data: (
|
||||||
|
@ -274,6 +282,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
.to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- throws an error if the data is too short
|
||||||
it("throws an error if the data is too short") {
|
it("throws an error if the data is too short") {
|
||||||
expect {
|
expect {
|
||||||
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||||
|
@ -291,6 +300,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if it cannot get the blinded keyPair") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
@ -324,6 +334,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if it cannot get the decryption key") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
@ -359,6 +370,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if the data version is not 0") {
|
||||||
expect {
|
expect {
|
||||||
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
try MessageReceiver.decryptWithSessionBlindingProtocol(
|
||||||
|
@ -380,6 +392,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- throws an error if it cannot decrypt the data
|
||||||
it("throws an error if it cannot decrypt the data") {
|
it("throws an error if it cannot decrypt the data") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -413,6 +426,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if the inner bytes are too short") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
@ -446,6 +460,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.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") {
|
it("throws an error if it cannot generate the blinding factor") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
@ -473,6 +488,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.invalidSignature))
|
.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") {
|
it("throws an error if it cannot generate the combined key") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
|
@ -498,6 +514,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.invalidSignature))
|
.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") {
|
it("throws an error if the combined key does not match kA") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
|
@ -523,6 +540,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
.to(throwError(MessageReceiverError.invalidSignature))
|
.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") {
|
it("throws an error if it cannot get the senders x25519 public key") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||||
|
|
|
@ -11,41 +11,35 @@ import Nimble
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class MessageSenderEncryptionSpec: QuickSpec {
|
class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
var mockStorage: Storage!
|
|
||||||
var mockCrypto: MockCrypto!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
describe("a MessageSender") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNMessagingKit.self
|
SNMessagingKit.self
|
||||||
]
|
],
|
||||||
|
initialData: { db in
|
||||||
|
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||||
|
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
mockCrypto = MockCrypto()
|
@TestState var mockCrypto: MockCrypto! = MockCrypto(
|
||||||
|
initialSetup: { crypto in
|
||||||
dependencies = Dependencies(
|
crypto
|
||||||
|
.when { try $0.perform(.generateNonce24()) }
|
||||||
|
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
storage: mockStorage,
|
storage: mockStorage,
|
||||||
crypto: mockCrypto
|
crypto: mockCrypto
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
// MARK: - a MessageSender
|
||||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
describe("a MessageSender") {
|
||||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
// MARK: -- when encrypting with the session protocol
|
||||||
}
|
|
||||||
mockCrypto
|
|
||||||
.when { try $0.perform(.generateNonce24()) }
|
|
||||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - when encrypting with the session protocol
|
|
||||||
context("when encrypting with the session protocol") {
|
context("when encrypting with the session protocol") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
|
@ -56,7 +50,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- can encrypt correctly
|
// MARK: ---- can encrypt correctly
|
||||||
it("can encrypt correctly") {
|
it("can encrypt correctly") {
|
||||||
let result: Data? = mockStorage.read { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionProtocol(
|
try? MessageSender.encryptWithSessionProtocol(
|
||||||
|
@ -72,7 +66,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(155))
|
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") {
|
it("returns the correct value when mocked") {
|
||||||
let result: Data? = mockStorage.read { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionProtocol(
|
try? MessageSender.encryptWithSessionProtocol(
|
||||||
|
@ -86,7 +80,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.bytes).to(equal([1, 2, 3]))
|
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") {
|
it("throws an error if there is no ed25519 keyPair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = 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") {
|
it("throws an error if the signature generation fails") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
.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") {
|
it("throws an error if the encryption fails") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
.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") {
|
context("when encrypting with the blinded session protocol") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
|
@ -186,7 +180,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
.thenReturn([2, 3, 4])
|
.thenReturn([2, 3, 4])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- can encrypt correctly
|
// MARK: ---- can encrypt correctly
|
||||||
it("can encrypt correctly") {
|
it("can encrypt correctly") {
|
||||||
let result: Data? = mockStorage.read { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
|
@ -203,7 +197,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(84))
|
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") {
|
it("returns the correct value when mocked") {
|
||||||
let result: Data? = mockStorage.read { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
|
@ -219,7 +213,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
|
.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") {
|
it("includes a version at the start of the encrypted value") {
|
||||||
let result: Data? = mockStorage.read { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
|
@ -234,7 +228,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.toHexString().prefix(2)).to(equal("00"))
|
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") {
|
it("includes the nonce at the end of the encrypted value") {
|
||||||
let maybeResult: Data? = mockStorage.read { db in
|
let maybeResult: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
|
@ -252,7 +246,7 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
|
.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") {
|
it("throws an error if the recipient isn't a blinded id") {
|
||||||
mockStorage.read { db in
|
mockStorage.read { db in
|
||||||
expect {
|
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") {
|
it("throws an error if there is no ed25519 keyPair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = 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") {
|
it("throws an error if it fails to generate a blinded keyPair") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.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") {
|
it("throws an error if it fails to generate an encryption key") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when { [dependencies = dependencies!] crypto in
|
.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") {
|
it("throws an error if it fails to encrypt") {
|
||||||
mockCrypto
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
|
|
|
@ -9,29 +9,12 @@ import SessionUtilitiesKit
|
||||||
@testable import SessionMessagingKit
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class SessionThreadViewModelSpec: QuickSpec {
|
class SessionThreadViewModelSpec: QuickSpec {
|
||||||
public struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
override class func spec() {
|
||||||
public static var databaseTableName: String { "testMessage" }
|
// MARK: Configuration
|
||||||
|
|
||||||
public typealias Columns = CodingKeys
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
customWriter: try! DatabaseQueue(),
|
||||||
case body
|
initialData: { db in
|
||||||
}
|
|
||||||
|
|
||||||
public let body: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
describe("a SessionThreadViewModel") {
|
|
||||||
var mockStorage: Storage!
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue()
|
|
||||||
)
|
|
||||||
|
|
||||||
mockStorage.write { db in
|
|
||||||
try db.create(table: TestMessage.self) { t in
|
try db.create(table: TestMessage.self) { t in
|
||||||
t.column(.body, .text).notNull()
|
t.column(.body, .text).notNull()
|
||||||
}
|
}
|
||||||
|
@ -43,23 +26,25 @@ class SessionThreadViewModelSpec: QuickSpec {
|
||||||
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") {
|
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") {
|
it("correctly generates a safe search term") {
|
||||||
expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\""))
|
expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\""))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- standardises odd quote characters
|
// MARK: ---- standardises odd quote characters
|
||||||
it("standardises odd quote characters") {
|
it("standardises odd quote characters") {
|
||||||
expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\""))
|
expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\""))
|
||||||
expect(SessionThreadViewModel.standardQuotes("”")).to(equal("\""))
|
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") {
|
it("splits on the space character") {
|
||||||
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
||||||
.to(equal([
|
.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") {
|
it("surrounds each split term with quotes") {
|
||||||
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
expect(SessionThreadViewModel.searchTermParts("Test Message"))
|
||||||
.to(equal([
|
.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") {
|
it("keeps words within quotes together") {
|
||||||
expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message"))
|
expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message"))
|
||||||
.to(equal([
|
.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") {
|
it("keeps words within weird quotes together") {
|
||||||
expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message"))
|
expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message"))
|
||||||
.to(equal([
|
.to(equal([
|
||||||
|
@ -123,7 +108,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- removes extra whitespace
|
// MARK: ---- removes extra whitespace
|
||||||
it("removes extra whitespace") {
|
it("removes extra whitespace") {
|
||||||
expect(SessionThreadViewModel.searchTermParts(" Test Message "))
|
expect(SessionThreadViewModel.searchTermParts(" Test Message "))
|
||||||
.to(equal([
|
.to(equal([
|
||||||
|
@ -133,7 +118,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - when searching
|
// MARK: -- when searching
|
||||||
context("when searching") {
|
context("when searching") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -156,7 +141,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- returns results
|
// MARK: ---- returns results
|
||||||
it("returns results") {
|
it("returns results") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
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") {
|
it("adds a wildcard to the final part") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
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") {
|
it("does not add a wildcard to other parts") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
||||||
|
@ -239,7 +224,7 @@ class SessionThreadViewModelSpec: QuickSpec {
|
||||||
.to(beEmpty())
|
.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") {
|
it("finds similar words without the wildcard due to the porter tokenizer") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
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") {
|
it("finds results containing the words regardless of the order") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
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") {
|
it("does not find quoted parts out of order") {
|
||||||
let results = mockStorage.read { db in
|
let results = mockStorage.read { db in
|
||||||
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
|
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
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class CryptoSMKSpec: QuickSpec {
|
class CryptoSMKSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var crypto: Crypto! = Crypto()
|
||||||
var crypto: Crypto!
|
@TestState var mockCrypto: MockCrypto! = MockCrypto()
|
||||||
var mockCrypto: MockCrypto!
|
@TestState var dependencies: Dependencies! = Dependencies(crypto: crypto)
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
crypto = Crypto()
|
|
||||||
mockCrypto = MockCrypto()
|
|
||||||
dependencies = Dependencies(crypto: crypto)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: - Crypto for SessionMessagingKit
|
||||||
describe("Crypto for SessionMessagingKit") {
|
describe("Crypto for SessionMessagingKit") {
|
||||||
|
// MARK: -- when extending Sign
|
||||||
// MARK: - when extending Sign
|
|
||||||
context("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") {
|
it("can convert an ed25519 public key into an x25519 public key") {
|
||||||
let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes))
|
let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes))
|
||||||
|
|
||||||
|
@ -35,7 +29,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
.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") {
|
it("can convert an ed25519 private key into an x25519 private key") {
|
||||||
let result = try? crypto.perform(.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes))
|
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") {
|
context("when extending Sodium") {
|
||||||
// MARK: -- and generating a blinding factor
|
// MARK: ---- and generating a blinding factor
|
||||||
context("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") {
|
it("successfully generates a blinding factor") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.generateBlindingFactor(
|
.generateBlindingFactor(
|
||||||
|
@ -61,7 +55,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
.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") {
|
it("fails if the serverPublicKey is not a hex string") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.generateBlindingFactor(
|
.generateBlindingFactor(
|
||||||
|
@ -73,7 +67,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
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") {
|
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||||
dependencies = Dependencies(crypto: mockCrypto)
|
dependencies = Dependencies(crypto: mockCrypto)
|
||||||
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") {
|
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") {
|
it("successfully generates a blinded key pair") {
|
||||||
let result = crypto.generate(
|
let result = crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
|
@ -112,7 +106,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
.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") {
|
it("fails if the edKeyPair public key length wrong") {
|
||||||
let result = crypto.generate(
|
let result = crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
|
@ -128,7 +122,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
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") {
|
it("fails if the edKeyPair secret key length wrong") {
|
||||||
let result = crypto.generate(
|
let result = crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
|
@ -144,7 +138,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beNil())
|
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") {
|
it("fails if it cannot generate a blinding factor") {
|
||||||
let result = crypto.generate(
|
let result = crypto.generate(
|
||||||
.blindedKeyPair(
|
.blindedKeyPair(
|
||||||
|
@ -161,9 +155,9 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- and generating a sogsSignature
|
// MARK: ---- and generating a sogsSignature
|
||||||
context("and generating a sogsSignature") {
|
context("and generating a sogsSignature") {
|
||||||
// MARK: --- generates a correct signature
|
// MARK: ------ generates a correct signature
|
||||||
it("generates a correct signature") {
|
it("generates a correct signature") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.sogsSignature(
|
.sogsSignature(
|
||||||
|
@ -182,9 +176,9 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- and combining keys
|
// MARK: ---- and combining keys
|
||||||
context("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") {
|
it("generates a correct combined key") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.combineKeys(
|
.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") {
|
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") {
|
it("generates a correct combined key") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.sharedBlindedEncryptionKey(
|
.sharedBlindedEncryptionKey(
|
||||||
|
@ -216,7 +210,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: --- fails if the scalar multiplication fails
|
// MARK: ------ fails if the scalar multiplication fails
|
||||||
it("fails if the scalar multiplication fails") {
|
it("fails if the scalar multiplication fails") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.sharedBlindedEncryptionKey(
|
.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") {
|
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") {
|
it("returns true when they match") {
|
||||||
let result = crypto.verify(
|
let result = crypto.verify(
|
||||||
.sessionId(
|
.sessionId(
|
||||||
|
@ -248,7 +242,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beTrue())
|
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") {
|
it("returns false if given an invalid session id") {
|
||||||
let result = crypto.verify(
|
let result = crypto.verify(
|
||||||
.sessionId(
|
.sessionId(
|
||||||
|
@ -262,7 +256,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beFalse())
|
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") {
|
it("returns false if given an invalid blinded id") {
|
||||||
let result = crypto.verify(
|
let result = crypto.verify(
|
||||||
.sessionId(
|
.sessionId(
|
||||||
|
@ -276,7 +270,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result).to(beFalse())
|
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") {
|
it("returns false if it fails to generate the blinding factor") {
|
||||||
let result = crypto.verify(
|
let result = crypto.verify(
|
||||||
.sessionId(
|
.sessionId(
|
||||||
|
@ -292,11 +286,11 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - when extending GenericHash
|
// MARK: -- when extending GenericHash
|
||||||
describe("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") {
|
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") {
|
it("generates a hash correctly") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.hashSaltPersonal(
|
.hashSaltPersonal(
|
||||||
|
@ -312,7 +306,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(32))
|
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") {
|
it("generates a hash correctly with no key") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.hashSaltPersonal(
|
.hashSaltPersonal(
|
||||||
|
@ -328,7 +322,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(32))
|
expect(result?.count).to(equal(32))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: --- fails if given invalid options
|
// MARK: ------ fails if given invalid options
|
||||||
it("fails if given invalid options") {
|
it("fails if given invalid options") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.hashSaltPersonal(
|
.hashSaltPersonal(
|
||||||
|
@ -345,11 +339,11 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - when extending AeadXChaCha20Poly1305Ietf
|
// MARK: -- when extending AeadXChaCha20Poly1305Ietf
|
||||||
context("when extending AeadXChaCha20Poly1305Ietf") {
|
context("when extending AeadXChaCha20Poly1305Ietf") {
|
||||||
// MARK: -- when encrypting
|
// MARK: ---- when encrypting
|
||||||
context("when encrypting") {
|
context("when encrypting") {
|
||||||
// MARK: --- encrypts correctly
|
// MARK: ------ encrypts correctly
|
||||||
it("encrypts correctly") {
|
it("encrypts correctly") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.encryptAeadXChaCha20(
|
.encryptAeadXChaCha20(
|
||||||
|
@ -365,7 +359,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(27))
|
expect(result?.count).to(equal(27))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: --- encrypts correctly with additional data
|
// MARK: ------ encrypts correctly with additional data
|
||||||
it("encrypts correctly with additional data") {
|
it("encrypts correctly with additional data") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.encryptAeadXChaCha20(
|
.encryptAeadXChaCha20(
|
||||||
|
@ -381,7 +375,7 @@ class CryptoSMKSpec: QuickSpec {
|
||||||
expect(result?.count).to(equal(27))
|
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") {
|
it("fails if given an invalid key") {
|
||||||
let result = try? crypto.perform(
|
let result = try? crypto.perform(
|
||||||
.encryptAeadXChaCha20(
|
.encryptAeadXChaCha20(
|
||||||
|
|
|
@ -11,77 +11,58 @@ import SessionUtilitiesKit
|
||||||
@testable import Session
|
@testable import Session
|
||||||
|
|
||||||
class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesSettingsViewModel.NavButton, ThreadDisappearingMessagesSettingsViewModel.Section, ThreadDisappearingMessagesSettingsViewModel.Item>
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
// MARK: - Spec
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
var mockStorage: Storage!
|
|
||||||
var cancellables: [AnyCancellable] = []
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
var viewModel: ThreadDisappearingMessagesSettingsViewModel!
|
|
||||||
|
|
||||||
describe("a ThreadDisappearingMessagesSettingsViewModel") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNSnodeKit.self,
|
SNSnodeKit.self,
|
||||||
SNMessagingKit.self,
|
SNMessagingKit.self,
|
||||||
SNUIKit.self
|
SNUIKit.self
|
||||||
]
|
],
|
||||||
)
|
initialData: { db in
|
||||||
dependencies = Dependencies(
|
|
||||||
storage: mockStorage,
|
|
||||||
scheduler: .immediate
|
|
||||||
)
|
|
||||||
mockStorage.write { db in
|
|
||||||
try SessionThread(
|
try SessionThread(
|
||||||
id: "TestId",
|
id: "TestId",
|
||||||
variant: .contact
|
variant: .contact
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
viewModel = ThreadDisappearingMessagesSettingsViewModel(
|
)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
scheduler: .immediate
|
||||||
|
)
|
||||||
|
@TestState var viewModel: ThreadDisappearingMessagesSettingsViewModel! = ThreadDisappearingMessagesSettingsViewModel(
|
||||||
threadId: "TestId",
|
threadId: "TestId",
|
||||||
threadVariant: .contact,
|
threadVariant: .contact,
|
||||||
config: DisappearingMessagesConfiguration.defaultWith("TestId"),
|
config: DisappearingMessagesConfiguration.defaultWith("TestId"),
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
cancellables.append(
|
|
||||||
|
@TestState var cancellables: [AnyCancellable]! = [
|
||||||
viewModel.observableTableData
|
viewModel.observableTableData
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { _ in },
|
receiveCompletion: { _ in },
|
||||||
receiveValue: { viewModel.updateTableData($0.0) }
|
receiveValue: { viewModel.updateTableData($0.0) }
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
}
|
|
||||||
|
|
||||||
afterEach {
|
|
||||||
cancellables.forEach { $0.cancel() }
|
|
||||||
|
|
||||||
mockStorage = nil
|
|
||||||
cancellables = []
|
|
||||||
dependencies = nil
|
|
||||||
viewModel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Basic Tests
|
|
||||||
|
|
||||||
|
// MARK: - a ThreadDisappearingMessagesSettingsViewModel
|
||||||
|
describe("a ThreadDisappearingMessagesSettingsViewModel") {
|
||||||
|
// MARK: -- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("DISAPPEARING_MESSAGES".localized()))
|
expect(viewModel.title).to(equal("DISAPPEARING_MESSAGES".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- has the correct number of items
|
||||||
it("has the correct number of items") {
|
it("has the correct number of items") {
|
||||||
expect(viewModel.tableData.count)
|
expect(viewModel.tableData.count).to(equal(1))
|
||||||
.to(equal(1))
|
expect(viewModel.tableData.first?.elements.count).to(equal(12))
|
||||||
expect(viewModel.tableData.first?.elements.count)
|
|
||||||
.to(equal(12))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- has the correct default state
|
||||||
it("has the correct default state") {
|
it("has the correct default state") {
|
||||||
expect(viewModel.tableData.first?.elements.first)
|
expect(viewModel.tableData.first?.elements.first)
|
||||||
.to(
|
.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") {
|
it("starts with the correct item active if not default") {
|
||||||
let config: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration
|
let config: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration
|
||||||
.defaultWith("TestId")
|
.defaultWith("TestId")
|
||||||
|
@ -176,6 +158,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- has no right bar button
|
||||||
it("has no right bar button") {
|
it("has no right bar button") {
|
||||||
var items: [ParentType.NavItem]?
|
var items: [ParentType.NavItem]?
|
||||||
|
|
||||||
|
@ -191,8 +174,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
expect(items).to(equal([]))
|
expect(items).to(equal([]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when changed from the previous setting
|
||||||
context("when changed from the previous setting") {
|
context("when changed from the previous setting") {
|
||||||
var items: [ParentType.NavItem]?
|
@TestState var items: [ParentType.NavItem]?
|
||||||
|
|
||||||
beforeEach {
|
beforeEach {
|
||||||
cancellables.append(
|
cancellables.append(
|
||||||
|
@ -207,6 +191,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
viewModel.tableData.first?.elements.last?.onTap?()
|
viewModel.tableData.first?.elements.last?.onTap?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- shows the save button
|
||||||
it("shows the save button") {
|
it("shows the save button") {
|
||||||
expect(items)
|
expect(items)
|
||||||
.to(equal([
|
.to(equal([
|
||||||
|
@ -218,7 +203,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- and saving
|
||||||
context("and saving") {
|
context("and saving") {
|
||||||
|
// MARK: ------ dismisses the screen
|
||||||
it("dismisses the screen") {
|
it("dismisses the screen") {
|
||||||
var didDismissScreen: Bool = false
|
var didDismissScreen: Bool = false
|
||||||
|
|
||||||
|
@ -236,6 +223,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
expect(didDismissScreen).to(beTrue())
|
expect(didDismissScreen).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ saves the updated config
|
||||||
it("saves the updated config") {
|
it("saves the updated config") {
|
||||||
items?.first?.action?()
|
items?.first?.action?()
|
||||||
|
|
||||||
|
@ -252,3 +240,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Types
|
||||||
|
|
||||||
|
fileprivate typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesSettingsViewModel.NavButton, ThreadDisappearingMessagesSettingsViewModel.Section, ThreadDisappearingMessagesSettingsViewModel.Item>
|
||||||
|
|
|
@ -11,69 +11,43 @@ import SessionUtilitiesKit
|
||||||
@testable import Session
|
@testable import Session
|
||||||
|
|
||||||
class ThreadSettingsViewModelSpec: QuickSpec {
|
class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
// MARK: - Spec
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
var mockStorage: Storage!
|
|
||||||
var mockCaches: MockCaches!
|
|
||||||
var mockGeneralCache: MockGeneralCache!
|
|
||||||
var disposables: [AnyCancellable] = []
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
var viewModel: ThreadSettingsViewModel!
|
|
||||||
var didTriggerSearchCallbackTriggered: Bool = false
|
|
||||||
|
|
||||||
describe("a ThreadSettingsViewModel") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNSnodeKit.self,
|
SNSnodeKit.self,
|
||||||
SNMessagingKit.self,
|
SNMessagingKit.self,
|
||||||
SNUIKit.self
|
SNUIKit.self
|
||||||
]
|
],
|
||||||
)
|
initialData: { db in
|
||||||
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(
|
try Identity(
|
||||||
variant: .x25519PublicKey,
|
variant: .x25519PublicKey,
|
||||||
data: Data(hex: TestConstants.publicKey)
|
data: Data(hex: TestConstants.publicKey)
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Profile(
|
try SessionThread(id: "TestId",variant: .contact).insert(db)
|
||||||
id: "05\(TestConstants.publicKey)",
|
try Profile(id: "05\(TestConstants.publicKey)", name: "TestMe").insert(db)
|
||||||
name: "TestMe",
|
try Profile(id: "TestId", name: "TestUser").insert(db)
|
||||||
lastNameUpdate: 0,
|
|
||||||
lastProfilePictureUpdate: 0,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
).insert(db)
|
|
||||||
|
|
||||||
try Profile(
|
|
||||||
id: "TestId",
|
|
||||||
name: "TestUser",
|
|
||||||
lastNameUpdate: 0,
|
|
||||||
lastProfilePictureUpdate: 0,
|
|
||||||
lastBlocksCommunityMessageRequests: 0
|
|
||||||
).insert(db)
|
|
||||||
}
|
}
|
||||||
viewModel = ThreadSettingsViewModel(
|
)
|
||||||
|
@TestState var mockGeneralCache: MockGeneralCache! = MockGeneralCache(
|
||||||
|
initialSetup: { cache in
|
||||||
|
cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@TestState var mockCaches: MockCaches! = MockCaches()
|
||||||
|
.setting(cache: .general, to: mockGeneralCache)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
caches: mockCaches,
|
||||||
|
scheduler: .immediate
|
||||||
|
)
|
||||||
|
@TestState var threadVariant: SessionThread.Variant! = .contact
|
||||||
|
@TestState var didTriggerSearchCallbackTriggered: Bool! = false
|
||||||
|
@TestState var viewModel: ThreadSettingsViewModel! = ThreadSettingsViewModel(
|
||||||
threadId: "TestId",
|
threadId: "TestId",
|
||||||
threadVariant: .contact,
|
threadVariant: .contact,
|
||||||
didTriggerSearch: {
|
didTriggerSearch: {
|
||||||
|
@ -81,29 +55,21 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
},
|
},
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
disposables.append(
|
|
||||||
|
@TestState var disposables: [AnyCancellable]! = [
|
||||||
viewModel.observableTableData
|
viewModel.observableTableData
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { _ in },
|
receiveCompletion: { _ in },
|
||||||
receiveValue: { viewModel.updateTableData($0.0) }
|
receiveValue: { viewModel.updateTableData($0.0) }
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
}
|
|
||||||
|
|
||||||
afterEach {
|
|
||||||
disposables.forEach { $0.cancel() }
|
|
||||||
|
|
||||||
mockStorage = nil
|
|
||||||
disposables = []
|
|
||||||
dependencies = nil
|
|
||||||
viewModel = nil
|
|
||||||
didTriggerSearchCallbackTriggered = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Basic Tests
|
|
||||||
|
|
||||||
|
// MARK: - a ThreadSettingsViewModel
|
||||||
|
describe("a ThreadSettingsViewModel") {
|
||||||
|
// MARK: -- with any conversation type
|
||||||
context("with any conversation type") {
|
context("with any conversation type") {
|
||||||
|
// MARK: ---- triggers the search callback when tapping search
|
||||||
it("triggers the search callback when tapping search") {
|
it("triggers the search callback when tapping search") {
|
||||||
viewModel.tableData
|
viewModel.tableData
|
||||||
.first(where: { $0.model == .content })?
|
.first(where: { $0.model == .content })?
|
||||||
|
@ -114,6 +80,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
expect(didTriggerSearchCallbackTriggered).to(beTrue())
|
expect(didTriggerSearchCallbackTriggered).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- mutes a conversation
|
||||||
it("mutes a conversation") {
|
it("mutes a conversation") {
|
||||||
viewModel.tableData
|
viewModel.tableData
|
||||||
.first(where: { $0.model == .content })?
|
.first(where: { $0.model == .content })?
|
||||||
|
@ -129,6 +96,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
.toNot(beNil())
|
.toNot(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- unmutes a conversation
|
||||||
it("unmutes a conversation") {
|
it("unmutes a conversation") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try SessionThread
|
try SessionThread
|
||||||
|
@ -160,6 +128,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- with a note-to-self conversation
|
||||||
context("with a note-to-self conversation") {
|
context("with a note-to-self conversation") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -189,10 +158,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- starts in the standard nav state
|
||||||
it("starts in the standard nav state") {
|
it("starts in the standard nav state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.to(equal(.standard))
|
||||||
|
@ -208,6 +179,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- has no mute button
|
||||||
it("has no mute button") {
|
it("has no mute button") {
|
||||||
expect(
|
expect(
|
||||||
viewModel.tableData
|
viewModel.tableData
|
||||||
|
@ -217,6 +189,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
).to(beNil())
|
).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- when entering edit mode
|
||||||
context("when entering edit mode") {
|
context("when entering edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.navState.sinkAndStore(in: &disposables)
|
viewModel.navState.sinkAndStore(in: &disposables)
|
||||||
|
@ -224,6 +197,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
viewModel.textChanged("TestNew", for: .nickname)
|
viewModel.textChanged("TestNew", for: .nickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ enters the editing state
|
||||||
it("enters the editing state") {
|
it("enters the editing state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.editing))
|
.to(equal(.editing))
|
||||||
|
@ -246,11 +220,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ when cancelling edit mode
|
||||||
context("when cancelling edit mode") {
|
context("when cancelling edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.leftNavItems.firstValue()??.first?.action?()
|
viewModel.leftNavItems.firstValue()??.first?.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- exits editing mode
|
||||||
it("exits editing mode") {
|
it("exits editing mode") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.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") {
|
it("does not update the nickname for the current user") {
|
||||||
expect(
|
expect(
|
||||||
mockStorage
|
mockStorage
|
||||||
|
@ -278,11 +255,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ when saving edit mode
|
||||||
context("when saving edit mode") {
|
context("when saving edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.rightNavItems.firstValue()??.first?.action?()
|
viewModel.rightNavItems.firstValue()??.first?.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- exits editing mode
|
||||||
it("exits editing mode") {
|
it("exits editing mode") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.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") {
|
it("updates the nickname for the current user") {
|
||||||
expect(
|
expect(
|
||||||
mockStorage
|
mockStorage
|
||||||
|
@ -312,6 +292,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- with a one-to-one conversation
|
||||||
context("with a one-to-one conversation") {
|
context("with a one-to-one conversation") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -324,10 +305,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
expect(viewModel.title).to(equal("vc_settings_title".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- starts in the standard nav state
|
||||||
it("starts in the standard nav state") {
|
it("starts in the standard nav state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.to(equal(.standard))
|
||||||
|
@ -343,6 +326,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- when entering edit mode
|
||||||
context("when entering edit mode") {
|
context("when entering edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.navState.sinkAndStore(in: &disposables)
|
viewModel.navState.sinkAndStore(in: &disposables)
|
||||||
|
@ -350,6 +334,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
viewModel.textChanged("TestUserNew", for: .nickname)
|
viewModel.textChanged("TestUserNew", for: .nickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ enters the editing state
|
||||||
it("enters the editing state") {
|
it("enters the editing state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.editing))
|
.to(equal(.editing))
|
||||||
|
@ -372,11 +357,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ when cancelling edit mode
|
||||||
context("when cancelling edit mode") {
|
context("when cancelling edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.leftNavItems.firstValue()??.first?.action?()
|
viewModel.leftNavItems.firstValue()??.first?.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- exits editing mode
|
||||||
it("exits editing mode") {
|
it("exits editing mode") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.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") {
|
it("does not update the nickname for the current user") {
|
||||||
expect(
|
expect(
|
||||||
mockStorage
|
mockStorage
|
||||||
|
@ -402,11 +390,13 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ when saving edit mode
|
||||||
context("when saving edit mode") {
|
context("when saving edit mode") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
viewModel.rightNavItems.firstValue()??.first?.action?()
|
viewModel.rightNavItems.firstValue()??.first?.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -------- exits editing mode
|
||||||
it("exits editing mode") {
|
it("exits editing mode") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.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") {
|
it("updates the nickname for the current user") {
|
||||||
expect(
|
expect(
|
||||||
mockStorage
|
mockStorage
|
||||||
|
@ -434,6 +425,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- with a group conversation
|
||||||
context("with a group conversation") {
|
context("with a group conversation") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -463,10 +455,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- starts in the standard nav state
|
||||||
it("starts in the standard nav state") {
|
it("starts in the standard nav state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.to(equal(.standard))
|
||||||
|
@ -476,6 +470,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- with a community conversation
|
||||||
context("with a community conversation") {
|
context("with a community conversation") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -505,10 +500,12 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
expect(viewModel.title).to(equal("vc_group_settings_title".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- starts in the standard nav state
|
||||||
it("starts in the standard nav state") {
|
it("starts in the standard nav state") {
|
||||||
expect(viewModel.navState.firstValue())
|
expect(viewModel.navState.firstValue())
|
||||||
.to(equal(.standard))
|
.to(equal(.standard))
|
||||||
|
@ -520,3 +517,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Types
|
||||||
|
|
||||||
|
fileprivate typealias ParentType = SessionTableViewModel<ThreadSettingsViewModel.NavButton, ThreadSettingsViewModel.Section, ThreadSettingsViewModel.Setting>
|
||||||
|
|
|
@ -11,19 +11,9 @@ import SessionUtilitiesKit
|
||||||
@testable import Session
|
@testable import Session
|
||||||
|
|
||||||
class NotificationContentViewModelSpec: QuickSpec {
|
class NotificationContentViewModelSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
override func spec() {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
var mockStorage: Storage!
|
|
||||||
var dataChangeCancellable: AnyCancellable?
|
|
||||||
var dismissCancellable: AnyCancellable?
|
|
||||||
var viewModel: NotificationContentViewModel!
|
|
||||||
|
|
||||||
describe("a NotificationContentViewModel") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
|
@ -32,31 +22,27 @@ class NotificationContentViewModelSpec: QuickSpec {
|
||||||
SNUIKit.self
|
SNUIKit.self
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate)
|
@TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel(
|
||||||
dataChangeCancellable = viewModel.observableTableData
|
storage: mockStorage,
|
||||||
|
scheduling: .immediate
|
||||||
|
)
|
||||||
|
|
||||||
|
@TestState var dataChangeCancellable: AnyCancellable? = viewModel.observableTableData
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { _ in },
|
receiveCompletion: { _ in },
|
||||||
receiveValue: { viewModel.updateTableData($0.0) }
|
receiveValue: { viewModel.updateTableData($0.0) }
|
||||||
)
|
)
|
||||||
}
|
@TestState var dismissCancellable: AnyCancellable?
|
||||||
|
|
||||||
afterEach {
|
|
||||||
dataChangeCancellable?.cancel()
|
|
||||||
dismissCancellable?.cancel()
|
|
||||||
|
|
||||||
mockStorage = nil
|
|
||||||
dataChangeCancellable = nil
|
|
||||||
dismissCancellable = nil
|
|
||||||
viewModel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Basic Tests
|
|
||||||
|
|
||||||
|
// MARK: - a NotificationContentViewModel
|
||||||
|
describe("a NotificationContentViewModel") {
|
||||||
|
// MARK: -- has the correct title
|
||||||
it("has the correct title") {
|
it("has the correct title") {
|
||||||
expect(viewModel.title).to(equal("NOTIFICATIONS_STYLE_CONTENT_TITLE".localized()))
|
expect(viewModel.title).to(equal("NOTIFICATIONS_STYLE_CONTENT_TITLE".localized()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- has the correct number of items
|
||||||
it("has the correct number of items") {
|
it("has the correct number of items") {
|
||||||
expect(viewModel.tableData.count)
|
expect(viewModel.tableData.count)
|
||||||
.to(equal(1))
|
.to(equal(1))
|
||||||
|
@ -64,6 +50,7 @@ class NotificationContentViewModelSpec: QuickSpec {
|
||||||
.to(equal(3))
|
.to(equal(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- has the correct default state
|
||||||
it("has the correct default state") {
|
it("has the correct default state") {
|
||||||
expect(viewModel.tableData.first?.elements)
|
expect(viewModel.tableData.first?.elements)
|
||||||
.to(
|
.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") {
|
it("starts with the correct item active if not default") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview
|
db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview
|
||||||
|
@ -139,7 +127,9 @@ class NotificationContentViewModelSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when tapping an item
|
||||||
context("when tapping an item") {
|
context("when tapping an item") {
|
||||||
|
// MARK: ---- updates the saved preference
|
||||||
it("updates the saved preference") {
|
it("updates the saved preference") {
|
||||||
viewModel.tableData.first?.elements.last?.onTap?()
|
viewModel.tableData.first?.elements.last?.onTap?()
|
||||||
|
|
||||||
|
@ -147,6 +137,7 @@ class NotificationContentViewModelSpec: QuickSpec {
|
||||||
.to(equal(Preferences.NotificationPreviewType.noNameNoPreview))
|
.to(equal(Preferences.NotificationPreviewType.noNameNoPreview))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- dismisses the screen
|
||||||
it("dismisses the screen") {
|
it("dismisses the screen") {
|
||||||
var didDismissScreen: Bool = false
|
var didDismissScreen: Bool = false
|
||||||
|
|
||||||
|
|
|
@ -82,11 +82,11 @@ internal enum Theme_ClassicDark: ThemeColors {
|
||||||
.alert_buttonBackground: .classicDark1,
|
.alert_buttonBackground: .classicDark1,
|
||||||
|
|
||||||
// ConversationButton
|
// ConversationButton
|
||||||
.conversationButton_background: .classicDark1,
|
.conversationButton_background: .classicDark0,
|
||||||
.conversationButton_unreadBackground: .classicDark2,
|
.conversationButton_unreadBackground: .classicDark1,
|
||||||
.conversationButton_unreadStripBackground: .primary,
|
.conversationButton_unreadStripBackground: .primary,
|
||||||
.conversationButton_unreadBubbleBackground: .classicDark3,
|
.conversationButton_unreadBubbleBackground: .primary,
|
||||||
.conversationButton_unreadBubbleText: .classicDark6,
|
.conversationButton_unreadBubbleText: .classicDark0,
|
||||||
.conversationButton_swipeDestructive: .dangerDark,
|
.conversationButton_swipeDestructive: .dangerDark,
|
||||||
.conversationButton_swipeSecondary: .classicDark2,
|
.conversationButton_swipeSecondary: .classicDark2,
|
||||||
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color,
|
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color,
|
||||||
|
|
|
@ -85,7 +85,7 @@ internal enum Theme_ClassicLight: ThemeColors {
|
||||||
.conversationButton_background: .classicLight6,
|
.conversationButton_background: .classicLight6,
|
||||||
.conversationButton_unreadBackground: .classicLight6,
|
.conversationButton_unreadBackground: .classicLight6,
|
||||||
.conversationButton_unreadStripBackground: .primary,
|
.conversationButton_unreadStripBackground: .primary,
|
||||||
.conversationButton_unreadBubbleBackground: .classicLight3,
|
.conversationButton_unreadBubbleBackground: .primary,
|
||||||
.conversationButton_unreadBubbleText: .classicLight0,
|
.conversationButton_unreadBubbleText: .classicLight0,
|
||||||
.conversationButton_swipeDestructive: .dangerLight,
|
.conversationButton_swipeDestructive: .dangerLight,
|
||||||
.conversationButton_swipeSecondary: .classicLight1,
|
.conversationButton_swipeSecondary: .classicLight1,
|
||||||
|
|
|
@ -82,8 +82,8 @@ internal enum Theme_OceanDark: ThemeColors {
|
||||||
.alert_buttonBackground: .oceanDark3,
|
.alert_buttonBackground: .oceanDark3,
|
||||||
|
|
||||||
// ConversationButton
|
// ConversationButton
|
||||||
.conversationButton_background: .oceanDark3,
|
.conversationButton_background: .oceanDark2,
|
||||||
.conversationButton_unreadBackground: .oceanDark4,
|
.conversationButton_unreadBackground: .oceanDark3,
|
||||||
.conversationButton_unreadStripBackground: .primary,
|
.conversationButton_unreadStripBackground: .primary,
|
||||||
.conversationButton_unreadBubbleBackground: .primary,
|
.conversationButton_unreadBubbleBackground: .primary,
|
||||||
.conversationButton_unreadBubbleText: .oceanDark0,
|
.conversationButton_unreadBubbleText: .oceanDark0,
|
||||||
|
|
|
@ -16,6 +16,10 @@ public class TypedTableDefinition<T> where T: TableRecord, T: ColumnExpressible
|
||||||
return definition.column(key.name, type)
|
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) {
|
public func primaryKey(_ columns: [T.Columns], onConflict: Database.ConflictResolution? = nil) {
|
||||||
definition.primaryKey(columns.map { $0.name }, onConflict: onConflict)
|
definition.primaryKey(columns.map { $0.name }, onConflict: onConflict)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,11 +81,14 @@ extension Atomic where Value: CustomDebugStringConvertible {
|
||||||
// MARK: - ReadWriteLock
|
// MARK: - ReadWriteLock
|
||||||
|
|
||||||
private class ReadWriteLock {
|
private class ReadWriteLock {
|
||||||
private var rwlock: pthread_rwlock_t = {
|
private var rwlock: pthread_rwlock_t
|
||||||
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)
|
pthread_rwlock_init(&rwlock, nil)
|
||||||
return rwlock
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
func writeLock() {
|
func writeLock() {
|
||||||
pthread_rwlock_wrlock(&rwlock)
|
pthread_rwlock_wrlock(&rwlock)
|
||||||
|
|
|
@ -4,6 +4,11 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
public enum HTTP {
|
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 seedNodeURLSession = URLSession(configuration: .ephemeral, delegate: seedNodeURLSessionDelegate, delegateQueue: nil)
|
||||||
private static let seedNodeURLSessionDelegate = SeedNodeURLSessionDelegateImplementation()
|
private static let seedNodeURLSessionDelegate = SeedNodeURLSessionDelegateImplementation()
|
||||||
private static let snodeURLSession = URLSession(configuration: .ephemeral, delegate: snodeURLSessionDelegate, delegateQueue: nil)
|
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
|
/// **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
|
/// 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
|
/// can't use the 10 year certificates that the other platforms use
|
||||||
private static let storageSeed1Cert: SecCertificate = {
|
private static let storageSeedCertificates: Atomic<Certificates> = {
|
||||||
let path = Bundle.main.path(forResource: "seed1-2023-2y", ofType: "der")!
|
let certFileNames: [String] = [
|
||||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
"seed1-2023-2y",
|
||||||
return SecCertificateCreateWithData(nil, data as CFData)!
|
"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) }
|
||||||
|
|
||||||
private static let storageSeed2Cert: SecCertificate = {
|
guard certificates.count == certFileNames.count else {
|
||||||
let path = Bundle.main.path(forResource: "seed2-2023-2y", ofType: "der")!
|
return Atomic(Certificates(isValid: false, certificates: []))
|
||||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
}
|
||||||
return SecCertificateCreateWithData(nil, data as CFData)!
|
|
||||||
}()
|
|
||||||
|
|
||||||
private static let storageSeed3Cert: SecCertificate = {
|
return Atomic(Certificates(isValid: true, certificates: certificates))
|
||||||
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)!
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Settings
|
// MARK: - Settings
|
||||||
|
@ -41,12 +45,15 @@ public enum HTTP {
|
||||||
private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate {
|
private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate {
|
||||||
|
|
||||||
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
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 {
|
guard let trust = challenge.protectionSpace.serverTrust else {
|
||||||
return completionHandler(.cancelAuthenticationChallenge, nil)
|
return completionHandler(.cancelAuthenticationChallenge, nil)
|
||||||
}
|
}
|
||||||
// Mark the seed node certificates as trusted
|
// Mark the seed node certificates as trusted
|
||||||
let certificates = [ storageSeed1Cert, storageSeed2Cert, storageSeed3Cert ]
|
guard SecTrustSetAnchorCertificates(trust, HTTP.storageSeedCertificates.wrappedValue.certificates as CFArray) == errSecSuccess else {
|
||||||
guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else {
|
|
||||||
SNLog("Failed to set seed node certificates.")
|
SNLog("Failed to set seed node certificates.")
|
||||||
return completionHandler(.cancelAuthenticationChallenge, nil)
|
return completionHandler(.cancelAuthenticationChallenge, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,19 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class IdentitySpec: QuickSpec {
|
class IdentitySpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
var mockStorage: Storage!
|
|
||||||
|
|
||||||
describe("an Identity") {
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self
|
SNUtilitiesKit.self
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: - an Identity
|
||||||
|
describe("an Identity") {
|
||||||
|
// MARK: -- correctly retrieves the user user public key
|
||||||
it("correctly retrieves the user user public key") {
|
it("correctly retrieves the user user public key") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .x25519PublicKey, data: "Test1".data(using: .utf8)!).insert(db)
|
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") {
|
it("correctly retrieves the user private key") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .x25519PrivateKey, data: "Test2".data(using: .utf8)!).insert(db)
|
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") {
|
it("correctly retrieves the user key pair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
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") {
|
it("correctly determines if the user exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
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") {
|
it("correctly retrieves the user ED25519 key pair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .ed25519PublicKey, data: "Test5".data(using: .utf8)!).insert(db)
|
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") {
|
it("correctly retrieves the hex encoded seed") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .seed, data: "Test7".data(using: .utf8)!).insert(db)
|
try Identity(variant: .seed, data: "Test7".data(using: .utf8)!).insert(db)
|
||||||
|
|
|
@ -9,113 +9,22 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class PersistableRecordUtilitiesSpec: QuickSpec {
|
class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
override class func spec() {
|
||||||
public static var databaseTableName: String { "TestType" }
|
// MARK: Configuration
|
||||||
|
|
||||||
public typealias Columns = CodingKeys
|
@TestState var customWriter: DatabaseQueue! = try! DatabaseQueue()
|
||||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
case columnA
|
|
||||||
case columnB
|
|
||||||
}
|
|
||||||
|
|
||||||
public let columnA: String
|
|
||||||
public let columnB: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
|
|
||||||
public static var databaseTableName: String { "MutableTestType" }
|
|
||||||
|
|
||||||
public typealias Columns = CodingKeys
|
|
||||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
|
||||||
case id
|
|
||||||
case columnA
|
|
||||||
case columnB
|
|
||||||
}
|
|
||||||
|
|
||||||
public var id: Int64?
|
|
||||||
public let columnA: String
|
|
||||||
public let columnB: String?
|
|
||||||
|
|
||||||
init(id: Int64? = nil, columnA: String, columnB: String?) {
|
|
||||||
self.id = id
|
|
||||||
self.columnA = columnA
|
|
||||||
self.columnB = columnB
|
|
||||||
}
|
|
||||||
|
|
||||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
||||||
self.id = inserted.rowID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TestInsertTestTypeMigration: Migration {
|
|
||||||
static let target: TargetMigrations.Identifier = .test
|
|
||||||
static let identifier: String = "TestInsertTestType"
|
|
||||||
static let needsConfigSync: Bool = false
|
|
||||||
static let minExpectedRunDuration: TimeInterval = 0
|
|
||||||
|
|
||||||
static func migrate(_ db: Database) throws {
|
|
||||||
try db.create(table: TestType.self) { t in
|
|
||||||
t.column(.columnA, .text).primaryKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
try db.create(table: MutableTestType.self) { t in
|
|
||||||
t.column(.id, .integer).primaryKey(autoincrement: true)
|
|
||||||
t.column(.columnA, .text).unique()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TestAddColumnMigration: Migration {
|
|
||||||
static let target: TargetMigrations.Identifier = .test
|
|
||||||
static let identifier: String = "TestAddColumn"
|
|
||||||
static let needsConfigSync: Bool = false
|
|
||||||
static let minExpectedRunDuration: TimeInterval = 0
|
|
||||||
|
|
||||||
static func migrate(_ db: Database) throws {
|
|
||||||
try db.alter(table: TestType.self) { t in
|
|
||||||
t.add(.columnB, .text)
|
|
||||||
}
|
|
||||||
|
|
||||||
try db.alter(table: MutableTestType.self) { t in
|
|
||||||
t.add(.columnB, .text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct TestTarget: MigratableTarget {
|
|
||||||
static func migrations(_ db: Database) -> TargetMigrations {
|
|
||||||
return TargetMigrations(
|
|
||||||
identifier: .test,
|
|
||||||
migrations: (0..<100)
|
|
||||||
.map { _ in [] }
|
|
||||||
.appending([TestInsertTestTypeMigration.self])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
var customWriter: DatabaseQueue!
|
|
||||||
var mockStorage: Storage!
|
|
||||||
|
|
||||||
describe("a PersistableRecord") {
|
|
||||||
beforeEach {
|
|
||||||
customWriter = try! DatabaseQueue()
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: customWriter,
|
customWriter: customWriter,
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
TestTarget.self
|
TestTarget.self
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
afterEach {
|
|
||||||
customWriter = nil
|
|
||||||
mockStorage = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: - a PersistableRecord
|
||||||
|
describe("a PersistableRecord") {
|
||||||
|
// MARK: -- before running the add column migration
|
||||||
context("before running the add column migration") {
|
context("before running the add column migration") {
|
||||||
|
// MARK: ---- fails when using the standard insert
|
||||||
it("fails when using the standard insert") {
|
it("fails when using the standard insert") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -125,6 +34,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- fails when using the standard inserted
|
||||||
it("fails when using the standard inserted") {
|
it("fails when using the standard inserted") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard save and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard saved and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard mutable upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard upsert and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("fails when using the standard mutable upsert and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -200,6 +116,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- succeeds when using the migration safe insert
|
||||||
it("succeeds when using the migration safe insert") {
|
it("succeeds when using the migration safe insert") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -214,6 +131,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- succeeds when using the migration safe inserted
|
||||||
it("succeeds when using the migration safe inserted") {
|
it("succeeds when using the migration safe inserted") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe save and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe saved and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
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
|
// MARK: ---- succeeds when using the standard saved and the item already exists
|
||||||
// due to the structure discrepancy but won't update the id as that only happens on
|
|
||||||
// insert
|
|
||||||
it("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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
try db.execute(
|
||||||
|
@ -339,6 +262,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- after running the add column migration
|
||||||
context("after running the add column migration") {
|
context("after running the add column migration") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
var migrator: DatabaseMigrator = DatabaseMigrator()
|
var migrator: DatabaseMigrator = DatabaseMigrator()
|
||||||
|
@ -352,6 +276,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
.toNot(throwError())
|
.toNot(throwError())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- succeeds when using the standard insert
|
||||||
it("succeeds when using the standard insert") {
|
it("succeeds when using the standard insert") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -366,6 +291,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- succeeds when using the standard inserted
|
||||||
it("succeeds when using the standard inserted") {
|
it("succeeds when using the standard inserted") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the standard save and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the standard saved and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the standard save and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -411,9 +340,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
// MARK: ---- succeeds when using the standard saved and the item already exists
|
||||||
// insert
|
|
||||||
it("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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
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") {
|
it("succeeds when using the standard upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the standard mutable upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the standard upsert and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -477,9 +409,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
// MARK: ---- succeeds when using the standard mutable upsert and the item already exists
|
||||||
// insert
|
|
||||||
it("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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
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") {
|
it("succeeds when using the migration safe insert") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -526,6 +459,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- succeeds when using the migration safe inserted
|
||||||
it("succeeds when using the migration safe inserted") {
|
it("succeeds when using the migration safe inserted") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe save and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe saved and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe save and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -578,9 +515,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
// MARK: ---- succeeds when using the migration safe saved and the item already exists
|
||||||
// insert
|
|
||||||
it("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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
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") {
|
it("succeeds when using the migration safe upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
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") {
|
it("succeeds when using the migration safe upsert and the item already exists") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
|
@ -645,9 +585,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The built-in 'update' method won't update the id as that only happens on
|
// MARK: ---- succeeds when using the migration safe mutable upsert and the item already exists
|
||||||
// insert
|
|
||||||
it("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
|
mockStorage.write { db in
|
||||||
expect {
|
expect {
|
||||||
try db.execute(
|
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
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class ArrayUtilitiesSpec: QuickSpec {
|
class ArrayUtilitiesSpec: QuickSpec {
|
||||||
private struct TestType: Equatable {
|
override class func spec() {
|
||||||
let stringValue: String
|
// MARK: - an Array
|
||||||
let intValue: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
describe("an Array") {
|
describe("an Array") {
|
||||||
|
// MARK: -- when grouping
|
||||||
context("when grouping") {
|
context("when grouping") {
|
||||||
|
// MARK: ---- maintains the original array ordering
|
||||||
it("maintains the original array ordering") {
|
it("maintains the original array ordering") {
|
||||||
let data: [TestType] = [
|
let data: [TestType] = [
|
||||||
TestType(stringValue: "b", intValue: 5),
|
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
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class DependenciesSpec: QuickSpec {
|
class DependenciesSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: Configuration
|
||||||
|
|
||||||
override func spec() {
|
@TestState var dependencies: Dependencies! = Dependencies()
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
|
// MARK: - Dependencies
|
||||||
describe("Dependencies") {
|
describe("Dependencies") {
|
||||||
beforeEach {
|
// MARK: -- when accessing dateNow
|
||||||
dependencies = Dependencies()
|
|
||||||
}
|
|
||||||
|
|
||||||
context("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") {
|
it("creates a new date every time when not overwritten") {
|
||||||
let date1 = dependencies.dateNow
|
let date1 = dependencies.dateNow
|
||||||
Thread.sleep(forTimeInterval: 0.05)
|
Thread.sleep(forTimeInterval: 0.05)
|
||||||
|
@ -27,6 +26,7 @@ class DependenciesSpec: QuickSpec {
|
||||||
expect(date1.timeIntervalSince1970).toNot(equal(date2.timeIntervalSince1970))
|
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") {
|
it("returns the same new date every time when overwritten") {
|
||||||
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
|
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,14 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class SessionIdSpec: QuickSpec {
|
class SessionIdSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a SessionId
|
||||||
override func spec() {
|
|
||||||
describe("a SessionId") {
|
describe("a SessionId") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- with an idString
|
||||||
context("with an idString") {
|
context("with an idString") {
|
||||||
|
// MARK: ------ succeeds when correct
|
||||||
it("succeeds when correct") {
|
it("succeeds when correct") {
|
||||||
let sessionId: SessionId? = SessionId(from: "05\(TestConstants.publicKey)")
|
let sessionId: SessionId? = SessionId(from: "05\(TestConstants.publicKey)")
|
||||||
|
|
||||||
|
@ -21,16 +23,20 @@ class SessionIdSpec: QuickSpec {
|
||||||
expect(sessionId?.publicKey).to(equal(TestConstants.publicKey))
|
expect(sessionId?.publicKey).to(equal(TestConstants.publicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails when too short
|
||||||
it("fails when too short") {
|
it("fails when too short") {
|
||||||
expect(SessionId(from: "")).to(beNil())
|
expect(SessionId(from: "")).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails with an invalid prefix
|
||||||
it("fails with an invalid prefix") {
|
it("fails with an invalid prefix") {
|
||||||
expect(SessionId(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
expect(SessionId(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- with a prefix and publicKey
|
||||||
context("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") {
|
it("converts the bytes into a hex string") {
|
||||||
let sessionId: SessionId? = SessionId(.standard, publicKey: [0, 1, 2, 3, 4, 5, 6, 7, 8])
|
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") {
|
it("generates the correct hex string") {
|
||||||
expect(SessionId(.unblinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString)
|
expect(SessionId(.unblinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString)
|
||||||
.to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
.to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"))
|
||||||
|
@ -52,9 +59,13 @@ class SessionIdSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - a SessionId Prefix
|
||||||
describe("a SessionId Prefix") {
|
describe("a SessionId Prefix") {
|
||||||
|
// MARK: -- when initializing
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
|
// MARK: ---- with just a prefix
|
||||||
context("with just a prefix") {
|
context("with just a prefix") {
|
||||||
|
// MARK: ------ succeeds when valid
|
||||||
it("succeeds when valid") {
|
it("succeeds when valid") {
|
||||||
expect(SessionId.Prefix(from: "00")).to(equal(.unblinded))
|
expect(SessionId.Prefix(from: "00")).to(equal(.unblinded))
|
||||||
expect(SessionId.Prefix(from: "05")).to(equal(.standard))
|
expect(SessionId.Prefix(from: "05")).to(equal(.standard))
|
||||||
|
@ -62,24 +73,30 @@ class SessionIdSpec: QuickSpec {
|
||||||
expect(SessionId.Prefix(from: "25")).to(equal(.blinded25))
|
expect(SessionId.Prefix(from: "25")).to(equal(.blinded25))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails when nil
|
||||||
it("fails when nil") {
|
it("fails when nil") {
|
||||||
expect(SessionId.Prefix(from: nil)).to(beNil())
|
expect(SessionId.Prefix(from: nil)).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails when invalid
|
||||||
it("fails when invalid") {
|
it("fails when invalid") {
|
||||||
expect(SessionId.Prefix(from: "AB")).to(beNil())
|
expect(SessionId.Prefix(from: "AB")).to(beNil())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- with a longer string
|
||||||
context("with a longer string") {
|
context("with a longer string") {
|
||||||
|
// MARK: ------ fails with invalid hex
|
||||||
it("fails with invalid hex") {
|
it("fails with invalid hex") {
|
||||||
expect(SessionId.Prefix(from: "Hello!!!")).to(beNil())
|
expect(SessionId.Prefix(from: "Hello!!!")).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails with the wrong length
|
||||||
it("fails with the wrong length") {
|
it("fails with the wrong length") {
|
||||||
expect(SessionId.Prefix(from: String(TestConstants.publicKey.prefix(10)))).to(beNil())
|
expect(SessionId.Prefix(from: String(TestConstants.publicKey.prefix(10)))).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ fails with an invalid prefix
|
||||||
it("fails with an invalid prefix") {
|
it("fails with an invalid prefix") {
|
||||||
expect(SessionId.Prefix(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
expect(SessionId.Prefix(from: "AB\(TestConstants.publicKey)")).to(beNil())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,120 +9,10 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class JobRunnerSpec: QuickSpec {
|
class JobRunnerSpec: QuickSpec {
|
||||||
struct TestDetails: Codable {
|
override class func spec() {
|
||||||
enum ResultType: Codable {
|
// MARK: Configuration
|
||||||
case success
|
|
||||||
case failure
|
|
||||||
case permanentFailure
|
|
||||||
case deferred
|
|
||||||
}
|
|
||||||
|
|
||||||
public let result: ResultType
|
@TestState var job1: Job! = Job(
|
||||||
public let completeTime: Int
|
|
||||||
public let intValue: Int64
|
|
||||||
public let stringValue: String
|
|
||||||
|
|
||||||
init(
|
|
||||||
result: ResultType = .success,
|
|
||||||
completeTime: Int = 0,
|
|
||||||
intValue: Int64 = 100,
|
|
||||||
stringValue: String = "200"
|
|
||||||
) {
|
|
||||||
self.result = result
|
|
||||||
self.completeTime = completeTime
|
|
||||||
self.intValue = intValue
|
|
||||||
self.stringValue = stringValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InvalidDetails: Codable {
|
|
||||||
func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed }
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TestJob: JobExecutor {
|
|
||||||
static let maxFailureCount: Int = 1
|
|
||||||
static let requiresThreadId: Bool = false
|
|
||||||
static let requiresInteractionId: Bool = false
|
|
||||||
|
|
||||||
static func run(
|
|
||||||
_ job: Job,
|
|
||||||
queue: DispatchQueue,
|
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
|
||||||
using dependencies: Dependencies
|
|
||||||
) {
|
|
||||||
guard
|
|
||||||
let detailsData: Data = job.details,
|
|
||||||
let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData)
|
|
||||||
else { return success(job, true, dependencies) }
|
|
||||||
|
|
||||||
let completeJob: () -> () = {
|
|
||||||
// Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job
|
|
||||||
// from immediately being run again or immediately completing afterwards
|
|
||||||
let updatedJob: Job = job
|
|
||||||
.with(nextRunTimestamp: TimeInterval(details.completeTime + 1))
|
|
||||||
.with(
|
|
||||||
details: TestDetails(
|
|
||||||
result: details.result,
|
|
||||||
completeTime: (details.completeTime + 2),
|
|
||||||
intValue: details.intValue,
|
|
||||||
stringValue: details.stringValue
|
|
||||||
)
|
|
||||||
)!
|
|
||||||
dependencies.storage.write { db in try _ = updatedJob.saved(db) }
|
|
||||||
|
|
||||||
switch details.result {
|
|
||||||
case .success: success(job, true, dependencies)
|
|
||||||
case .failure: failure(job, nil, false, dependencies)
|
|
||||||
case .permanentFailure: failure(job, nil, true, dependencies)
|
|
||||||
case .deferred: deferred(updatedJob, dependencies)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard dependencies.fixedTime < details.completeTime else {
|
|
||||||
return queue.async(using: dependencies) {
|
|
||||||
completeJob()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies.asyncExecutions.appendTo(details.completeTime) {
|
|
||||||
queue.async(using: dependencies) {
|
|
||||||
completeJob()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
|
||||||
|
|
||||||
override func spec() {
|
|
||||||
var jobRunner: JobRunnerType!
|
|
||||||
var job1: Job!
|
|
||||||
var job2: Job!
|
|
||||||
var mockStorage: Storage!
|
|
||||||
var dependencies: Dependencies!
|
|
||||||
|
|
||||||
describe("a JobRunner") {
|
|
||||||
// MARK: - Configuration
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
mockStorage = SynchronousStorage(
|
|
||||||
customWriter: try! DatabaseQueue(),
|
|
||||||
customMigrationTargets: [
|
|
||||||
SNUtilitiesKit.self
|
|
||||||
]
|
|
||||||
)
|
|
||||||
dependencies = Dependencies(
|
|
||||||
storage: mockStorage,
|
|
||||||
dateNow: Date(timeIntervalSince1970: 0),
|
|
||||||
forceSynchronous: true
|
|
||||||
)
|
|
||||||
|
|
||||||
// Migrations add jobs which we don't want so delete them
|
|
||||||
mockStorage.write { db in try Job.deleteAll(db) }
|
|
||||||
|
|
||||||
job1 = Job(
|
|
||||||
id: 100,
|
id: 100,
|
||||||
failureCount: 0,
|
failureCount: 0,
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -134,7 +24,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
details: nil
|
details: nil
|
||||||
)
|
)
|
||||||
job2 = Job(
|
@TestState var job2: Job! = Job(
|
||||||
id: 101,
|
id: 101,
|
||||||
failureCount: 0,
|
failureCount: 0,
|
||||||
variant: .attachmentUpload,
|
variant: .attachmentUpload,
|
||||||
|
@ -146,28 +36,44 @@ class JobRunnerSpec: QuickSpec {
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
details: nil
|
details: nil
|
||||||
)
|
)
|
||||||
|
@TestState var mockStorage: Storage! = SynchronousStorage(
|
||||||
jobRunner = JobRunner(isTestingJobRunner: true, using: dependencies)
|
customWriter: try! DatabaseQueue(),
|
||||||
jobRunner.setExecutor(TestJob.self, for: .messageSend)
|
customMigrationTargets: [
|
||||||
jobRunner.setExecutor(TestJob.self, for: .attachmentUpload)
|
SNUtilitiesKit.self
|
||||||
jobRunner.setExecutor(TestJob.self, for: .messageReceive)
|
],
|
||||||
|
initialData: { db in
|
||||||
|
// Migrations add jobs which we don't want so delete them
|
||||||
|
try Job.deleteAll(db)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@TestState var dependencies: Dependencies! = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
dateNow: Date(timeIntervalSince1970: 0),
|
||||||
|
forceSynchronous: true
|
||||||
|
)
|
||||||
|
@TestState var jobRunner: JobRunnerType! = {
|
||||||
|
let result = JobRunner(isTestingJobRunner: true, using: dependencies)
|
||||||
|
result.setExecutor(TestJob.self, for: .messageSend)
|
||||||
|
result.setExecutor(TestJob.self, for: .attachmentUpload)
|
||||||
|
result.setExecutor(TestJob.self, for: .messageReceive)
|
||||||
|
|
||||||
// Need to assign this to ensure it's used by nested dependencies
|
// Need to assign this to ensure it's used by nested dependencies
|
||||||
dependencies.jobRunner = jobRunner
|
dependencies.jobRunner = result
|
||||||
}
|
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - a JobRunner
|
||||||
|
describe("a JobRunner") {
|
||||||
afterEach {
|
afterEach {
|
||||||
/// We **must** set `fixedTime` to ensure we break any loops within the `TestJob` executor
|
/// We **must** set `fixedTime` to ensure we break any loops within the `TestJob` executor
|
||||||
dependencies.fixedTime = Int.max
|
dependencies.fixedTime = Int.max
|
||||||
jobRunner.stopAndClearPendingJobs()
|
jobRunner.stopAndClearPendingJobs()
|
||||||
jobRunner = nil
|
|
||||||
mockStorage = nil
|
|
||||||
dependencies = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - when configuring
|
// MARK: -- when configuring
|
||||||
context("when configuring") {
|
context("when configuring") {
|
||||||
// MARK: -- adds an executor correctly
|
// MARK: ---- adds an executor correctly
|
||||||
it("adds an executor correctly") {
|
it("adds an executor correctly") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 101,
|
id: 101,
|
||||||
|
@ -222,28 +128,28 @@ class JobRunnerSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when managing state
|
// MARK: ---- when managing state
|
||||||
context("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") {
|
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") {
|
it("returns false when not given a job") {
|
||||||
expect(jobRunner.isCurrentlyRunning(nil)).to(beFalse())
|
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") {
|
it("returns false when given a job that has not been persisted") {
|
||||||
job1 = Job(variant: .messageSend)
|
job1 = Job(variant: .messageSend)
|
||||||
|
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
it("returns false when given a job that is not running") {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
it("returns true when given a non blocking job that is running") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
|
@ -261,7 +167,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
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") {
|
it("returns true when given a blocking job that is running") {
|
||||||
job2 = Job(
|
job2 = Job(
|
||||||
id: 101,
|
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") {
|
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") {
|
it("returns an empty dictionary when there are no jobs") {
|
||||||
expect(jobRunner.allJobInfo()).to(equal([:]))
|
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") {
|
it("returns an empty dictionary when there are no jobs matching the filters") {
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
jobRunner.appDidBecomeActive(using: dependencies)
|
jobRunner.appDidBecomeActive(using: dependencies)
|
||||||
|
@ -317,7 +223,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.jobInfoFor(state: .running, variant: .messageSend)).to(equal([:]))
|
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") {
|
it("can filter to specific jobs") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
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") {
|
it("can filter to running jobs") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -450,7 +356,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
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") {
|
it("can filter to pending jobs") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -514,7 +420,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
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") {
|
it("can filter to specific variants") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
job2 = job2.with(details: TestDetails(completeTime: 2))
|
||||||
|
@ -552,7 +458,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101]))
|
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") {
|
it("includes non blocking jobs") {
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
|
@ -580,7 +486,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ------ includes blocking jobs
|
// MARK: -------- includes blocking jobs
|
||||||
it("includes blocking jobs") {
|
it("includes blocking jobs") {
|
||||||
job2 = Job(
|
job2 = Job(
|
||||||
id: 101,
|
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") {
|
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") {
|
it("returns false for a queue that doesn't exist") {
|
||||||
jobRunner = JobRunner(
|
jobRunner = JobRunner(
|
||||||
isTestingJobRunner: true,
|
isTestingJobRunner: true,
|
||||||
|
@ -636,19 +542,19 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(beFalse())
|
.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") {
|
it("returns false when the provided details fail to decode") {
|
||||||
expect(jobRunner.hasJob(of: .attachmentUpload, with: InvalidDetails()))
|
expect(jobRunner.hasJob(of: .attachmentUpload, with: InvalidDetails()))
|
||||||
.to(beFalse())
|
.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") {
|
it("returns false when there is not a pending or running job") {
|
||||||
expect(jobRunner.hasJob(of: .attachmentUpload, with: TestDetails()))
|
expect(jobRunner.hasJob(of: .attachmentUpload, with: TestDetails()))
|
||||||
.to(beFalse())
|
.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") {
|
it("returns true when there is a pending job") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -703,7 +609,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(beTrue())
|
.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") {
|
it("returns true when there is a running job") {
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
|
@ -724,7 +630,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(beTrue())
|
.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") {
|
it("returns true when there is a blocking job") {
|
||||||
job2 = Job(
|
job2 = Job(
|
||||||
id: 101,
|
id: 101,
|
||||||
|
@ -760,7 +666,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(beTrue())
|
.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") {
|
it("returns true when there is a non blocking job") {
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
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") {
|
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") {
|
it("does not start a job before getting the app launch call") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
|
|
||||||
|
@ -800,7 +706,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
it("does nothing if there are no app launch jobs") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
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") {
|
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") {
|
it("does not start a job before getting the app active call") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
|
|
||||||
|
@ -836,7 +742,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
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))
|
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||||
job2 = Job(
|
job2 = Job(
|
||||||
|
@ -881,7 +787,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
it("does not start the job queues if there are app active jobs and blocking jobs are running") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -939,7 +845,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse())
|
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") {
|
it("starts the job queues if there are no app active jobs") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
|
@ -962,7 +868,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
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") {
|
it("starts the job queues if there are app active jobs") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1020,7 +926,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job2)).to(beTrue())
|
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") {
|
it("starts the job queues after completing blocking app launch jobs") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 2))
|
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||||
job2 = Job(
|
job2 = Job(
|
||||||
|
@ -1074,7 +980,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue())
|
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") {
|
it("starts the job queues alongside non blocking app launch jobs") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
job2 = Job(
|
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") {
|
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") {
|
it("does not add a general job to the queue before launch") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1151,7 +1057,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.allJobInfo()).to(beEmpty())
|
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") {
|
it("adds a launch job to the queue in a pending state before launch") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1180,7 +1086,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.jobInfoFor(state: [.pending]).keys)).to(equal([100]))
|
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") {
|
it("does not add a general job to the queue after launch but before becoming active") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1210,7 +1116,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(jobRunner.allJobInfo()).to(beEmpty())
|
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") {
|
it("adds a launch job to the queue in a pending state after launch but before becoming active") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1240,7 +1146,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.jobInfoFor(state: .pending).keys)).to(equal([100]))
|
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") {
|
it("adds a general job to the queue after becoming active") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1271,7 +1177,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
expect(Array(jobRunner.allJobInfo().keys)).to(equal([100]))
|
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") {
|
it("adds a launch job to the queue and starts it after becoming active") {
|
||||||
job1 = Job(
|
job1 = Job(
|
||||||
id: 100,
|
id: 100,
|
||||||
|
@ -1304,16 +1210,16 @@ class JobRunnerSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- when running jobs
|
// MARK: ---- when running jobs
|
||||||
context("when running jobs") {
|
context("when running jobs") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
jobRunner.appDidBecomeActive(using: dependencies)
|
jobRunner.appDidBecomeActive(using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ---- by adding
|
// MARK: ------ by adding
|
||||||
context("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") {
|
it("does not start until after the db transaction completes") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
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") {
|
context("with job dependencies") {
|
||||||
// MARK: ------ starts dependencies first
|
// MARK: -------- starts dependencies first
|
||||||
it("starts dependencies first") {
|
it("starts dependencies first") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
job2 = job2.with(details: TestDetails(completeTime: 2))
|
||||||
|
@ -1347,7 +1253,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(equal([101]))
|
.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") {
|
it("removes the initial job from the queue") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 1))
|
job1 = job1.with(details: TestDetails(completeTime: 1))
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 2))
|
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))
|
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") {
|
it("starts the initial job when the dependencies succeed") {
|
||||||
job1 = job1.with(details: TestDetails(completeTime: 2))
|
job1 = job1.with(details: TestDetails(completeTime: 2))
|
||||||
job2 = job2.with(details: TestDetails(completeTime: 1))
|
job2 = job2.with(details: TestDetails(completeTime: 1))
|
||||||
|
@ -1390,7 +1296,7 @@ class JobRunnerSpec: QuickSpec {
|
||||||
.to(equal([100]))
|
.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") {
|
it("does not start the initial job if the dependencies are deferred") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||||
job2 = job2.with(details: TestDetails(result: .deferred, completeTime: 1))
|
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())
|
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") {
|
it("does not start the initial job if the dependencies fail") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||||
job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1))
|
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())
|
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") {
|
it("does not delete the initial job if the dependencies fail") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||||
job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1))
|
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))
|
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") {
|
it("deletes the initial job if the dependencies permanently fail") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2))
|
||||||
job2 = job2.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
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") {
|
context("when completing jobs") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
jobRunner.appDidFinishLaunching(using: dependencies)
|
jobRunner.appDidFinishLaunching(using: dependencies)
|
||||||
jobRunner.appDidBecomeActive(using: dependencies)
|
jobRunner.appDidBecomeActive(using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ---- by succeeding
|
// MARK: ------ by succeeding
|
||||||
context("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") {
|
it("removes the job from the queue") {
|
||||||
job1 = job1.with(details: TestDetails(result: .success, completeTime: 1))
|
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())
|
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ------ deletes the job
|
// MARK: -------- deletes the job
|
||||||
it("deletes the job") {
|
it("deletes the job") {
|
||||||
job1 = job1.with(details: TestDetails(result: .success, completeTime: 1))
|
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") {
|
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") {
|
it("reschedules the job to run again later") {
|
||||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
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") {
|
it("does not delete the job") {
|
||||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
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))
|
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") {
|
it("fails the job if it is deferred too many times") {
|
||||||
job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1))
|
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") {
|
context("by failing") {
|
||||||
// MARK: ------ removes the job from the queue
|
// MARK: -------- removes the job from the queue
|
||||||
it("removes the job from the queue") {
|
it("removes the job from the queue") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1))
|
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())
|
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") {
|
it("does not delete the job") {
|
||||||
job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1))
|
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") {
|
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") {
|
it("removes the job from the queue") {
|
||||||
job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
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())
|
expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ------ deletes the job
|
// MARK: -------- deletes the job
|
||||||
it("deletes the job") {
|
it("deletes the job") {
|
||||||
job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1))
|
||||||
|
|
||||||
|
@ -1753,3 +1659,90 @@ class JobRunnerSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Types
|
||||||
|
|
||||||
|
fileprivate struct TestDetails: Codable {
|
||||||
|
enum ResultType: Codable {
|
||||||
|
case success
|
||||||
|
case failure
|
||||||
|
case permanentFailure
|
||||||
|
case deferred
|
||||||
|
}
|
||||||
|
|
||||||
|
public let result: ResultType
|
||||||
|
public let completeTime: Int
|
||||||
|
public let intValue: Int64
|
||||||
|
public let stringValue: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
result: ResultType = .success,
|
||||||
|
completeTime: Int = 0,
|
||||||
|
intValue: Int64 = 100,
|
||||||
|
stringValue: String = "200"
|
||||||
|
) {
|
||||||
|
self.result = result
|
||||||
|
self.completeTime = completeTime
|
||||||
|
self.intValue = intValue
|
||||||
|
self.stringValue = stringValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate struct InvalidDetails: Codable {
|
||||||
|
func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate enum TestJob: JobExecutor {
|
||||||
|
static let maxFailureCount: Int = 1
|
||||||
|
static let requiresThreadId: Bool = false
|
||||||
|
static let requiresInteractionId: Bool = false
|
||||||
|
|
||||||
|
static func run(
|
||||||
|
_ job: Job,
|
||||||
|
queue: DispatchQueue,
|
||||||
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) {
|
||||||
|
guard
|
||||||
|
let detailsData: Data = job.details,
|
||||||
|
let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData)
|
||||||
|
else { return success(job, true, dependencies) }
|
||||||
|
|
||||||
|
let completeJob: () -> () = {
|
||||||
|
// Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job
|
||||||
|
// from immediately being run again or immediately completing afterwards
|
||||||
|
let updatedJob: Job = job
|
||||||
|
.with(nextRunTimestamp: TimeInterval(details.completeTime + 1))
|
||||||
|
.with(
|
||||||
|
details: TestDetails(
|
||||||
|
result: details.result,
|
||||||
|
completeTime: (details.completeTime + 2),
|
||||||
|
intValue: details.intValue,
|
||||||
|
stringValue: details.stringValue
|
||||||
|
)
|
||||||
|
)!
|
||||||
|
dependencies.storage.write { db in try _ = updatedJob.saved(db) }
|
||||||
|
|
||||||
|
switch details.result {
|
||||||
|
case .success: success(job, true, dependencies)
|
||||||
|
case .failure: failure(job, nil, false, dependencies)
|
||||||
|
case .permanentFailure: failure(job, nil, true, dependencies)
|
||||||
|
case .deferred: deferred(updatedJob, dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard dependencies.fixedTime < details.completeTime else {
|
||||||
|
return queue.async(using: dependencies) {
|
||||||
|
completeJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies.asyncExecutions.appendTo(details.completeTime) {
|
||||||
|
queue.async(using: dependencies) {
|
||||||
|
completeJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,145 +9,13 @@ import Nimble
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class BatchResponseSpec: QuickSpec {
|
class BatchResponseSpec: QuickSpec {
|
||||||
struct TestType: Codable, Equatable {
|
override class func spec() {
|
||||||
let stringValue: String
|
// MARK: Configuration
|
||||||
}
|
|
||||||
struct TestType2: Codable, Equatable {
|
|
||||||
let intValue: Int
|
|
||||||
let stringValue2: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Spec
|
@TestState var responseInfo: ResponseInfoType! = HTTP.ResponseInfo(code: 200, headers: [:])
|
||||||
|
@TestState var testType: TestType! = TestType(stringValue: "test1")
|
||||||
override func spec() {
|
@TestState var testType2: TestType2! = TestType2(intValue: 123, stringValue2: "test2")
|
||||||
// MARK: - HTTP.BatchSubResponse<T>
|
@TestState var data: Data! = """
|
||||||
|
|
||||||
describe("an HTTP.BatchSubResponse<T>") {
|
|
||||||
context("when decoding") {
|
|
||||||
it("decodes correctly") {
|
|
||||||
let jsonString: String = """
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"headers": {
|
|
||||||
"testKey": "testValue"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"stringValue": "testValue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
|
||||||
HTTP.BatchSubResponse<TestType>.self,
|
|
||||||
from: jsonString.data(using: .utf8)!
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(subResponse).toNot(beNil())
|
|
||||||
expect(subResponse?.body).toNot(beNil())
|
|
||||||
}
|
|
||||||
|
|
||||||
it("decodes with invalid body data") {
|
|
||||||
let jsonString: String = """
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"headers": {
|
|
||||||
"testKey": "testValue"
|
|
||||||
},
|
|
||||||
"body": "Hello!!!"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
|
||||||
HTTP.BatchSubResponse<TestType>.self,
|
|
||||||
from: jsonString.data(using: .utf8)!
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(subResponse).toNot(beNil())
|
|
||||||
}
|
|
||||||
|
|
||||||
it("flags invalid body data as invalid") {
|
|
||||||
let jsonString: String = """
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"headers": {
|
|
||||||
"testKey": "testValue"
|
|
||||||
},
|
|
||||||
"body": "Hello!!!"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
|
||||||
HTTP.BatchSubResponse<TestType>.self,
|
|
||||||
from: jsonString.data(using: .utf8)!
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(subResponse).toNot(beNil())
|
|
||||||
expect(subResponse?.body).to(beNil())
|
|
||||||
expect(subResponse?.failedToParseBody).to(beTrue())
|
|
||||||
}
|
|
||||||
|
|
||||||
it("does not flag a missing or invalid optional body as invalid") {
|
|
||||||
let jsonString: String = """
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"headers": {
|
|
||||||
"testKey": "testValue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
let subResponse: HTTP.BatchSubResponse<TestType?>? = try? JSONDecoder().decode(
|
|
||||||
HTTP.BatchSubResponse<TestType?>.self,
|
|
||||||
from: jsonString.data(using: .utf8)!
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(subResponse).toNot(beNil())
|
|
||||||
expect(subResponse?.body).to(beNil())
|
|
||||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
|
||||||
}
|
|
||||||
|
|
||||||
it("does not flag a NoResponse body as invalid") {
|
|
||||||
let jsonString: String = """
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"headers": {
|
|
||||||
"testKey": "testValue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
let subResponse: HTTP.BatchSubResponse<NoResponse>? = try? JSONDecoder().decode(
|
|
||||||
HTTP.BatchSubResponse<NoResponse>.self,
|
|
||||||
from: jsonString.data(using: .utf8)!
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(subResponse).toNot(beNil())
|
|
||||||
expect(subResponse?.body).to(beNil())
|
|
||||||
expect(subResponse?.failedToParseBody).to(beFalse())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Convenience
|
|
||||||
// MARK: --Decodable
|
|
||||||
|
|
||||||
describe("a Decodable") {
|
|
||||||
it("decodes correctly") {
|
|
||||||
let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)!
|
|
||||||
let result: TestType? = try? TestType.decoded(from: jsonData)
|
|
||||||
|
|
||||||
expect(result).to(equal(TestType(stringValue: "testValue")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - --Combine
|
|
||||||
|
|
||||||
describe("a (ResponseInfoType, Data?) Publisher") {
|
|
||||||
var responseInfo: ResponseInfoType!
|
|
||||||
var testType: TestType!
|
|
||||||
var testType2: TestType2!
|
|
||||||
var data: Data!
|
|
||||||
|
|
||||||
beforeEach {
|
|
||||||
responseInfo = HTTP.ResponseInfo(code: 200, headers: [:])
|
|
||||||
testType = TestType(stringValue: "test1")
|
|
||||||
testType2 = TestType2(intValue: 123, stringValue2: "test2")
|
|
||||||
data = """
|
|
||||||
[\([
|
[\([
|
||||||
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
|
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
|
||||||
HTTP.BatchSubResponse(
|
HTTP.BatchSubResponse(
|
||||||
|
@ -169,8 +37,129 @@ class BatchResponseSpec: QuickSpec {
|
||||||
.map { String(data: $0, encoding: .utf8)! }
|
.map { String(data: $0, encoding: .utf8)! }
|
||||||
.joined(separator: ","))]
|
.joined(separator: ","))]
|
||||||
""".data(using: .utf8)!
|
""".data(using: .utf8)!
|
||||||
|
|
||||||
|
// MARK: - an HTTP.BatchSubResponse<T>
|
||||||
|
describe("an HTTP.BatchSubResponse<T>") {
|
||||||
|
// MARK: -- when decoding
|
||||||
|
context("when decoding") {
|
||||||
|
// MARK: ---- decodes correctly
|
||||||
|
it("decodes correctly") {
|
||||||
|
let jsonString: String = """
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"headers": {
|
||||||
|
"testKey": "testValue"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"stringValue": "testValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||||
|
HTTP.BatchSubResponse<TestType>.self,
|
||||||
|
from: jsonString.data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subResponse).toNot(beNil())
|
||||||
|
expect(subResponse?.body).toNot(beNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- decodes with invalid body data
|
||||||
|
it("decodes with invalid body data") {
|
||||||
|
let jsonString: String = """
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"headers": {
|
||||||
|
"testKey": "testValue"
|
||||||
|
},
|
||||||
|
"body": "Hello!!!"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||||
|
HTTP.BatchSubResponse<TestType>.self,
|
||||||
|
from: jsonString.data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subResponse).toNot(beNil())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: ---- flags invalid body data as invalid
|
||||||
|
it("flags invalid body data as invalid") {
|
||||||
|
let jsonString: String = """
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"headers": {
|
||||||
|
"testKey": "testValue"
|
||||||
|
},
|
||||||
|
"body": "Hello!!!"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let subResponse: HTTP.BatchSubResponse<TestType>? = try? JSONDecoder().decode(
|
||||||
|
HTTP.BatchSubResponse<TestType>.self,
|
||||||
|
from: jsonString.data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subResponse).toNot(beNil())
|
||||||
|
expect(subResponse?.body).to(beNil())
|
||||||
|
expect(subResponse?.failedToParseBody).to(beTrue())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: ---- does not flag a missing or invalid optional body as invalid
|
||||||
|
it("does not flag a missing or invalid optional body as invalid") {
|
||||||
|
let jsonString: String = """
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"headers": {
|
||||||
|
"testKey": "testValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let subResponse: HTTP.BatchSubResponse<TestType?>? = try? JSONDecoder().decode(
|
||||||
|
HTTP.BatchSubResponse<TestType?>.self,
|
||||||
|
from: jsonString.data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subResponse).toNot(beNil())
|
||||||
|
expect(subResponse?.body).to(beNil())
|
||||||
|
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: ---- does not flag a NoResponse body as invalid
|
||||||
|
it("does not flag a NoResponse body as invalid") {
|
||||||
|
let jsonString: String = """
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"headers": {
|
||||||
|
"testKey": "testValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let subResponse: HTTP.BatchSubResponse<NoResponse>? = try? JSONDecoder().decode(
|
||||||
|
HTTP.BatchSubResponse<NoResponse>.self,
|
||||||
|
from: jsonString.data(using: .utf8)!
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(subResponse).toNot(beNil())
|
||||||
|
expect(subResponse?.body).to(beNil())
|
||||||
|
expect(subResponse?.failedToParseBody).to(beFalse())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - a Decodable
|
||||||
|
describe("a Decodable") {
|
||||||
|
// MARK: -- decodes correctly
|
||||||
|
it("decodes correctly") {
|
||||||
|
let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)!
|
||||||
|
let result: TestType? = try? TestType.decoded(from: jsonData)
|
||||||
|
|
||||||
|
expect(result).to(equal(TestType(stringValue: "testValue")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - a (ResponseInfoType, Data?) Publisher
|
||||||
|
describe("a (ResponseInfoType, Data?) Publisher") {
|
||||||
|
// MARK: -- decodes valid data correctly
|
||||||
it("decodes valid data correctly") {
|
it("decodes valid data correctly") {
|
||||||
var result: HTTP.BatchResponse?
|
var result: HTTP.BatchResponse?
|
||||||
Just((responseInfo, data))
|
Just((responseInfo, data))
|
||||||
|
@ -191,6 +180,7 @@ class BatchResponseSpec: QuickSpec {
|
||||||
.to(equal(testType2))
|
.to(equal(testType2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- fails if there is no data
|
||||||
it("fails if there is no data") {
|
it("fails if there is no data") {
|
||||||
var error: Error?
|
var error: Error?
|
||||||
Just((responseInfo, nil))
|
Just((responseInfo, nil))
|
||||||
|
@ -203,6 +193,7 @@ class BatchResponseSpec: QuickSpec {
|
||||||
expect(error).to(matchError(HTTPError.parsingFailed))
|
expect(error).to(matchError(HTTPError.parsingFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- fails if the data is not JSON
|
||||||
it("fails if the data is not JSON") {
|
it("fails if the data is not JSON") {
|
||||||
var error: Error?
|
var error: Error?
|
||||||
Just((responseInfo, Data([1, 2, 3])))
|
Just((responseInfo, Data([1, 2, 3])))
|
||||||
|
@ -215,6 +206,7 @@ class BatchResponseSpec: QuickSpec {
|
||||||
expect(error).to(matchError(HTTPError.parsingFailed))
|
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") {
|
it("fails if the data is not a JSON array") {
|
||||||
var error: Error?
|
var error: Error?
|
||||||
Just((responseInfo, "{}".data(using: .utf8)))
|
Just((responseInfo, "{}".data(using: .utf8)))
|
||||||
|
@ -227,6 +219,7 @@ class BatchResponseSpec: QuickSpec {
|
||||||
expect(error).to(matchError(HTTPError.parsingFailed))
|
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") {
|
it("fails if the JSON array does not have the same number of items as the expected types") {
|
||||||
var error: Error?
|
var error: Error?
|
||||||
Just((responseInfo, data))
|
Just((responseInfo, data))
|
||||||
|
@ -243,6 +236,7 @@ class BatchResponseSpec: QuickSpec {
|
||||||
expect(error).to(matchError(HTTPError.parsingFailed))
|
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") {
|
it("fails if one of the JSON array values fails to decode") {
|
||||||
data = """
|
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
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class BencodeSpec: QuickSpec {
|
class BencodeSpec: QuickSpec {
|
||||||
struct TestType: Codable, Equatable {
|
override class func spec() {
|
||||||
let intValue: Int
|
// MARK: - Bencode
|
||||||
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() {
|
|
||||||
describe("Bencode") {
|
describe("Bencode") {
|
||||||
// MARK: - when decoding
|
// MARK: -- when decoding
|
||||||
context("when decoding") {
|
context("when decoding") {
|
||||||
// MARK: -- should decode a basic string
|
// MARK: ---- should decode a basic string
|
||||||
it("should decode a basic string") {
|
it("should decode a basic string") {
|
||||||
let basicStringData: Data = "5:howdy".data(using: .utf8)!
|
let basicStringData: Data = "5:howdy".data(using: .utf8)!
|
||||||
let result = try? Bencode.decode(String.self, from: basicStringData)
|
let result = try? Bencode.decode(String.self, from: basicStringData)
|
||||||
|
@ -51,7 +21,7 @@ class BencodeSpec: QuickSpec {
|
||||||
expect(result).to(equal("howdy"))
|
expect(result).to(equal("howdy"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- should decode a basic integer
|
// MARK: ---- should decode a basic integer
|
||||||
it("should decode a basic integer") {
|
it("should decode a basic integer") {
|
||||||
let basicIntegerData: Data = "i3e".data(using: .utf8)!
|
let basicIntegerData: Data = "i3e".data(using: .utf8)!
|
||||||
let result = try? Bencode.decode(Int.self, from: basicIntegerData)
|
let result = try? Bencode.decode(Int.self, from: basicIntegerData)
|
||||||
|
@ -59,7 +29,7 @@ class BencodeSpec: QuickSpec {
|
||||||
expect(result).to(equal(3))
|
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") {
|
it("should decode a list of integers") {
|
||||||
let basicIntListData: Data = "li1ei2ee".data(using: .utf8)!
|
let basicIntListData: Data = "li1ei2ee".data(using: .utf8)!
|
||||||
let result = try? Bencode.decode([Int].self, from: basicIntListData)
|
let result = try? Bencode.decode([Int].self, from: basicIntListData)
|
||||||
|
@ -67,7 +37,7 @@ class BencodeSpec: QuickSpec {
|
||||||
expect(result).to(equal([1, 2]))
|
expect(result).to(equal([1, 2]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- should decode a basic dict
|
// MARK: ---- should decode a basic dict
|
||||||
it("should decode a basic dict") {
|
it("should decode a basic dict") {
|
||||||
let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)!
|
let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)!
|
||||||
let result = try? Bencode.decode([String: [String]].self, from: basicDictData)
|
let result = try? Bencode.decode([String: [String]].self, from: basicDictData)
|
||||||
|
@ -75,7 +45,7 @@ class BencodeSpec: QuickSpec {
|
||||||
expect(result).to(equal(["spam": ["a", "b"]]))
|
expect(result).to(equal(["spam": ["a", "b"]]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -- decodes a decodable type
|
// MARK: ---- decodes a decodable type
|
||||||
it("decodes a decodable type") {
|
it("decodes a decodable type") {
|
||||||
let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)!
|
let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)!
|
||||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
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")))
|
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") {
|
it("decodes a stringified decodable type") {
|
||||||
let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)!
|
let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)!
|
||||||
let result: TestType? = try? Bencode.decode(TestType.self, from: data)
|
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") {
|
context("when decoding a response") {
|
||||||
// MARK: -- with a decodable type
|
// MARK: ---- with a decodable type
|
||||||
context("with a decodable type") {
|
context("with a decodable type") {
|
||||||
// MARK: ---- decodes successfully
|
// MARK: ------ decodes successfully
|
||||||
it("decodes successfully") {
|
it("decodes successfully") {
|
||||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||||
.data(using: .utf8)!
|
.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") {
|
it("decodes successfully with no body") {
|
||||||
let data: Data = "ld8:intValuei100e11:stringValue4:Teste"
|
let data: Data = "ld8:intValuei100e11:stringValue4:Teste"
|
||||||
.data(using: .utf8)!
|
.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") {
|
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"
|
let data: Data = "ld12:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||||
.data(using: .utf8)!
|
.data(using: .utf8)!
|
||||||
|
@ -143,7 +113,7 @@ class BencodeSpec: QuickSpec {
|
||||||
}.to(throwError(HTTPError.parsingFailed))
|
}.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") {
|
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"
|
let data: Data = "ld7:INVALIDi100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||||
.data(using: .utf8)!
|
.data(using: .utf8)!
|
||||||
|
@ -154,7 +124,7 @@ class BencodeSpec: QuickSpec {
|
||||||
}.to(throwError(HTTPError.parsingFailed))
|
}.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") {
|
it("decodes correctly when trying to decode an int to a bool with custom handling") {
|
||||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||||
.data(using: .utf8)!
|
.data(using: .utf8)!
|
||||||
|
@ -165,7 +135,7 @@ class BencodeSpec: QuickSpec {
|
||||||
}.toNot(throwError(HTTPError.parsingFailed))
|
}.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") {
|
it("throws a parsing error when trying to decode an int to a bool") {
|
||||||
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
let data: Data = "ld9:boolValuei1e11:stringValue4:testee"
|
||||||
.data(using: .utf8)!
|
.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") {
|
context("with stringified json info") {
|
||||||
// MARK: -- decodes successfully
|
// MARK: ------ decodes successfully
|
||||||
it("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"
|
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||||
.data(using: .utf8)!
|
.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") {
|
it("decodes successfully with no body") {
|
||||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e"
|
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e"
|
||||||
.data(using: .utf8)!
|
.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") {
|
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"
|
let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||||
.data(using: .utf8)!
|
.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") {
|
context("with a string value") {
|
||||||
// MARK: ---- decodes successfully
|
// MARK: ------ decodes successfully
|
||||||
it("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 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)
|
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") {
|
it("decodes successfully with no body") {
|
||||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
let data: Data = "l4:Teste".data(using: .utf8)!
|
||||||
let result: BencodeResponse<String>? = try? Bencode.decodeResponse(from: data)
|
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") {
|
it("throws a parsing error when invalid") {
|
||||||
let data: Data = "l10:Teste".data(using: .utf8)!
|
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") {
|
context("with an int value") {
|
||||||
// MARK: ---- decodes successfully
|
// MARK: ------ decodes successfully
|
||||||
it("decodes successfully") {
|
it("decodes successfully") {
|
||||||
let data: Data = "li100e5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)!
|
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)
|
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") {
|
it("decodes successfully with no body") {
|
||||||
let data: Data = "li100ee".data(using: .utf8)!
|
let data: Data = "li100ee".data(using: .utf8)!
|
||||||
let result: BencodeResponse<Int>? = try? Bencode.decodeResponse(from: data)
|
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") {
|
it("throws a parsing error when invalid") {
|
||||||
let data: Data = "l4:Teste".data(using: .utf8)!
|
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
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class VersionSpec: QuickSpec {
|
class VersionSpec: QuickSpec {
|
||||||
// MARK: - Spec
|
override class func spec() {
|
||||||
|
// MARK: - a Version
|
||||||
override func spec() {
|
|
||||||
describe("a Version") {
|
describe("a Version") {
|
||||||
|
// MARK: -- can be created from a string
|
||||||
it("can be created from a string") {
|
it("can be created from a string") {
|
||||||
let version: Version = Version.from("1.20.3")
|
let version: Version = Version.from("1.20.3")
|
||||||
|
|
||||||
|
@ -20,13 +20,16 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version.patch).to(equal(3))
|
expect(version.patch).to(equal(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- correctly exposes a string value
|
||||||
it("correctly exposes a string value") {
|
it("correctly exposes a string value") {
|
||||||
let version: Version = Version(major: 1, minor: 20, patch: 3)
|
let version: Version = Version(major: 1, minor: 20, patch: 3)
|
||||||
|
|
||||||
expect(version.stringValue).to(equal("1.20.3"))
|
expect(version.stringValue).to(equal("1.20.3"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when checking equality
|
||||||
context("when checking equality") {
|
context("when checking equality") {
|
||||||
|
// MARK: ---- returns true if the values match
|
||||||
it("returns true if the values match") {
|
it("returns true if the values match") {
|
||||||
let version1: Version = Version.from("1.0.0")
|
let version1: Version = Version.from("1.0.0")
|
||||||
let version2: Version = Version.from("1.0.0")
|
let version2: Version = Version.from("1.0.0")
|
||||||
|
@ -35,6 +38,7 @@ class VersionSpec: QuickSpec {
|
||||||
.to(beTrue())
|
.to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns false if the values do not match
|
||||||
it("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 version1: Version = Version.from("1.0.0")
|
||||||
let version2: Version = Version.from("1.0.1")
|
let version2: Version = Version.from("1.0.1")
|
||||||
|
@ -44,7 +48,9 @@ class VersionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- when comparing versions
|
||||||
context("when comparing versions") {
|
context("when comparing versions") {
|
||||||
|
// MARK: ---- returns correctly for a simple major difference
|
||||||
it("returns correctly for a simple major difference") {
|
it("returns correctly for a simple major difference") {
|
||||||
let version1: Version = Version.from("1.0.0")
|
let version1: Version = Version.from("1.0.0")
|
||||||
let version2: Version = Version.from("2.0.0")
|
let version2: Version = Version.from("2.0.0")
|
||||||
|
@ -53,6 +59,7 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version2 > version1).to(beTrue())
|
expect(version2 > version1).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns correctly for a complex major difference
|
||||||
it("returns correctly for a complex major difference") {
|
it("returns correctly for a complex major difference") {
|
||||||
let version1a: Version = Version.from("2.90.90")
|
let version1a: Version = Version.from("2.90.90")
|
||||||
let version2a: Version = Version.from("10.0.0")
|
let version2a: Version = Version.from("10.0.0")
|
||||||
|
@ -65,6 +72,7 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version2b > version1b).to(beTrue())
|
expect(version2b > version1b).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns correctly for a simple minor difference
|
||||||
it("returns correctly for a simple minor difference") {
|
it("returns correctly for a simple minor difference") {
|
||||||
let version1: Version = Version.from("1.0.0")
|
let version1: Version = Version.from("1.0.0")
|
||||||
let version2: Version = Version.from("1.1.0")
|
let version2: Version = Version.from("1.1.0")
|
||||||
|
@ -73,6 +81,7 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version2 > version1).to(beTrue())
|
expect(version2 > version1).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns correctly for a complex minor difference
|
||||||
it("returns correctly for a complex minor difference") {
|
it("returns correctly for a complex minor difference") {
|
||||||
let version1a: Version = Version.from("90.2.90")
|
let version1a: Version = Version.from("90.2.90")
|
||||||
let version2a: Version = Version.from("90.10.0")
|
let version2a: Version = Version.from("90.10.0")
|
||||||
|
@ -85,6 +94,7 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version2b > version1b).to(beTrue())
|
expect(version2b > version1b).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns correctly for a simple patch difference
|
||||||
it("returns correctly for a simple patch difference") {
|
it("returns correctly for a simple patch difference") {
|
||||||
let version1: Version = Version.from("1.0.0")
|
let version1: Version = Version.from("1.0.0")
|
||||||
let version2: Version = Version.from("1.0.1")
|
let version2: Version = Version.from("1.0.1")
|
||||||
|
@ -93,6 +103,7 @@ class VersionSpec: QuickSpec {
|
||||||
expect(version2 > version1).to(beTrue())
|
expect(version2 > version1).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- returns correctly for a complex patch difference
|
||||||
it("returns correctly for a complex patch difference") {
|
it("returns correctly for a complex patch difference") {
|
||||||
let version1a: Version = Version.from("90.90.2")
|
let version1a: Version = Version.from("90.90.2")
|
||||||
let version2a: Version = Version.from("90.90.10")
|
let version2a: Version = Version.from("90.90.10")
|
||||||
|
|
|
@ -17,9 +17,13 @@ public class Mock<T> {
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
internal required init(functionHandler: MockFunctionHandler? = nil) {
|
internal required init(
|
||||||
|
functionHandler: MockFunctionHandler? = nil,
|
||||||
|
initialSetup: ((Mock<T>) -> ())? = nil
|
||||||
|
) {
|
||||||
self.functionConsumer = FunctionConsumer()
|
self.functionConsumer = FunctionConsumer()
|
||||||
self.functionHandler = (functionHandler ?? self.functionConsumer)
|
self.functionHandler = (functionHandler ?? self.functionConsumer)
|
||||||
|
initialSetup?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MockFunctionHandler
|
// MARK: - MockFunctionHandler
|
||||||
|
@ -103,7 +107,7 @@ internal class MockFunction {
|
||||||
|
|
||||||
internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
||||||
private let callBlock: (inout T) throws -> R
|
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 functionName: String?
|
||||||
private var parameterCount: Int?
|
private var parameterCount: Int?
|
||||||
private var parameterSummary: String?
|
private var parameterSummary: String?
|
||||||
|
@ -113,7 +117,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
||||||
|
|
||||||
// MARK: - Initialization
|
// 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.callBlock = callBlock
|
||||||
self.mockInit = mockInit
|
self.mockInit = mockInit
|
||||||
}
|
}
|
||||||
|
@ -141,7 +145,7 @@ internal class MockFunctionBuilder<T, R>: MockFunctionHandler {
|
||||||
// MARK: - Build
|
// MARK: - Build
|
||||||
|
|
||||||
func build() throws -> MockFunction {
|
func build() throws -> MockFunction {
|
||||||
var completionMock = mockInit(self) as! T
|
var completionMock = mockInit(self, nil) as! T
|
||||||
_ = try? callBlock(&completionMock)
|
_ = try? callBlock(&completionMock)
|
||||||
|
|
||||||
guard let name: String = functionName, let parameterCount: Int = parameterCount, let parameterSummary: String = parameterSummary else {
|
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) } }
|
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
|
// MARK: - Mutable Access
|
||||||
|
|
||||||
@discardableResult public func mutate<M, I, R>(
|
@discardableResult public func mutate<M, I, R>(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
// MARK: - Mocked
|
// MARK: - Mocked
|
||||||
|
@ -32,6 +33,20 @@ func any() -> Double { 0 }
|
||||||
func any() -> String { "" }
|
func any() -> String { "" }
|
||||||
func any() -> Data { Data() }
|
func any() -> Data { Data() }
|
||||||
func any() -> Bool { false }
|
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 anyAny() -> Any { 0 } // Unique name for compilation performance reasons
|
||||||
func anyArray<R>() -> [R] { [] } // 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,
|
matchingParameters: Bool = false,
|
||||||
exclusive: Bool = false,
|
exclusive: Bool = false,
|
||||||
functionBlock: @escaping (inout T) throws -> R
|
functionBlock: @escaping (inout T) throws -> R
|
||||||
) -> Predicate<M> where M: Mock<T> {
|
) -> Nimble.Predicate<M> where M: Mock<T> {
|
||||||
return Predicate.define { actualExpression in
|
return Predicate.define { actualExpression in
|
||||||
let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock)
|
let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock)
|
||||||
let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls
|
let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls
|
||||||
|
|
|
@ -6,6 +6,16 @@ import GRDB
|
||||||
@testable import SessionUtilitiesKit
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
class SynchronousStorage: Storage {
|
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>(
|
@discardableResult override func write<T>(
|
||||||
fileName: String = #file,
|
fileName: String = #file,
|
||||||
functionName: String = #function,
|
functionName: String = #function,
|
||||||
|
|
Loading…
Reference in New Issue