Skip to content

Commit 115915b

Browse files
committed
Add GetMetadata
1 parent 2ec723e commit 115915b

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Foundation
2+
3+
public struct GetMetadata: Sendable {
4+
public struct Params: Sendable, Equatable, Encodable {
5+
public init(
6+
path: String,
7+
includeMediaInfo: Bool? = nil,
8+
includeDeleted: Bool? = nil
9+
) {
10+
self.path = path
11+
self.includeMediaInfo = includeMediaInfo
12+
self.includeDeleted = includeDeleted
13+
}
14+
15+
public var path: String
16+
public var includeMediaInfo: Bool?
17+
public var includeDeleted: Bool?
18+
}
19+
20+
public enum Error: Swift.Error, Sendable, Equatable {
21+
case notAuthorized
22+
case response(statusCode: Int?, data: Data)
23+
}
24+
25+
public typealias Run = @Sendable (Params) async throws -> Metadata
26+
27+
public init(run: @escaping Run) {
28+
self.run = run
29+
}
30+
31+
public var run: Run
32+
33+
public func callAsFunction(_ params: Params) async throws -> Metadata {
34+
try await run(params)
35+
}
36+
37+
public func callAsFunction(
38+
path: String
39+
) async throws -> Metadata {
40+
try await run(.init(
41+
path: path
42+
))
43+
}
44+
}
45+
46+
extension GetMetadata {
47+
public static func live(
48+
auth: Auth,
49+
keychain: Keychain,
50+
httpClient: HTTPClient
51+
) -> GetMetadata {
52+
GetMetadata { params in
53+
try await auth.refreshToken()
54+
55+
guard let credentials = await keychain.loadCredentials() else {
56+
throw Error.notAuthorized
57+
}
58+
59+
let request: URLRequest = try {
60+
var components = URLComponents()
61+
components.scheme = "https"
62+
components.host = "api.dropboxapi.com"
63+
components.path = "/2/files/get_metadata"
64+
65+
var request = URLRequest(url: components.url!)
66+
request.httpMethod = "POST"
67+
request.setValue(
68+
"\(credentials.tokenType) \(credentials.accessToken)",
69+
forHTTPHeaderField: "Authorization"
70+
)
71+
request.setValue(
72+
"application/json",
73+
forHTTPHeaderField: "Content-Type"
74+
)
75+
request.httpBody = try JSONEncoder.api.encode(params)
76+
77+
return request
78+
}()
79+
80+
let (responseData, response) = try await httpClient.data(for: request)
81+
let statusCode = (response as? HTTPURLResponse)?.statusCode
82+
83+
guard let statusCode, (200..<300).contains(statusCode) else {
84+
throw Error.response(statusCode: statusCode, data: responseData)
85+
}
86+
87+
let metadata = try JSONDecoder.api.decode(Metadata.self, from: responseData)
88+
return metadata
89+
}
90+
}
91+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import XCTest
2+
@testable import DropboxClient
3+
4+
final class GetMetadataTests: XCTestCase {
5+
func testGetMetadata() async throws {
6+
let params = GetMetadata.Params(
7+
path: "/Prime_Numbers.txt",
8+
includeMediaInfo: true,
9+
includeDeleted: false
10+
)
11+
let credentials = Credentials.test
12+
let didRefreshToken = ActorIsolated(0)
13+
let httpRequests = ActorIsolated<[URLRequest]>([])
14+
let getMetadata = GetMetadata.live(
15+
auth: {
16+
var auth = Auth.unimplemented()
17+
auth.refreshToken = {
18+
await didRefreshToken.withValue { $0 += 1 }
19+
}
20+
return auth
21+
}(),
22+
keychain: {
23+
var keychain = Keychain.unimplemented()
24+
keychain.loadCredentials = { credentials }
25+
return keychain
26+
}(),
27+
httpClient: .init { request in
28+
await httpRequests.withValue { $0.append(request) }
29+
return (
30+
"""
31+
{
32+
".tag": "file",
33+
"id": "id:a4ayc_80_OEAAAAAAAAAXw",
34+
"name": "Prime_Numbers.txt",
35+
"path_display": "/Homework/math/Prime_Numbers.txt",
36+
"path_lower": "/homework/math/prime_numbers.txt",
37+
"client_modified": "2023-07-06T15:50:38Z",
38+
"server_modified": "2023-07-06T22:10:00Z",
39+
"is_downloadable": true
40+
}
41+
""".data(using: .utf8)!,
42+
HTTPURLResponse(
43+
url: URL(filePath: "/"),
44+
statusCode: 200,
45+
httpVersion: nil,
46+
headerFields: nil
47+
)!
48+
)
49+
}
50+
)
51+
52+
let result = try await getMetadata(params)
53+
54+
await didRefreshToken.withValue {
55+
XCTAssertEqual($0, 1)
56+
}
57+
try await httpRequests.withValue {
58+
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
59+
var expectedRequest = URLRequest(url: url)
60+
expectedRequest.httpMethod = "POST"
61+
expectedRequest.allHTTPHeaderFields = [
62+
"Authorization": "\(credentials.tokenType) \(credentials.accessToken)",
63+
"Content-Type": "application/json"
64+
]
65+
expectedRequest.httpBody = try JSONEncoder.api.encode(params)
66+
67+
XCTAssertEqual($0, [expectedRequest])
68+
XCTAssertEqual($0.first?.httpBody, expectedRequest.httpBody!)
69+
}
70+
XCTAssertEqual(result, Metadata(
71+
tag: .file,
72+
id: "id:a4ayc_80_OEAAAAAAAAAXw",
73+
name: "Prime_Numbers.txt",
74+
pathDisplay: "/Homework/math/Prime_Numbers.txt",
75+
pathLower: "/homework/math/prime_numbers.txt",
76+
clientModified: Calendar(identifier: .gregorian)
77+
.date(from: DateComponents(
78+
timeZone: TimeZone(secondsFromGMT: 0),
79+
year: 2023, month: 7, day: 6,
80+
hour: 15, minute: 50, second: 38
81+
))!,
82+
serverModified: Calendar(identifier: .gregorian)
83+
.date(from: DateComponents(
84+
timeZone: TimeZone(secondsFromGMT: 0),
85+
year: 2023, month: 7, day: 6,
86+
hour: 22, minute: 10
87+
))!,
88+
isDownloadable: true
89+
))
90+
}
91+
92+
func testGetMetadataErrorResponse() async {
93+
let getMetadata = GetMetadata.live(
94+
auth: {
95+
var auth = Auth.unimplemented()
96+
auth.refreshToken = {}
97+
return auth
98+
}(),
99+
keychain: {
100+
var keychain = Keychain.unimplemented()
101+
keychain.loadCredentials = { .test }
102+
return keychain
103+
}(),
104+
httpClient: .init { request in
105+
(
106+
"Error!!!".data(using: .utf8)!,
107+
HTTPURLResponse(
108+
url: URL(filePath: "/"),
109+
statusCode: 500,
110+
httpVersion: nil,
111+
headerFields: nil
112+
)!
113+
)
114+
}
115+
)
116+
117+
do {
118+
_ = try await getMetadata(path: "/test.txt")
119+
XCTFail("Expected to throw, but didn't")
120+
} catch {
121+
XCTAssertEqual(
122+
error as? GetMetadata.Error,
123+
.response(
124+
statusCode: 500,
125+
data: "Error!!!".data(using: .utf8)!
126+
),
127+
"Expected to throw response error, got \(error)"
128+
)
129+
}
130+
}
131+
132+
func testGetMetadataWhenNotAuthorized() async {
133+
let getMetadata = GetMetadata.live(
134+
auth: {
135+
var auth = Auth.unimplemented()
136+
auth.refreshToken = {}
137+
return auth
138+
}(),
139+
keychain: {
140+
var keychain = Keychain.unimplemented()
141+
keychain.loadCredentials = { nil }
142+
return keychain
143+
}(),
144+
httpClient: .unimplemented()
145+
)
146+
147+
do {
148+
_ = try await getMetadata(path: "/test.txt")
149+
XCTFail("Expected to throw, but didn't")
150+
} catch {
151+
XCTAssertEqual(
152+
error as? GetMetadata.Error, .notAuthorized,
153+
"Expected to throw .notAuthorized, got \(error)"
154+
)
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)