Signal-iOS/SignalServiceKit/tests/Network/Upload/AttachmentUploadManagerTestHelper.swift

216 lines
8.0 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
@testable import SignalServiceKit
typealias TSRequestDataTaskBlock = ((TSRequest, Bool) -> Promise<HTTPResponse>)
typealias DataTaskPromiseBlock = ((URLRequest) -> Promise<HTTPResponse>)
typealias UploadTaskPromiseBlock = ((URLRequest, URL) -> Promise<HTTPResponse>)
enum MockRequestType {
case uploadForm(TSRequestDataTaskBlock)
case uploadLocation(DataTaskPromiseBlock)
case uploadProgress(DataTaskPromiseBlock)
case uploadTask(UploadTaskPromiseBlock)
}
enum MockResultType {
case uploadForm(TSRequest)
case uploadLocation(URLRequest)
case uploadProgress(URLRequest)
case uploadTask(URLRequest)
}
class AttachmentUploadManagerMockHelper {
var mockDate = Date()
lazy var mockDateProvider = { return self.mockDate }
var mockDB = MockDB()
var mockURLSession = AttachmentUpload.Mocks.URLSession()
var mockNetworkManager = AttachmentUpload.Mocks.NetworkManager()
var mockServiceManager = OWSSignalServiceMock()
var mockChatConnectionManager = AttachmentUpload.Mocks.ChatConnectionManager()
var mockFileSystem = AttachmentUpload.Mocks.FileSystem()
var mockInteractionStore = MockInteractionStore()
var mockStoryStore = StoryStoreMock()
var mockAttachmentStore = AttachmentUploadStoreMock()
var mockAttachmentEncrypter = AttachmentUpload.Mocks.AttachmentEncrypter()
var capturedRequests = [MockResultType]()
// List of auth requests.
var authFormRequestBlock = [MockRequestType]()
// Map of auth header to resume location maps
var authToUploadRequestMockMap = [String: [MockRequestType]]()
// auth set the active location Requests (and the active URL)
var activeUploadRequestMocks = [MockRequestType]()
func setup(encryptedSize: UInt32, unencryptedSize: UInt32) {
setup(
encryptedUploadSize: encryptedSize,
mockAttachment: MockAttachmentStream.mock(
streamInfo: .mock(
encryptedByteCount: encryptedSize,
unenecryptedByteCount: unencryptedSize
)
).attachment
)
}
func setup(
encryptedUploadSize: UInt32,
mockAttachment: Attachment
) {
self.mockAttachmentStore.mockFetcher = { _ in
return mockAttachment
}
self.mockFileSystem.size = Int(clamping: encryptedUploadSize)
mockServiceManager.mockUrlSessionBuilder = { (info: SignalServiceInfo, endpoint: OWSURLSessionEndpoint, config: URLSessionConfiguration? ) in
return self.mockURLSession
}
mockNetworkManager.performRequestBlock = { request, canUseWebSocket in
let item = self.authFormRequestBlock.removeFirst()
guard case let .uploadForm(authDataTaskBlock) = item else {
return .init(error: OWSAssertionError("Mock request missing"))
}
self.capturedRequests.append(.uploadForm(request))
return authDataTaskBlock(request, canUseWebSocket)
}
mockURLSession.promiseForDataTaskBlock = { request in
let urlString = request.url!.absoluteString
switch self.activeUploadRequestMocks.removeFirst() {
case .uploadLocation(let requestBlock):
self.capturedRequests.append(.uploadLocation(request))
return requestBlock(request)
case .uploadProgress(let requestBlock):
self.capturedRequests.append(.uploadProgress(request))
return requestBlock(request)
case .uploadForm, .uploadTask:
return .init(error: OWSAssertionError("Mock request missing"))
}
}
mockURLSession.promiseForUploadFileTaskBlock = { request, url, _, _ in
guard case let .uploadTask(requestBlock) = self.activeUploadRequestMocks.removeFirst() else {
return .init(error: OWSAssertionError("Mock request missing"))
}
self.capturedRequests.append(.uploadTask(request))
return requestBlock(request, url)
}
}
func addFormRequestMock(version: UInt32, statusCode: Int = 200) -> (auth: String, location: String) {
let authString = UUID().uuidString
// Create a random, yet identifiable URL. Helps with debugging the captured requests.
let location = "https://upload/location/\(UUID().uuidString)"
authFormRequestBlock.append(.uploadForm({ request, _ in
let headers = [ "Auth": authString, ]
let form = Upload.Form(
headers: headers,
signedUploadLocation: location,
cdnKey: UUID().uuidString,
cdnNumber: version
)
self.activeUploadRequestMocks = self.authToUploadRequestMockMap[authString] ?? .init()
return .value(HTTPResponseImpl(
requestUrl: request.url!,
status: statusCode,
headers: OWSHttpHeaders(),
bodyData: try! JSONEncoder().encode(form)
))
}))
return (authString, location)
}
func addResumeLocationMock(auth: String, statusCode: Int = 201) -> String {
// Create a random, yet identifiable URL. Helps with debugging the captured requests.
let location = "https://resume/location/\(UUID().uuidString)"
enqueue(auth: auth, request: .uploadLocation({ request in
let headers = [ "Location": location ]
return .value(HTTPResponseImpl(
requestUrl: request.url!,
status: statusCode,
headers: OWSHttpHeaders(httpHeaders: headers, overwriteOnConflict: true),
bodyData: nil
))
}))
return location
}
enum ResumeProgressType {
case complete
case progress(count: Int)
case newUpload
case missingRange
case malformedRange
case otherStatusCode(Int)
}
func addResumeProgressMock(auth: String, location: String, type: ResumeProgressType) {
enqueue(auth: auth, request: .uploadProgress({ request in
var headers = ["Location": "\(location)"]
var statusCode = 308
switch type {
case .progress(let count):
headers["Range"] = "bytes=0-\(count)"
case .newUpload:
break
case .missingRange:
headers["Range"] = "bytes="
case .malformedRange:
headers["Range"] = "bytes=0-baddata"
case .otherStatusCode(let code):
statusCode = code
case .complete:
statusCode = 201 // This could also be a 200
}
return .value(HTTPResponseImpl(
requestUrl: request.url!,
status: statusCode,
headers: OWSHttpHeaders(httpHeaders: headers, overwriteOnConflict: true),
bodyData: nil
))
}))
}
enum UploadResultType {
case success
case failure
case networkError
}
func addUploadRequestMock(auth: String, location: String, type: UploadResultType) {
enqueue(auth: auth, request: .uploadTask({ request, url in
var statusCode = 200
switch type {
case .networkError:
return .init(error: OWSHTTPError.networkFailure(requestUrl: request.url!))
case .failure:
statusCode = 500
fallthrough // Use the same response code as success
case .success:
return .value(HTTPResponseImpl(
requestUrl: request.url!,
status: statusCode,
headers: OWSHttpHeaders(),
bodyData: nil
))
}
}))
}
private func enqueue(auth: String, request: MockRequestType) {
var mocks = authToUploadRequestMockMap[auth] ?? [MockRequestType]()
mocks.append(request)
authToUploadRequestMockMap[auth] = mocks
}
}