WebSocketTests.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. //
  2. // WebSocketTests.swift
  3. // Alamofire
  4. //
  5. // Created by Jon Shier on 1/17/21.
  6. // Copyright © 2021 Alamofire. All rights reserved.
  7. //
  8. #if canImport(Darwin) && !canImport(FoundationNetworking)
  9. import Alamofire
  10. import Foundation
  11. import XCTest
  12. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  13. final class WebSocketTests: BaseTestCase {
  14. func testThatWebSocketsCanReceiveMessageEvents() {
  15. // Given
  16. let didConnect = expectation(description: "didConnect")
  17. let didReceiveMessage = expectation(description: "didReceiveMessage")
  18. let didDisconnect = expectation(description: "didDisconnect")
  19. let didComplete = expectation(description: "didComplete")
  20. let session = stored(Session())
  21. var connectedProtocol: String?
  22. var message: URLSessionWebSocketTask.Message?
  23. var closeCode: URLSessionWebSocketTask.CloseCode?
  24. var closeReason: Data?
  25. var receivedCompletion: WebSocketRequest.Completion?
  26. // When
  27. session.websocketRequest(.websocket()).streamMessageEvents { event in
  28. switch event.kind {
  29. case let .connected(`protocol`):
  30. connectedProtocol = `protocol`
  31. didConnect.fulfill()
  32. case let .receivedMessage(receivedMessage):
  33. message = receivedMessage
  34. didReceiveMessage.fulfill()
  35. case let .disconnected(code, reason):
  36. closeCode = code
  37. closeReason = reason
  38. didDisconnect.fulfill()
  39. case let .completed(completion):
  40. receivedCompletion = completion
  41. didComplete.fulfill()
  42. }
  43. }
  44. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  45. timeout: timeout,
  46. enforceOrder: true)
  47. // Then
  48. XCTAssertNil(connectedProtocol)
  49. XCTAssertNotNil(message)
  50. XCTAssertEqual(closeCode, .normalClosure)
  51. XCTAssertNil(closeReason)
  52. XCTAssertNil(receivedCompletion?.error)
  53. }
  54. func testThatWebSocketsCanReceiveAMessage() {
  55. // Given
  56. let didReceiveMessage = expectation(description: "didReceiveMessage")
  57. let session = stored(Session())
  58. var receivedMessage: URLSessionWebSocketTask.Message?
  59. // When
  60. session.websocketRequest(.websocket()).streamMessages { message in
  61. receivedMessage = message
  62. didReceiveMessage.fulfill()
  63. }
  64. waitForExpectations(timeout: timeout)
  65. // Then
  66. XCTAssertNotNil(receivedMessage)
  67. XCTAssertNotNil(receivedMessage?.data)
  68. }
  69. func testThatWebSocketsCanReceiveADecodableMessage() {
  70. // Given
  71. let didConnect = expectation(description: "didConnect")
  72. let didReceiveMessage = expectation(description: "didReceiveMessage")
  73. let didDisconnect = expectation(description: "didDisconnect")
  74. let didComplete = expectation(description: "didComplete")
  75. let session = stored(Session())
  76. var connectedProtocol: String?
  77. var message: TestResponse?
  78. var closeCode: URLSessionWebSocketTask.CloseCode?
  79. var closeReason: Data?
  80. var receivedCompletion: WebSocketRequest.Completion?
  81. // When
  82. session.websocketRequest(.websocketCount(1)).streamDecodableEvents(TestResponse.self) { event in
  83. switch event.kind {
  84. case let .connected(`protocol`):
  85. connectedProtocol = `protocol`
  86. didConnect.fulfill()
  87. case let .receivedMessage(receivedMessage):
  88. message = receivedMessage
  89. didReceiveMessage.fulfill()
  90. case let .serializerFailed(error):
  91. XCTFail("websocket message serialization failed with error: \(error)")
  92. case let .disconnected(code, reason):
  93. closeCode = code
  94. closeReason = reason
  95. didDisconnect.fulfill()
  96. case let .completed(completion):
  97. receivedCompletion = completion
  98. didComplete.fulfill()
  99. }
  100. }
  101. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  102. timeout: timeout,
  103. enforceOrder: true)
  104. // Then
  105. XCTAssertNil(connectedProtocol)
  106. XCTAssertNotNil(message)
  107. XCTAssertEqual(closeCode, .normalClosure)
  108. XCTAssertNil(closeReason)
  109. XCTAssertNil(receivedCompletion?.error)
  110. }
  111. func testThatWebSocketsCanReceiveADecodableValue() {
  112. // Given
  113. let didReceiveValue = expectation(description: "didReceiveMessage")
  114. let session = stored(Session())
  115. var receivedValue: TestResponse?
  116. // When
  117. session.websocketRequest(.websocket()).streamDecodable(TestResponse.self) { value in
  118. receivedValue = value
  119. didReceiveValue.fulfill()
  120. }
  121. waitForExpectations(timeout: timeout)
  122. // Then
  123. XCTAssertNotNil(receivedValue)
  124. }
  125. func testThatWebSocketsCanReceiveAMessageWithAProtocol() {
  126. // Given
  127. let didConnect = expectation(description: "didConnect")
  128. let didReceiveMessage = expectation(description: "didReceiveMessage")
  129. let didDisconnect = expectation(description: "didDisconnect")
  130. let didComplete = expectation(description: "didComplete")
  131. let session = stored(Session())
  132. let `protocol` = "protocol"
  133. var connectedProtocol: String?
  134. var message: URLSessionWebSocketTask.Message?
  135. var closeCode: URLSessionWebSocketTask.CloseCode?
  136. var closeReason: Data?
  137. var receivedCompletion: WebSocketRequest.Completion?
  138. // When
  139. session.websocketRequest(.websocket(), protocol: `protocol`).streamMessageEvents { event in
  140. switch event.kind {
  141. case let .connected(`protocol`):
  142. connectedProtocol = `protocol`
  143. didConnect.fulfill()
  144. case let .receivedMessage(receivedMessage):
  145. message = receivedMessage
  146. didReceiveMessage.fulfill()
  147. case let .disconnected(code, reason):
  148. closeCode = code
  149. closeReason = reason
  150. didDisconnect.fulfill()
  151. case let .completed(completion):
  152. receivedCompletion = completion
  153. didComplete.fulfill()
  154. }
  155. }
  156. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  157. timeout: timeout,
  158. enforceOrder: true)
  159. // Then
  160. XCTAssertEqual(connectedProtocol, `protocol`)
  161. XCTAssertNotNil(message)
  162. XCTAssertEqual(closeCode, .normalClosure)
  163. XCTAssertNil(closeReason)
  164. XCTAssertNil(receivedCompletion?.error)
  165. }
  166. func testThatWebSocketsCanReceiveMultipleMessages() {
  167. // Given
  168. let count = 5
  169. let didConnect = expectation(description: "didConnect")
  170. let didReceiveMessage = expectation(description: "didReceiveMessage")
  171. didReceiveMessage.expectedFulfillmentCount = count
  172. let didDisconnect = expectation(description: "didDisconnect")
  173. let didComplete = expectation(description: "didComplete")
  174. let session = stored(Session())
  175. var connectedProtocol: String?
  176. var messages: [URLSessionWebSocketTask.Message] = []
  177. var closeCode: URLSessionWebSocketTask.CloseCode?
  178. var closeReason: Data?
  179. var receivedCompletion: WebSocketRequest.Completion?
  180. // When
  181. session.websocketRequest(.websocketCount(count)).streamMessageEvents { event in
  182. switch event.kind {
  183. case let .connected(`protocol`):
  184. connectedProtocol = `protocol`
  185. didConnect.fulfill()
  186. case let .receivedMessage(receivedMessage):
  187. messages.append(receivedMessage)
  188. didReceiveMessage.fulfill()
  189. case let .disconnected(code, reason):
  190. closeCode = code
  191. closeReason = reason
  192. didDisconnect.fulfill()
  193. case let .completed(completion):
  194. receivedCompletion = completion
  195. didComplete.fulfill()
  196. }
  197. }
  198. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete], timeout: timeout, enforceOrder: true)
  199. // Then
  200. XCTAssertNil(connectedProtocol)
  201. XCTAssertEqual(messages.count, count)
  202. XCTAssertEqual(closeCode, .normalClosure)
  203. XCTAssertNil(closeReason)
  204. XCTAssertNil(receivedCompletion?.error)
  205. }
  206. func testThatWebSocketsCanSendAndReceiveMessages() {
  207. // Given
  208. let didConnect = expectation(description: "didConnect")
  209. let didSend = expectation(description: "didSend")
  210. let didReceiveMessage = expectation(description: "didReceiveMessage")
  211. let didDisconnect = expectation(description: "didDisconnect")
  212. let didComplete = expectation(description: "didComplete")
  213. let session = stored(Session())
  214. let sentMessage = URLSessionWebSocketTask.Message.string("Echo")
  215. var connectedProtocol: String?
  216. var message: URLSessionWebSocketTask.Message?
  217. var closeCode: URLSessionWebSocketTask.CloseCode?
  218. var closeReason: Data?
  219. var receivedCompletion: WebSocketRequest.Completion?
  220. // When
  221. let request = session.websocketRequest(.websocketEcho)
  222. request.streamMessageEvents { event in
  223. switch event.kind {
  224. case let .connected(`protocol`):
  225. connectedProtocol = `protocol`
  226. didConnect.fulfill()
  227. request.send(sentMessage) { _ in didSend.fulfill() }
  228. case let .receivedMessage(receivedMessage):
  229. message = receivedMessage
  230. event.close(sending: .normalClosure)
  231. didReceiveMessage.fulfill()
  232. case let .disconnected(code, reason):
  233. closeCode = code
  234. closeReason = reason
  235. didDisconnect.fulfill()
  236. case let .completed(completion):
  237. receivedCompletion = completion
  238. didComplete.fulfill()
  239. }
  240. }
  241. wait(for: [didConnect, didSend, didReceiveMessage, didDisconnect, didComplete],
  242. timeout: timeout,
  243. enforceOrder: true)
  244. // Then
  245. XCTAssertNil(connectedProtocol)
  246. XCTAssertNotNil(message)
  247. XCTAssertEqual(sentMessage, message)
  248. XCTAssertEqual(closeCode, .normalClosure)
  249. XCTAssertNil(closeReason)
  250. XCTAssertNil(receivedCompletion?.error)
  251. }
  252. func testThatWebSocketsCanBeCancelled() {
  253. // Given
  254. let didConnect = expectation(description: "didConnect")
  255. let didComplete = expectation(description: "didComplete")
  256. let session = stored(Session())
  257. var connectedProtocol: String?
  258. var receivedCompletion: WebSocketRequest.Completion?
  259. // When
  260. let request = session.websocketRequest(.websocketEcho)
  261. request.streamMessageEvents { event in
  262. switch event.kind {
  263. case let .connected(`protocol`):
  264. connectedProtocol = `protocol`
  265. didConnect.fulfill()
  266. request.cancel()
  267. case let .receivedMessage(receivedMessage):
  268. XCTFail("cancelled socket received message: \(receivedMessage)")
  269. case .disconnected:
  270. XCTFail("cancelled socket shouldn't receive disconnected event")
  271. case let .completed(completion):
  272. receivedCompletion = completion
  273. didComplete.fulfill()
  274. }
  275. }
  276. wait(for: [didConnect, didComplete], timeout: timeout, enforceOrder: true)
  277. // Then
  278. XCTAssertNil(connectedProtocol)
  279. XCTAssertTrue(receivedCompletion?.error?.isExplicitlyCancelledError == true)
  280. XCTAssertTrue(request.error?.isExplicitlyCancelledError == true)
  281. }
  282. func testOnePingOnly() {
  283. // Given
  284. let didConnect = expectation(description: "didConnect")
  285. let didSend = expectation(description: "didSend")
  286. let didReceiveMessage = expectation(description: "didReceiveMessage")
  287. let didReceivePong = expectation(description: "didReceivePong")
  288. didReceivePong.expectedFulfillmentCount = 100
  289. let didDisconnect = expectation(description: "didDisconnect")
  290. let didComplete = expectation(description: "didComplete")
  291. let session = stored(Session())
  292. let sentMessage = URLSessionWebSocketTask.Message.string("Echo")
  293. var connectedProtocol: String?
  294. var message: URLSessionWebSocketTask.Message?
  295. var receivedPong: WebSocketRequest.PingResponse.Pong?
  296. var closeCode: URLSessionWebSocketTask.CloseCode?
  297. var closeReason: Data?
  298. var receivedCompletion: WebSocketRequest.Completion?
  299. // When
  300. let request = session.websocketRequest(.websocketEcho)
  301. request.streamMessageEvents { event in
  302. switch event.kind {
  303. case let .connected(`protocol`):
  304. connectedProtocol = `protocol`
  305. didConnect.fulfill()
  306. request.send(sentMessage) { _ in didSend.fulfill() }
  307. case let .receivedMessage(receivedMessage):
  308. message = receivedMessage
  309. didReceiveMessage.fulfill()
  310. for count in 0..<100 {
  311. request.sendPing { response in
  312. switch response {
  313. case let .pong(pong):
  314. receivedPong = pong
  315. default:
  316. break
  317. }
  318. didReceivePong.fulfill()
  319. if count == 99 {
  320. request.close(sending: .normalClosure)
  321. }
  322. }
  323. }
  324. case let .disconnected(code, reason):
  325. closeCode = code
  326. closeReason = reason
  327. didDisconnect.fulfill()
  328. case let .completed(completion):
  329. receivedCompletion = completion
  330. didComplete.fulfill()
  331. }
  332. }
  333. wait(for: [didConnect, didSend, didReceiveMessage, didReceivePong, didDisconnect, didComplete],
  334. timeout: timeout,
  335. enforceOrder: true)
  336. // Then
  337. XCTAssertNil(connectedProtocol)
  338. XCTAssertNotNil(message)
  339. XCTAssertEqual(sentMessage, message)
  340. XCTAssertEqual(closeCode, .normalClosure)
  341. XCTAssertNil(closeReason)
  342. XCTAssertNotNil(receivedCompletion)
  343. XCTAssertNil(receivedCompletion?.error)
  344. XCTAssertNotNil(receivedPong)
  345. }
  346. func testThatTimePingsOccur() {
  347. // Given
  348. let didConnect = expectation(description: "didConnect")
  349. let didDisconnect = expectation(description: "didDisconnect")
  350. let didComplete = expectation(description: "didComplete")
  351. let session = stored(Session())
  352. var connectedProtocol: String?
  353. var closeCode: URLSessionWebSocketTask.CloseCode?
  354. var closeReason: Data?
  355. var receivedCompletion: WebSocketRequest.Completion?
  356. // When
  357. let request = session.websocketRequest(.websocketPings(), pingInterval: 0.01)
  358. request.streamMessageEvents { event in
  359. switch event.kind {
  360. case let .connected(`protocol`):
  361. connectedProtocol = `protocol`
  362. didConnect.fulfill()
  363. case .receivedMessage:
  364. break
  365. case let .disconnected(code, reason):
  366. closeCode = code
  367. closeReason = reason
  368. didDisconnect.fulfill()
  369. case let .completed(completion):
  370. receivedCompletion = completion
  371. didComplete.fulfill()
  372. }
  373. }
  374. wait(for: [didConnect, didDisconnect, didComplete], timeout: timeout, enforceOrder: true)
  375. // Then
  376. XCTAssertNil(connectedProtocol)
  377. XCTAssertEqual(closeCode, .goingAway) // Default Vapor close() code.
  378. XCTAssertNil(closeReason)
  379. XCTAssertNotNil(receivedCompletion)
  380. XCTAssertNil(receivedCompletion?.error)
  381. }
  382. func testThatWebSocketFailsWithTooSmallMaximumMessageSize() {
  383. // Given
  384. let didConnect = expectation(description: "didConnect")
  385. let didComplete = expectation(description: "didComplete")
  386. let session = stored(Session())
  387. var connectedProtocol: String?
  388. var receivedCompletion: WebSocketRequest.Completion?
  389. // When
  390. session.websocketRequest(.websocket(), maximumMessageSize: 1).streamMessageEvents { event in
  391. switch event.kind {
  392. case let .connected(`protocol`):
  393. connectedProtocol = `protocol`
  394. didConnect.fulfill()
  395. case .receivedMessage, .disconnected:
  396. break
  397. case let .completed(completion):
  398. receivedCompletion = completion
  399. didComplete.fulfill()
  400. }
  401. }
  402. wait(for: [didConnect, didComplete], timeout: timeout, enforceOrder: true)
  403. // Then
  404. XCTAssertNil(connectedProtocol)
  405. XCTAssertNotNil(receivedCompletion?.error)
  406. }
  407. func testThatWebSocketsFinishAfterNonNormalResponseCode() {
  408. // Given
  409. let didConnect = expectation(description: "didConnect")
  410. let didReceiveMessage = expectation(description: "didReceiveMessage")
  411. let didDisconnect = expectation(description: "didDisconnect")
  412. let didComplete = expectation(description: "didComplete")
  413. let session = stored(Session())
  414. var connectedProtocol: String?
  415. var message: URLSessionWebSocketTask.Message?
  416. var closeCode: URLSessionWebSocketTask.CloseCode?
  417. var closeReason: Data?
  418. var receivedCompletion: WebSocketRequest.Completion?
  419. // When
  420. session.websocketRequest(.websocket(closeCode: .goingAway)).streamMessageEvents { event in
  421. switch event.kind {
  422. case let .connected(`protocol`):
  423. connectedProtocol = `protocol`
  424. didConnect.fulfill()
  425. case let .receivedMessage(receivedMessage):
  426. message = receivedMessage
  427. didReceiveMessage.fulfill()
  428. case let .disconnected(code, reason):
  429. closeCode = code
  430. closeReason = reason
  431. didDisconnect.fulfill()
  432. case let .completed(completion):
  433. receivedCompletion = completion
  434. didComplete.fulfill()
  435. }
  436. }
  437. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  438. timeout: timeout,
  439. enforceOrder: true)
  440. // Then
  441. XCTAssertNil(connectedProtocol)
  442. XCTAssertNotNil(message)
  443. XCTAssertEqual(closeCode, .goingAway)
  444. XCTAssertNil(closeReason)
  445. XCTAssertNil(receivedCompletion?.error)
  446. }
  447. func testThatWebSocketsCanHaveMultipleHandlers() {
  448. // Given
  449. let didConnect = expectation(description: "didConnect")
  450. didConnect.expectedFulfillmentCount = 2
  451. let didReceiveMessage = expectation(description: "didReceiveMessage")
  452. didReceiveMessage.expectedFulfillmentCount = 2
  453. let didDisconnect = expectation(description: "didDisconnect")
  454. didDisconnect.expectedFulfillmentCount = 2
  455. let didComplete = expectation(description: "didComplete")
  456. didComplete.expectedFulfillmentCount = 2
  457. let session = stored(Session())
  458. var firstConnectedProtocol: String?
  459. var firstMessage: URLSessionWebSocketTask.Message?
  460. var firstCloseCode: URLSessionWebSocketTask.CloseCode?
  461. var firstCloseReason: Data?
  462. var firstReceivedCompletion: WebSocketRequest.Completion?
  463. var secondConnectedProtocol: String?
  464. var secondMessage: URLSessionWebSocketTask.Message?
  465. var secondCloseCode: URLSessionWebSocketTask.CloseCode?
  466. var secondCloseReason: Data?
  467. var secondReceivedCompletion: WebSocketRequest.Completion?
  468. // When
  469. session.websocketRequest(.websocket(closeCode: .goingAway)).streamMessageEvents { event in
  470. switch event.kind {
  471. case let .connected(`protocol`):
  472. firstConnectedProtocol = `protocol`
  473. didConnect.fulfill()
  474. case let .receivedMessage(receivedMessage):
  475. firstMessage = receivedMessage
  476. didReceiveMessage.fulfill()
  477. case let .disconnected(code, reason):
  478. firstCloseCode = code
  479. firstCloseReason = reason
  480. didDisconnect.fulfill()
  481. case let .completed(completion):
  482. firstReceivedCompletion = completion
  483. didComplete.fulfill()
  484. }
  485. }
  486. .streamMessageEvents { event in
  487. switch event.kind {
  488. case let .connected(`protocol`):
  489. secondConnectedProtocol = `protocol`
  490. didConnect.fulfill()
  491. case let .receivedMessage(receivedMessage):
  492. secondMessage = receivedMessage
  493. didReceiveMessage.fulfill()
  494. case let .disconnected(code, reason):
  495. secondCloseCode = code
  496. secondCloseReason = reason
  497. didDisconnect.fulfill()
  498. case let .completed(completion):
  499. secondReceivedCompletion = completion
  500. didComplete.fulfill()
  501. }
  502. }
  503. wait(for: [didConnect, didReceiveMessage, didDisconnect, didComplete],
  504. timeout: timeout,
  505. enforceOrder: true)
  506. // Then
  507. XCTAssertNil(firstConnectedProtocol)
  508. XCTAssertEqual(firstConnectedProtocol, secondConnectedProtocol)
  509. XCTAssertNotNil(firstMessage)
  510. XCTAssertEqual(firstMessage, secondMessage)
  511. XCTAssertEqual(firstCloseCode, .goingAway)
  512. XCTAssertEqual(firstCloseCode, secondCloseCode)
  513. XCTAssertNil(firstCloseReason)
  514. XCTAssertEqual(firstCloseReason, secondCloseReason)
  515. XCTAssertNil(firstReceivedCompletion?.error)
  516. XCTAssertNil(secondReceivedCompletion?.error)
  517. }
  518. }
  519. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  520. extension URLSessionWebSocketTask.Message: Equatable {
  521. public static func ==(lhs: URLSessionWebSocketTask.Message, rhs: URLSessionWebSocketTask.Message) -> Bool {
  522. switch (lhs, rhs) {
  523. case let (.string(left), .string(right)):
  524. return left == right
  525. case let (.data(left), .data(right)):
  526. return left == right
  527. default:
  528. return false
  529. }
  530. }
  531. var string: String? {
  532. guard case let .string(string) = self else { return nil }
  533. return string
  534. }
  535. var data: Data? {
  536. guard case let .data(data) = self else { return nil }
  537. return data
  538. }
  539. }
  540. #endif