@@ -9,6 +9,20 @@ import DatadogInternal
99@testable import DatadogRUM
1010@testable import TestUtilities
1111
12+ // Extension to make Path conform to Equatable for testing
13+ extension RUMResourceEvent . Resource . Graphql . Errors . Path : Equatable {
14+ public static func == ( lhs: RUMResourceEvent . Resource . Graphql . Errors . Path , rhs: RUMResourceEvent . Resource . Graphql . Errors . Path ) -> Bool {
15+ switch ( lhs, rhs) {
16+ case ( . string( let lhsValue) , . string( let rhsValue) ) :
17+ return lhsValue == rhsValue
18+ case ( . integer( let lhsValue) , . integer( let rhsValue) ) :
19+ return lhsValue == rhsValue
20+ default :
21+ return false
22+ }
23+ }
24+ }
25+
1226class RUMResourceScopeTests : XCTestCase {
1327 let context : DatadogContext = . mockWith(
1428 service: " test-service " ,
@@ -1511,4 +1525,199 @@ class RUMResourceScopeTests: XCTestCase {
15111525 // Then
15121526 XCTAssertEqual ( metric. resourcesDropped, [ resourceUUID] )
15131527 }
1528+
1529+ // MARK: - GraphQL Error Parsing Tests
1530+
1531+ func testGivenResourceWithComplexGraphQLResponse_whenResourceEnds_itParsesAllErrorsCorrectly( ) throws {
1532+ // Given
1533+ let scope = RUMResourceScope . mockWith (
1534+ parent: provider,
1535+ dependencies: dependencies,
1536+ resourceKey: " /graphql " ,
1537+ startTime: . mockDecember15th2019At10AMUTC( ) ,
1538+ url: " https://api.example.com/graphql " ,
1539+ httpMethod: . post
1540+ )
1541+
1542+ let graphQLResponseJSON = """
1543+ {
1544+ " errors " : [
1545+ {
1546+ " message " : " Book not found " ,
1547+ " locations " : [
1548+ { " line " : 2, " column " : 7 },
1549+ { " line " : 5, " column " : 12 }
1550+ ],
1551+ " path " : [ " library " , " book " , " 1234 " ],
1552+ " extensions " : {
1553+ " code " : " NOT_FOUND " ,
1554+ " timestamp " : " 2024-01-15T10:00:00Z "
1555+ }
1556+ },
1557+ {
1558+ " message " : " Unauthorized access to user profile " ,
1559+ " locations " : [{ " line " : 10, " column " : 3 }],
1560+ " path " : [ " user " , " profile " ],
1561+ " extensions " : {
1562+ " code " : " UNAUTHORIZED "
1563+ }
1564+ }
1565+ ],
1566+ " data " : {
1567+ " library " : {
1568+ " book " : null
1569+ },
1570+ " user " : null,
1571+ " firstShip " : " 3001 " ,
1572+ " secondShip " : null,
1573+ " launch " : [
1574+ {
1575+ " id " : " 1 " ,
1576+ " status " : null
1577+ }
1578+ ],
1579+ " oldField " : " some value "
1580+ }
1581+ }
1582+ """
1583+
1584+ // When
1585+ XCTAssertFalse (
1586+ scope. process (
1587+ command: RUMStopResourceCommand (
1588+ resourceKey: " /graphql " ,
1589+ time: . mockDecember15th2019At10AMUTC( addingTimeInterval: 1 ) ,
1590+ attributes: [
1591+ CrossPlatformAttributes . graphqlErrors: graphQLResponseJSON,
1592+ CrossPlatformAttributes . graphqlOperationType: " query " ,
1593+ CrossPlatformAttributes . graphqlOperationName: " GetBookAndUser "
1594+ ] ,
1595+ kind: . xhr,
1596+ httpStatusCode: 200 ,
1597+ size: nil
1598+ ) ,
1599+ context: context,
1600+ writer: writer
1601+ )
1602+ )
1603+
1604+ // Then
1605+ let event = try XCTUnwrap ( writer. events ( ofType: RUMResourceEvent . self) . first)
1606+ let graphql = try XCTUnwrap ( event. resource. graphql)
1607+
1608+ // Verify error count
1609+ XCTAssertEqual ( graphql. errorCount, 2 )
1610+
1611+ // Verify errors array structure
1612+ let errors = try XCTUnwrap ( graphql. errors)
1613+ XCTAssertEqual ( errors. count, 2 )
1614+
1615+ // Verify first error
1616+ let error1 = errors [ 0 ]
1617+ XCTAssertEqual ( error1. message, " Book not found " )
1618+ XCTAssertEqual ( error1. code, " NOT_FOUND " )
1619+ let path1 = try XCTUnwrap ( error1. path)
1620+ XCTAssertEqual ( path1. count, 3 )
1621+ XCTAssertEqual ( path1 [ 0 ] . self, . string( value: " library " ) )
1622+ XCTAssertEqual ( path1 [ 1 ] , . string( value: " book " ) )
1623+ XCTAssertEqual ( path1 [ 2 ] , . string( value: " 1234 " ) )
1624+ let locations1 = try XCTUnwrap ( error1. locations)
1625+ XCTAssertEqual ( locations1. count, 2 )
1626+ XCTAssertEqual ( locations1 [ 0 ] . line, 2 )
1627+ XCTAssertEqual ( locations1 [ 0 ] . column, 7 )
1628+ XCTAssertEqual ( locations1 [ 1 ] . line, 5 )
1629+ XCTAssertEqual ( locations1 [ 1 ] . column, 12 )
1630+
1631+ // Verify second error
1632+ let error2 = errors [ 1 ]
1633+ XCTAssertEqual ( error2. message, " Unauthorized access to user profile " )
1634+ XCTAssertEqual ( error2. code, " UNAUTHORIZED " )
1635+ let path2 = try XCTUnwrap ( error2. path)
1636+ XCTAssertEqual ( path2. count, 2 )
1637+ XCTAssertEqual ( path2 [ 0 ] , . string( value: " user " ) )
1638+ XCTAssertEqual ( path2 [ 1 ] , . string( value: " profile " ) )
1639+ let locations2 = try XCTUnwrap ( error2. locations)
1640+ XCTAssertEqual ( locations2. count, 1 )
1641+ XCTAssertEqual ( locations2 [ 0 ] . line, 10 )
1642+ XCTAssertEqual ( locations2 [ 0 ] . column, 3 )
1643+ }
1644+
1645+ func testGivenResourceWithInvalidGraphQLJSON_whenResourceEnds_itHandlesGracefully( ) throws {
1646+ let scope = RUMResourceScope . mockWith (
1647+ parent: provider,
1648+ dependencies: dependencies,
1649+ resourceKey: " /graphql " ,
1650+ startTime: . mockDecember15th2019At10AMUTC( ) ,
1651+ url: " https://api.example.com/graphql " ,
1652+ httpMethod: . post
1653+ )
1654+
1655+ // When - Invalid JSON should not crash
1656+ XCTAssertFalse (
1657+ scope. process (
1658+ command: RUMStopResourceCommand (
1659+ resourceKey: " /graphql " ,
1660+ time: . mockDecember15th2019At10AMUTC( addingTimeInterval: 1 ) ,
1661+ attributes: [
1662+ CrossPlatformAttributes . graphqlErrors: " { invalid json } " ,
1663+ CrossPlatformAttributes . graphqlOperationType: " query "
1664+ ] ,
1665+ kind: . xhr,
1666+ httpStatusCode: 200 ,
1667+ size: nil
1668+ ) ,
1669+ context: context,
1670+ writer: writer
1671+ )
1672+ )
1673+
1674+ // Then
1675+ let event = try XCTUnwrap ( writer. events ( ofType: RUMResourceEvent . self) . first)
1676+ let graphql = try XCTUnwrap ( event. resource. graphql)
1677+ XCTAssertNil ( graphql. errors)
1678+ XCTAssertNil ( graphql. errorCount)
1679+ }
1680+
1681+ func testGivenResourceWithEmptyGraphQLErrorsArray_whenResourceEnds_itDoesNotSetErrors( ) throws {
1682+ let scope = RUMResourceScope . mockWith (
1683+ parent: provider,
1684+ dependencies: dependencies,
1685+ resourceKey: " /graphql " ,
1686+ startTime: . mockDecember15th2019At10AMUTC( ) ,
1687+ url: " https://api.example.com/graphql " ,
1688+ httpMethod: . post
1689+ )
1690+
1691+ let emptyErrorsJSON = """
1692+ {
1693+ " errors " : [],
1694+ " data " : null
1695+ }
1696+ """
1697+
1698+ // When
1699+ XCTAssertFalse (
1700+ scope. process (
1701+ command: RUMStopResourceCommand (
1702+ resourceKey: " /graphql " ,
1703+ time: . mockDecember15th2019At10AMUTC( addingTimeInterval: 1 ) ,
1704+ attributes: [
1705+ CrossPlatformAttributes . graphqlErrors: emptyErrorsJSON,
1706+ CrossPlatformAttributes . graphqlOperationType: " query "
1707+ ] ,
1708+ kind: . xhr,
1709+ httpStatusCode: 200 ,
1710+ size: nil
1711+ ) ,
1712+ context: context,
1713+ writer: writer
1714+ )
1715+ )
1716+
1717+ // Then
1718+ let event = try XCTUnwrap ( writer. events ( ofType: RUMResourceEvent . self) . first)
1719+ let graphql = try XCTUnwrap ( event. resource. graphql)
1720+ XCTAssertNil ( graphql. errors)
1721+ XCTAssertNil ( graphql. errorCount)
1722+ }
15141723}
0 commit comments