session-ios/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift

3049 lines
143 KiB
Swift
Raw Normal View History

// 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,
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha20Poly1305Ietf,
sign: mockSign,
genericHash: mockGenericHash,
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 { args in (args.first as? ((Any) -> Void))?(testTransaction as Any) }
.thenReturn(Promise.value(()))
mockStorage
.when { $0.write(with: { _ in }, completion: { }) }
.then { 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(100))
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(100))
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(100))
expect(mockOGMCache)
.toEventually(
call(matchingParameters: true) {
$0.pollers = ["testServer": OpenGroupAPI.Poller(for: "testServer")]
},
timeout: .milliseconds(100)
)
}
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(100))
expect(mockStorage)
.toEventuallyNot(
call {
$0.removeOpenGroupSequenceNumber(
for: "testRoom",
on: "testServer",
using: testTransaction! as Any
)
},
timeout: .milliseconds(100)
)
expect(mockStorage)
.toEventuallyNot(
call {
$0.setOpenGroupPublicKey(
for: "testRoom",
to: "testKey",
using: testTransaction! as Any
)
},
timeout: .milliseconds(100)
)
}
}
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(100)
)
}
}
}
// 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(100))
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(100))
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(100))
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(100))
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(100))
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(100)
)
}
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(100))
expect(mockOGMCache)
.toEventually(
call(matchingParameters: true) {
$0.moderators = ["testServer": ["testRoom": Set(arrayLiteral: "TestMod")]]
},
timeout: .milliseconds(100)
)
}
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(100))
expect(mockOGMCache)
.toEventually(
call(matchingParameters: true) {
$0.moderators = ["testServer": ["testRoom": Set()]]
},
timeout: .milliseconds(100)
)
}
}
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(100))
expect(mockOGMCache)
.toEventually(
call(matchingParameters: true) {
$0.admins = ["testServer": ["testRoom": Set(arrayLiteral: "TestAdmin")]]
},
timeout: .milliseconds(100)
)
}
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(100))
expect(mockOGMCache)
.toEventually(
call(matchingParameters: true) {
$0.admins = ["testServer": ["testRoom": Set()]]
},
timeout: .milliseconds(100)
)
}
}
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(100))
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(100))
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(100))
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(100))
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(100))
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(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(2), // Call to save the open group and then to save the image
timeout: .milliseconds(100)
)
}
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(100))
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(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(2), // Call to save the open group and then to save the image
timeout: .milliseconds(100)
)
}
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(100))
expect(mockStorage)
.to(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(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(2), // Call to save the open group and then to save the image
timeout: .milliseconds(100)
)
}
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(100))
expect(testGroupThread.groupModel.groupImage)
.toEventually(
beNil(),
timeout: .milliseconds(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(1),
timeout: .milliseconds(100)
)
}
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(100))
expect(testGroupThread.groupModel.groupImage)
.toEventually(
beNil(),
timeout: .milliseconds(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(1),
timeout: .milliseconds(100)
)
}
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(100))
expect(testGroupThread.groupModel.groupImage)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(testGroupThread.numSaveCalls)
.toEventually(
equal(2), // Call to save the open group and then to save the image
timeout: .milliseconds(100)
)
}
}
}
// 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(100)
)
}
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(100)
)
}
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(100)
)
}
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(100)
)
}
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(100)
)
}
}
}
// 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()) }
.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: - 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
)
}
}