Signal-iOS/SignalServiceKit/tests/Network/Upload/AttachmentUploadManagerTestHelper.swift
Jordan Rose 0ac5558493 Move libsignal-net proxy handling into SignalProxy
Since this already affects more than just the chat connection, having
it in OWSChatConnection isn't really the best choice. This way we can
be sure the libsignal Net instance's proxy settings will be updated
before any notifications go out.
2024-05-17 16:20:13 -07:00

215 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 = AttachmentUploadManagerImpl.Mocks.URLSession()
var mockNetworkManager = AttachmentUploadManagerImpl.Mocks.NetworkManager(libsignalNet: nil)
var mockServiceManager = OWSSignalServiceMock()
var mockChatConnectionManager = AttachmentUploadManagerImpl.Mocks.ChatConnectionManager()
var mockFileSystem = AttachmentUploadManagerImpl.Mocks.FileSystem()
var mockInteractionStore = MockInteractionStore()
var mockStoryStore = StoryStoreMock()
var mockAttachmentStore = AttachmentUploadStoreMock()
var mockAttachmentEncrypter = AttachmentUploadManagerImpl.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,
unencryptedByteCount: 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
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
}
}