Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(url-loader): pass variables correctly on HTTP GET #1898

Merged
merged 1 commit into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/loaders/url/src/index.ts
Expand Up @@ -109,7 +109,9 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
case 'GET':
const urlObj = new URL(HTTP_URL);
urlObj.searchParams.set('query', query);
urlObj.searchParams.set(variables, JSON.stringify(variables));
if (variables && Object.keys(variables).length > 0) {
urlObj.searchParams.set('variables', JSON.stringify(variables));
}
const finalUrl = urlObj.toString();
fetchResult = await fetch(finalUrl, {
method: 'GET',
Expand Down
82 changes: 66 additions & 16 deletions packages/loaders/url/tests/url-loader.spec.ts
Expand Up @@ -4,6 +4,7 @@ import { printSchemaWithDirectives } from '@graphql-tools/utils';
import nock from 'nock';
import { mockGraphQLServer } from '../../../testing/utils';
import { cwd } from 'process';
import { execute, parse } from 'graphql';

const SHOULD_NOT_GET_HERE_ERROR = 'SHOULD_NOT_GET_HERE';

Expand All @@ -15,20 +16,29 @@ schema { query: CustomQuery }
"""Test type comment"""
type CustomQuery {
"""Test field comment"""
a: String
a(testVariable: String): String
}
`.trim();

const testSchema = makeExecutableSchema({ typeDefs: testTypeDefs });
const testResolvers = {
CustomQuery: {
a: (_: never, { testVariable }: { testVariable: string }) => testVariable || 'a',
}
}

const testSchema = makeExecutableSchema({ typeDefs: testTypeDefs, resolvers: testResolvers });

const testHost = `http://localhost:3000`;
const testPath = `/graphql`;
const testPath = '/graphql';
const testPathChecker = (path: string) => {
return path.startsWith(testPath);
};
const testUrl = `${testHost}${testPath}`;

describe('handle', () => {
it('Should throw an error when introspection is not valid', async () => {
const brokenData = {data: {}};
const scope = nock(testHost).post(testPath).reply(200, brokenData);
const brokenData = { data: {} };
const scope = nock(testHost).post(testPathChecker).reply(200, brokenData);

try {
await loader.load(testUrl, {});
Expand All @@ -42,7 +52,7 @@ type CustomQuery {
});

it('Should return a valid schema when request is valid', async () => {
const server = mockGraphQLServer({schema: testSchema, host: testHost, path: testPath});
const server = mockGraphQLServer({ schema: testSchema, host: testHost, path: testPathChecker });

const schema = await loader.load(testUrl, {});

Expand All @@ -55,9 +65,11 @@ type CustomQuery {
it('Should pass default headers', async () => {
let headers: Record<string, string> = {}

const server = mockGraphQLServer({schema: testSchema, host: testHost, path: testPath, intercept(ctx) {
headers = ctx.req.headers
}});
const server = mockGraphQLServer({
schema: testSchema, host: testHost, path: testPathChecker, intercept(ctx) {
headers = ctx.req.headers
}
});

const schema = await loader.load(testUrl, {});

Expand All @@ -73,9 +85,11 @@ type CustomQuery {

it('Should pass extra headers when they are specified as object', async () => {
let headers: Record<string, string> = {}
const server = mockGraphQLServer({schema: testSchema, host: testHost, path: testPath, intercept(ctx) {
headers = ctx.req.headers
}});
const server = mockGraphQLServer({
schema: testSchema, host: testHost, path: testPathChecker, intercept(ctx) {
headers = ctx.req.headers
}
});

const schema = await loader.load(testUrl, { headers: { Auth: '1' } });

Expand All @@ -92,9 +106,11 @@ type CustomQuery {

it('Should pass extra headers when they are specified as array', async () => {
let headers: Record<string, string> = {}
const server = mockGraphQLServer({schema: testSchema, host: testHost, path: testPath, intercept(ctx) {
headers = ctx.req.headers
}});
const server = mockGraphQLServer({
schema: testSchema, host: testHost, path: testPathChecker, intercept(ctx) {
headers = ctx.req.headers
}
});
const schema = await loader.load(testUrl, { headers: [{ A: '1' }, { B: '2', C: '3' }] });

server.done();
Expand All @@ -111,7 +127,7 @@ type CustomQuery {
});

it('Should utilize extra introspection options', async () => {
const server = mockGraphQLServer({schema: testSchema, host: testHost, path: testPath});
const server = mockGraphQLServer({ schema: testSchema, host: testHost, path: testPathChecker });
const source = await loader.load(testUrl, { descriptions: false });

server.done();
Expand All @@ -123,5 +139,39 @@ type CustomQuery {
it('Absolute file path should not be accepted as URL', async () => {
expect(await loader.canLoad(cwd(), {})).toBeFalsy();
});
it('should handle useGETForQueries correctly', async () => {

const server = mockGraphQLServer({ schema: testSchema, host: testHost, path: testPathChecker, method: 'GET' });

const source = await loader.load(testUrl, {
descriptions: false,
useGETForQueries: true
});

server.done();

const testVariableValue = 'A';

const server2 = mockGraphQLServer({ schema: testSchema, host: testHost, path: testPathChecker, method: 'GET' });

const result = await execute({
schema: source.schema,
document: parse(/* GraphQL */`
query TestQuery($testVariable: String) {
a(testVariable: $testVariable)
}
`),
variableValues: {
testVariable: testVariableValue
}
});

server2.done();

expect(result?.errors).toBeFalsy();

expect(result?.data?.a).toBe(testVariableValue);

})
});
});
62 changes: 43 additions & 19 deletions packages/testing/utils.ts
Expand Up @@ -86,29 +86,53 @@ export function mockGraphQLServer({
host,
path,
intercept,
method = 'POST',
}: {
schema: GraphQLSchema;
host: string;
path: string;
path: string | RegExp | ((path: string) => boolean);
intercept?: (obj: nock.ReplyFnContext) => void;
method?: string;
}) {
return nock(host)
.post(path)
.reply(async function (_: any, body: any) {
try {
if (intercept) {
intercept(this);
}

const result = await execute({
schema,
document: parse(body.query),
operationName: body.operationName,
variableValues: body.variables,
switch (method) {
case 'GET':
return nock(host)
.get(path)
.reply(async function (uri) {
const urlObj = new URL(host + uri);
try {
if (intercept) {
intercept(this);
}
const result = await execute({
schema,
document: parse(urlObj.searchParams.get('query')),
operationName: urlObj.searchParams.get('operationName'),
variableValues: JSON.parse(urlObj.searchParams.get('variables') || '{}'),
});
return [200, result];
} catch (error) {
return [500, error];
}
});
return [200, result];
} catch (error) {
return [500, error];
}
});
case 'POST':
return nock(host)
.post(path)
.reply(async function (_: string, body: any) {
try {
if (intercept) {
intercept(this);
}
const result = await execute({
schema,
document: parse(body.query),
operationName: body.operationName,
variableValues: body.variables,
});
return [200, result];
} catch (error) {
return [500, error];
}
});
}
}