Skip to content

Commit

Permalink
3️⃣ Test schema loaders, fix `Expected signal to be an instanceof Abo…
Browse files Browse the repository at this point in the history
…rtSignal` error when `UrlLoader` is used (#542)
  • Loading branch information
Dimitri POSTOLOV committed Jul 29, 2021
1 parent 5065482 commit 5e8ebd8
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-rules-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': patch
---

add tests for schema loaders
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
"pre-commit": "yarn generate:docs"
},
"scripts": {
"generate:docs": "ts-node --compiler-options='{\"module\":\"commonjs\"}' scripts/generate-docs.ts",
"generate:docs": "ts-node scripts/generate-docs.ts",
"postinstall": "patch-package",
"lint": "eslint --config .eslintrc.json --ext .ts .",
"lint": "eslint --config .eslintrc.json --ext ts,js .",
"prebuild": "rimraf packages/*/dist",
"transpile-ts": "tsc --project tsconfig.json",
"build": "yarn transpile-ts && bob build",
"postbuild": "cp -r README.md docs ./packages/plugin/dist/",
"test": "jest --no-watchman --forceExit --noStackTrace",
"test": "jest --no-watchman --forceExit --noStackTrace --detectOpenHandles",
"prerelease": "yarn build",
"release": "changeset publish",
"release:canary": "(node scripts/canary-release.js && yarn build && yarn changeset publish --tag alpha) || echo Skipping Canary...",
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/graphql-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GraphQLConfig, GraphQLExtensionDeclaration, loadConfigSync } from 'graphql-config';
import { GraphQLConfig, GraphQLExtensionDeclaration, loadConfigSync, SchemaPointer } from 'graphql-config';
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
import { ParserOptions } from './types';

Expand Down Expand Up @@ -26,7 +26,7 @@ export function loadGraphqlConfig(options: ParserOptions): GraphQLConfig {
config: options.projects
? { projects: options.projects }
: {
schema: options.schema || '', // if `options.schema` is `undefined` will throw error `Project 'default' not found`
schema: (options.schema || '') as SchemaPointer, // if `schema` is `undefined` will throw error `Project 'default' not found`
documents: options.documents || options.operations,
extensions: options.extensions,
include: options.include,
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getOnDiskFilepath } from './utils';

const schemaCache: Map<string, GraphQLSchema> = new Map();

export function getSchema(options: ParserOptions, gqlConfig: GraphQLConfig): GraphQLSchema | null {
export function getSchema(options: ParserOptions = {}, gqlConfig: GraphQLConfig): GraphQLSchema | null {
const realFilepath = options.filePath ? getOnDiskFilepath(options.filePath) : null;
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
const schemaKey = asArray(projectForFile.schema)
Expand All @@ -21,7 +21,7 @@ export function getSchema(options: ParserOptions, gqlConfig: GraphQLConfig): Gra
return schemaCache.get(schemaKey);
}

const schema = projectForFile.getSchemaSync();
const schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', options.schemaOptions);
schemaCache.set(schemaKey, schema);

return schema;
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/src/sibling-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type SiblingOperations = {
const operationsCache: Map<string, Source[]> = new Map();
const siblingOperationsCache: Map<Source[], SiblingOperations> = new Map();

const getSiblings = (filePath: string, gqlConfig: GraphQLConfig): Source[] | null => {
const getSiblings = (filePath: string, gqlConfig: GraphQLConfig): Source[] => {
const realFilepath = filePath ? getOnDiskFilepath(filePath) : null;
const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
const documentsKey = asArray(projectForFile.documents)
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import { SiblingOperations } from './sibling-operations';
import { getReachableTypes, getUsedFields } from './graphql-ast';

export interface ParserOptions {
schema?: SchemaPointer;
schema?: SchemaPointer | Record<string, { headers: Record<string, string> }>;
documents?: DocumentPointer;
operations?: DocumentPointer; // legacy
extensions?: IExtensions;
include?: string | string[];
exclude?: string | string[];
projects?: Record<string, IGraphQLProject>;
schemaOptions?: Omit<GraphQLParseOptions, 'assumeValidSDL'>;
schemaOptions?: Omit<GraphQLParseOptions, 'assumeValidSDL'> & {
headers: Record<string, string>;
};
graphQLParserOptions?: Omit<GraphQLParseOptions, 'noLocation'>;
skipGraphQLConfig?: boolean;
filePath?: string;
Expand Down
70 changes: 70 additions & 0 deletions packages/plugin/tests/mocks/graphql-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { buildSchema, getIntrospectionQuery, graphqlSync } from 'graphql';

const sdlSchema = readFileSync(resolve(__dirname, 'user-schema.graphql'), 'utf8');
const graphqlSchemaObj = buildSchema(sdlSchema);
const introspectionQueryResult = graphqlSync(graphqlSchemaObj, getIntrospectionQuery());

class TestGraphQLServer {
private server: Server;
private base: string;

constructor(private port = 1337) {
this.server = createServer(this.router.bind(this));
this.base = `http://localhost:${this.port}`;
}

start(): Promise<{ url: string }> {
return new Promise(resolve => {
this.server.listen(this.port, () => {
resolve({ url: this.base });
});
});
}

stop(): Promise<void> {
return new Promise((resolve, reject) => {
this.server.close(err => {
err ? reject(err) : resolve();
});
});
}

private async router(req: IncomingMessage, res: ServerResponse): Promise<void> {
const { pathname } = new URL(req.url, this.base);

if (pathname === '/') {
const { query } = await this.parseData(req);
if (query.includes('query IntrospectionQuery')) {
res.end(JSON.stringify(introspectionQueryResult));
}
} else if (pathname === '/my-headers') {
res.end(JSON.stringify(req.headers));
}
}

parseData(req: IncomingMessage): Promise<any | string> {
return new Promise(resolve => {
let data = '';
req.on('data', chunk => {
data += chunk;
});
req.on('end', () => {
if (req.headers['content-type'] === 'application/json') {
resolve(JSON.parse(data));
} else {
resolve(data);
}
});
});
}
}

const graphqlServer = new TestGraphQLServer();

graphqlServer.start().then(({ url }) => {
// eslint-disable-next-line no-console
console.log(url);
});
2 changes: 2 additions & 0 deletions packages/plugin/tests/mocks/unique-fragment.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// eslint-disable-next-line no-undef
const USER_FIELDS = gql`
fragment UserFields on User {
id
}
`;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const GET_USER = /* GraphQL */ `
query User {
user {
Expand Down
64 changes: 58 additions & 6 deletions packages/plugin/tests/schema.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { resolve } from 'path';
import { readFileSync } from 'fs';
import { spawn } from 'child_process';
import { GraphQLSchema, printSchema } from 'graphql';
import { getSchema } from '../src/schema';
import { loadGraphqlConfig } from '../src/graphql-config';
Expand Down Expand Up @@ -37,10 +38,61 @@ describe('schema', () => {
});
});

// TODO: make works when url-loader will be updated to v7
// describe('UrlLoader', () => {
// it('should load schema from URL', () => {
// testSchema(url);
// });
// });
describe('UrlLoader', () => {
let local;
let url;

beforeAll(done => {
const tsNodeCommand = resolve(process.cwd(), 'node_modules/.bin/ts-node');
const serverPath = resolve(__dirname, 'mocks/graphql-server.ts');

// Import `TestGraphQLServer` and run it in this file will don't work
// because `@graphql-tools/url-loader` under the hood uses `sync-fetch` package that uses
// `child_process.execFileSync` that block Node.js event loop
local = spawn(tsNodeCommand, [serverPath]);
local.stdout.on('data', chunk => {
url = chunk.toString().trimRight();
done();
});
});

afterAll(done => {
local.on('close', done);
local.kill();
});

it('should load schema from URL', () => {
testSchema(url);
});

it('should passe headers', () => {
expect.assertions(2);
const schemaUrl = `${url}/my-headers`;
const schemaOptions = {
headers: {
Authorization: 'Bearer Foo',
},
};

try {
// https://graphql-config.com/schema#passing-headers
const gqlConfig = loadGraphqlConfig({
schema: {
[schemaUrl]: schemaOptions,
},
});
getSchema(undefined, gqlConfig);
} catch (e) {
expect(e.message).toMatch('"authorization":"Bearer Foo"');
}

try {
// https://github.com/dotansimha/graphql-eslint/blob/master/docs/parser-options.md#schemaoptions
const gqlConfig = loadGraphqlConfig({ schema: schemaUrl });
getSchema({ schemaOptions }, gqlConfig);
} catch (e) {
expect(e.message).toMatch('"authorization":"Bearer Foo"');
}
});
});
});
11 changes: 6 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
}
},
"include": ["packages"],
"exclude": [
"**/dist",
"**/temp",
"**/tests"
]
"exclude": ["**/dist", "**/temp", "**/tests"],
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}

0 comments on commit 5e8ebd8

Please sign in to comment.