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): should preserve ws and http in the middle of a pointer #1991

Merged
merged 1 commit into from
Sep 3, 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
18 changes: 16 additions & 2 deletions packages/loaders/url/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
defaultMethod: 'GET' | 'POST';
useGETForQueries: boolean;
}): AsyncExecutor {
const HTTP_URL = pointer.replace('ws:', 'http:').replace('wss:', 'https:');
const HTTP_URL = switchProtocols(pointer, {
wss: 'https',
ws: 'http',
});
return async ({ document, variables }: { document: DocumentNode; variables: any; info: GraphQLResolveInfo }) => {
let method = defaultMethod;
if (useGETForQueries) {
Expand Down Expand Up @@ -134,7 +137,10 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
}

buildSubscriber(pointer: string, webSocketImpl: typeof w3cwebsocket): Subscriber {
const WS_URL = pointer.replace('http:', 'ws:').replace('https:', 'wss:');
const WS_URL = switchProtocols(pointer, {
https: 'wss',
http: 'ws',
});
const subscriptionClient = new SubscriptionClient(WS_URL, {}, webSocketImpl);
return async <TReturn, TArgs>({ document, variables }: { document: DocumentNode; variables: TArgs }) => {
return observableToAsyncIterable(
Expand Down Expand Up @@ -237,3 +243,11 @@ export class UrlLoader implements DocumentLoader<LoadFromUrlOptions> {
throw new Error('Loader Url has no sync mode');
}
}

function switchProtocols(pointer: string, protocolMap: Record<string, string>): string {
const protocols: [string, string][] = Object.keys(protocolMap).map(source => [source, protocolMap[source]]);
return protocols.reduce(
(prev, [source, target]) => prev.replace(`${source}://`, `${target}://`).replace(`${source}:\\`, `${target}:\\`),
pointer
);
}
102 changes: 81 additions & 21 deletions packages/loaders/url/tests/url-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const SHOULD_NOT_GET_HERE_ERROR = 'SHOULD_NOT_GET_HERE';
describe('Schema URL Loader', () => {
const loader = new UrlLoader();

const testTypeDefs = /* GraphQL */`
const testTypeDefs = /* GraphQL */ `
schema { query: CustomQuery }
"""Test type comment"""
type CustomQuery {
Expand All @@ -23,8 +23,8 @@ type CustomQuery {
const testResolvers = {
CustomQuery: {
a: (_: never, { testVariable }: { testVariable: string }) => testVariable || 'a',
}
}
},
};

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

Expand Down Expand Up @@ -63,12 +63,15 @@ type CustomQuery {
});

it('Should pass default headers', async () => {
let headers: Record<string, string> = {}
let headers: Record<string, string> = {};

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

const schema = await loader.load(testUrl, {});
Expand All @@ -84,11 +87,14 @@ type CustomQuery {
});

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

const schema = await loader.load(testUrl, { headers: { Auth: '1' } });
Expand All @@ -105,11 +111,14 @@ type CustomQuery {
});

it('Should pass extra headers when they are specified as array', async () => {
let headers: Record<string, string> = {}
let headers: Record<string, string> = {};
const server = mockGraphQLServer({
schema: testSchema, host: testHost, path: testPathChecker, intercept(ctx) {
headers = ctx.req.headers
}
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' }] });

Expand Down Expand Up @@ -140,12 +149,11 @@ type CustomQuery {
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
useGETForQueries: true,
});

server.done();
Expand All @@ -156,22 +164,74 @@ type CustomQuery {

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

server2.done();

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

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

it('Should preserve "ws" and "http" in the middle of a pointer', async () => {
const address = {
host: 'http://foo.ws:8080',
path: '/graphql',
};
const url = address.host + address.path;
const server = mockGraphQLServer({ schema: testSchema, host: address.host, path: address.path });
const result = await loader.load(url, {});

server.done();

expect(result.schema).toBeDefined();
expect(printSchemaWithDirectives(result.schema)).toBe(testTypeDefs);
});

it('Should replace ws:// with http:// in buildAsyncExecutor', async () => {
const address = {
host: 'ws://foo:8080',
path: '/graphql',
};
const url = address.host + address.path;
const server = mockGraphQLServer({
schema: testSchema,
host: address.host.replace('ws', 'http'),
path: address.path,
});
const result = await loader.load(url, {});

})
server.done();

expect(result.schema).toBeDefined();
expect(printSchemaWithDirectives(result.schema)).toBe(testTypeDefs);
});

it('Should replace wss:// with https:// in buildAsyncExecutor', async () => {
const address = {
host: 'wss://foo:8080',
path: '/graphql',
};
const url = address.host + address.path;
const server = mockGraphQLServer({
schema: testSchema,
host: address.host.replace('wss', 'https'),
path: address.path,
});
const result = await loader.load(url, {});

server.done();

expect(result.schema).toBeDefined();
expect(printSchemaWithDirectives(result.schema)).toBe(testTypeDefs);
});
});
});