@@ -160,7 +160,7 @@ class SystemWebSocketTests: XCTestCase {
160160 else { return XCTFail ( " Received wrong error: \( error) " ) }
161161 }
162162
163- await fulfillment ( of: [ secondCloseEx] , timeout: 0.1 )
163+ await fulfillment ( of: [ secondCloseEx] , timeout: 0.05 )
164164 }
165165
166166 func testDelegateDoesNotReorderOpenAndCloseCallbacks( ) async throws {
@@ -292,6 +292,81 @@ class SystemWebSocketTests: XCTestCase {
292292 await fulfillment ( of: [ receivedEx] , timeout: 2 )
293293 }
294294
295+ func testServerPingReceivesPongAndDoesNotCloseClient( ) async throws {
296+ let closeEx = expectation ( description: " Should not close after ping " )
297+ closeEx. isInverted = true
298+ let shouldFailOnClose = Locked ( true )
299+
300+ let ( server, client) = try await makeServerAndClient (
301+ onClose: { _ in
302+ guard shouldFailOnClose. access ( { $0 } ) else { return }
303+ closeEx. fulfill ( )
304+ }
305+ )
306+ defer { server. shutDown ( ) }
307+
308+ let pingPayload = Data ( " server ping " . utf8)
309+ let pongEx = expectation ( description: " Server should receive pong " )
310+ let pongSub = server. pongPublisher
311+ . sink { pong in
312+ XCTAssertEqual ( pingPayload, pong)
313+ pongEx. fulfill ( )
314+ }
315+ defer { pongSub. cancel ( ) }
316+
317+ let readyEx = expectation ( description: " Should receive initial app message " )
318+ let receivedEx = expectation ( description: " Should still receive app messages " )
319+ let receivedSub = client. sink { message in
320+ guard case let . text( text) = message else {
321+ return XCTFail ( " Should have received text " )
322+ }
323+
324+ switch text {
325+ case " ready " :
326+ readyEx. fulfill ( )
327+
328+ case " still open " :
329+ receivedEx. fulfill ( )
330+
331+ default :
332+ XCTFail ( " Received unexpected text: \( text) " )
333+ }
334+ }
335+ defer { receivedSub. cancel ( ) }
336+
337+ let sentEx = expectation ( description: " Server should receive client message " )
338+ let sentSub = server. inputPublisher
339+ . sink { message in
340+ guard case let . text( text) = message else {
341+ return XCTFail ( " Should have received text " )
342+ }
343+ XCTAssertEqual ( " client ready " , text)
344+ sentEx. fulfill ( )
345+ }
346+ defer { sentSub. cancel ( ) }
347+
348+ try await client. open ( )
349+
350+ try await client. send ( . text( " client ready " ) )
351+ await fulfillment ( of: [ sentEx] , timeout: 2 )
352+
353+ subject. send ( . message( . text( " ready " ) ) )
354+ await fulfillment ( of: [ readyEx] , timeout: 2 )
355+
356+ subject. send ( . ping( pingPayload) )
357+ await fulfillment ( of: [ pongEx] , timeout: 2 )
358+
359+ let isOpen = await client. isOpen
360+ XCTAssertTrue ( isOpen)
361+
362+ subject. send ( . message( . text( " still open " ) ) )
363+ await fulfillment ( of: [ receivedEx] , timeout: 2 )
364+ await fulfillment ( of: [ closeEx] , timeout: 0.05 )
365+
366+ shouldFailOnClose. access { $0 = false }
367+ try await client. close ( )
368+ }
369+
295370 @available( iOS 15.0 , macOS 12.0 , * )
296371 func testPushAndReceiveDataWithAsyncPublisher ( ) async throws {
297372 let ( server, client) = try await makeServerAndClient ( )
0 commit comments