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.
		
		
		
		
		
			
		
			
				
	
	
		
			436 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			436 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import GRDB
 | |
| 
 | |
| import Quick
 | |
| import Nimble
 | |
| 
 | |
| @testable import SessionMessagingKit
 | |
| @testable import SessionUtilitiesKit
 | |
| 
 | |
| extension Job: @retroactive MutableIdentifiable {
 | |
|     public mutating func setId(_ id: Int64?) { self.id = id }
 | |
| }
 | |
| 
 | |
| class MessageSendJobSpec: QuickSpec {
 | |
|     override class func spec() {
 | |
|         // MARK: Configuration
 | |
|         
 | |
|         @TestState var job: Job!
 | |
|         @TestState var interaction: Interaction!
 | |
|         @TestState var attachment: Attachment! = Attachment(
 | |
|             id: "200",
 | |
|             variant: .standard,
 | |
|             state: .failedDownload,
 | |
|             contentType: "text/plain",
 | |
|             byteCount: 200
 | |
|         )
 | |
|         @TestState var interactionAttachment: InteractionAttachment!
 | |
|         @TestState var dependencies: TestDependencies! = TestDependencies { dependencies in
 | |
|             dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
 | |
|         }
 | |
|         @TestState(cache: .libSession, in: dependencies) var mockLibSessionCache: MockLibSessionCache! = MockLibSessionCache(
 | |
|             initialSetup: { cache in
 | |
|                 cache.when { $0.config(for: .any, sessionId: .any) }.thenReturn(nil)
 | |
|                 cache
 | |
|                     .when { $0.pinnedPriority(.any, threadId: .any, threadVariant: .any) }
 | |
|                     .thenReturn(LibSession.defaultNewThreadPriority)
 | |
|                 cache.when { $0.disappearingMessagesConfig(threadId: .any, threadVariant: .any) }
 | |
|                     .thenReturn(nil)
 | |
|             }
 | |
|         )
 | |
|         @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage(
 | |
|             customWriter: try! DatabaseQueue(),
 | |
|             migrationTargets: [
 | |
|                 SNUtilitiesKit.self,
 | |
|                 SNMessagingKit.self
 | |
|             ],
 | |
|             using: dependencies,
 | |
|             initialData: { db in
 | |
|                 try SessionThread.upsert(
 | |
|                     db,
 | |
|                     id: "Test1",
 | |
|                     variant: .contact,
 | |
|                     values: SessionThread.TargetValues(
 | |
|                         creationDateTimestamp: .setTo(1234567890),
 | |
|                         // False is the default and will mean we don't need libSession loaded
 | |
|                         shouldBeVisible: .setTo(false)
 | |
|                     ),
 | |
|                     using: dependencies
 | |
|                 )
 | |
|             }
 | |
|         )
 | |
|         @TestState(singleton: .jobRunner, in: dependencies) var mockJobRunner: MockJobRunner! = MockJobRunner(
 | |
|             initialSetup: { jobRunner in
 | |
|                 jobRunner
 | |
|                     .when {
 | |
|                         $0.jobInfoFor(
 | |
|                             jobs: nil,
 | |
|                             state: .running,
 | |
|                             variant: .attachmentUpload
 | |
|                         )
 | |
|                     }
 | |
|                     .thenReturn([:])
 | |
|                 jobRunner
 | |
|                     .when { $0.insert(.any, job: .any, before: .any) }
 | |
|                     .then { args, untrackedArgs in
 | |
|                         let db: Database = untrackedArgs[0] as! Database
 | |
|                         var job: Job = args[0] as! Job
 | |
|                         job.id = 1000
 | |
|                         
 | |
|                         try! job.insert(db)
 | |
|                     }
 | |
|                     .thenReturn((1000, Job(variant: .messageSend)))
 | |
|             }
 | |
|         )
 | |
|         
 | |
|         // MARK: - a MessageSendJob
 | |
|         describe("a MessageSendJob") {
 | |
|             // MARK: -- fails when not given any details
 | |
|             it("fails when not given any details") {
 | |
|                 job = Job(variant: .messageSend)
 | |
|                 
 | |
|                 var error: Error? = nil
 | |
|                 var permanentFailure: Bool = false
 | |
|                 
 | |
|                 MessageSendJob.run(
 | |
|                     job,
 | |
|                     scheduler: DispatchQueue.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: -- fails when given incorrect details
 | |
|             it("fails when given incorrect details") {
 | |
|                 job = Job(
 | |
|                     variant: .messageSend,
 | |
|                     details: MessageReceiveJob.Details(
 | |
|                         messages: [MessageReceiveJob.Details.MessageInfo]()
 | |
|                     )
 | |
|                 )
 | |
|                 
 | |
|                 var error: Error? = nil
 | |
|                 var permanentFailure: Bool = false
 | |
|                 
 | |
|                 MessageSendJob.run(
 | |
|                     job,
 | |
|                     scheduler: DispatchQueue.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: -- of VisibleMessage
 | |
|             context("of VisibleMessage") {
 | |
|                 beforeEach {
 | |
|                     interaction = Interaction(
 | |
|                         id: 100,
 | |
|                         serverHash: nil,
 | |
|                         messageUuid: nil,
 | |
|                         threadId: "Test1",
 | |
|                         authorId: "Test",
 | |
|                         variant: .standardOutgoing,
 | |
|                         body: "Test",
 | |
|                         timestampMs: 1234567890,
 | |
|                         receivedAtTimestampMs: 1234567900,
 | |
|                         wasRead: false,
 | |
|                         hasMention: false,
 | |
|                         expiresInSeconds: nil,
 | |
|                         expiresStartedAtMs: nil,
 | |
|                         linkPreviewUrl: nil,
 | |
|                         openGroupServerMessageId: nil,
 | |
|                         openGroupWhisper: false,
 | |
|                         openGroupWhisperMods: false,
 | |
|                         openGroupWhisperTo: nil,
 | |
|                         state: .sending,
 | |
|                         recipientReadTimestampMs: nil,
 | |
|                         mostRecentFailureText: nil,
 | |
|                         transientDependencies: nil
 | |
|                     )
 | |
|                     job = Job(
 | |
|                         variant: .messageSend,
 | |
|                         interactionId: interaction.id!,
 | |
|                         details: MessageSendJob.Details(
 | |
|                             destination: .contact(publicKey: "Test"),
 | |
|                             message: VisibleMessage(
 | |
|                                 text: "Test"
 | |
|                             )
 | |
|                         )
 | |
|                     )
 | |
|                     
 | |
|                     mockStorage.write { db in
 | |
|                         try interaction.insert(db)
 | |
|                         try job.insert(db, withRowId: 54321)
 | |
|                     }
 | |
|                 }
 | |
|                 
 | |
|                 // MARK: ---- fails when there is no job id
 | |
|                 it("fails when there is no job id") {
 | |
|                     job = Job(
 | |
|                         variant: .messageSend,
 | |
|                         interactionId: interaction.id!,
 | |
|                         details: MessageSendJob.Details(
 | |
|                             destination: .contact(publicKey: "Test"),
 | |
|                             message: VisibleMessage(
 | |
|                                 text: "Test"
 | |
|                             )
 | |
|                         )
 | |
|                     )
 | |
|                     
 | |
|                     var error: Error? = nil
 | |
|                     var permanentFailure: Bool = false
 | |
|                     
 | |
|                     MessageSendJob.run(
 | |
|                         job,
 | |
|                         scheduler: DispatchQueue.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: ---- fails when there is no interaction id
 | |
|                 it("fails when there is no interaction id") {
 | |
|                     job = Job(
 | |
|                         variant: .messageSend,
 | |
|                         details: MessageSendJob.Details(
 | |
|                             destination: .contact(publicKey: "Test"),
 | |
|                             message: VisibleMessage(
 | |
|                                 text: "Test"
 | |
|                             )
 | |
|                         )
 | |
|                     )
 | |
|                     
 | |
|                     var error: Error? = nil
 | |
|                     var permanentFailure: Bool = false
 | |
|                     
 | |
|                     MessageSendJob.run(
 | |
|                         job,
 | |
|                         scheduler: DispatchQueue.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: ---- fails when there is no interaction for the provided interaction id
 | |
|                 it("fails when there is no interaction for the provided interaction id") {
 | |
|                     job = Job(
 | |
|                         variant: .messageSend,
 | |
|                         interactionId: 12345,
 | |
|                         details: MessageSendJob.Details(
 | |
|                             destination: .contact(publicKey: "Test"),
 | |
|                             message: VisibleMessage(
 | |
|                                 text: "Test"
 | |
|                             )
 | |
|                         )
 | |
|                     )
 | |
|                     mockStorage.write { db in try job.insert(db, withRowId: 54321) }
 | |
|                     
 | |
|                     var error: Error? = nil
 | |
|                     var permanentFailure: Bool = false
 | |
|                     
 | |
|                     MessageSendJob.run(
 | |
|                         job,
 | |
|                         scheduler: DispatchQueue.main,
 | |
|                         success: { _, _ in },
 | |
|                         failure: { _, runError, runPermanentFailure in
 | |
|                             error = runError
 | |
|                             permanentFailure = runPermanentFailure
 | |
|                         },
 | |
|                         deferred: { _ in },
 | |
|                         using: dependencies
 | |
|                     )
 | |
|                     
 | |
|                     expect(error).to(matchError(StorageError.objectNotFound))
 | |
|                     expect(permanentFailure).to(beTrue())
 | |
|                 }
 | |
|                 
 | |
|                 // MARK: ---- with an attachment
 | |
|                 context("with an attachment") {
 | |
|                     beforeEach {
 | |
|                         interactionAttachment = InteractionAttachment(
 | |
|                             albumIndex: 0,
 | |
|                             interactionId: interaction.id!,
 | |
|                             attachmentId: attachment.id
 | |
|                         )
 | |
|                         
 | |
|                         mockStorage.write { db in
 | |
|                             try attachment.insert(db)
 | |
|                             try interactionAttachment.insert(db)
 | |
|                         }
 | |
|                     }
 | |
|                     
 | |
|                     // MARK: ------ it fails when trying to send with an attachment which previously failed to download
 | |
|                     it("it fails when trying to send with an attachment which previously failed to download") {
 | |
|                         mockStorage.write { db in
 | |
|                             try attachment.with(state: .failedDownload, using: dependencies).upsert(db)
 | |
|                         }
 | |
|                         
 | |
|                         var error: Error? = nil
 | |
|                         var permanentFailure: Bool = false
 | |
|                         
 | |
|                         MessageSendJob.run(
 | |
|                             job,
 | |
|                             scheduler: DispatchQueue.main,
 | |
|                             success: { _, _ in },
 | |
|                             failure: { _, runError, runPermanentFailure in
 | |
|                                 error = runError
 | |
|                                 permanentFailure = runPermanentFailure
 | |
|                             },
 | |
|                             deferred: { _ in },
 | |
|                             using: dependencies
 | |
|                         )
 | |
|                         
 | |
|                         expect(error).to(matchError(AttachmentError.notUploaded))
 | |
|                         expect(permanentFailure).to(beTrue())
 | |
|                     }
 | |
|                     
 | |
|                     // MARK: ------ with a pending upload
 | |
|                     context("with a pending upload") {
 | |
|                         beforeEach {
 | |
|                             mockStorage.write { db in
 | |
|                                 try attachment.with(state: .uploading, using: dependencies).upsert(db)
 | |
|                             }
 | |
|                         }
 | |
|                         
 | |
|                         // MARK: -------- it defers when trying to send with an attachment which is still pending upload
 | |
|                         it("it defers when trying to send with an attachment which is still pending upload") {
 | |
|                             var didDefer: Bool = false
 | |
|                             
 | |
|                             mockStorage.write { db in
 | |
|                                 try attachment.with(state: .uploading, using: dependencies).upsert(db)
 | |
|                             }
 | |
|                             
 | |
|                             MessageSendJob.run(
 | |
|                                 job,
 | |
|                                 scheduler: DispatchQueue.main,
 | |
|                                 success: { _, _ in },
 | |
|                                 failure: { _, _, _ in },
 | |
|                                 deferred: { _ in didDefer = true },
 | |
|                                 using: dependencies
 | |
|                             )
 | |
|                             
 | |
|                             expect(didDefer).to(beTrue())
 | |
|                         }
 | |
|                         
 | |
|                         // MARK: -------- it defers when trying to send with an uploaded attachment that has an invalid downloadUrl
 | |
|                         it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") {
 | |
|                             var didDefer: Bool = false
 | |
|                             
 | |
|                             mockStorage.write { db in
 | |
|                                 try attachment
 | |
|                                     .with(
 | |
|                                         state: .uploaded,
 | |
|                                         downloadUrl: nil,
 | |
|                                         using: dependencies
 | |
|                                     )
 | |
|                                     .upsert(db)
 | |
|                             }
 | |
|                             
 | |
|                             MessageSendJob.run(
 | |
|                                 job,
 | |
|                                 scheduler: DispatchQueue.main,
 | |
|                                 success: { _, _ in },
 | |
|                                 failure: { _, _, _ in },
 | |
|                                 deferred: { _ in didDefer = true },
 | |
|                                 using: dependencies
 | |
|                             )
 | |
|                             
 | |
|                             expect(didDefer).to(beTrue())
 | |
|                         }
 | |
|                         
 | |
|                         // MARK: -------- inserts an attachment upload job before the message send job
 | |
|                         it("inserts an attachment upload job before the message send job") {
 | |
|                             mockJobRunner
 | |
|                                 .when {
 | |
|                                     $0.jobInfoFor(
 | |
|                                         jobs: nil,
 | |
|                                         state: .running,
 | |
|                                         variant: .attachmentUpload
 | |
|                                     )
 | |
|                                 }
 | |
|                                 .thenReturn([:])
 | |
|                             
 | |
|                             MessageSendJob.run(
 | |
|                                 job,
 | |
|                                 scheduler: DispatchQueue.main,
 | |
|                                 success: { _, _ in },
 | |
|                                 failure: { _, _, _ in },
 | |
|                                 deferred: { _ in },
 | |
|                                 using: dependencies
 | |
|                             )
 | |
|                             
 | |
|                             expect(mockJobRunner)
 | |
|                                 .to(call(.exactly(times: 1), matchingParameters: .all) {
 | |
|                                     $0.insert(
 | |
|                                         .any,
 | |
|                                         job: Job(
 | |
|                                             variant: .attachmentUpload,
 | |
|                                             behaviour: .runOnce,
 | |
|                                             shouldBlock: false,
 | |
|                                             shouldSkipLaunchBecomeActive: false,
 | |
|                                             interactionId: 100,
 | |
|                                             details: AttachmentUploadJob.Details(
 | |
|                                                 messageSendJobId: 54321,
 | |
|                                                 attachmentId: "200"
 | |
|                                             )
 | |
|                                         ),
 | |
|                                         before: job
 | |
|                                     )
 | |
|                                 })
 | |
|                         }
 | |
|                         
 | |
|                         // MARK: -------- creates a dependency between the new job and the existing one
 | |
|                         it("creates a dependency between the new job and the existing one") {
 | |
|                             MessageSendJob.run(
 | |
|                                 job,
 | |
|                                 scheduler: DispatchQueue.main,
 | |
|                                 success: { _, _ in },
 | |
|                                 failure: { _, _, _ in },
 | |
|                                 deferred: { _ in },
 | |
|                                 using: dependencies
 | |
|                             )
 | |
|                             
 | |
|                             expect(mockStorage.read { db in try JobDependencies.fetchOne(db) })
 | |
|                                 .to(equal(JobDependencies(jobId: 54321, dependantId: 1000)))
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |