-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from darrarski/feature/download-file
Download File
- Loading branch information
Showing
6 changed files
with
229 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import Foundation | ||
|
||
public struct DownloadFile: Sendable { | ||
public struct Params: Sendable, Equatable, Encodable { | ||
public init(path: String) { | ||
self.path = path | ||
} | ||
|
||
public var path: String | ||
} | ||
|
||
public enum Error: Swift.Error, Sendable, Equatable { | ||
case notAuthorized | ||
case response(statusCode: Int?, data: Data) | ||
} | ||
|
||
public typealias Run = @Sendable (Params) async throws -> Data | ||
|
||
public init(run: @escaping Run) { | ||
self.run = run | ||
} | ||
|
||
public var run: Run | ||
|
||
public func callAsFunction(_ params: Params) async throws -> Data { | ||
try await run(params) | ||
} | ||
|
||
public func callAsFunction(path: String) async throws -> Data { | ||
try await run(.init(path: path)) | ||
} | ||
} | ||
|
||
extension DownloadFile { | ||
public static func live( | ||
keychain: Keychain, | ||
httpClient: HTTPClient | ||
) -> DownloadFile { | ||
DownloadFile { params in | ||
guard let credentials = await keychain.loadCredentials() else { | ||
throw Error.notAuthorized | ||
} | ||
|
||
let request: URLRequest = try { | ||
var components = URLComponents() | ||
components.scheme = "https" | ||
components.host = "content.dropboxapi.com" | ||
components.path = "/2/files/download" | ||
|
||
var request = URLRequest(url: components.url!) | ||
request.httpMethod = "POST" | ||
request.setValue( | ||
"\(credentials.tokenType) \(credentials.accessToken)", | ||
forHTTPHeaderField: "Authorization" | ||
) | ||
request.setValue( | ||
String(data: try JSONEncoder.api.encode(params), encoding: .utf8), | ||
forHTTPHeaderField: "Dropbox-API-Arg" | ||
) | ||
|
||
return request | ||
}() | ||
|
||
let (responseData, response) = try await httpClient.data(for: request) | ||
let statusCode = (response as? HTTPURLResponse)?.statusCode | ||
|
||
guard let statusCode, (200..<300).contains(statusCode) else { | ||
throw Error.response(statusCode: statusCode, data: responseData) | ||
} | ||
|
||
return responseData | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import XCTest | ||
@testable import DropboxClient | ||
|
||
final class DownloadFileTests: XCTestCase { | ||
func testDownloadFile() async throws { | ||
let credentials = Credentials.test | ||
let httpRequests = ActorIsolated<[URLRequest]>([]) | ||
let downloadFile = DownloadFile.live( | ||
keychain: { | ||
var keychain = Keychain.unimplemented() | ||
keychain.loadCredentials = { credentials } | ||
return keychain | ||
}(), | ||
httpClient: .init { request in | ||
await httpRequests.withValue { $0.append(request) } | ||
return ( | ||
"test file content".data(using: .utf8)!, | ||
HTTPURLResponse( | ||
url: URL(filePath: "/"), | ||
statusCode: 200, | ||
httpVersion: nil, | ||
headerFields: nil | ||
)! | ||
) | ||
} | ||
) | ||
let params = DownloadFile.Params(path: "test-path") | ||
|
||
let result = try await downloadFile(params) | ||
|
||
await httpRequests.withValue { | ||
let expectedRequest: URLRequest = { | ||
let url = URL(string: "https://content.dropboxapi.com/2/files/download")! | ||
var request = URLRequest(url: url) | ||
request.httpMethod = "POST" | ||
request.allHTTPHeaderFields = [ | ||
"Authorization": "\(credentials.tokenType) \(credentials.accessToken)", | ||
"Dropbox-API-Arg": String( | ||
data: try! JSONEncoder.api.encode(params), | ||
encoding: .utf8 | ||
)! | ||
] | ||
return request | ||
}() | ||
|
||
XCTAssertEqual($0, [expectedRequest]) | ||
XCTAssertNil($0.first?.httpBody) | ||
} | ||
XCTAssertEqual(result, "test file content".data(using: .utf8)!) | ||
} | ||
|
||
func testDownloadFileWhenNotAuthorized() async { | ||
let downloadFile = DownloadFile.live( | ||
keychain: { | ||
var keychain = Keychain.unimplemented() | ||
keychain.loadCredentials = { nil } | ||
return keychain | ||
}(), | ||
httpClient: .unimplemented() | ||
) | ||
|
||
do { | ||
_ = try await downloadFile(path: "") | ||
XCTFail("Expected to throw, but didn't") | ||
} catch { | ||
XCTAssertEqual( | ||
error as? DownloadFile.Error, .notAuthorized, | ||
"Expected to throw .notAuthorized, got \(error)" | ||
) | ||
} | ||
} | ||
|
||
func testDownloadFileErrorResponse() async { | ||
let downloadFile = DownloadFile.live( | ||
keychain: { | ||
var keychain = Keychain.unimplemented() | ||
keychain.loadCredentials = { .test } | ||
return keychain | ||
}(), | ||
httpClient: .init { _ in | ||
( | ||
"Error!!!".data(using: .utf8)!, | ||
HTTPURLResponse( | ||
url: URL(filePath: "/"), | ||
statusCode: 500, | ||
httpVersion: nil, | ||
headerFields: nil | ||
)! | ||
) | ||
} | ||
) | ||
|
||
do { | ||
_ = try await downloadFile(path: "") | ||
XCTFail("Expected to throw, but didn't") | ||
} catch { | ||
XCTAssertEqual( | ||
error as? DownloadFile.Error, | ||
.response( | ||
statusCode: 500, | ||
data: "Error!!!".data(using: .utf8)! | ||
), | ||
"Expected to throw response error, got \(error)" | ||
) | ||
} | ||
} | ||
} |