mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1263 lines
65 KiB
Swift
1263 lines
65 KiB
Swift
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Foundation
|
|
import GRDB
|
|
|
|
import Quick
|
|
import Nimble
|
|
|
|
@testable import SessionSnodeKit
|
|
@testable import SessionMessagingKit
|
|
@testable import SessionUtilitiesKit
|
|
|
|
class DisplayPictureDownloadJobSpec: QuickSpec {
|
|
override class func spec() {
|
|
// MARK: Configuration
|
|
|
|
@TestState var job: Job!
|
|
@TestState var profile: Profile!
|
|
@TestState var group: ClosedGroup!
|
|
@TestState var community: OpenGroup!
|
|
@TestState var dependencies: TestDependencies! = TestDependencies { dependencies in
|
|
dependencies.forceSynchronous = true
|
|
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
|
|
}
|
|
@TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage(
|
|
customWriter: try! DatabaseQueue(),
|
|
migrationTargets: [
|
|
SNUtilitiesKit.self,
|
|
SNMessagingKit.self
|
|
],
|
|
using: dependencies,
|
|
initialData: { db in
|
|
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db)
|
|
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db)
|
|
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
|
|
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db)
|
|
}
|
|
)
|
|
@TestState var imageData: Data! = Data(
|
|
hex: "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c" +
|
|
"489000000017352474200aece1ce90000000d49444154185763f8cfc0f01f0005000" +
|
|
"1ffa65c9b5d0000000049454e44ae426082"
|
|
)
|
|
@TestState var encryptionKey: Data! = Data(hex: "c8e52eb1016702a663ac9a1ab5522daa128ab40762a514de271eddf598e3b8d4")
|
|
@TestState var encryptedData: Data! = Data(
|
|
hex: "778921bdd0e432227b53ee49c23421aeb796b7e5663468ff79daffb1af08cd1" +
|
|
"a68343377fe05ab01917ce0fb8732c746a60f157f7798cdf999364b37ff9016ab2fe" +
|
|
"673120e153a5cb6b869380744d493068ebc418266d6596d728cfc60b30662a089376" +
|
|
"f2761e3bb6ee837a26b24b5"
|
|
)
|
|
@TestState var filenameUuid: UUID! = UUID(uuidString: "00000000-0000-0000-0000-000000001234")
|
|
@TestState(singleton: .network, in: dependencies) var mockNetwork: MockNetwork! = MockNetwork(
|
|
initialSetup: { network in
|
|
network
|
|
.when { $0.send(.selectedNetworkRequest(.any, to: .any, with: .any, timeout: .any, using: .any)) }
|
|
.thenReturn(MockNetwork.response(data: encryptedData))
|
|
}
|
|
)
|
|
@TestState(singleton: .fileManager, in: dependencies) var mockFileManager: MockFileManager! = MockFileManager(
|
|
initialSetup: { fileManager in
|
|
fileManager
|
|
.when { $0.createFile(atPath: .any, contents: .any, attributes: .any) }
|
|
.thenReturn(true)
|
|
|
|
fileManager
|
|
.when { $0.containerURL(forSecurityApplicationGroupIdentifier: .any) }
|
|
.thenReturn(URL(fileURLWithPath: "/test"))
|
|
}
|
|
)
|
|
@TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto(
|
|
initialSetup: { crypto in
|
|
crypto.when { $0.generate(.uuid()) }.thenReturn(filenameUuid)
|
|
crypto
|
|
.when { $0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any)) }
|
|
.thenReturn(imageData)
|
|
crypto
|
|
.when { $0.generate(.hash(message: .any, outputLength: .any)) }
|
|
.thenReturn([])
|
|
crypto
|
|
.when { crypto in
|
|
crypto.generate(.blindedKeyPair(serverPublicKey: .any, edKeyPair: .any, using: .any))
|
|
}
|
|
.thenReturn(
|
|
KeyPair(
|
|
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
|
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
|
)
|
|
)
|
|
crypto
|
|
.when { $0.generate(.nonce16()) }
|
|
.thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes)
|
|
crypto
|
|
.when {
|
|
$0.generate(
|
|
.signatureSOGS(
|
|
message: .any,
|
|
secretKey: .any,
|
|
blindedSecretKey: .any,
|
|
blindedPublicKey: .any
|
|
)
|
|
)
|
|
}
|
|
.thenReturn("TestSogsSignature".bytes)
|
|
}
|
|
)
|
|
@TestState(cache: .displayPicture, in: dependencies) var mockDisplayPictureCache: MockDisplayPictureCache! = MockDisplayPictureCache(
|
|
initialSetup: { displayPictureCache in
|
|
displayPictureCache.when { $0.imageData }.thenReturn([:])
|
|
displayPictureCache.when { $0.imageData = .any }.thenReturn(())
|
|
}
|
|
)
|
|
|
|
// MARK: - a DisplayPictureDownloadJob
|
|
describe("a DisplayPictureDownloadJob") {
|
|
// MARK: -- fails when not given any details
|
|
it("fails when not given any details") {
|
|
job = Job(variant: .displayPictureDownload)
|
|
|
|
var error: Error? = nil
|
|
var permanentFailure: Bool = false
|
|
|
|
DisplayPictureDownloadJob.run(
|
|
job,
|
|
queue: .main,
|
|
success: { _, _, _ in },
|
|
failure: { _, runError, runPermanentFailure, _ in
|
|
error = runError
|
|
permanentFailure = runPermanentFailure
|
|
},
|
|
deferred: { _, _ in },
|
|
using: dependencies
|
|
)
|
|
|
|
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
|
expect(permanentFailure).to(beTrue())
|
|
}
|
|
|
|
// MARK: -- when initialising details
|
|
context("when initialising details") {
|
|
// MARK: ---- for a profile
|
|
context("for a profile") {
|
|
// MARK: ------ with a target
|
|
context("with a target") {
|
|
// MARK: -------- returns nil when given an empty url
|
|
it("returns nil when given an empty url") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .profile(id: "", url: "", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a url which does not have a file id
|
|
it("returns nil when given a url which does not have a file id") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .profile(id: "", url: "http://oxen.io", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a url which does not have a file id
|
|
it("returns nil when given a url which does not have a file id") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .profile(id: "", url: "http://oxen.io", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given encryption key data with the wrong length
|
|
it("returns nil when given encryption key data with the wrong length") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .profile(id: "", url: "http://oxen.io/1234/", encryptionKey: Data([1, 2, 3])),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .profile(
|
|
id: "",
|
|
url: "http://oxen.io/1234/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 0
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ with an owner
|
|
context("with an owner") {
|
|
// MARK: -------- returns nil when given a null url
|
|
it("returns nil when given a null url") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .user(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: nil,
|
|
profileEncryptionKey: encryptionKey
|
|
)
|
|
)
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a null encryption key
|
|
it("returns nil when given a null encryption key") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .user(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/1234/",
|
|
profileEncryptionKey: nil
|
|
)
|
|
)
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .user(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/1234/",
|
|
profileEncryptionKey: encryptionKey
|
|
)
|
|
)
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: ---- for a group
|
|
context("for a group") {
|
|
// MARK: ------ with a target
|
|
context("with a target") {
|
|
// MARK: -------- returns nil when given an empty url
|
|
it("returns nil when given an empty url") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .group(id: "", url: "", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a url which does not have a file id
|
|
it("returns nil when given a url which does not have a file id") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .group(id: "", url: "http://oxen.io", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a url which does not have a file id
|
|
it("returns nil when given a url which does not have a file id") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .group(id: "", url: "http://oxen.io", encryptionKey: Data()),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given encryption key data with the wrong length
|
|
it("returns nil when given encryption key data with the wrong length") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .group(id: "", url: "http://oxen.io/1234/", encryptionKey: Data([1, 2, 3])),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .group(
|
|
id: "",
|
|
url: "http://oxen.io/1234/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 0
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ with an owner
|
|
context("with an owner") {
|
|
// MARK: -------- returns nil when given a null url
|
|
it("returns nil when given a null url") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .group(
|
|
ClosedGroup(
|
|
threadId: "1234",
|
|
name: "test",
|
|
formationTimestamp: 0,
|
|
displayPictureUrl: nil,
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
shouldPoll: nil,
|
|
invited: nil
|
|
)
|
|
)
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns nil when given a null encryption key
|
|
it("returns nil when given a null encryption key") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .group(
|
|
ClosedGroup(
|
|
threadId: "1234",
|
|
name: "test",
|
|
formationTimestamp: 0,
|
|
displayPictureUrl: "http://oxen.io/1234/",
|
|
displayPictureEncryptionKey: nil,
|
|
shouldPoll: nil,
|
|
invited: nil
|
|
)
|
|
)
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .group(
|
|
ClosedGroup(
|
|
threadId: "1234",
|
|
name: "test",
|
|
formationTimestamp: 0,
|
|
displayPictureUrl: "http://oxen.io/1234/",
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
shouldPoll: nil,
|
|
invited: nil
|
|
)
|
|
)
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: ---- for a community
|
|
context("for a community") {
|
|
// MARK: ------ with a target
|
|
context("with a target") {
|
|
// MARK: -------- returns nil when given an empty imageId
|
|
it("returns nil when given an empty imageId") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "", roomToken: "", server: ""),
|
|
timestamp: 0
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "", server: ""),
|
|
timestamp: 0
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ with an owner
|
|
context("with an owner") {
|
|
// MARK: -------- returns nil when given an empty imageId
|
|
it("returns nil when given an empty imageId") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .community(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "1234",
|
|
isActive: false,
|
|
name: "test",
|
|
imageId: nil,
|
|
userCount: 0,
|
|
infoUpdates: 0
|
|
)
|
|
)
|
|
)
|
|
).to(beNil())
|
|
}
|
|
|
|
// MARK: -------- returns a value when given valid data
|
|
it("returns a value when given valid data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
owner: .community(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "1234",
|
|
isActive: false,
|
|
name: "test",
|
|
imageId: "12",
|
|
userCount: 0,
|
|
infoUpdates: 0
|
|
)
|
|
)
|
|
)
|
|
).toNot(beNil())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -- when hashing
|
|
context("when hashing") {
|
|
// MARK: ---- generates the same hash with the same data
|
|
it("generates the same hash with the same data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "test", server: "test"),
|
|
timestamp: 1234
|
|
)?.hashValue
|
|
).to(equal(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "test", server: "test"),
|
|
timestamp: 1234
|
|
)?.hashValue
|
|
))
|
|
}
|
|
|
|
// MARK: ---- generates a different hash with different data
|
|
it("generates a different hash with different data") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "test", server: "test"),
|
|
timestamp: 1234
|
|
)?.hashValue
|
|
).toNot(equal(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "13", roomToken: "test", server: "test"),
|
|
timestamp: 1234
|
|
)?.hashValue
|
|
))
|
|
}
|
|
|
|
// MARK: ---- excludes the timestamp when generating the hash value
|
|
it("excludes the timestamp when generating the hash value") {
|
|
expect(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "test", server: "test"),
|
|
timestamp: 1234
|
|
)?.hashValue
|
|
).to(equal(
|
|
DisplayPictureDownloadJob.Details(
|
|
target: .community(imageId: "12", roomToken: "test", server: "test"),
|
|
timestamp: 4321
|
|
)?.hashValue
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: -- generates a FileServer download request correctly
|
|
it("generates a FileServer download request correctly") {
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .profile(
|
|
id: "",
|
|
url: "http://oxen.io/1234/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 0
|
|
)
|
|
)
|
|
let expectedRequest: URLRequest = try FileServerAPI
|
|
.preparedDownload(
|
|
fileId: "1234",
|
|
useOldServer: false,
|
|
using: dependencies
|
|
)
|
|
.request
|
|
|
|
DisplayPictureDownloadJob.run(
|
|
job,
|
|
queue: .main,
|
|
success: { _, _, _ in },
|
|
failure: { _, _, _, _ in },
|
|
deferred: { _, _ in },
|
|
using: dependencies
|
|
)
|
|
|
|
expect(mockNetwork)
|
|
.to(call(.exactly(times: 1), matchingParameters: .all) { network in
|
|
network.send(
|
|
.selectedNetworkRequest(
|
|
expectedRequest,
|
|
to: FileServerAPI.server,
|
|
with: FileServerAPI.serverPublicKey,
|
|
timeout: FileServerAPI.fileDownloadTimeout,
|
|
using: .any
|
|
)
|
|
)
|
|
})
|
|
}
|
|
|
|
// MARK: -- generates a SOGS download request correctly
|
|
it("generates a SOGS download request correctly") {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: TestConstants.serverPublicKey,
|
|
isActive: false,
|
|
name: "test",
|
|
userCount: 0,
|
|
infoUpdates: 0
|
|
).insert(db)
|
|
}
|
|
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .community(
|
|
imageId: "12",
|
|
roomToken: "testRoom",
|
|
server: "testServer"
|
|
),
|
|
timestamp: 0
|
|
)
|
|
)
|
|
let expectedRequest: URLRequest = mockStorage
|
|
.read(using: dependencies) { db in
|
|
try OpenGroupAPI.preparedDownloadFile(
|
|
db,
|
|
fileId: "12",
|
|
from: "testRoom",
|
|
on: "testserver",
|
|
using: dependencies
|
|
)
|
|
}!
|
|
.request
|
|
|
|
DisplayPictureDownloadJob.run(
|
|
job,
|
|
queue: .main,
|
|
success: { _, _, _ in },
|
|
failure: { _, _, _, _ in },
|
|
deferred: { _, _ in },
|
|
using: dependencies
|
|
)
|
|
|
|
expect(mockNetwork)
|
|
.to(call(.exactly(times: 1), matchingParameters: .all) { network in
|
|
network.send(
|
|
.selectedNetworkRequest(
|
|
expectedRequest,
|
|
to: "testserver",
|
|
with: TestConstants.serverPublicKey,
|
|
timeout: FileServerAPI.fileDownloadTimeout,
|
|
using: .any
|
|
)
|
|
)
|
|
})
|
|
}
|
|
|
|
// MARK: -- checking if a downloaded display picture is valid
|
|
context("checking if a downloaded display picture is valid") {
|
|
@TestState var jobResult: JobRunner.JobResult! = .notFound
|
|
|
|
beforeEach {
|
|
profile = Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: nil,
|
|
profilePictureFileName: nil,
|
|
profileEncryptionKey: nil,
|
|
lastProfilePictureUpdate: nil
|
|
)
|
|
mockStorage.write(using: dependencies) { db in try profile.insert(db) }
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .profile(
|
|
id: "1234",
|
|
url: "http://oxen.io/100/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 1234567891
|
|
)
|
|
)
|
|
}
|
|
|
|
justBeforeEach {
|
|
DisplayPictureDownloadJob.run(
|
|
job,
|
|
queue: .main,
|
|
success: { _, _, _ in jobResult = .succeeded },
|
|
failure: { _, error, permanent, _ in jobResult = .failed(error, permanent) },
|
|
deferred: { _, _ in jobResult = .deferred },
|
|
using: dependencies
|
|
)
|
|
}
|
|
|
|
// MARK: ---- when it fails to decrypt the data
|
|
context("when it fails to decrypt the data") {
|
|
beforeEach {
|
|
mockCrypto
|
|
.when { $0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any)) }
|
|
.thenReturn(nil)
|
|
}
|
|
|
|
// MARK: ------ does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) }).to(equal(profile))
|
|
}
|
|
}
|
|
|
|
// MARK: ---- when it decrypts invalid image data
|
|
context("when it decrypts invalid image data") {
|
|
beforeEach {
|
|
mockCrypto
|
|
.when { $0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any)) }
|
|
.thenReturn(Data([1, 2, 3]))
|
|
}
|
|
|
|
// MARK: ------ does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) }).to(equal(profile))
|
|
}
|
|
}
|
|
|
|
// MARK: ---- when it fails to write to disk
|
|
context("when it fails to write to disk") {
|
|
beforeEach {
|
|
mockFileManager
|
|
.when { $0.createFile(atPath: .any, contents: .any, attributes: .any) }
|
|
.thenReturn(false)
|
|
}
|
|
|
|
// MARK: ------ does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) }).to(equal(profile))
|
|
}
|
|
}
|
|
|
|
// MARK: ---- writes the file to disk
|
|
it("writes the file to disk") {
|
|
expect(mockFileManager)
|
|
.to(call(.exactly(times: 1), matchingParameters: .all) { mockFileManager in
|
|
mockFileManager.createFile(
|
|
atPath: "/test/ProfileAvatars/\(filenameUuid.uuidString).jpg",
|
|
contents: imageData,
|
|
attributes: nil
|
|
)
|
|
})
|
|
}
|
|
|
|
// MARK: ---- adds the image data to the displayPicture cache
|
|
it("adds the image data to the displayPicture cache") {
|
|
expect(mockDisplayPictureCache)
|
|
.to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.imageData = ["\(filenameUuid.uuidString).jpg": imageData]
|
|
})
|
|
}
|
|
|
|
// MARK: ---- successfully completes the job
|
|
it("successfully completes the job") {
|
|
expect(jobResult).to(equal(.succeeded))
|
|
}
|
|
|
|
// MARK: ---- for a profile
|
|
context("for a profile") {
|
|
beforeEach {
|
|
profile = Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/100/",
|
|
profilePictureFileName: nil,
|
|
profileEncryptionKey: encryptionKey,
|
|
lastProfilePictureUpdate: 1234567890
|
|
)
|
|
mockStorage.write(using: dependencies) { db in
|
|
_ = try Profile.deleteAll(db)
|
|
try profile.insert(db)
|
|
}
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .profile(
|
|
id: "1234",
|
|
url: "http://oxen.io/100/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 1234567891
|
|
)
|
|
)
|
|
}
|
|
|
|
// MARK: ------ that does not exist
|
|
context("that does not exist") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in try Profile.deleteAll(db) }
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) }).to(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a different encryption key and more recent update
|
|
context("that has a different encryption key and more recent update") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try Profile
|
|
.updateAll(
|
|
db,
|
|
Profile.Columns.profileEncryptionKey.set(to: Data([1, 2, 3])),
|
|
Profile.Columns.lastProfilePictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) })
|
|
.toNot(equal(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/100/",
|
|
profilePictureFileName: "\(filenameUuid.uuidString).jpg",
|
|
profileEncryptionKey: encryptionKey,
|
|
lastProfilePictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a different url and more recent update
|
|
context("that has a different url and more recent update") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try Profile
|
|
.updateAll(
|
|
db,
|
|
Profile.Columns.profilePictureUrl.set(to: "testUrl"),
|
|
Profile.Columns.lastProfilePictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) })
|
|
.toNot(equal(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/100/",
|
|
profilePictureFileName: "\(filenameUuid.uuidString).jpg",
|
|
profileEncryptionKey: encryptionKey,
|
|
lastProfilePictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a more recent update but the same url and encryption key
|
|
context("that has a more recent update but the same url and encryption key") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try Profile
|
|
.updateAll(
|
|
db,
|
|
Profile.Columns.lastProfilePictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- saves the picture
|
|
it("saves the picture") {
|
|
expect(mockCrypto)
|
|
.to(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.createFile(
|
|
atPath: "/test/ProfileAvatars/\(filenameUuid.uuidString).jpg",
|
|
contents: imageData,
|
|
attributes: nil
|
|
)
|
|
})
|
|
expect(mockDisplayPictureCache).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.imageData = ["\(filenameUuid.uuidString).jpg": imageData]
|
|
})
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) })
|
|
.to(equal(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/100/",
|
|
profilePictureFileName: "\(filenameUuid.uuidString).jpg",
|
|
profileEncryptionKey: encryptionKey,
|
|
lastProfilePictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ updates the database values
|
|
it("updates the database values") {
|
|
expect(mockStorage.read { db in try Profile.fetchOne(db) })
|
|
.to(equal(
|
|
Profile(
|
|
id: "1234",
|
|
name: "test",
|
|
profilePictureUrl: "http://oxen.io/100/",
|
|
profilePictureFileName: "\(filenameUuid.uuidString).jpg",
|
|
profileEncryptionKey: encryptionKey,
|
|
lastProfilePictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ---- for a group
|
|
context("for a group") {
|
|
beforeEach {
|
|
group = ClosedGroup(
|
|
threadId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
name: "TestGroup",
|
|
groupDescription: nil,
|
|
formationTimestamp: 1234567890,
|
|
displayPictureUrl: "http://oxen.io/100/",
|
|
displayPictureFilename: nil,
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
lastDisplayPictureUpdate: 1234567890,
|
|
shouldPoll: true,
|
|
groupIdentityPrivateKey: nil,
|
|
authData: Data([1, 2, 3]),
|
|
invited: false
|
|
)
|
|
mockStorage.write(using: dependencies) { db in
|
|
_ = try ClosedGroup.deleteAll(db)
|
|
try SessionThread.fetchOrCreate(
|
|
db,
|
|
id: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
variant: .group,
|
|
shouldBeVisible: true,
|
|
calledFromConfigHandling: false
|
|
).upsert(db)
|
|
try group.insert(db)
|
|
}
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .group(
|
|
id: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
url: "http://oxen.io/100/",
|
|
encryptionKey: encryptionKey
|
|
),
|
|
timestamp: 1234567891
|
|
)
|
|
)
|
|
}
|
|
|
|
// MARK: ------ that does not exist
|
|
context("that does not exist") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in try ClosedGroup.deleteAll(db) }
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try ClosedGroup.fetchOne(db) }).to(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a different encryption key and more recent update
|
|
context("that has a different encryption key and more recent update") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try ClosedGroup
|
|
.updateAll(
|
|
db,
|
|
ClosedGroup.Columns.displayPictureEncryptionKey.set(to: Data([1, 2, 3])),
|
|
ClosedGroup.Columns.lastDisplayPictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try ClosedGroup.fetchOne(db) })
|
|
.toNot(equal(
|
|
ClosedGroup(
|
|
threadId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
name: "TestGroup",
|
|
groupDescription: nil,
|
|
formationTimestamp: 1234567890,
|
|
displayPictureUrl: "http://oxen.io/100/",
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
lastDisplayPictureUpdate: 1234567891,
|
|
shouldPoll: true,
|
|
groupIdentityPrivateKey: nil,
|
|
authData: Data([1, 2, 3]),
|
|
invited: false
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a different url and more recent update
|
|
context("that has a different url and more recent update") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try ClosedGroup
|
|
.updateAll(
|
|
db,
|
|
ClosedGroup.Columns.displayPictureUrl.set(to: "testUrl"),
|
|
ClosedGroup.Columns.lastDisplayPictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockCrypto)
|
|
.toNot(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try ClosedGroup.fetchOne(db) })
|
|
.toNot(equal(
|
|
ClosedGroup(
|
|
threadId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
name: "TestGroup",
|
|
groupDescription: nil,
|
|
formationTimestamp: 1234567890,
|
|
displayPictureUrl: "http://oxen.io/100/",
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
lastDisplayPictureUpdate: 1234567891,
|
|
shouldPoll: true,
|
|
groupIdentityPrivateKey: nil,
|
|
authData: Data([1, 2, 3]),
|
|
invited: false
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a more recent update but the same url and encryption key
|
|
context("that has a more recent update but the same url and encryption key") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try ClosedGroup
|
|
.updateAll(
|
|
db,
|
|
ClosedGroup.Columns.lastDisplayPictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- saves the picture
|
|
it("saves the picture") {
|
|
expect(mockCrypto)
|
|
.to(call {
|
|
$0.generate(.decryptedDataDisplayPicture(data: .any, key: .any, using: .any))
|
|
})
|
|
expect(mockFileManager).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.createFile(
|
|
atPath: "/test/ProfileAvatars/\(filenameUuid.uuidString).jpg",
|
|
contents: imageData,
|
|
attributes: nil
|
|
)
|
|
})
|
|
expect(mockDisplayPictureCache).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.imageData = ["\(filenameUuid.uuidString).jpg": imageData]
|
|
})
|
|
expect(mockStorage.read { db in try ClosedGroup.fetchOne(db) })
|
|
.to(equal(
|
|
ClosedGroup(
|
|
threadId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
name: "TestGroup",
|
|
groupDescription: nil,
|
|
formationTimestamp: 1234567890,
|
|
displayPictureUrl: "http://oxen.io/100/",
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
lastDisplayPictureUpdate: 1234567891,
|
|
shouldPoll: true,
|
|
groupIdentityPrivateKey: nil,
|
|
authData: Data([1, 2, 3]),
|
|
invited: false
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ updates the database values
|
|
it("updates the database values") {
|
|
expect(mockStorage.read { db in try ClosedGroup.fetchOne(db) })
|
|
.to(equal(
|
|
ClosedGroup(
|
|
threadId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
name: "TestGroup",
|
|
groupDescription: nil,
|
|
formationTimestamp: 1234567890,
|
|
displayPictureUrl: "http://oxen.io/100/",
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
displayPictureEncryptionKey: encryptionKey,
|
|
lastDisplayPictureUpdate: 1234567891,
|
|
shouldPoll: true,
|
|
groupIdentityPrivateKey: nil,
|
|
authData: Data([1, 2, 3]),
|
|
invited: false
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ---- for a community
|
|
context("for a community") {
|
|
beforeEach {
|
|
community = OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
isActive: true,
|
|
name: "name",
|
|
imageId: "100",
|
|
userCount: 1,
|
|
infoUpdates: 1,
|
|
displayPictureFilename: nil,
|
|
lastDisplayPictureUpdate: 1234567890
|
|
)
|
|
mockStorage.write(using: dependencies) { db in
|
|
_ = try OpenGroup.deleteAll(db)
|
|
try SessionThread.fetchOrCreate(
|
|
db,
|
|
id: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
|
variant: .community,
|
|
shouldBeVisible: true,
|
|
calledFromConfigHandling: false
|
|
).upsert(db)
|
|
try community.insert(db)
|
|
}
|
|
job = Job(
|
|
variant: .displayPictureDownload,
|
|
shouldBeUnique: true,
|
|
details: DisplayPictureDownloadJob.Details(
|
|
target: .community(
|
|
imageId: "100",
|
|
roomToken: "testRoom",
|
|
server: "testServer"
|
|
),
|
|
timestamp: 1234567891
|
|
)
|
|
)
|
|
|
|
// SOGS doesn't encrypt it's images so replace the encrypted mock response
|
|
mockNetwork
|
|
.when { $0.send(.selectedNetworkRequest(.any, to: .any, with: .any, timeout: .any, using: .any)) }
|
|
.thenReturn(MockNetwork.response(data: imageData))
|
|
}
|
|
|
|
// MARK: ------ that does not exist
|
|
context("that does not exist") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in try OpenGroup.deleteAll(db) }
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try OpenGroup.fetchOne(db) }).to(beNil())
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a different imageId and more recent update
|
|
context("that has a different imageId and more recent update") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try OpenGroup
|
|
.updateAll(
|
|
db,
|
|
OpenGroup.Columns.imageId.set(to: "101"),
|
|
OpenGroup.Columns.lastDisplayPictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- does not save the picture
|
|
it("does not save the picture") {
|
|
expect(mockFileManager)
|
|
.toNot(call { $0.createFile(atPath: .any, contents: .any, attributes: .any) })
|
|
expect(mockDisplayPictureCache).toNot(call { $0.imageData = .any })
|
|
expect(mockStorage.read { db in try OpenGroup.fetchOne(db) })
|
|
.toNot(equal(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
isActive: true,
|
|
name: "name",
|
|
imageId: "100",
|
|
userCount: 1,
|
|
infoUpdates: 1,
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
lastDisplayPictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ that has a more recent update but the same imageId
|
|
context("that has a more recent update but the same imageId") {
|
|
beforeEach {
|
|
mockStorage.write(using: dependencies) { db in
|
|
try OpenGroup
|
|
.updateAll(
|
|
db,
|
|
OpenGroup.Columns.imageId.set(to: "100"),
|
|
OpenGroup.Columns.lastDisplayPictureUpdate.set(to: 9999999999)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: -------- saves the picture
|
|
it("saves the picture") {
|
|
expect(mockFileManager).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.createFile(
|
|
atPath: "/test/ProfileAvatars/\(filenameUuid.uuidString).jpg",
|
|
contents: imageData,
|
|
attributes: nil
|
|
)
|
|
})
|
|
expect(mockDisplayPictureCache).to(call(.exactly(times: 1), matchingParameters: .all) {
|
|
$0.imageData = ["\(filenameUuid.uuidString).jpg": imageData]
|
|
})
|
|
expect(mockStorage.read { db in try OpenGroup.fetchOne(db) })
|
|
.to(equal(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
isActive: true,
|
|
name: "name",
|
|
imageId: "100",
|
|
userCount: 1,
|
|
infoUpdates: 1,
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
lastDisplayPictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
|
|
// MARK: ------ updates the database values
|
|
it("updates the database values") {
|
|
expect(mockStorage.read { db in try OpenGroup.fetchOne(db) })
|
|
.to(equal(
|
|
OpenGroup(
|
|
server: "testServer",
|
|
roomToken: "testRoom",
|
|
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
|
|
isActive: true,
|
|
name: "name",
|
|
imageId: "100",
|
|
userCount: 1,
|
|
infoUpdates: 1,
|
|
displayPictureFilename: "\(filenameUuid.uuidString).jpg",
|
|
lastDisplayPictureUpdate: 1234567891
|
|
)
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|