@@ -1492,4 +1492,149 @@ func TestErrorPropagation(t *testing.T) {
14921492 require .Equal (t , expected , resp .Body )
14931493 })
14941494 })
1495+
1496+ }
1497+
1498+ func TestErrorLocations (t * testing.T ) {
1499+ t .Parallel ()
1500+
1501+ t .Run ("Handle invalid locations" , func (t * testing.T ) {
1502+ t .Parallel ()
1503+
1504+ tests := []struct {
1505+ name string
1506+ subgraphErrorsInput string
1507+ expectedWrappedResponse string
1508+ expectedPassthroughResponse string
1509+ }{
1510+ {
1511+ name : "all locations invalid - removes locations field" ,
1512+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":-1,"column":1}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1513+ expectedWrappedResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1514+ expectedPassthroughResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1515+ },
1516+ {
1517+ name : "mixed valid and invalid locations - keeps only valid" ,
1518+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":0,"column":10},{"line":3,"column":-2},{"line":4,"column":15}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1519+ expectedWrappedResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":4,"column":15}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1520+ expectedPassthroughResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":4,"column":15}],"extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1521+ },
1522+ {
1523+ name : "location with missing line field - removes that location" ,
1524+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"column":10}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1525+ expectedWrappedResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1526+ expectedPassthroughResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5}],"extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1527+ },
1528+ {
1529+ name : "all locations missing fields - removes locations field" ,
1530+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":1},{"column":5}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1531+ expectedWrappedResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1532+ expectedPassthroughResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1533+ },
1534+ {
1535+ name : "location with invalid type - removes that location" ,
1536+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":"invalid","column":10}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1537+ expectedWrappedResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1538+ expectedPassthroughResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5}],"extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1539+ },
1540+ {
1541+ name : "all locations with invalid types - removes locations field" ,
1542+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":"invalid","column":5},{"line":2,"column":"invalid"}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1543+ expectedWrappedResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1544+ expectedPassthroughResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1545+ },
1546+ {
1547+ name : "locations is not an array - removes locations field" ,
1548+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":"invalid","extensions":{"code":"UNAUTHORIZED"}}]` ,
1549+ expectedWrappedResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1550+ expectedPassthroughResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1551+ },
1552+ {
1553+ name : "multiple errors with different location scenarios" ,
1554+ subgraphErrorsInput : `[{"message":"Error 1","locations":[{"line":1,"column":5}],"extensions":{"code":"ERR1"}},{"message":"Error 2","locations":[{"line":0,"column":0}],"extensions":{"code":"ERR2"}},{"message":"Error 3","locations":[{"line":3,"column":10},{"line":-1,"column":5}],"extensions":{"code":"ERR3"}}]` ,
1555+ expectedWrappedResponse : `[{"message":"Error 1","locations":[{"line":1,"column":5}],"extensions":{"code":"ERR1"}},{"message":"Error 2","extensions":{"code":"ERR2"}},{"message":"Error 3","locations":[{"line":3,"column":10}],"extensions":{"code":"ERR3"}}]` ,
1556+ expectedPassthroughResponse : `[{"message":"Error 1","locations":[{"line":1,"column":5}],"extensions":{"code":"ERR1","statusCode":200}},{"message":"Error 2","extensions":{"code":"ERR2","statusCode":200}},{"message":"Error 3","locations":[{"line":3,"column":10}],"extensions":{"code":"ERR3","statusCode":200}}]` ,
1557+ },
1558+ {
1559+ name : "valid locations - unchanged" ,
1560+ subgraphErrorsInput : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":2,"column":10}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1561+ expectedWrappedResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":2,"column":10}],"extensions":{"code":"UNAUTHORIZED"}}]` ,
1562+ expectedPassthroughResponse : `[{"message":"Unauthorized","locations":[{"line":1,"column":5},{"line":2,"column":10}],"extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1563+ },
1564+ {
1565+ name : "no locations field - unchanged" ,
1566+ subgraphErrorsInput : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1567+ expectedWrappedResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED"}}]` ,
1568+ expectedPassthroughResponse : `[{"message":"Unauthorized","extensions":{"code":"UNAUTHORIZED","statusCode":200}}]` ,
1569+ },
1570+ }
1571+
1572+ for _ , tt := range tests {
1573+ t .Run (tt .name , func (t * testing.T ) {
1574+ t .Parallel ()
1575+
1576+ t .Run ("wrapped mode" , func (t * testing.T ) {
1577+ t .Parallel ()
1578+ testenv .Run (t , & testenv.Config {
1579+ ModifySubgraphErrorPropagation : func (cfg * config.SubgraphErrorPropagationConfiguration ) {
1580+ cfg .Enabled = true
1581+ cfg .Mode = config .SubgraphErrorPropagationModeWrapped
1582+ cfg .OmitLocations = false
1583+ },
1584+ Subgraphs : testenv.SubgraphsConfig {
1585+ Employees : testenv.SubgraphConfig {
1586+ Middleware : func (handler http.Handler ) http.Handler {
1587+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1588+ w .WriteHeader (http .StatusOK )
1589+ subgraphResponse := `{"errors":` + tt .subgraphErrorsInput + `}`
1590+ if _ , err := w .Write ([]byte (subgraphResponse )); err != nil {
1591+ http .Error (w , err .Error (), http .StatusInternalServerError )
1592+ }
1593+ })
1594+ },
1595+ },
1596+ },
1597+ }, func (t * testing.T , xEnv * testenv.Environment ) {
1598+ res := xEnv .MakeGraphQLRequestOK (testenv.GraphQLRequest {
1599+ Query : `{ employees { id details { forename surname } notes } }` ,
1600+ })
1601+
1602+ expectedBody := `{"errors":[{"message":"Failed to fetch from Subgraph 'employees'.","extensions":{"errors":` + tt .expectedWrappedResponse + `,"statusCode":200}}],"data":{"employees":null}}`
1603+ require .JSONEq (t , expectedBody , res .Body )
1604+ })
1605+ })
1606+
1607+ t .Run ("passthrough mode" , func (t * testing.T ) {
1608+ t .Parallel ()
1609+ testenv .Run (t , & testenv.Config {
1610+ ModifySubgraphErrorPropagation : func (cfg * config.SubgraphErrorPropagationConfiguration ) {
1611+ cfg .Enabled = true
1612+ cfg .Mode = config .SubgraphErrorPropagationModePassthrough
1613+ cfg .OmitLocations = false
1614+ },
1615+ Subgraphs : testenv.SubgraphsConfig {
1616+ Employees : testenv.SubgraphConfig {
1617+ Middleware : func (handler http.Handler ) http.Handler {
1618+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1619+ w .WriteHeader (http .StatusOK )
1620+ subgraphResponse := `{"errors":` + tt .subgraphErrorsInput + `}`
1621+ if _ , err := w .Write ([]byte (subgraphResponse )); err != nil {
1622+ http .Error (w , err .Error (), http .StatusInternalServerError )
1623+ }
1624+ })
1625+ },
1626+ },
1627+ },
1628+ }, func (t * testing.T , xEnv * testenv.Environment ) {
1629+ res := xEnv .MakeGraphQLRequestOK (testenv.GraphQLRequest {
1630+ Query : `{ employees { id details { forename surname } notes } }` ,
1631+ })
1632+
1633+ expectedBody := `{"errors":` + tt .expectedPassthroughResponse + `,"data":{"employees":null}}`
1634+ require .JSONEq (t , expectedBody , res .Body )
1635+ })
1636+ })
1637+ })
1638+ }
1639+ })
14951640}
0 commit comments