@apollo/gateway 0.44.1 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -3
- package/dist/__generated__/graphqlTypes.d.ts +13 -12
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/config.d.ts +3 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -17
- package/dist/config.js.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +1 -1
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -37
- package/dist/index.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +13 -5
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.js +34 -7
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/outOfBandReporter.d.ts +10 -12
- package/dist/outOfBandReporter.d.ts.map +1 -1
- package/dist/outOfBandReporter.js +70 -73
- package/dist/outOfBandReporter.js.map +1 -1
- package/package.json +7 -7
- package/src/__generated__/graphqlTypes.ts +13 -12
- package/src/__tests__/executeQueryPlan.test.ts +636 -0
- package/src/__tests__/gateway/reporting.test.ts +5 -3
- package/src/__tests__/integration/configuration.test.ts +32 -11
- package/src/__tests__/integration/networkRequests.test.ts +22 -22
- package/src/__tests__/integration/nockMocks.ts +12 -6
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +101 -377
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +10 -43
- package/src/executeQueryPlan.ts +11 -1
- package/src/index.ts +36 -56
- package/src/loadSupergraphSdlFromStorage.ts +54 -8
- package/src/outOfBandReporter.ts +87 -89
- package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
- package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
- package/dist/legacyLoadServicesFromStorage.js +0 -62
- package/dist/legacyLoadServicesFromStorage.js.map +0 -1
- package/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/legacyLoadServicesFromStorage.ts +0 -170
|
@@ -1636,4 +1636,640 @@ describe('executeQueryPlan', () => {
|
|
|
1636
1636
|
});
|
|
1637
1637
|
});
|
|
1638
1638
|
});
|
|
1639
|
+
|
|
1640
|
+
describe('@requires', () => {
|
|
1641
|
+
test('handles null in required field correctly (with nullable fields)', async () => {
|
|
1642
|
+
const s1 = {
|
|
1643
|
+
name: 'S1',
|
|
1644
|
+
typeDefs: gql`
|
|
1645
|
+
type T1 @key(fields: "id") {
|
|
1646
|
+
id: Int!
|
|
1647
|
+
f1: String
|
|
1648
|
+
}
|
|
1649
|
+
`
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const s2 = {
|
|
1653
|
+
name: 'S2',
|
|
1654
|
+
typeDefs: gql`
|
|
1655
|
+
type Query {
|
|
1656
|
+
getT1s: [T1]
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
extend type T1 @key(fields: "id") {
|
|
1660
|
+
id: Int! @external
|
|
1661
|
+
f1: String @external
|
|
1662
|
+
f2: T2 @requires(fields: "f1")
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
type T2 {
|
|
1666
|
+
a: String
|
|
1667
|
+
}
|
|
1668
|
+
`
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1672
|
+
|
|
1673
|
+
const s1_data = [
|
|
1674
|
+
{ id: 0, f1: "foo" },
|
|
1675
|
+
{ id: 1, f1: null },
|
|
1676
|
+
{ id: 2, f1: "bar" },
|
|
1677
|
+
];
|
|
1678
|
+
|
|
1679
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1680
|
+
T1: {
|
|
1681
|
+
__resolveReference(ref) {
|
|
1682
|
+
return s1_data[ref.id];
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1688
|
+
Query: {
|
|
1689
|
+
getT1s() {
|
|
1690
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
1691
|
+
},
|
|
1692
|
+
},
|
|
1693
|
+
T1: {
|
|
1694
|
+
__resolveReference(ref) {
|
|
1695
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
1696
|
+
return ref;
|
|
1697
|
+
},
|
|
1698
|
+
f2(o) {
|
|
1699
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
const operationDocument = gql`
|
|
1705
|
+
query {
|
|
1706
|
+
getT1s {
|
|
1707
|
+
id
|
|
1708
|
+
f1
|
|
1709
|
+
f2 {
|
|
1710
|
+
a
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
`;
|
|
1715
|
+
|
|
1716
|
+
const operationContext = buildOperationContext({
|
|
1717
|
+
schema,
|
|
1718
|
+
operationDocument,
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
1722
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1723
|
+
QueryPlan {
|
|
1724
|
+
Sequence {
|
|
1725
|
+
Fetch(service: "S2") {
|
|
1726
|
+
{
|
|
1727
|
+
getT1s {
|
|
1728
|
+
id
|
|
1729
|
+
__typename
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
Flatten(path: "getT1s.@") {
|
|
1734
|
+
Fetch(service: "S1") {
|
|
1735
|
+
{
|
|
1736
|
+
... on T1 {
|
|
1737
|
+
__typename
|
|
1738
|
+
id
|
|
1739
|
+
}
|
|
1740
|
+
} =>
|
|
1741
|
+
{
|
|
1742
|
+
... on T1 {
|
|
1743
|
+
f1
|
|
1744
|
+
__typename
|
|
1745
|
+
id
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
},
|
|
1749
|
+
},
|
|
1750
|
+
Flatten(path: "getT1s.@") {
|
|
1751
|
+
Fetch(service: "S2") {
|
|
1752
|
+
{
|
|
1753
|
+
... on T1 {
|
|
1754
|
+
__typename
|
|
1755
|
+
id
|
|
1756
|
+
f1
|
|
1757
|
+
}
|
|
1758
|
+
} =>
|
|
1759
|
+
{
|
|
1760
|
+
... on T1 {
|
|
1761
|
+
f2 {
|
|
1762
|
+
a
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
},
|
|
1767
|
+
},
|
|
1768
|
+
},
|
|
1769
|
+
}
|
|
1770
|
+
`);
|
|
1771
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
1772
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1773
|
+
Object {
|
|
1774
|
+
"getT1s": Array [
|
|
1775
|
+
Object {
|
|
1776
|
+
"f1": "foo",
|
|
1777
|
+
"f2": Object {
|
|
1778
|
+
"a": "t1:foo",
|
|
1779
|
+
},
|
|
1780
|
+
"id": 0,
|
|
1781
|
+
},
|
|
1782
|
+
Object {
|
|
1783
|
+
"f1": null,
|
|
1784
|
+
"f2": null,
|
|
1785
|
+
"id": 1,
|
|
1786
|
+
},
|
|
1787
|
+
Object {
|
|
1788
|
+
"f1": "bar",
|
|
1789
|
+
"f2": Object {
|
|
1790
|
+
"a": "t1:bar",
|
|
1791
|
+
},
|
|
1792
|
+
"id": 2,
|
|
1793
|
+
},
|
|
1794
|
+
],
|
|
1795
|
+
}
|
|
1796
|
+
`);
|
|
1797
|
+
expect(response.errors).toBeUndefined();
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
test('handles null in required field correctly (with @require field non-nullable)', async () => {
|
|
1801
|
+
const s1 = {
|
|
1802
|
+
name: 'S1',
|
|
1803
|
+
typeDefs: gql`
|
|
1804
|
+
type T1 @key(fields: "id") {
|
|
1805
|
+
id: Int!
|
|
1806
|
+
f1: String
|
|
1807
|
+
}
|
|
1808
|
+
`
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const s2 = {
|
|
1812
|
+
name: 'S2',
|
|
1813
|
+
typeDefs: gql`
|
|
1814
|
+
type Query {
|
|
1815
|
+
getT1s: [T1]
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
extend type T1 @key(fields: "id") {
|
|
1819
|
+
id: Int! @external
|
|
1820
|
+
f1: String @external
|
|
1821
|
+
f2: T2! @requires(fields: "f1")
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
type T2 {
|
|
1825
|
+
a: String
|
|
1826
|
+
}
|
|
1827
|
+
`
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1831
|
+
|
|
1832
|
+
const s1_data = [
|
|
1833
|
+
{ id: 0, f1: "foo" },
|
|
1834
|
+
{ id: 1, f1: null },
|
|
1835
|
+
{ id: 2, f1: "bar" },
|
|
1836
|
+
];
|
|
1837
|
+
|
|
1838
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1839
|
+
T1: {
|
|
1840
|
+
__resolveReference(ref) {
|
|
1841
|
+
return s1_data[ref.id];
|
|
1842
|
+
},
|
|
1843
|
+
},
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1847
|
+
Query: {
|
|
1848
|
+
getT1s() {
|
|
1849
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
T1: {
|
|
1853
|
+
__resolveReference(ref) {
|
|
1854
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
1855
|
+
return ref;
|
|
1856
|
+
},
|
|
1857
|
+
f2(o) {
|
|
1858
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
const operationDocument = gql`
|
|
1864
|
+
query {
|
|
1865
|
+
getT1s {
|
|
1866
|
+
id
|
|
1867
|
+
f1
|
|
1868
|
+
f2 {
|
|
1869
|
+
a
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
`;
|
|
1874
|
+
|
|
1875
|
+
const operationContext = buildOperationContext({
|
|
1876
|
+
schema,
|
|
1877
|
+
operationDocument,
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
1881
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1882
|
+
QueryPlan {
|
|
1883
|
+
Sequence {
|
|
1884
|
+
Fetch(service: "S2") {
|
|
1885
|
+
{
|
|
1886
|
+
getT1s {
|
|
1887
|
+
id
|
|
1888
|
+
__typename
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
Flatten(path: "getT1s.@") {
|
|
1893
|
+
Fetch(service: "S1") {
|
|
1894
|
+
{
|
|
1895
|
+
... on T1 {
|
|
1896
|
+
__typename
|
|
1897
|
+
id
|
|
1898
|
+
}
|
|
1899
|
+
} =>
|
|
1900
|
+
{
|
|
1901
|
+
... on T1 {
|
|
1902
|
+
f1
|
|
1903
|
+
__typename
|
|
1904
|
+
id
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
},
|
|
1909
|
+
Flatten(path: "getT1s.@") {
|
|
1910
|
+
Fetch(service: "S2") {
|
|
1911
|
+
{
|
|
1912
|
+
... on T1 {
|
|
1913
|
+
__typename
|
|
1914
|
+
id
|
|
1915
|
+
f1
|
|
1916
|
+
}
|
|
1917
|
+
} =>
|
|
1918
|
+
{
|
|
1919
|
+
... on T1 {
|
|
1920
|
+
f2 {
|
|
1921
|
+
a
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
},
|
|
1927
|
+
},
|
|
1928
|
+
}
|
|
1929
|
+
`);
|
|
1930
|
+
|
|
1931
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
1932
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
1933
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1934
|
+
Object {
|
|
1935
|
+
"getT1s": Array [
|
|
1936
|
+
Object {
|
|
1937
|
+
"f1": "foo",
|
|
1938
|
+
"f2": Object {
|
|
1939
|
+
"a": "t1:foo",
|
|
1940
|
+
},
|
|
1941
|
+
"id": 0,
|
|
1942
|
+
},
|
|
1943
|
+
null,
|
|
1944
|
+
Object {
|
|
1945
|
+
"f1": "bar",
|
|
1946
|
+
"f2": Object {
|
|
1947
|
+
"a": "t1:bar",
|
|
1948
|
+
},
|
|
1949
|
+
"id": 2,
|
|
1950
|
+
},
|
|
1951
|
+
],
|
|
1952
|
+
}
|
|
1953
|
+
`);
|
|
1954
|
+
|
|
1955
|
+
// We returning `null` for f2 which isn't nullable, so it bubbled up and we should have an error
|
|
1956
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f2.']);
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
test('handles null in required field correctly (with non-nullable required field)', async () => {
|
|
1960
|
+
const s1 = {
|
|
1961
|
+
name: 'S1',
|
|
1962
|
+
typeDefs: gql`
|
|
1963
|
+
type T1 @key(fields: "id") {
|
|
1964
|
+
id: Int!
|
|
1965
|
+
f1: String!
|
|
1966
|
+
}
|
|
1967
|
+
`
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const s2 = {
|
|
1971
|
+
name: 'S2',
|
|
1972
|
+
typeDefs: gql`
|
|
1973
|
+
type Query {
|
|
1974
|
+
getT1s: [T1]
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
extend type T1 @key(fields: "id") {
|
|
1978
|
+
id: Int! @external
|
|
1979
|
+
f1: String! @external
|
|
1980
|
+
f2: T2 @requires(fields: "f1")
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
type T2 {
|
|
1984
|
+
a: String
|
|
1985
|
+
}
|
|
1986
|
+
`
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1990
|
+
|
|
1991
|
+
const s1_data = [
|
|
1992
|
+
{ id: 0, f1: "foo" },
|
|
1993
|
+
{ id: 1, f1: null },
|
|
1994
|
+
{ id: 2, f1: "bar" },
|
|
1995
|
+
];
|
|
1996
|
+
|
|
1997
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1998
|
+
T1: {
|
|
1999
|
+
__resolveReference(ref) {
|
|
2000
|
+
return s1_data[ref.id];
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
2006
|
+
Query: {
|
|
2007
|
+
getT1s() {
|
|
2008
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2009
|
+
},
|
|
2010
|
+
},
|
|
2011
|
+
T1: {
|
|
2012
|
+
__resolveReference(ref) {
|
|
2013
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2014
|
+
return ref;
|
|
2015
|
+
},
|
|
2016
|
+
f2(o) {
|
|
2017
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
const operationDocument = gql`
|
|
2023
|
+
query {
|
|
2024
|
+
getT1s {
|
|
2025
|
+
id
|
|
2026
|
+
f1
|
|
2027
|
+
f2 {
|
|
2028
|
+
a
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
`;
|
|
2033
|
+
const operationContext = buildOperationContext({
|
|
2034
|
+
schema,
|
|
2035
|
+
operationDocument,
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
2039
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2040
|
+
QueryPlan {
|
|
2041
|
+
Sequence {
|
|
2042
|
+
Fetch(service: "S2") {
|
|
2043
|
+
{
|
|
2044
|
+
getT1s {
|
|
2045
|
+
id
|
|
2046
|
+
__typename
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
2050
|
+
Flatten(path: "getT1s.@") {
|
|
2051
|
+
Fetch(service: "S1") {
|
|
2052
|
+
{
|
|
2053
|
+
... on T1 {
|
|
2054
|
+
__typename
|
|
2055
|
+
id
|
|
2056
|
+
}
|
|
2057
|
+
} =>
|
|
2058
|
+
{
|
|
2059
|
+
... on T1 {
|
|
2060
|
+
f1
|
|
2061
|
+
__typename
|
|
2062
|
+
id
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
Flatten(path: "getT1s.@") {
|
|
2068
|
+
Fetch(service: "S2") {
|
|
2069
|
+
{
|
|
2070
|
+
... on T1 {
|
|
2071
|
+
__typename
|
|
2072
|
+
id
|
|
2073
|
+
f1
|
|
2074
|
+
}
|
|
2075
|
+
} =>
|
|
2076
|
+
{
|
|
2077
|
+
... on T1 {
|
|
2078
|
+
f2 {
|
|
2079
|
+
a
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
},
|
|
2084
|
+
},
|
|
2085
|
+
},
|
|
2086
|
+
}
|
|
2087
|
+
`);
|
|
2088
|
+
|
|
2089
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
2090
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
2091
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2092
|
+
Object {
|
|
2093
|
+
"getT1s": Array [
|
|
2094
|
+
Object {
|
|
2095
|
+
"f1": "foo",
|
|
2096
|
+
"f2": Object {
|
|
2097
|
+
"a": "t1:foo",
|
|
2098
|
+
},
|
|
2099
|
+
"id": 0,
|
|
2100
|
+
},
|
|
2101
|
+
null,
|
|
2102
|
+
Object {
|
|
2103
|
+
"f1": "bar",
|
|
2104
|
+
"f2": Object {
|
|
2105
|
+
"a": "t1:bar",
|
|
2106
|
+
},
|
|
2107
|
+
"id": 2,
|
|
2108
|
+
},
|
|
2109
|
+
],
|
|
2110
|
+
}
|
|
2111
|
+
`);
|
|
2112
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f1.']);
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
test('handles errors in required field correctly (with nullable fields)', async () => {
|
|
2116
|
+
const s1 = {
|
|
2117
|
+
name: 'S1',
|
|
2118
|
+
typeDefs: gql`
|
|
2119
|
+
type T1 @key(fields: "id") {
|
|
2120
|
+
id: Int!
|
|
2121
|
+
f1: String
|
|
2122
|
+
}
|
|
2123
|
+
`
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
const s2 = {
|
|
2127
|
+
name: 'S2',
|
|
2128
|
+
typeDefs: gql`
|
|
2129
|
+
type Query {
|
|
2130
|
+
getT1s: [T1]
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
extend type T1 @key(fields: "id") {
|
|
2134
|
+
id: Int! @external
|
|
2135
|
+
f1: String @external
|
|
2136
|
+
f2: T2 @requires(fields: "f1")
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
type T2 {
|
|
2140
|
+
a: String
|
|
2141
|
+
}
|
|
2142
|
+
`
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2146
|
+
|
|
2147
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
2148
|
+
T1: {
|
|
2149
|
+
__resolveReference(ref) {
|
|
2150
|
+
return ref;
|
|
2151
|
+
},
|
|
2152
|
+
f1(o) {
|
|
2153
|
+
switch (o.id) {
|
|
2154
|
+
case 0: return "foo";
|
|
2155
|
+
case 1: return [ "invalid" ]; // This will effectively throw
|
|
2156
|
+
case 2: return "bar";
|
|
2157
|
+
default: throw new Error('Not handled');
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
},
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
2164
|
+
Query: {
|
|
2165
|
+
getT1s() {
|
|
2166
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2167
|
+
},
|
|
2168
|
+
},
|
|
2169
|
+
T1: {
|
|
2170
|
+
__resolveReference(ref) {
|
|
2171
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2172
|
+
return ref;
|
|
2173
|
+
},
|
|
2174
|
+
f2(o) {
|
|
2175
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
const operationDocument = gql`
|
|
2181
|
+
query {
|
|
2182
|
+
getT1s {
|
|
2183
|
+
id
|
|
2184
|
+
f1
|
|
2185
|
+
f2 {
|
|
2186
|
+
a
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
`;
|
|
2191
|
+
const operationContext = buildOperationContext({
|
|
2192
|
+
schema,
|
|
2193
|
+
operationDocument,
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
2197
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2198
|
+
QueryPlan {
|
|
2199
|
+
Sequence {
|
|
2200
|
+
Fetch(service: "S2") {
|
|
2201
|
+
{
|
|
2202
|
+
getT1s {
|
|
2203
|
+
id
|
|
2204
|
+
__typename
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
},
|
|
2208
|
+
Flatten(path: "getT1s.@") {
|
|
2209
|
+
Fetch(service: "S1") {
|
|
2210
|
+
{
|
|
2211
|
+
... on T1 {
|
|
2212
|
+
__typename
|
|
2213
|
+
id
|
|
2214
|
+
}
|
|
2215
|
+
} =>
|
|
2216
|
+
{
|
|
2217
|
+
... on T1 {
|
|
2218
|
+
f1
|
|
2219
|
+
__typename
|
|
2220
|
+
id
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
},
|
|
2225
|
+
Flatten(path: "getT1s.@") {
|
|
2226
|
+
Fetch(service: "S2") {
|
|
2227
|
+
{
|
|
2228
|
+
... on T1 {
|
|
2229
|
+
__typename
|
|
2230
|
+
id
|
|
2231
|
+
f1
|
|
2232
|
+
}
|
|
2233
|
+
} =>
|
|
2234
|
+
{
|
|
2235
|
+
... on T1 {
|
|
2236
|
+
f2 {
|
|
2237
|
+
a
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
},
|
|
2243
|
+
},
|
|
2244
|
+
}
|
|
2245
|
+
`);
|
|
2246
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
2247
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2248
|
+
Object {
|
|
2249
|
+
"getT1s": Array [
|
|
2250
|
+
Object {
|
|
2251
|
+
"f1": "foo",
|
|
2252
|
+
"f2": Object {
|
|
2253
|
+
"a": "t1:foo",
|
|
2254
|
+
},
|
|
2255
|
+
"id": 0,
|
|
2256
|
+
},
|
|
2257
|
+
Object {
|
|
2258
|
+
"f1": null,
|
|
2259
|
+
"f2": null,
|
|
2260
|
+
"id": 1,
|
|
2261
|
+
},
|
|
2262
|
+
Object {
|
|
2263
|
+
"f1": "bar",
|
|
2264
|
+
"f2": Object {
|
|
2265
|
+
"a": "t1:bar",
|
|
2266
|
+
},
|
|
2267
|
+
"id": 2,
|
|
2268
|
+
},
|
|
2269
|
+
],
|
|
2270
|
+
}
|
|
2271
|
+
`);
|
|
2272
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['String cannot represent value: ["invalid"]']);
|
|
2273
|
+
});
|
|
2274
|
+
});
|
|
1639
2275
|
});
|
|
@@ -12,6 +12,7 @@ import { ApolloGateway } from '../..';
|
|
|
12
12
|
import { Plugin, Config, Refs } from 'pretty-format';
|
|
13
13
|
import { Report, Trace } from 'apollo-reporting-protobuf';
|
|
14
14
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
15
|
+
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
15
16
|
|
|
16
17
|
// Normalize specific fields that change often (eg timestamps) to static values,
|
|
17
18
|
// to make snapshot testing viable. (If these helpers are more generally
|
|
@@ -89,7 +90,6 @@ describe('reporting', () => {
|
|
|
89
90
|
let gatewayServer: ApolloServer;
|
|
90
91
|
let gatewayUrl: string;
|
|
91
92
|
let reportPromise: Promise<any>;
|
|
92
|
-
let nockScope: nock.Scope;
|
|
93
93
|
|
|
94
94
|
beforeEach(async () => {
|
|
95
95
|
let reportResolver: (report: any) => void;
|
|
@@ -97,7 +97,8 @@ describe('reporting', () => {
|
|
|
97
97
|
reportResolver = resolve;
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
nockBeforeEach();
|
|
101
|
+
nock('https://usage-reporting.api.apollographql.com')
|
|
101
102
|
.post('/api/ingress/traces')
|
|
102
103
|
.reply(200, (_: any, requestBody: string) => {
|
|
103
104
|
reportResolver(requestBody);
|
|
@@ -137,7 +138,8 @@ describe('reporting', () => {
|
|
|
137
138
|
if (gatewayServer) {
|
|
138
139
|
await gatewayServer.stop();
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
|
|
142
|
+
nockAfterEach();
|
|
141
143
|
});
|
|
142
144
|
|
|
143
145
|
it(`queries three services`, async () => {
|