@@ -498,6 +498,78 @@ class SentryNetworkTrackerTests: XCTestCase {
498498 XCTAssertEqual ( payloadData [ " fragment " ] as? String , " fragment " )
499499 }
500500
501+ /// Simple case - when network details are enabled, `addBreadcrumbForSessionTask` will include
502+ /// serialized network details in the breadcrumb data.
503+ func testAddBreadcrumb_withNetworkDetails_shouldIncludeSerializedDetailsInBreadcrumbData( ) throws {
504+ // -- Arrange --
505+ let testUrl = URL ( string: " https://api.example.com/users " ) !
506+ let options = Options ( )
507+ options
. dsn
= " https://[email protected] /1234 " 508+ options. sessionReplay. networkDetailAllowUrls = [ " api.example.com " ]
509+ options. sessionReplay. networkResponseHeaders = [ " Cache-Control " ]
510+ options. sessionReplay. networkCaptureBodies = false
511+
512+ let scope = Scope ( )
513+ let client = TestClient ( options: options)
514+ let hub = TestHub ( client: client, andScope: scope)
515+ SentrySDKInternal . setCurrentHub ( hub)
516+ SentrySDK . setStart ( with: options)
517+
518+ let tracker = SentryNetworkTracker . sharedInstance
519+ tracker. enableNetworkTracking ( )
520+ tracker. enableNetworkBreadcrumbs ( )
521+
522+ var request = URLRequest ( url: testUrl)
523+ request. httpMethod = " POST "
524+ request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
525+ let task = URLSessionDataTaskMock ( request: request)
526+
527+ let httpResponse = try XCTUnwrap ( HTTPURLResponse (
528+ url: testUrl, statusCode: 200 , httpVersion: " 1.1 " ,
529+ headerFields: [ " Content-Type " : " application/json " , " Cache-Control " : " no-cache " ]
530+ ) )
531+ task. setResponse ( httpResponse)
532+
533+ // -- Act --
534+ // 1. setState(.running) triggers captureRequestDetails (associates details with task).
535+ tracker. urlSessionTask ( task, setState: . running)
536+
537+ // 2. completionHandler fires, capturing response details on the associated object.
538+ tracker. captureResponseDetails (
539+ Data ( ) , response: httpResponse, request: testUrl, task: task
540+ )
541+
542+ // 3. setState(.completed) triggers addBreadcrumbForSessionTask, which serializes
543+ // the now-complete details (request + response) into the breadcrumb.
544+ tracker. urlSessionTask ( task, setState: . completed)
545+
546+ // -- Assert --
547+ let breadcrumbs = try XCTUnwrap ( Dynamic ( scope) . breadcrumbArray as [ Breadcrumb ] ? )
548+ let breadcrumb = try XCTUnwrap ( breadcrumbs. first)
549+ let detailsObject = try XCTUnwrap (
550+ breadcrumb. data ? [ SentryReplayNetworkDetails . replayNetworkDetailsKey] as? SentryReplayNetworkDetails
551+ )
552+ let networkDetails = detailsObject. serialize ( )
553+
554+ XCTAssertEqual ( networkDetails [ " method " ] as? String , " POST " )
555+ XCTAssertEqual ( networkDetails [ " statusCode " ] as? Int , 200 )
556+
557+ // Verify request details show up
558+ let requestDict = try XCTUnwrap ( networkDetails [ " request " ] as? [ String : Any ] )
559+ let requestHeaders = try XCTUnwrap ( requestDict [ " headers " ] as? [ String : String ] )
560+ // "Content-Type" is always extracted when present.
561+ XCTAssertEqual ( requestHeaders [ " Content-Type " ] , " application/json " )
562+
563+ // Verify response details show up
564+ let responseDict = try XCTUnwrap ( networkDetails [ " response " ] as? [ String : Any ] )
565+ let responseHeaders = try XCTUnwrap ( responseDict [ " headers " ] as? [ String : String ] )
566+ // "Content-Type" is always extracted when present.
567+ XCTAssertEqual ( responseHeaders [ " Content-Type " ] , " application/json " )
568+ XCTAssertEqual ( responseHeaders [ " Cache-Control " ] , " no-cache " )
569+
570+ clearTestState ( )
571+ }
572+
501573 func testBreadcrumb_GraphQLEnabled( ) throws {
502574 let body = """
503575 {
0 commit comments