@@ -18,6 +18,7 @@ import FoundationNetworking
18
18
#endif
19
19
20
20
class DownloaderTests : XCTestCase {
21
+
21
22
func testSuccess( ) {
22
23
// FIXME: Remove once https://github.com/apple/swift-corelibs-foundation/pull/2593 gets inside a toolchain.
23
24
#if os(macOS)
@@ -72,6 +73,140 @@ class DownloaderTests: XCTestCase {
72
73
}
73
74
#endif
74
75
}
76
+
77
+ #if os(macOS)
78
+ @available ( OSX 10 . 13 , * )
79
+ /// Netrc feature depends upon `NSTextCheckingResult.range(withName name: String) -> NSRange`,
80
+ /// which is only available in macOS 10.13+ at this time.
81
+ func testAuthenticatedSuccess( ) {
82
+ let netrcContent = " machine protected.downloader-tests.com login anonymous password qwerty "
83
+ guard case . success( let netrc) = Netrc . from ( netrcContent) else {
84
+ return XCTFail ( " Cannot load netrc content " )
85
+ }
86
+ let authData = " anonymous:qwerty " . data ( using: . utf8) !
87
+ let testAuthHeader = " Basic \( authData. base64EncodedString ( ) ) "
88
+
89
+ let configuration = URLSessionConfiguration . default
90
+ configuration. protocolClasses = [ MockAuthenticatingURLProtocol . self]
91
+ let downloader = FoundationDownloader ( configuration: configuration)
92
+
93
+ mktmpdir { tmpdir in
94
+ let url = URL ( string: " https://protected.downloader-tests.com/testBasics.zip " ) !
95
+ let destination = tmpdir. appending ( component: " download " )
96
+
97
+ let didStartLoadingExpectation = XCTestExpectation ( description: " didStartLoading " )
98
+ let progress50Expectation = XCTestExpectation ( description: " progress50 " )
99
+ let progress100Expectation = XCTestExpectation ( description: " progress100 " )
100
+ let successExpectation = XCTestExpectation ( description: " success " )
101
+ MockAuthenticatingURLProtocol . notifyDidStartLoading ( for: url, completion: { didStartLoadingExpectation. fulfill ( ) } )
102
+
103
+ downloader. downloadFile ( at: url, to: destination, withAuthorizationProvider: netrc, progress: { bytesDownloaded, totalBytesToDownload in
104
+
105
+ XCTAssertEqual ( MockAuthenticatingURLProtocol . authenticationHeader ( for: url) , testAuthHeader)
106
+
107
+ switch ( bytesDownloaded, totalBytesToDownload) {
108
+ case ( 512 , 1024 ) :
109
+ progress50Expectation. fulfill ( )
110
+ case ( 1024 , 1024 ) :
111
+ progress100Expectation. fulfill ( )
112
+ default :
113
+ XCTFail ( " unexpected progress " )
114
+ }
115
+ } , completion: { result in
116
+ switch result {
117
+ case . success:
118
+ XCTAssert ( localFileSystem. exists ( destination) )
119
+ let bytes = ByteString ( Array ( repeating: 0xbe , count: 512 ) + Array( repeating: 0xef , count: 512 ) )
120
+ XCTAssertEqual ( try ! localFileSystem. readFileContents ( destination) , bytes)
121
+ successExpectation. fulfill ( )
122
+ case . failure( let error) :
123
+ XCTFail ( " \( error) " )
124
+ }
125
+ } )
126
+
127
+ wait ( for: [ didStartLoadingExpectation] , timeout: 1.0 )
128
+
129
+ let response = HTTPURLResponse ( url: url, statusCode: 200 , httpVersion: " 1.1 " , headerFields: [
130
+ " Content-Length " : " 1024 "
131
+ ] ) !
132
+
133
+ MockAuthenticatingURLProtocol . sendResponse ( response, for: url)
134
+ MockAuthenticatingURLProtocol . sendData ( Data ( repeating: 0xbe , count: 512 ) , for: url)
135
+ wait ( for: [ progress50Expectation] , timeout: 1.0 )
136
+ MockAuthenticatingURLProtocol . sendData ( Data ( repeating: 0xef , count: 512 ) , for: url)
137
+ wait ( for: [ progress100Expectation] , timeout: 1.0 )
138
+ MockAuthenticatingURLProtocol . sendCompletion ( for: url)
139
+ wait ( for: [ successExpectation] , timeout: 1.0 )
140
+ }
141
+ }
142
+ #endif
143
+
144
+ #if os(macOS)
145
+ @available ( OSX 10 . 13 , * )
146
+ /// Netrc feature depends upon `NSTextCheckingResult.range(withName name: String) -> NSRange`,
147
+ /// which is only available in macOS 10.13+ at this time.
148
+ func testDefaultAuthenticationSuccess( ) {
149
+ let netrcContent = " default login default password default "
150
+ guard case . success( let netrc) = Netrc . from ( netrcContent) else {
151
+ return XCTFail ( " Cannot load netrc content " )
152
+ }
153
+ let authData = " default:default " . data ( using: . utf8) !
154
+ let testAuthHeader = " Basic \( authData. base64EncodedString ( ) ) "
155
+
156
+ let configuration = URLSessionConfiguration . default
157
+ configuration. protocolClasses = [ MockAuthenticatingURLProtocol . self]
158
+ let downloader = FoundationDownloader ( configuration: configuration)
159
+
160
+ mktmpdir { tmpdir in
161
+ let url = URL ( string: " https://restricted.downloader-tests.com/testBasics.zip " ) !
162
+ let destination = tmpdir. appending ( component: " download " )
163
+
164
+ let didStartLoadingExpectation = XCTestExpectation ( description: " didStartLoading " )
165
+ let progress50Expectation = XCTestExpectation ( description: " progress50 " )
166
+ let progress100Expectation = XCTestExpectation ( description: " progress100 " )
167
+ let successExpectation = XCTestExpectation ( description: " success " )
168
+ MockAuthenticatingURLProtocol . notifyDidStartLoading ( for: url, completion: { didStartLoadingExpectation. fulfill ( ) } )
169
+
170
+ downloader. downloadFile ( at: url, to: destination, withAuthorizationProvider: netrc, progress: { bytesDownloaded, totalBytesToDownload in
171
+
172
+ XCTAssertEqual ( MockAuthenticatingURLProtocol . authenticationHeader ( for: url) , testAuthHeader)
173
+
174
+ switch ( bytesDownloaded, totalBytesToDownload) {
175
+ case ( 512 , 1024 ) :
176
+ progress50Expectation. fulfill ( )
177
+ case ( 1024 , 1024 ) :
178
+ progress100Expectation. fulfill ( )
179
+ default :
180
+ XCTFail ( " unexpected progress " )
181
+ }
182
+ } , completion: { result in
183
+ switch result {
184
+ case . success:
185
+ XCTAssert ( localFileSystem. exists ( destination) )
186
+ let bytes = ByteString ( Array ( repeating: 0xbe , count: 512 ) + Array( repeating: 0xef , count: 512 ) )
187
+ XCTAssertEqual ( try ! localFileSystem. readFileContents ( destination) , bytes)
188
+ successExpectation. fulfill ( )
189
+ case . failure( let error) :
190
+ XCTFail ( " \( error) " )
191
+ }
192
+ } )
193
+
194
+ wait ( for: [ didStartLoadingExpectation] , timeout: 1.0 )
195
+
196
+ let response = HTTPURLResponse ( url: url, statusCode: 200 , httpVersion: " 1.1 " , headerFields: [
197
+ " Content-Length " : " 1024 "
198
+ ] ) !
199
+
200
+ MockAuthenticatingURLProtocol . sendResponse ( response, for: url)
201
+ MockAuthenticatingURLProtocol . sendData ( Data ( repeating: 0xbe , count: 512 ) , for: url)
202
+ wait ( for: [ progress50Expectation] , timeout: 1.0 )
203
+ MockAuthenticatingURLProtocol . sendData ( Data ( repeating: 0xef , count: 512 ) , for: url)
204
+ wait ( for: [ progress100Expectation] , timeout: 1.0 )
205
+ MockAuthenticatingURLProtocol . sendCompletion ( for: url)
206
+ wait ( for: [ successExpectation] , timeout: 1.0 )
207
+ }
208
+ }
209
+ #endif
75
210
76
211
func testClientError( ) {
77
212
// FIXME: Remove once https://github.com/apple/swift-corelibs-foundation/pull/2593 gets inside a toolchain.
@@ -208,6 +343,16 @@ private struct DummyError: Error {
208
343
209
344
private typealias Action = ( ) -> Void
210
345
346
+ private class MockAuthenticatingURLProtocol : MockURLProtocol {
347
+
348
+ fileprivate static func authenticationHeader( for url: Foundation . URL ) -> String ? {
349
+ guard let instance = instance ( for: url) else {
350
+ fatalError ( " url did not start loading " )
351
+ }
352
+ return instance. request. allHTTPHeaderFields ? [ " Authorization " ]
353
+ }
354
+ }
355
+
211
356
private class MockURLProtocol : URLProtocol {
212
357
private static var queue = DispatchQueue ( label: " org.swift.swiftpm.basic-tests.mock-url-protocol " )
213
358
private static var observers : [ Foundation . URL : Action ] = [ : ]
@@ -309,6 +454,10 @@ private class MockURLProtocol: URLProtocol {
309
454
Self . instances [ url] = nil
310
455
}
311
456
}
457
+
458
+ fileprivate static func instance( for url: Foundation . URL ) -> URLProtocol ? {
459
+ return Self . instances [ url]
460
+ }
312
461
}
313
462
314
463
class FailingFileSystem : FileSystem {
0 commit comments