11import XCTest
22@testable import LDSwiftEventSource
33
4+ #if os(Linux)
5+ import FoundationNetworking
6+ #endif
7+
48final class LDSwiftEventSourceTests : XCTestCase {
9+ private var mockHandler : MockHandler !
10+
11+ override func setUp( ) {
12+ super. setUp ( )
13+ mockHandler = MockHandler ( )
14+ XCTAssertTrue ( URLProtocol . registerClass ( MockingProtocol . self) )
15+ }
16+
17+ override func tearDown( ) {
18+ super. tearDown ( )
19+ URLProtocol . unregisterClass ( MockingProtocol . self)
20+ // Enforce that tests consume all mocked network requests
21+ MockingProtocol . requested. expectNoEvent ( within: 0.01 )
22+ MockingProtocol . resetRequested ( )
23+ // Enforce that tests consume all calls to the mock handler
24+ mockHandler. events. expectNoEvent ( within: 0.01 )
25+ mockHandler = nil
26+ }
27+
528 func testConfigDefaults( ) {
629 let url = URL ( string: " abc " ) !
7- let config = EventSource . Config ( handler: MockHandler ( ) , url: url)
30+ let config = EventSource . Config ( handler: mockHandler , url: url)
831 XCTAssertEqual ( config. url, url)
932 XCTAssertEqual ( config. method, " GET " )
1033 XCTAssertEqual ( config. body, nil )
@@ -20,7 +43,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
2043
2144 func testConfigModification( ) {
2245 let url = URL ( string: " abc " ) !
23- var config = EventSource . Config ( handler: MockHandler ( ) , url: url)
46+ var config = EventSource . Config ( handler: mockHandler , url: url)
2447
2548 let testBody = " test data " . data ( using: . utf8)
2649 let testHeaders = [ " Authorization " : " basic abc " ]
@@ -50,7 +73,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
5073 }
5174
5275 func testConfigUrlSession( ) {
53- var config = EventSource . Config ( handler: MockHandler ( ) , url: URL ( string: " abc " ) !)
76+ var config = EventSource . Config ( handler: mockHandler , url: URL ( string: " abc " ) !)
5477 let defaultSessionConfig = config. urlSessionConfiguration
5578 XCTAssertEqual ( defaultSessionConfig. timeoutIntervalForRequest, 300.0 )
5679 XCTAssertEqual ( defaultSessionConfig. httpAdditionalHeaders ? [ " Accept " ] as? String , " text/event-stream " )
@@ -71,7 +94,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
7194 }
7295
7396 func testLastEventIdFromConfig( ) {
74- var config = EventSource . Config ( handler: MockHandler ( ) , url: URL ( string: " abc " ) !)
97+ var config = EventSource . Config ( handler: mockHandler , url: URL ( string: " abc " ) !)
7598 var es = EventSource ( config: config)
7699 XCTAssertEqual ( es. getLastEventId ( ) , nil )
77100 config. lastEventId = " def "
@@ -80,7 +103,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
80103 }
81104
82105 func testCreatedSession( ) {
83- let config = EventSource . Config ( handler: MockHandler ( ) , url: URL ( string: " abc " ) !)
106+ let config = EventSource . Config ( handler: mockHandler , url: URL ( string: " abc " ) !)
84107 let session = EventSourceDelegate ( config: config) . createSession ( )
85108 XCTAssertEqual ( session. configuration. timeoutIntervalForRequest, config. idleTimeout)
86109 XCTAssertEqual ( session. configuration. httpAdditionalHeaders ? [ " Accept " ] as? String , " text/event-stream " )
@@ -89,7 +112,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
89112
90113 func testCreateRequest( ) {
91114 // 192.0.2.1 is assigned as TEST-NET-1 reserved usage.
92- var config = EventSource . Config ( handler: MockHandler ( ) , url: URL ( string: " http://192.0.2.1 " ) !)
115+ var config = EventSource . Config ( handler: mockHandler , url: URL ( string: " http://192.0.2.1 " ) !)
93116 // Testing default configs
94117 var request = EventSourceDelegate ( config: config) . createRequest ( )
95118 XCTAssertEqual ( request. url, config. url)
@@ -119,28 +142,203 @@ final class LDSwiftEventSourceTests: XCTestCase {
119142 }
120143
121144 func testDispatchError( ) {
122- let handler = MockHandler ( )
123145 var connectionErrorHandlerCallCount = 0
124146 var connectionErrorAction : ConnectionErrorAction = . proceed
125- var config = EventSource . Config ( handler: handler , url: URL ( string: " abc " ) !)
147+ var config = EventSource . Config ( handler: mockHandler , url: URL ( string: " abc " ) !)
126148 config. connectionErrorHandler = { _ in
127149 connectionErrorHandlerCallCount += 1
128150 return connectionErrorAction
129151 }
130152 let es = EventSourceDelegate ( config: config)
131153 XCTAssertEqual ( es. dispatchError ( error: DummyError ( ) ) , . proceed)
132154 XCTAssertEqual ( connectionErrorHandlerCallCount, 1 )
133- guard case . error( let err) = handler . takeEvent ( ) , err is DummyError
155+ guard case . error( let err) = mockHandler . events . expectEvent ( ) , err is DummyError
134156 else {
135157 XCTFail ( " handler should receive error if EventSource is not shutting down " )
136158 return
137159 }
138- XCTAssertTrue ( handler . receivedEvents . isEmpty )
160+ mockHandler . events . expectNoEvent ( )
139161 connectionErrorAction = . shutdown
140162 XCTAssertEqual ( es. dispatchError ( error: DummyError ( ) ) , . shutdown)
141163 XCTAssertEqual ( connectionErrorHandlerCallCount, 2 )
142- XCTAssertTrue ( handler. receivedEvents. isEmpty)
143164 }
165+
166+ func sessionWithMockProtocol( ) -> URLSessionConfiguration {
167+ let sessionConfig = URLSessionConfiguration . default
168+ sessionConfig. protocolClasses = [ MockingProtocol . self] + ( sessionConfig. protocolClasses ?? [ ] )
169+ return sessionConfig
170+ }
171+
172+ #if !os(Linux)
173+ func testStartDefaultRequest( ) {
174+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
175+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
176+ let es = EventSource ( config: config)
177+ es. start ( )
178+ let handler = MockingProtocol . requested. expectEvent ( )
179+ XCTAssertEqual ( handler. request. url, config. url)
180+ XCTAssertEqual ( handler. request. httpMethod, config. method)
181+ XCTAssertEqual ( handler. request. httpBody, config. body)
182+ XCTAssertEqual ( handler. request. timeoutInterval, config. idleTimeout)
183+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " Accept " ] , " text/event-stream " )
184+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " Cache-Control " ] , " no-cache " )
185+ XCTAssertNil ( handler. request. allHTTPHeaderFields ? [ " Last-Event-Id " ] )
186+ es. stop ( )
187+ }
188+
189+ func testStartRequestWithConfiguration( ) {
190+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
191+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
192+ config. method = " REPORT "
193+ config. body = Data ( " test body " . utf8)
194+ config. idleTimeout = 500.0
195+ config. lastEventId = " abc "
196+ config. headers = [ " X-LD-Header " : " def " ]
197+ let es = EventSource ( config: config)
198+ es. start ( )
199+ let handler = MockingProtocol . requested. expectEvent ( )
200+ XCTAssertEqual ( handler. request. url, config. url)
201+ XCTAssertEqual ( handler. request. httpMethod, config. method)
202+ XCTAssertEqual ( handler. request. bodyStreamAsData ( ) , config. body)
203+ XCTAssertEqual ( handler. request. timeoutInterval, config. idleTimeout)
204+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " Accept " ] , " text/event-stream " )
205+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " Cache-Control " ] , " no-cache " )
206+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " Last-Event-Id " ] , config. lastEventId)
207+ XCTAssertEqual ( handler. request. allHTTPHeaderFields ? [ " X-LD-Header " ] , " def " )
208+ es. stop ( )
209+ }
210+
211+ func testSuccessfulResponseOpens( ) {
212+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
213+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
214+ let es = EventSource ( config: config)
215+ es. start ( )
216+ let handler = MockingProtocol . requested. expectEvent ( )
217+ handler. respond ( statusCode: 200 )
218+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . opened)
219+ es. stop ( )
220+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . closed)
221+ }
222+
223+ func testLastEventIdUpdatedByEvents( ) {
224+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
225+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
226+ config. reconnectTime = 0.1
227+ let es = EventSource ( config: config)
228+ es. start ( )
229+ let handler = MockingProtocol . requested. expectEvent ( )
230+ handler. respond ( statusCode: 200 )
231+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . opened)
232+ XCTAssertNil ( es. getLastEventId ( ) )
233+ handler. respond ( didLoad: " id: abc \n \n " )
234+ // Comment used for synchronization
235+ handler. respond ( didLoad: " :comment \n " )
236+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . comment( " comment " ) )
237+ XCTAssertEqual ( es. getLastEventId ( ) , " abc " )
238+ handler. finish ( )
239+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . closed)
240+ // Expect to reconnect and include new event id
241+ let reconnectHandler = MockingProtocol . requested. expectEvent ( )
242+ XCTAssertEqual ( reconnectHandler. request. allHTTPHeaderFields ? [ " Last-Event-Id " ] , " abc " )
243+ es. stop ( )
244+ }
245+
246+ func testUsesRetryTime( ) {
247+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
248+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
249+ // Long enough to cause a timeout if the retry time is not updated
250+ config. reconnectTime = 5
251+ let es = EventSource ( config: config)
252+ es. start ( )
253+ let handler = MockingProtocol . requested. expectEvent ( )
254+ handler. respond ( statusCode: 200 )
255+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . opened)
256+ handler. respond ( didLoad: " retry: 100 \n \n " )
257+ handler. finish ( )
258+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . closed)
259+ // Expect to reconnect before this times out
260+ _ = MockingProtocol . requested. expectEvent ( )
261+ es. stop ( )
262+ }
263+
264+ func testCallsHandlerWithMessage( ) {
265+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
266+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
267+ let es = EventSource ( config: config)
268+ es. start ( )
269+ let handler = MockingProtocol . requested. expectEvent ( )
270+ handler. respond ( statusCode: 200 )
271+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . opened)
272+ handler. respond ( didLoad: " event: custom \n data: {} \n \n " )
273+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . message( " custom " , MessageEvent ( data: " {} " , lastEventId: nil ) ) )
274+ es. stop ( )
275+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . closed)
276+ }
277+
278+ func testRetryOnInvalidResponseCode( ) {
279+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
280+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
281+ config. reconnectTime = 0.1
282+ let es = EventSource ( config: config)
283+ es. start ( )
284+ let handler = MockingProtocol . requested. expectEvent ( )
285+ handler. respond ( statusCode: 400 )
286+ guard case let . error( err) = mockHandler. events. expectEvent ( ) ,
287+ let responseErr = err as? UnsuccessfulResponseError
288+ else {
289+ XCTFail ( " Expected UnsuccessfulResponseError to be given to handler " )
290+ return
291+ }
292+ XCTAssertEqual ( responseErr. responseCode, 400 )
293+ // Expect the client to reconnect
294+ _ = MockingProtocol . requested. expectEvent ( )
295+ es. stop ( )
296+ }
297+
298+ func testShutdownByErrorHandlerOnInitialErrorResponse( ) {
299+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
300+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
301+ config. reconnectTime = 0.1
302+ config. connectionErrorHandler = { err in
303+ if let responseErr = err as? UnsuccessfulResponseError {
304+ XCTAssertEqual ( responseErr. responseCode, 400 )
305+ } else {
306+ XCTFail ( " Expected UnsuccessfulResponseError to be given to handler " )
307+ }
308+ return . shutdown
309+ }
310+ let es = EventSource ( config: config)
311+ es. start ( )
312+ let handler = MockingProtocol . requested. expectEvent ( )
313+ handler. respond ( statusCode: 400 )
314+ // Expect the client not to reconnect
315+ MockingProtocol . requested. expectNoEvent ( within: 1.0 )
316+ es. stop ( )
317+ // Error should not have been given to the handler
318+ mockHandler. events. expectNoEvent ( )
319+ }
320+
321+ func testShutdownByErrorHandlerOnResponseCompletionError( ) {
322+ var config = EventSource . Config ( handler: mockHandler, url: URL ( string: " http://example.com " ) !)
323+ config. urlSessionConfiguration = sessionWithMockProtocol ( )
324+ config. reconnectTime = 0.1
325+ config. connectionErrorHandler = { _ in
326+ return . shutdown
327+ }
328+ let es = EventSource ( config: config)
329+ es. start ( )
330+ let handler = MockingProtocol . requested. expectEvent ( )
331+ handler. respond ( statusCode: 200 )
332+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . opened)
333+ handler. finishWith ( error: DummyError ( ) )
334+ XCTAssertEqual ( mockHandler. events. expectEvent ( ) , . closed)
335+ // Expect the client not to reconnect
336+ MockingProtocol . requested. expectNoEvent ( within: 1.0 )
337+ es. stop ( )
338+ // Error should not have been given to the handler
339+ mockHandler. events. expectNoEvent ( )
340+ }
341+ #endif
144342}
145343
146344private enum ReceivedEvent : Equatable {
@@ -165,31 +363,13 @@ private enum ReceivedEvent: Equatable {
165363}
166364
167365private class MockHandler : EventHandler {
168- var receivedEvents : [ ReceivedEvent ] = [ ]
366+ var events = EventSink < ReceivedEvent > ( )
169367
170- func onOpened( ) {
171- receivedEvents. append ( . opened)
172- }
173-
174- func onClosed( ) {
175- receivedEvents. append ( . closed)
176- }
177-
178- func onMessage( eventType: String , messageEvent: MessageEvent ) {
179- receivedEvents. append ( . message( eventType, messageEvent) )
180- }
181-
182- func onComment( comment: String ) {
183- receivedEvents. append ( . comment( comment) )
184- }
185-
186- func onError( error: Error ) {
187- receivedEvents. append ( . error( error) )
188- }
189-
190- func takeEvent( ) -> ReceivedEvent {
191- receivedEvents. remove ( at: 0 )
192- }
368+ func onOpened( ) { events. record ( . opened) }
369+ func onClosed( ) { events. record ( . closed) }
370+ func onMessage( eventType: String , messageEvent: MessageEvent ) { events. record ( . message( eventType, messageEvent) ) }
371+ func onComment( comment: String ) { events. record ( . comment( comment) ) }
372+ func onError( error: Error ) { events. record ( . error( error) ) }
193373}
194374
195375private class DummyError : Error { }
0 commit comments