WebSocketTests.swift 23 KB

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