mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Fixed a possible divide by zero error Cleaned up some of the id blinding methods (ie. removing handling for impossible error states) Added unit tests for the new Sodium methods (used for id blinding) Added unit tests for some of the shared code Added unit tests for the MessageSender+Encryption extension functions Added unit tests for the MessageReceiver+Decryption extension functions Updated the unit test key constants to be consistent with the SOGS auth-example keys for consistency
3667 lines
174 KiB
Swift
3667 lines
174 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import PromiseKit
|
|
import Sodium
|
|
import SessionSnodeKit
|
|
|
|
import Quick
|
|
import Nimble
|
|
|
|
@testable import SessionMessagingKit
|
|
|
|
// MARK: - OpenGroupManagerSpec
|
|
|
|
class OpenGroupManagerSpec: QuickSpec {
|
|
class TestCapabilitiesAndRoomApi: TestOnionRequestAPI {
|
|
static let capabilitiesData: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: nil)
|
|
static let roomData: OpenGroupAPI.Room = OpenGroupAPI.Room(
|
|
token: "test",
|
|
name: "test",
|
|
roomDescription: nil,
|
|
infoUpdates: 10,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: nil,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
|
|
override class var mockResponse: Data? {
|
|
let responses: [Data] = [
|
|
try! JSONEncoder().encode(
|
|
OpenGroupAPI.BatchSubResponse(
|
|
code: 200,
|
|
headers: [:],
|
|
body: capabilitiesData,
|
|
failedToParseBody: false
|
|
)
|
|
),
|
|
try! JSONEncoder().encode(
|
|
OpenGroupAPI.BatchSubResponse(
|
|
code: 200,
|
|
headers: [:],
|
|
body: roomData,
|
|
failedToParseBody: false
|
|
)
|
|
)
|
|
]
|
|
|
|
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
|
|
}
|
|
}
|
|
|
|
// MARK: - Spec
|
|
|
|
override func spec() {
|
|
var mockOGMCache: MockOGMCache!
|
|
var mockIdentityManager: MockIdentityManager!
|
|
var mockStorage: MockStorage!
|
|
var mockSodium: MockSodium!
|
|
var mockAeadXChaCha20Poly1305Ietf: MockAeadXChaCha20Poly1305Ietf!
|
|
var mockGenericHash: MockGenericHash!
|
|
var mockSign: MockSign!
|
|
var mockNonce16Generator: MockNonce16Generator!
|
|
var mockNonce24Generator: MockNonce24Generator!
|
|
var mockUserDefaults: MockUserDefaults!
|
|
var dependencies: OpenGroupManager.OGMDependencies!
|
|
|
|
var testInteraction: TestInteraction!
|
|
var testIncomingMessage: TestIncomingMessage!
|
|
var testGroupThread: TestGroupThread!
|
|
var testContactThread: TestContactThread!
|
|
var testTransaction: TestTransaction!
|
|
var testOpenGroup: OpenGroup!
|
|
var testPollInfo: OpenGroupAPI.RoomPollInfo!
|
|
var testMessage: OpenGroupAPI.Message!
|
|
var testDirectMessage: OpenGroupAPI.DirectMessage!
|
|
|
|
var cache: OpenGroupManager.Cache!
|
|
var openGroupManager: OpenGroupManager!
|
|
|
|
describe("an OpenGroupManager") {
|
|
// MARK: - Configuration
|
|
|
|
beforeEach {
|
|
mockOGMCache = MockOGMCache()
|
|
mockIdentityManager = MockIdentityManager()
|
|
mockStorage = MockStorage()
|
|
mockSodium = MockSodium()
|
|
mockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
|
mockGenericHash = MockGenericHash()
|
|
mockSign = MockSign()
|
|
mockNonce16Generator = MockNonce16Generator()
|
|
mockNonce24Generator = MockNonce24Generator()
|
|
mockUserDefaults = MockUserDefaults()
|
|
dependencies = OpenGroupManager.OGMDependencies(
|
|
cache: Atomic(mockOGMCache),
|
|
onionApi: TestCapabilitiesAndRoomApi.self,
|
|
identityManager: mockIdentityManager,
|
|
storage: mockStorage,
|
|
sodium: mockSodium,
|
|
genericHash: mockGenericHash,
|
|
sign: mockSign,
|
|
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha20Poly1305Ietf,
|
|
ed25519: MockEd25519(),
|
|
nonceGenerator16: mockNonce16Generator,
|
|
nonceGenerator24: mockNonce24Generator,
|
|
standardUserDefaults: mockUserDefaults,
|
|
date: Date(timeIntervalSince1970: 1234567890)
|
|
)
|
|
testInteraction = TestInteraction()
|
|
testInteraction.mockData[.uniqueId] = "TestInteractionId"
|
|
testInteraction.mockData[.timestamp] = UInt64(123)
|
|
|
|
testIncomingMessage = TestIncomingMessage(uniqueId: "TestMessageId")
|
|
testIncomingMessage.openGroupServerMessageID = 127
|
|
|
|
testGroupThread = TestGroupThread()
|
|
testGroupThread.mockData[.uniqueId] = "TestGroupId"
|
|
testGroupThread.mockData[.groupModel] = TSGroupModel(
|
|
title: "TestTitle",
|
|
memberIds: [],
|
|
image: nil,
|
|
groupId: LKGroupUtilities.getEncodedOpenGroupIDAsData("testServer.testRoom"),
|
|
groupType: .openGroup,
|
|
adminIds: [],
|
|
moderatorIds: []
|
|
)
|
|
testGroupThread.mockData[.interactions] = [testInteraction, testIncomingMessage]
|
|
|
|
testContactThread = TestContactThread()
|
|
testContactThread.mockData[.uniqueId] = "TestContactId"
|
|
testContactThread.mockData[.interactions] = [testInteraction, testIncomingMessage]
|
|
|
|
testTransaction = TestTransaction()
|
|
testTransaction.mockData[.objectForKey] = testGroupThread
|
|
|
|
testOpenGroup = OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 10
|
|
)
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: TestCapabilitiesAndRoomApi.roomData
|
|
)
|
|
testMessage = OpenGroupAPI.Message(
|
|
id: 127,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 124,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: [
|
|
"Cg0KC1Rlc3RNZXNzYWdlg",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAAAAAAAAAAAAAAAAA",
|
|
"AA"
|
|
].joined(),
|
|
base64EncodedSignature: nil
|
|
)
|
|
testDirectMessage = OpenGroupAPI.DirectMessage(
|
|
id: 128,
|
|
sender: "15\(TestConstants.publicKey)",
|
|
recipient: "15\(TestConstants.publicKey)",
|
|
posted: 1234567890,
|
|
expires: 1234567990,
|
|
base64EncodedMessage: Data(
|
|
Bytes(arrayLiteral: 0) +
|
|
"TestMessage".bytes +
|
|
Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes
|
|
).base64EncodedString()
|
|
)
|
|
|
|
mockIdentityManager
|
|
.when { $0.identityKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.write(with: { _ in }) }
|
|
.then { [testTransaction] args in (args.first as? ((Any) -> Void))?(testTransaction! as Any) }
|
|
.thenReturn(Promise.value(()))
|
|
mockStorage
|
|
.when { $0.write(with: { _ in }, completion: { }) }
|
|
.then { [testTransaction] args in
|
|
(args.first as? ((Any) -> Void))?(testTransaction! as Any)
|
|
(args.last as? (() -> Void))?()
|
|
}
|
|
.thenReturn(Promise.value(()))
|
|
mockStorage
|
|
.when { $0.getUserKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getAllOpenGroups() }
|
|
.thenReturn([
|
|
"0": testOpenGroup
|
|
])
|
|
mockStorage
|
|
.when { $0.getOpenGroup(for: any()) }
|
|
.thenReturn(testOpenGroup)
|
|
mockStorage
|
|
.when { $0.getOpenGroupServer(name: any()) }
|
|
.thenReturn(
|
|
OpenGroupAPI.Server(
|
|
name: "testServer",
|
|
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getOpenGroupPublicKey(for: any()) }
|
|
.thenReturn(TestConstants.publicKey)
|
|
|
|
mockGenericHash.when { $0.hash(message: anyArray(), outputLength: any()) }.thenReturn([])
|
|
mockSodium
|
|
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
mockSodium
|
|
.when {
|
|
$0.sogsSignature(
|
|
message: anyArray(),
|
|
secretKey: anyArray(),
|
|
blindedSecretKey: anyArray(),
|
|
blindedPublicKey: anyArray()
|
|
)
|
|
}
|
|
.thenReturn("TestSogsSignature".bytes)
|
|
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn("TestSignature".bytes)
|
|
|
|
mockNonce16Generator
|
|
.when { $0.nonce() }
|
|
.thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes)
|
|
mockNonce24Generator
|
|
.when { $0.nonce() }
|
|
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
|
|
|
cache = OpenGroupManager.Cache()
|
|
openGroupManager = OpenGroupManager()
|
|
}
|
|
|
|
afterEach {
|
|
OpenGroupManager.shared.stopPolling() // Need to stop any pollers which get created during tests
|
|
openGroupManager.stopPolling() // Assuming it's different from the above
|
|
|
|
mockOGMCache = nil
|
|
mockStorage = nil
|
|
mockSodium = nil
|
|
mockAeadXChaCha20Poly1305Ietf = nil
|
|
mockGenericHash = nil
|
|
mockSign = nil
|
|
mockUserDefaults = nil
|
|
dependencies = nil
|
|
|
|
testInteraction = nil
|
|
testGroupThread = nil
|
|
testContactThread = nil
|
|
testTransaction = nil
|
|
testOpenGroup = nil
|
|
|
|
openGroupManager = nil
|
|
}
|
|
|
|
// MARK: - Cache
|
|
|
|
context("cache data") {
|
|
it("defaults the time since last open to greatestFiniteMagnitude") {
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(nil)
|
|
|
|
expect(cache.getTimeSinceLastOpen(using: dependencies))
|
|
.to(beCloseTo(.greatestFiniteMagnitude))
|
|
}
|
|
|
|
it("returns the time since the last open") {
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567880))
|
|
dependencies = dependencies.with(date: Date(timeIntervalSince1970: 1234567890))
|
|
|
|
expect(cache.getTimeSinceLastOpen(using: dependencies))
|
|
.to(beCloseTo(10))
|
|
}
|
|
|
|
it("caches the time since the last open") {
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567770))
|
|
dependencies = dependencies.with(date: Date(timeIntervalSince1970: 1234567780))
|
|
|
|
expect(cache.getTimeSinceLastOpen(using: dependencies))
|
|
.to(beCloseTo(10))
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
|
|
|
// Cached value shouldn't have been updated
|
|
expect(cache.getTimeSinceLastOpen(using: dependencies))
|
|
.to(beCloseTo(10))
|
|
}
|
|
}
|
|
|
|
// MARK: - Polling
|
|
|
|
context("when starting polling") {
|
|
beforeEach {
|
|
mockStorage
|
|
.when { $0.getAllOpenGroups() }
|
|
.thenReturn([
|
|
"0": testOpenGroup,
|
|
"1": OpenGroup(
|
|
server: "testServer1",
|
|
room: "testRoom1",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test1",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 0
|
|
)
|
|
])
|
|
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupServer(any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
|
|
|
mockOGMCache.when { $0.hasPerformedInitialPoll }.thenReturn([:])
|
|
mockOGMCache.when { $0.timeSinceLastPoll }.thenReturn([:])
|
|
mockOGMCache.when { $0.getTimeSinceLastOpen(using: dependencies) }.thenReturn(0)
|
|
mockOGMCache.when { $0.isPolling }.thenReturn(false)
|
|
mockOGMCache.when { $0.pollers }.thenReturn([:])
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
|
}
|
|
|
|
it("creates pollers for all of the open groups") {
|
|
openGroupManager.startPolling(using: dependencies)
|
|
|
|
expect(mockOGMCache)
|
|
.to(call(matchingParameters: true) {
|
|
$0.pollers = [
|
|
"testserver": OpenGroupAPI.Poller(for: "testserver"),
|
|
"testserver1": OpenGroupAPI.Poller(for: "testserver1")
|
|
]
|
|
})
|
|
}
|
|
|
|
it("updates the isPolling flag") {
|
|
openGroupManager.startPolling(using: dependencies)
|
|
|
|
expect(mockOGMCache).to(call(matchingParameters: true) { $0.isPolling = true })
|
|
}
|
|
|
|
it("does nothing if already polling") {
|
|
mockOGMCache.when { $0.isPolling }.thenReturn(true)
|
|
|
|
openGroupManager.startPolling(using: dependencies)
|
|
|
|
expect(mockOGMCache).toNot(call { $0.pollers })
|
|
}
|
|
}
|
|
|
|
context("when stopping polling") {
|
|
beforeEach {
|
|
mockStorage
|
|
.when { $0.getAllOpenGroups() }
|
|
.thenReturn([
|
|
"0": testOpenGroup,
|
|
"1": OpenGroup(
|
|
server: "testServer1",
|
|
room: "testRoom1",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test1",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 0
|
|
)
|
|
])
|
|
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupServer(any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
|
|
|
mockOGMCache.when { $0.isPolling }.thenReturn(true)
|
|
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
|
|
|
openGroupManager.startPolling(using: dependencies)
|
|
}
|
|
|
|
it("removes all pollers") {
|
|
openGroupManager.stopPolling(using: dependencies)
|
|
|
|
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
|
|
}
|
|
|
|
it("updates the isPolling flag") {
|
|
openGroupManager.stopPolling(using: dependencies)
|
|
|
|
expect(mockOGMCache).to(call(matchingParameters: true) { $0.isPolling = false })
|
|
}
|
|
}
|
|
|
|
// MARK: - Adding & Removing
|
|
|
|
// MARK: - --add
|
|
|
|
context("when adding") {
|
|
beforeEach {
|
|
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupServer(any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
|
|
|
mockOGMCache.when { $0.pollers }.thenReturn([:])
|
|
mockOGMCache.when { $0.moderators }.thenReturn([:])
|
|
mockOGMCache.when { $0.admins }.thenReturn([:])
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
|
}
|
|
|
|
it("resets the sequence number of the open group") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
openGroupManager
|
|
.add(
|
|
roomToken: "testRoom",
|
|
server: "testServer",
|
|
publicKey: "testKey",
|
|
isConfigMessage: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
.map { _ -> Void in didComplete = true }
|
|
.retainUntilComplete()
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(
|
|
call(.exactly(times: 1)) {
|
|
$0.removeOpenGroupSequenceNumber(
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction! as Any
|
|
)
|
|
}
|
|
)
|
|
}
|
|
|
|
it("sets the public key of the open group server") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
openGroupManager
|
|
.add(
|
|
roomToken: "testRoom",
|
|
server: "testServer",
|
|
publicKey: "testKey",
|
|
isConfigMessage: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
.map { _ -> Void in didComplete = true }
|
|
.retainUntilComplete()
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(
|
|
call(.exactly(times: 1)) {
|
|
$0.setOpenGroupPublicKey(
|
|
for: "testRoom",
|
|
to: "testKey",
|
|
using: testTransaction! as Any
|
|
)
|
|
}
|
|
)
|
|
}
|
|
|
|
it("adds a poller") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
openGroupManager
|
|
.add(
|
|
roomToken: "testRoom",
|
|
server: "testServer",
|
|
publicKey: "testKey",
|
|
isConfigMessage: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
.map { _ -> Void in didComplete = true }
|
|
.retainUntilComplete()
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.pollers = ["testServer": OpenGroupAPI.Poller(for: "testServer")]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
context("an existing room") {
|
|
beforeEach {
|
|
mockOGMCache.when { $0.pollers }.thenReturn(["testServer": OpenGroupAPI.Poller(for: "testServer")])
|
|
}
|
|
|
|
it("does not reset the sequence number or update the public key") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
openGroupManager
|
|
.add(
|
|
roomToken: "testRoom",
|
|
server: "testServer",
|
|
publicKey: "testKey",
|
|
isConfigMessage: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
.map { _ -> Void in didComplete = true }
|
|
.retainUntilComplete()
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.toEventuallyNot(
|
|
call {
|
|
$0.removeOpenGroupSequenceNumber(
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction! as Any
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(mockStorage)
|
|
.toEventuallyNot(
|
|
call {
|
|
$0.setOpenGroupPublicKey(
|
|
for: "testRoom",
|
|
to: "testKey",
|
|
using: testTransaction! as Any
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
|
|
context("with an invalid response") {
|
|
beforeEach {
|
|
class TestApi: TestOnionRequestAPI {
|
|
override class var mockResponse: Data? { return Data() }
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestApi.self)
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(Date(timeIntervalSince1970: 1234567890))
|
|
}
|
|
|
|
it("fails with the error") {
|
|
var error: Error?
|
|
|
|
let promise = openGroupManager
|
|
.add(
|
|
roomToken: "testRoom",
|
|
server: "testServer",
|
|
publicKey: "testKey",
|
|
isConfigMessage: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
promise.catch { error = $0 }
|
|
promise.retainUntilComplete()
|
|
|
|
expect(error?.localizedDescription)
|
|
.toEventually(
|
|
equal(HTTP.Error.parsingFailed.localizedDescription),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - --delete
|
|
|
|
context("when deleting") {
|
|
beforeEach {
|
|
testGroupThread.mockData[.interactions] = [testInteraction]
|
|
|
|
mockStorage
|
|
.when { $0.updateMessageIDCollectionByPruningMessagesWithIDs(anySet(), using: anyAny()) }
|
|
.thenReturn(())
|
|
mockStorage.when { $0.removeReceivedMessageTimestamps(anySet(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.removeOpenGroupSequenceNumber(for: any(), on: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.removeOpenGroup(for: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.removeOpenGroupServer(name: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.removeOpenGroupPublicKey(for: any(), using: anyAny()) }.thenReturn(())
|
|
|
|
mockOGMCache.when { $0.pollers }.thenReturn([:])
|
|
}
|
|
|
|
it("removes messages for the given thread") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.updateMessageIDCollectionByPruningMessagesWithIDs(
|
|
Set(arrayLiteral: testInteraction.uniqueId!),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("removes received timestamps for the given thread") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.removeReceivedMessageTimestamps(
|
|
Set(arrayLiteral: testInteraction.timestamp),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("removes the sequence number for the given thread") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.removeOpenGroupSequenceNumber(
|
|
for: "testRoom",
|
|
on: "testserver",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("removes all interactions for the given thread") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: YapDatabaseReadWriteTransaction(),
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testGroupThread.didCallRemoveAllThreadInteractions).to(beTrue())
|
|
}
|
|
|
|
it("removes the given thread") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: YapDatabaseReadWriteTransaction(),
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testGroupThread.didCallRemove).to(beTrue())
|
|
}
|
|
|
|
it("removes the open group") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.removeOpenGroup(
|
|
for: testGroupThread.uniqueId!,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
context("and there is only one open group for this server") {
|
|
it("stops the poller") {
|
|
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
|
|
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
|
|
}
|
|
|
|
it("removes the open group server") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.removeOpenGroupServer(
|
|
name: "testserver",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("removes the open group public key") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.removeOpenGroupPublicKey(
|
|
for: "testserver",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
context("and the are multiple open groups for this server") {
|
|
beforeEach {
|
|
mockStorage
|
|
.when { $0.getAllOpenGroups() }
|
|
.thenReturn([
|
|
"0": testOpenGroup,
|
|
"1": OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom1",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test1",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 0
|
|
)
|
|
])
|
|
}
|
|
|
|
it("does not stop the poller") {
|
|
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
|
|
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockOGMCache).toNot(call { $0.pollers })
|
|
}
|
|
|
|
it("does not remove the open group server") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage).toNot(call { $0.removeOpenGroupServer(name: any(), using: anyAny()) })
|
|
}
|
|
|
|
it("does not remove the open group public key") {
|
|
openGroupManager
|
|
.delete(
|
|
testOpenGroup,
|
|
associatedWith: testGroupThread,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage).toNot(call { $0.removeOpenGroupPublicKey(for: any(), using: anyAny()) })
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Response Processing
|
|
|
|
// MARK: - --handleCapabilities
|
|
|
|
context("when handling capabilities") {
|
|
beforeEach {
|
|
mockStorage.when { $0.setOpenGroupServer(any(), using: anyAny()) }.thenReturn(())
|
|
|
|
OpenGroupManager
|
|
.handleCapabilities(
|
|
OpenGroupAPI.Capabilities(capabilities: [], missing: []),
|
|
on: "testserver",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
}
|
|
|
|
it("stores the capabilities") {
|
|
expect(mockStorage).to(call { $0.setOpenGroupServer(any(), using: anyAny()) })
|
|
}
|
|
}
|
|
|
|
// MARK: - --handlePollInfo
|
|
|
|
context("when handling room poll info") {
|
|
beforeEach {
|
|
mockStorage.when { $0.setOpenGroup(any(), for: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setOpenGroupServer(any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.setUserCount(to: any(), forOpenGroupWithID: any(), using: anyAny()) }.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupSequenceNumber(for: any(), on: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupInboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
mockStorage.when { $0.getOpenGroupOutboxLatestMessageId(for: any()) }.thenReturn(nil)
|
|
|
|
mockOGMCache.when { $0.pollers }.thenReturn([:])
|
|
mockOGMCache.when { $0.moderators }.thenReturn([:])
|
|
mockOGMCache.when { $0.admins }.thenReturn([:])
|
|
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) }
|
|
.thenReturn(nil)
|
|
}
|
|
|
|
it("attempts to retrieve the existing thread") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(testGroupThread.numSaveCalls).to(equal(1))
|
|
}
|
|
|
|
it("attempts to retrieve the existing open group") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage).to(call { $0.getOpenGroup(for: any()) })
|
|
}
|
|
|
|
it("saves the thread") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(testGroupThread.numSaveCalls).to(equal(1))
|
|
}
|
|
|
|
it("saves the open group") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage).to(call { $0.setOpenGroup(any(), for: any(), using: anyAny()) })
|
|
}
|
|
|
|
it("saves the updated user count") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setUserCount(to: 10, forOpenGroupWithID: "testServer.testRoom", using: testTransaction! as Any)
|
|
})
|
|
}
|
|
|
|
it("calls the completion block") {
|
|
var didCallComplete: Bool = false
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) {
|
|
didCallComplete = true
|
|
}
|
|
|
|
expect(didCallComplete)
|
|
.toEventually(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
context("and updating the moderator list") {
|
|
it("successfully updates") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.moderators }.thenReturn([:])
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: TestCapabilitiesAndRoomApi.roomData.with(moderators: ["TestMod"], admins: [])
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.moderators = ["testServer": ["testRoom": Set(arrayLiteral: "TestMod")]]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("defaults to an empty array if no moderators are provided") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.moderators }.thenReturn([:])
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: nil
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.moderators = ["testServer": ["testRoom": Set()]]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
|
|
context("and updating the admin list") {
|
|
it("successfully updates") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.admins }.thenReturn([:])
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: TestCapabilitiesAndRoomApi.roomData.with(moderators: [], admins: ["TestAdmin"])
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.admins = ["testServer": ["testRoom": Set(arrayLiteral: "TestAdmin")]]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("defaults to an empty array if no moderators are provided") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.admins }.thenReturn([:])
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: nil
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.admins = ["testServer": ["testRoom": Set()]]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
|
|
context("when it cannot get the thread id") {
|
|
it("does not save the thread") {
|
|
testGroupThread.mockData[.uniqueId] = nil
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testGroupThread.numSaveCalls).to(equal(0))
|
|
}
|
|
}
|
|
|
|
context("when not given a public key") {
|
|
it("saves the open group with the existing public key") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: nil,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroup(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "test",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 10
|
|
),
|
|
for: "TestGroupId",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
context("when it cannot get the public key") {
|
|
it("does not save the thread") {
|
|
mockStorage.when { $0.getOpenGroup(for: any()) }.thenReturn(nil)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: nil,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testGroupThread.numSaveCalls).to(equal(0))
|
|
}
|
|
}
|
|
|
|
context("when storing the open group") {
|
|
it("defaults the infoUpdates to zero") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockStorage.when { $0.getOpenGroup(for: any()) }.thenReturn(nil)
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: nil
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroup(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "TestTitle",
|
|
groupDescription: nil,
|
|
imageID: nil,
|
|
infoUpdates: 0
|
|
),
|
|
for: "TestGroupId",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
context("when checking to start polling") {
|
|
it("starts a new poller when not already polling") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.pollers }.thenReturn([:])
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache)
|
|
.to(call(matchingParameters: true) {
|
|
$0.pollers = ["testServer": OpenGroupAPI.Poller(for: "testServer")]
|
|
})
|
|
}
|
|
|
|
it("does not start a new poller when already polling") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.pollers }.thenReturn(["testServer": OpenGroupAPI.Poller(for: "testServer")])
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockOGMCache).to(call(.exactly(times: 1)) { $0.pollers })
|
|
}
|
|
}
|
|
|
|
context("when trying to get the room image") {
|
|
beforeEach {
|
|
let image: UIImage = UIImage(color: .red, size: CGSize(width: 1, height: 1))
|
|
let imageData: Data = image.pngData()!
|
|
mockStorage.when { $0.getOpenGroupImage(for: any(), on: any()) }.thenReturn(nil)
|
|
|
|
mockOGMCache.when { $0.groupImagePromises }
|
|
.thenReturn(["testServer.testRoom": Promise.value(imageData)])
|
|
}
|
|
|
|
it("uses the provided room image id if available") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: OpenGroupAPI.Room(
|
|
token: "test",
|
|
name: "test",
|
|
roomDescription: nil,
|
|
infoUpdates: 0,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 10,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroup(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "test",
|
|
groupDescription: nil,
|
|
imageID: "10",
|
|
infoUpdates: 0
|
|
),
|
|
for: "TestGroupId",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventuallyNot(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(2), // Call to save the open group and then to save the image
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("uses the existing room image id if none is provided") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockStorage
|
|
.when { $0.getOpenGroup(for: any()) }
|
|
.thenReturn(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test",
|
|
groupDescription: nil,
|
|
imageID: "12",
|
|
infoUpdates: 10
|
|
)
|
|
)
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: nil
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroup(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "TestTitle",
|
|
groupDescription: nil,
|
|
imageID: "12",
|
|
infoUpdates: 10
|
|
),
|
|
for: "TestGroupId",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventuallyNot(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(2), // Call to save the open group and then to save the image
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("uses the new room image id if there is an existing one") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
testGroupThread.mockData[.groupModel] = TSGroupModel(
|
|
title: "TestTitle",
|
|
memberIds: [],
|
|
image: UIImage(color: .blue, size: CGSize(width: 1, height: 1)),
|
|
groupId: LKGroupUtilities.getEncodedOpenGroupIDAsData("testServer.testRoom"),
|
|
groupType: .openGroup,
|
|
adminIds: [],
|
|
moderatorIds: []
|
|
)
|
|
mockStorage
|
|
.when { $0.getOpenGroup(for: any()) }
|
|
.thenReturn(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "Test",
|
|
groupDescription: nil,
|
|
imageID: "12",
|
|
infoUpdates: 10
|
|
)
|
|
)
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: OpenGroupAPI.Room(
|
|
token: "test",
|
|
name: "test",
|
|
roomDescription: nil,
|
|
infoUpdates: 10,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 10,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.toEventually(call(matchingParameters: true) {
|
|
$0.setOpenGroup(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
room: "testRoom",
|
|
publicKey: TestConstants.publicKey,
|
|
name: "test",
|
|
groupDescription: nil,
|
|
imageID: "10",
|
|
infoUpdates: 10
|
|
),
|
|
for: "TestGroupId",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventuallyNot(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(.exactly(times: 1)) { $0.groupImagePromises },
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(2), // Call to save the open group and then to save the image
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("does nothing if there is no room image") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventually(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(1),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("does nothing if it fails to retrieve the room image") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
mockOGMCache.when { $0.groupImagePromises }
|
|
.thenReturn(["testServer.testRoom": Promise(error: HTTP.Error.generic)])
|
|
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: OpenGroupAPI.Room(
|
|
token: "test",
|
|
name: "test",
|
|
roomDescription: nil,
|
|
infoUpdates: 0,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 10,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventually(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(1),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("saves the retrieved room image") {
|
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
|
|
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
|
token: "testRoom",
|
|
activeUsers: 10,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil,
|
|
details: OpenGroupAPI.Room(
|
|
token: "test",
|
|
name: "test",
|
|
roomDescription: nil,
|
|
infoUpdates: 10,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 10,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handlePollInfo(
|
|
testPollInfo,
|
|
publicKey: TestConstants.publicKey,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
) { didComplete = true }
|
|
|
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(testGroupThread.groupModel.groupImage)
|
|
.toEventuallyNot(
|
|
beNil(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(testGroupThread.numSaveCalls)
|
|
.toEventually(
|
|
equal(2), // Call to save the open group and then to save the image
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - --handleMessages
|
|
|
|
context("when handling messages") {
|
|
beforeEach {
|
|
testTransaction.mockData[.objectForKey] = [
|
|
"TestGroupId": testGroupThread,
|
|
"TestMessageId": testIncomingMessage
|
|
]
|
|
|
|
mockStorage
|
|
.when {
|
|
$0.setOpenGroupSequenceNumber(
|
|
for: any(),
|
|
on: any(),
|
|
to: any(),
|
|
using: testTransaction as Any
|
|
)
|
|
}
|
|
.thenReturn(())
|
|
mockStorage.when { $0.getUserPublicKey() }.thenReturn("05\(TestConstants.publicKey)")
|
|
mockStorage.when { $0.getReceivedMessageTimestamps(using: testTransaction as Any) }.thenReturn([])
|
|
mockStorage.when { $0.addReceivedMessageTimestamp(any(), using: testTransaction as Any) }.thenReturn(())
|
|
mockStorage.when { $0.persist(anyArray(), using: testTransaction as Any) }.thenReturn([])
|
|
mockStorage
|
|
.when {
|
|
$0.getOrCreateThread(
|
|
for: any(),
|
|
groupPublicKey: any(),
|
|
openGroupID: any(),
|
|
using: testTransaction as Any
|
|
)
|
|
}
|
|
.thenReturn("TestGroupId")
|
|
mockStorage
|
|
.when {
|
|
$0.persist(
|
|
any(),
|
|
quotedMessage: nil,
|
|
linkPreview: nil,
|
|
groupPublicKey: any(),
|
|
openGroupID: any(),
|
|
using: testTransaction as Any
|
|
)
|
|
}
|
|
.thenReturn("TestMessageId")
|
|
mockStorage.when { $0.getContact(with: any()) }.thenReturn(nil)
|
|
}
|
|
|
|
it("updates the sequence number when there are messages") {
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 1,
|
|
sender: nil,
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 124,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: nil,
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroupSequenceNumber(
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
to: 124,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("does not update the sequence number if there are no messages") {
|
|
OpenGroupManager.handleMessages(
|
|
[],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.toNot(call {
|
|
$0.setOpenGroupSequenceNumber(for: any(), on: any(), to: any(), using: testTransaction as Any)
|
|
})
|
|
}
|
|
|
|
it("ignores a message with no sender") {
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 1,
|
|
sender: nil,
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 124,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallSave).toEventuallyNot(beTrue(), timeout: .milliseconds(50))
|
|
expect(testIncomingMessage.didCallRemove).toEventuallyNot(beTrue(), timeout: .milliseconds(50))
|
|
}
|
|
|
|
it("ignores a message with invalid data") {
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 1,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 124,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallSave).toEventuallyNot(beTrue(), timeout: .milliseconds(50))
|
|
expect(testIncomingMessage.didCallRemove).toEventuallyNot(beTrue(), timeout: .milliseconds(50))
|
|
}
|
|
|
|
it("processes a message with valid data") {
|
|
OpenGroupManager.handleMessages(
|
|
[testMessage],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallSave)
|
|
.toEventually(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("processes valid messages when combined with invalid ones") {
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 2,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 122,
|
|
edited: nil,
|
|
seqNo: 123,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: Data([1, 2, 3]).base64EncodedString(),
|
|
base64EncodedSignature: nil
|
|
),
|
|
testMessage,
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallSave)
|
|
.toEventually(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
context("with no data") {
|
|
it("deletes the message if we have the message") {
|
|
testTransaction.mockData[.objectForKey] = testGroupThread
|
|
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 127,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 123,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: nil,
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallRemove)
|
|
.toEventually(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("does nothing if we do not have the thread") {
|
|
testTransaction.mockData[.objectForKey] = nil
|
|
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 1,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 123,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: nil,
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallRemove)
|
|
.toEventuallyNot(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("does nothing if we do not have the message") {
|
|
testGroupThread.mockData[.interactions] = [testInteraction]
|
|
testTransaction.mockData[.objectForKey] = testGroupThread
|
|
|
|
OpenGroupManager.handleMessages(
|
|
[
|
|
OpenGroupAPI.Message(
|
|
id: 127,
|
|
sender: "05\(TestConstants.publicKey)",
|
|
posted: 123,
|
|
edited: nil,
|
|
seqNo: 123,
|
|
whisper: false,
|
|
whisperMods: false,
|
|
whisperTo: nil,
|
|
base64EncodedData: nil,
|
|
base64EncodedSignature: nil
|
|
)
|
|
],
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testIncomingMessage.didCallRemove)
|
|
.toEventuallyNot(
|
|
beTrue(),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - --handleDirectMessages
|
|
|
|
context("when handling direct messages") {
|
|
beforeEach {
|
|
testTransaction.mockData[.objectForKey] = testContactThread
|
|
|
|
mockStorage
|
|
.when { $0.setOpenGroupInboxLatestMessageId(for: any(), to: any(), using: testTransaction as Any) }
|
|
.thenReturn(())
|
|
|
|
mockStorage
|
|
.when { $0.setOpenGroupOutboxLatestMessageId(for: any(), to: any(), using: testTransaction as Any) }
|
|
.thenReturn(())
|
|
mockStorage.when { $0.getUserPublicKey() }.thenReturn("05\(TestConstants.publicKey)")
|
|
mockStorage.when { $0.getReceivedMessageTimestamps(using: testTransaction as Any) }.thenReturn([])
|
|
mockStorage.when { $0.addReceivedMessageTimestamp(any(), using: testTransaction as Any) }.thenReturn(())
|
|
mockSodium
|
|
.when {
|
|
$0.sharedBlindedEncryptionKey(
|
|
secretKey: anyArray(),
|
|
otherBlindedPublicKey: anyArray(),
|
|
fromBlindedPublicKey: anyArray(),
|
|
toBlindedPublicKey: anyArray(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn([])
|
|
mockSodium
|
|
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
|
.thenReturn([])
|
|
mockAeadXChaCha20Poly1305Ietf
|
|
.when {
|
|
$0.decrypt(
|
|
authenticatedCipherText: anyArray(),
|
|
secretKey: anyArray(),
|
|
nonce: anyArray()
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Data(base64Encoded:"ChQKC1Rlc3RNZXNzYWdlONCI7I/3Iw==")!.bytes +
|
|
[UInt8](repeating: 0, count: 32)
|
|
)
|
|
mockSign
|
|
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
|
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
|
mockStorage.when { $0.persist(anyArray(), using: testTransaction as Any) }.thenReturn([])
|
|
mockStorage
|
|
.when {
|
|
$0.getOrCreateThread(
|
|
for: any(),
|
|
groupPublicKey: any(),
|
|
openGroupID: any(),
|
|
using: testTransaction as Any
|
|
)
|
|
}
|
|
.thenReturn("TestContactId")
|
|
mockStorage
|
|
.when {
|
|
$0.persist(
|
|
any(),
|
|
quotedMessage: nil,
|
|
linkPreview: nil,
|
|
groupPublicKey: any(),
|
|
openGroupID: any(),
|
|
using: testTransaction as Any
|
|
)
|
|
}
|
|
.thenReturn("TestMessageId")
|
|
mockStorage.when { $0.getContact(with: any()) }.thenReturn(nil)
|
|
mockStorage
|
|
.when { $0.getBlindedIdMapping(with: any(), using: testTransaction) }
|
|
.thenReturn(nil)
|
|
mockStorage
|
|
.when { $0.enumerateBlindedIdMapping(using: testTransaction, with: { _, _ in }) }
|
|
.then { args in
|
|
guard let block = args.first as? (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> () else {
|
|
return
|
|
}
|
|
|
|
var stop: ObjCBool = false
|
|
block(any(), &stop)
|
|
}
|
|
.thenReturn(())
|
|
}
|
|
|
|
it("does nothing if there are no messages") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.numSaveCalls).to(equal(0))
|
|
expect(mockStorage)
|
|
.toNot(call {
|
|
$0.setOpenGroupInboxLatestMessageId(
|
|
for: any(),
|
|
to: any(),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
expect(mockStorage)
|
|
.toNot(call {
|
|
$0.setOpenGroupOutboxLatestMessageId(
|
|
for: any(),
|
|
to: any(),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("does nothing if it cannot get the open group public key") {
|
|
mockStorage
|
|
.when { $0.getOpenGroupPublicKey(for: any()) }
|
|
.thenReturn(nil)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.numSaveCalls).to(equal(0))
|
|
expect(mockStorage)
|
|
.toNot(call {
|
|
$0.setOpenGroupInboxLatestMessageId(
|
|
for: any(),
|
|
to: any(),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
expect(mockStorage)
|
|
.toNot(call {
|
|
$0.setOpenGroupOutboxLatestMessageId(
|
|
for: any(),
|
|
to: any(),
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("ignores messages with non base64 encoded data") {
|
|
testDirectMessage = OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted,
|
|
expires: testDirectMessage.expires,
|
|
base64EncodedMessage: "TestMessage%%%"
|
|
)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.numSaveCalls).to(equal(0))
|
|
}
|
|
|
|
context("for the inbox") {
|
|
beforeEach {
|
|
mockSodium
|
|
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
|
.thenReturn(Data(hex: testDirectMessage.sender.removingIdPrefixIfNeeded()).bytes)
|
|
}
|
|
|
|
it("updates the inbox latest message id") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroupInboxLatestMessageId(
|
|
for: "testServer",
|
|
to: 128,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("ignores a message with invalid data") {
|
|
testDirectMessage = OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted,
|
|
expires: testDirectMessage.expires,
|
|
base64EncodedMessage: Data([1, 2, 3]).base64EncodedString()
|
|
)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.numSaveCalls).to(equal(0))
|
|
}
|
|
|
|
it("processes a message with valid data") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
// Saved once per valid inbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(1))
|
|
}
|
|
|
|
it("processes valid messages when combined with invalid ones") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[
|
|
OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted,
|
|
expires: testDirectMessage.expires,
|
|
base64EncodedMessage: Data([1, 2, 3]).base64EncodedString()
|
|
),
|
|
testDirectMessage
|
|
],
|
|
fromOutbox: false,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
// Saved once per valid inbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(1))
|
|
}
|
|
}
|
|
|
|
context("for the outbox") {
|
|
beforeEach {
|
|
mockSodium
|
|
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
|
.thenReturn(Data(hex: testDirectMessage.recipient.removingIdPrefixIfNeeded()).bytes)
|
|
}
|
|
|
|
it("updates the outbox latest message id") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call {
|
|
$0.setOpenGroupOutboxLatestMessageId(
|
|
for: "testServer",
|
|
to: 128,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("retrieves an existing blinded id mapping") {
|
|
mockStorage
|
|
.when { $0.getBlindedIdMapping(with: any(), using: testTransaction) }
|
|
.thenReturn(
|
|
BlindedIdMapping(
|
|
blindedId: "15\(TestConstants.publicKey)",
|
|
sessionId: "TestSessionId",
|
|
serverPublicKey: "05\(TestConstants.publicKey)"
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(.exactly(times: 1)) {
|
|
$0.getBlindedIdMapping(with: any(), using: testTransaction)
|
|
})
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.getOrCreateThread(
|
|
for: "TestSessionId",
|
|
groupPublicKey: nil,
|
|
openGroupID: nil,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
|
|
// Saved twice per valid outbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(2))
|
|
}
|
|
|
|
it("locally caches blinded id mappings for the same recipient") {
|
|
mockStorage
|
|
.when { $0.getBlindedIdMapping(with: any(), using: testTransaction) }
|
|
.thenReturn(
|
|
BlindedIdMapping(
|
|
blindedId: "15\(TestConstants.publicKey)",
|
|
sessionId: "TestSessionId",
|
|
serverPublicKey: "05\(TestConstants.publicKey)"
|
|
)
|
|
)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[
|
|
testDirectMessage,
|
|
OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id + 1,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted + 1,
|
|
expires: testDirectMessage.expires + 1,
|
|
base64EncodedMessage: testDirectMessage.base64EncodedMessage
|
|
)
|
|
],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(.exactly(times: 1)) {
|
|
$0.getBlindedIdMapping(with: any(), using: testTransaction)
|
|
})
|
|
|
|
// Saved twice per valid outbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(4))
|
|
}
|
|
|
|
it("falls back to using the blinded id if no mapping is found") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(mockStorage)
|
|
.to(call(.exactly(times: 1)) {
|
|
$0.getBlindedIdMapping(with: any(), using: testTransaction)
|
|
})
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.getOrCreateThread(
|
|
for: "15\(TestConstants.publicKey)",
|
|
groupPublicKey: nil,
|
|
openGroupID: nil,
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
|
|
// Saved twice per valid outbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(2))
|
|
}
|
|
|
|
it("ignores a message with invalid data") {
|
|
testDirectMessage = OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted,
|
|
expires: testDirectMessage.expires,
|
|
base64EncodedMessage: Data([1, 2, 3]).base64EncodedString()
|
|
)
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.numSaveCalls).to(equal(0))
|
|
}
|
|
|
|
it("processes a message with valid data") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
// Saved twice per valid outbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(2))
|
|
}
|
|
|
|
it("processes valid messages when combined with invalid ones") {
|
|
OpenGroupManager.handleDirectMessages(
|
|
[
|
|
OpenGroupAPI.DirectMessage(
|
|
id: testDirectMessage.id,
|
|
sender: testDirectMessage.sender,
|
|
recipient: testDirectMessage.recipient,
|
|
posted: testDirectMessage.posted,
|
|
expires: testDirectMessage.expires,
|
|
base64EncodedMessage: Data([1, 2, 3]).base64EncodedString()
|
|
),
|
|
testDirectMessage
|
|
],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
// Saved twice per valid outbox message
|
|
expect(testContactThread.numSaveCalls).to(equal(2))
|
|
}
|
|
|
|
it("updates the contact thread with the open group information") {
|
|
expect(testContactThread.originalOpenGroupServer).to(beNil())
|
|
expect(testContactThread.originalOpenGroupPublicKey).to(beNil())
|
|
|
|
OpenGroupManager.handleDirectMessages(
|
|
[testDirectMessage],
|
|
fromOutbox: true,
|
|
on: "testServer",
|
|
isBackgroundPoll: false,
|
|
using: testTransaction,
|
|
dependencies: dependencies
|
|
)
|
|
|
|
expect(testContactThread.originalOpenGroupServer).to(equal("testServer"))
|
|
expect(testContactThread.originalOpenGroupPublicKey).to(equal(TestConstants.publicKey))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Convenience
|
|
|
|
// MARK: - --isUserModeratorOrAdmin
|
|
|
|
context("when determining if a user is a moderator or an admin") {
|
|
beforeEach {
|
|
mockOGMCache.when { $0.moderators }.thenReturn([:])
|
|
mockOGMCache.when { $0.admins }.thenReturn([:])
|
|
}
|
|
|
|
it("uses an empty set for moderators by default") {
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("uses an empty set for admins by default") {
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns true if the key is in the moderator set") {
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "05\(TestConstants.publicKey)")
|
|
]
|
|
])
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
|
|
it("returns true if the key is in the admin set") {
|
|
mockOGMCache.when { $0.admins }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "05\(TestConstants.publicKey)")
|
|
]
|
|
])
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
|
|
it("returns false if the key is not a valid session id") {
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"InvalidValue",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
context("and the key is a standard session id") {
|
|
it("returns false if the key is not the users session id") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockIdentityManager
|
|
.when { $0.identityKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: otherKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users unblinded id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "00\(otherKey)")
|
|
]
|
|
])
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users blinded id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "15\(otherKey)")
|
|
]
|
|
])
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"05\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
}
|
|
|
|
context("and the key is unblinded") {
|
|
it("returns false if unable to retrieve the user ed25519 key") {
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(nil)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"00\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns false if the key is not the users unblinded id") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"00\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users session id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "05\(otherKey)")
|
|
]
|
|
])
|
|
mockIdentityManager
|
|
.when { $0.identityKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: otherKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"00\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users blinded id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "15\(otherKey)")
|
|
]
|
|
])
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"00\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
}
|
|
|
|
context("and the key is blinded") {
|
|
it("returns false if unable to retrieve the user ed25519 key") {
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(nil)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns false if unable to retrieve the public key for the open group server") {
|
|
mockStorage
|
|
.when { $0.getOpenGroupPublicKey(for: any()) }
|
|
.thenReturn(nil)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns false if unable generate a blinded key") {
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(nil)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns false if the key is not the users blinded id") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beFalse())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users session id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "05\(otherKey)")
|
|
]
|
|
])
|
|
mockIdentityManager
|
|
.when { $0.identityKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: otherKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
|
|
it("returns true if the key is the current users and the users unblinded id is a moderator or admin") {
|
|
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
|
|
mockOGMCache.when { $0.moderators }
|
|
.thenReturn([
|
|
"testServer": [
|
|
"testRoom": Set(arrayLiteral: "00\(otherKey)")
|
|
]
|
|
])
|
|
mockIdentityManager
|
|
.when { $0.identityKeyPair() }
|
|
.thenReturn(
|
|
try! ECKeyPair(
|
|
publicKeyData: Data.data(fromHex: TestConstants.publicKey)!,
|
|
privateKeyData: Data.data(fromHex: TestConstants.privateKey)!
|
|
)
|
|
)
|
|
mockStorage
|
|
.when { $0.getUserED25519KeyPair() }
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: otherKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
mockSodium
|
|
.when {
|
|
$0.blindedKeyPair(
|
|
serverPublicKey: any(),
|
|
edKeyPair: any(),
|
|
genericHash: mockGenericHash
|
|
)
|
|
}
|
|
.thenReturn(
|
|
Box.KeyPair(
|
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
|
)
|
|
)
|
|
|
|
expect(
|
|
OpenGroupManager.isUserModeratorOrAdmin(
|
|
"15\(TestConstants.publicKey)",
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(beTrue())
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - --getDefaultRoomsIfNeeded
|
|
|
|
context("when getting the default rooms if needed") {
|
|
beforeEach {
|
|
class TestRoomsApi: TestOnionRequestAPI {
|
|
static let roomsData: [OpenGroupAPI.Room] = [
|
|
TestCapabilitiesAndRoomApi.roomData,
|
|
OpenGroupAPI.Room(
|
|
token: "test2",
|
|
name: "test2",
|
|
roomDescription: nil,
|
|
infoUpdates: 11,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 12,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
]
|
|
|
|
override class var mockResponse: Data? {
|
|
return try! JSONEncoder().encode(roomsData)
|
|
}
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
|
|
|
mockOGMCache.when { $0.defaultRoomsPromise }.thenReturn(nil)
|
|
mockOGMCache.when { $0.groupImagePromises }.thenReturn([:])
|
|
mockStorage
|
|
.when { $0.setOpenGroupPublicKey(for: any(), to: any(), using: anyAny())}
|
|
.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupImage(for: any(), on: any()) }.thenReturn(nil)
|
|
mockStorage
|
|
.when { $0.setOpenGroupImage(to: any(), for: any(), on: any(), using: anyAny()) }
|
|
.thenReturn(())
|
|
mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil)
|
|
mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(())
|
|
}
|
|
|
|
it("caches the promise if there is no cached promise") {
|
|
let promise = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
|
|
expect(mockOGMCache)
|
|
.to(call(matchingParameters: true) {
|
|
$0.defaultRoomsPromise = promise
|
|
})
|
|
}
|
|
|
|
it("returns the cached promise if there is one") {
|
|
let (promise, _) = Promise<[OpenGroupAPI.Room]>.pending()
|
|
mockOGMCache.when { $0.defaultRoomsPromise }.thenReturn(promise)
|
|
|
|
expect(OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies))
|
|
.to(equal(promise))
|
|
}
|
|
|
|
it("stores the public key information") {
|
|
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
|
|
expect(mockStorage)
|
|
.to(call(matchingParameters: true) {
|
|
$0.setOpenGroupPublicKey(
|
|
for: "http://116.203.70.33",
|
|
to: "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238",
|
|
using: testTransaction! as Any
|
|
)
|
|
})
|
|
}
|
|
|
|
it("fetches rooms for the server") {
|
|
var response: [OpenGroupAPI.Room]?
|
|
|
|
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
.done { response = $0 }
|
|
.retainUntilComplete()
|
|
|
|
expect(response)
|
|
.toEventually(
|
|
equal(
|
|
[
|
|
TestCapabilitiesAndRoomApi.roomData,
|
|
OpenGroupAPI.Room(
|
|
token: "test2",
|
|
name: "test2",
|
|
roomDescription: nil,
|
|
infoUpdates: 11,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 12,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
]
|
|
),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("will retry fetching rooms 8 times before it fails") {
|
|
class TestRoomsApi: TestOnionRequestAPI {
|
|
static var callCounter: Int = 0
|
|
|
|
override class var mockResponse: Data? {
|
|
callCounter += 1
|
|
return nil
|
|
}
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
|
|
|
var error: Error?
|
|
|
|
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
.catch { error = $0 }
|
|
.retainUntilComplete()
|
|
|
|
expect(error?.localizedDescription)
|
|
.toEventually(
|
|
equal(HTTP.Error.invalidResponse.localizedDescription),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(TestRoomsApi.callCounter).to(equal(9)) // First attempt + 8 retries
|
|
}
|
|
|
|
it("removes the cache promise if all retries fail") {
|
|
class TestRoomsApi: TestOnionRequestAPI {
|
|
override class var mockResponse: Data? { return nil }
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
|
|
|
var error: Error?
|
|
|
|
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
.catch { error = $0 }
|
|
.retainUntilComplete()
|
|
|
|
expect(error?.localizedDescription)
|
|
.toEventually(
|
|
equal(HTTP.Error.invalidResponse.localizedDescription),
|
|
timeout: .milliseconds(50)
|
|
)
|
|
expect(mockOGMCache)
|
|
.to(call(matchingParameters: true) {
|
|
$0.defaultRoomsPromise = nil
|
|
})
|
|
}
|
|
|
|
it("fetches the image for any rooms with images") {
|
|
class TestRoomsApi: TestOnionRequestAPI {
|
|
static let roomsData: [OpenGroupAPI.Room] = [
|
|
OpenGroupAPI.Room(
|
|
token: "test2",
|
|
name: "test2",
|
|
roomDescription: nil,
|
|
infoUpdates: 11,
|
|
messageSequence: 0,
|
|
created: 0,
|
|
activeUsers: 0,
|
|
activeUsersCutoff: 0,
|
|
imageId: 12,
|
|
pinnedMessages: nil,
|
|
admin: false,
|
|
globalAdmin: false,
|
|
admins: [],
|
|
hiddenAdmins: nil,
|
|
moderator: false,
|
|
globalModerator: false,
|
|
moderators: [],
|
|
hiddenModerators: nil,
|
|
read: false,
|
|
defaultRead: nil,
|
|
defaultAccessible: nil,
|
|
write: false,
|
|
defaultWrite: nil,
|
|
upload: false,
|
|
defaultUpload: nil
|
|
)
|
|
]
|
|
|
|
override class var mockResponse: Data? {
|
|
return try! JSONEncoder().encode(roomsData)
|
|
}
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
|
|
|
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
|
|
|
expect(mockStorage)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.setOpenGroupImage(
|
|
to: TestRoomsApi.mockResponse!,
|
|
for: "test2",
|
|
on: "http://116.203.70.33",
|
|
using: testTransaction! as Any
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - --roomImage
|
|
|
|
context("when getting a room image") {
|
|
beforeEach {
|
|
class TestImageApi: TestOnionRequestAPI {
|
|
override class var mockResponse: Data? { return Data([1, 2, 3]) }
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestImageApi.self)
|
|
|
|
mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil)
|
|
mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(())
|
|
mockStorage.when { $0.getOpenGroupImage(for: any(), on: any()) }.thenReturn(nil)
|
|
mockStorage
|
|
.when { $0.setOpenGroupImage(to: any(), for: any(), on: any(), using: anyAny()) }
|
|
.thenReturn(())
|
|
mockOGMCache.when { $0.groupImagePromises }.thenReturn([:])
|
|
}
|
|
|
|
it("retrieves the image retrieval promise from the cache if it exists") {
|
|
let (promise, _) = Promise<Data>.pending()
|
|
mockOGMCache
|
|
.when { $0.groupImagePromises }
|
|
.thenReturn(["testServer.testRoom": promise])
|
|
|
|
expect(
|
|
OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
).to(equal(promise))
|
|
}
|
|
|
|
it("does not save the fetched image to storage") {
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.toEventuallyNot(
|
|
call(matchingParameters: true) {
|
|
$0.setOpenGroupImage(
|
|
to: Data([1, 2, 3]),
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: testTransaction! as Any
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("does not update the image update timestamp") {
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockUserDefaults)
|
|
.toEventuallyNot(
|
|
call(matchingParameters: true) {
|
|
$0.set(
|
|
dependencies.date,
|
|
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("adds the image retrieval promise to the cache") {
|
|
class TestNeverReturningApi: OnionRequestAPIType {
|
|
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPI.Version, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
|
return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise
|
|
}
|
|
|
|
static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, using version: OnionRequestAPI.Version, associatedWith publicKey: String?) -> Promise<Data> {
|
|
return Promise.value(Data())
|
|
}
|
|
}
|
|
dependencies = dependencies.with(onionApi: TestNeverReturningApi.self)
|
|
|
|
let promise = OpenGroupManager.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: "testServer",
|
|
using: dependencies
|
|
)
|
|
|
|
expect(mockOGMCache)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.groupImagePromises = ["testServer.testRoom": promise]
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
context("for the default server") {
|
|
it("fetches a new image if there is no cached one") {
|
|
var result: Data?
|
|
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: dependencies
|
|
)
|
|
.done { result = $0 }
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50))
|
|
}
|
|
|
|
it("saves the fetched image to storage") {
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: dependencies
|
|
)
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockStorage)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.setOpenGroupImage(
|
|
to: Data([1, 2, 3]),
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: testTransaction! as Any
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
it("updates the image update timestamp") {
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: dependencies
|
|
)
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(mockUserDefaults)
|
|
.toEventually(
|
|
call(matchingParameters: true) {
|
|
$0.set(
|
|
dependencies.date,
|
|
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
|
|
)
|
|
},
|
|
timeout: .milliseconds(50)
|
|
)
|
|
}
|
|
|
|
context("and there is a cached image") {
|
|
beforeEach {
|
|
mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(dependencies.date)
|
|
mockStorage
|
|
.when { $0.getOpenGroupImage(for: any(), on: any()) }
|
|
.thenReturn(Data([2, 3, 4]))
|
|
}
|
|
|
|
it("retrieves the cached image") {
|
|
var result: Data?
|
|
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: dependencies
|
|
)
|
|
.done { result = $0 }
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(result).toEventually(equal(Data([2, 3, 4])), timeout: .milliseconds(50))
|
|
}
|
|
|
|
it("fetches a new image if the cached on is older than a week") {
|
|
mockUserDefaults
|
|
.when { $0.object(forKey: any()) }
|
|
.thenReturn(
|
|
Date(timeIntervalSince1970:
|
|
(dependencies.date.timeIntervalSince1970 - (7 * 24 * 60 * 60) - 1)
|
|
)
|
|
)
|
|
|
|
var result: Data?
|
|
|
|
let promise = OpenGroupManager
|
|
.roomImage(
|
|
1,
|
|
for: "testRoom",
|
|
on: OpenGroupAPI.defaultServer,
|
|
using: dependencies
|
|
)
|
|
.done { result = $0 }
|
|
promise.retainUntilComplete()
|
|
|
|
expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
|
|
expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - --parseOpenGroup
|
|
|
|
context("when parsing an open group url") {
|
|
it("handles the example urls correctly") {
|
|
let validUrls: [String] = [
|
|
"https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"http://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"https://143.198.213.225:443/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
"143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
]
|
|
let processedValues: [(room: String, server: String, publicKey: String)] = validUrls
|
|
.map { OpenGroupManager.parseOpenGroup(from: $0) }
|
|
.compactMap { $0 }
|
|
let processedRooms: [String] = processedValues.map { $0.room }
|
|
let processedServers: [String] = processedValues.map { $0.server }
|
|
let processedPublicKeys: [String] = processedValues.map { $0.publicKey }
|
|
let expectedRooms: [String] = [String](repeating: "main", count: 10)
|
|
let expectedServers: [String] = [
|
|
"https://sessionopengroup.co",
|
|
"https://sessionopengroup.co",
|
|
"http://sessionopengroup.co",
|
|
"http://sessionopengroup.co",
|
|
"http://sessionopengroup.co",
|
|
"http://sessionopengroup.co",
|
|
"https://143.198.213.225:443",
|
|
"https://143.198.213.225:443",
|
|
"http://143.198.213.255:80",
|
|
"http://143.198.213.255:80"
|
|
]
|
|
let expectedPublicKeys: [String] = [String](
|
|
repeating: "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
|
|
count: 10
|
|
)
|
|
|
|
expect(processedValues.count).to(equal(validUrls.count))
|
|
expect(processedRooms).to(equal(expectedRooms))
|
|
expect(processedServers).to(equal(expectedServers))
|
|
expect(processedPublicKeys).to(equal(expectedPublicKeys))
|
|
}
|
|
|
|
it("handles the r prefix if present") {
|
|
let info = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)
|
|
|
|
expect(info?.room).to(equal("main"))
|
|
expect(info?.server).to(equal("https://sessionopengroup.co"))
|
|
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
|
|
}
|
|
|
|
it("fails if there is no room") {
|
|
let info = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)
|
|
|
|
expect(info?.room).to(beNil())
|
|
expect(info?.server).to(beNil())
|
|
expect(info?.publicKey).to(beNil())
|
|
}
|
|
|
|
it("fails if there is no public key parameter") {
|
|
let info = OpenGroupManager.parseOpenGroup(
|
|
from: "https://sessionopengroup.co/r/main"
|
|
)
|
|
|
|
expect(info?.room).to(beNil())
|
|
expect(info?.server).to(beNil())
|
|
expect(info?.publicKey).to(beNil())
|
|
}
|
|
|
|
it("fails if the public key parameter is not 64 characters") {
|
|
let info = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231"
|
|
].joined()
|
|
)
|
|
|
|
expect(info?.room).to(beNil())
|
|
expect(info?.server).to(beNil())
|
|
expect(info?.publicKey).to(beNil())
|
|
}
|
|
|
|
it("fails if the public key parameter is not a hex string") {
|
|
let info = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co/r/main?",
|
|
"public_key=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
].joined()
|
|
)
|
|
|
|
expect(info?.room).to(beNil())
|
|
expect(info?.server).to(beNil())
|
|
expect(info?.publicKey).to(beNil())
|
|
}
|
|
|
|
it("maintains the same TLS") {
|
|
let server1 = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)?.server
|
|
let server2 = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"http://sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)?.server
|
|
let server3 = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)?.server
|
|
|
|
expect(server1).to(equal("http://sessionopengroup.co"))
|
|
expect(server2).to(equal("http://sessionopengroup.co"))
|
|
expect(server3).to(equal("https://sessionopengroup.co"))
|
|
}
|
|
|
|
it("maintains the same port") {
|
|
let server1 = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)?.server
|
|
let server2 = OpenGroupManager.parseOpenGroup(
|
|
from: [
|
|
"https://sessionopengroup.co:1234/r/main?",
|
|
"public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
|
].joined()
|
|
)?.server
|
|
|
|
expect(server1).to(equal("https://sessionopengroup.co"))
|
|
expect(server2).to(equal("https://sessionopengroup.co:1234"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Room Convenience Extensions
|
|
|
|
extension OpenGroupAPI.Room {
|
|
func with(moderators: [String], admins: [String]) -> OpenGroupAPI.Room {
|
|
return OpenGroupAPI.Room(
|
|
token: self.token,
|
|
name: self.name,
|
|
roomDescription: self.roomDescription,
|
|
infoUpdates: self.infoUpdates,
|
|
messageSequence: self.messageSequence,
|
|
created: self.created,
|
|
activeUsers: self.activeUsers,
|
|
activeUsersCutoff: self.activeUsersCutoff,
|
|
imageId: self.imageId,
|
|
pinnedMessages: self.pinnedMessages,
|
|
admin: self.admin,
|
|
globalAdmin: self.globalAdmin,
|
|
admins: admins,
|
|
hiddenAdmins: self.hiddenAdmins,
|
|
moderator: self.moderator,
|
|
globalModerator: self.globalModerator,
|
|
moderators: moderators,
|
|
hiddenModerators: self.hiddenModerators,
|
|
read: self.read,
|
|
defaultRead: self.defaultRead,
|
|
defaultAccessible: self.defaultAccessible,
|
|
write: self.write,
|
|
defaultWrite: self.defaultWrite,
|
|
upload: self.upload,
|
|
defaultUpload: self.defaultUpload
|
|
)
|
|
}
|
|
}
|