UNPKG

apollo-language-server

Version:

A language server for Apollo GraphQL projects

505 lines (418 loc) 14.1 kB
import { loadConfig } from "../"; import * as path from "path"; import * as fs from "fs"; import { DefaultClientConfig, DefaultServiceConfig, DefaultEngineConfig, } from "../config"; import { Debug } from "../../utilities"; const makeNestedDir = (dir) => { if (fs.existsSync(dir)) return; try { fs.mkdirSync(dir); } catch (err) { if (err.code == "ENOENT") { makeNestedDir(path.dirname(dir)); //create parent dir makeNestedDir(dir); //create dir } } }; const deleteFolderRecursive = (path) => { // don't delete files on windows -- will get a resource locked error if (require("os").type().includes("Windows")) return; if (fs.existsSync(path)) { fs.readdirSync(path).forEach(function (file, index) { var curPath = path + "/" + file; if (fs.lstatSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } }; const writeFilesToDir = (dir: string, files: Record<string, string>) => { Object.keys(files).forEach((key) => { if (key.includes("/")) makeNestedDir(path.dirname(key)); fs.writeFileSync(`${dir}/${key}`, files[key]); }); }; describe("loadConfig", () => { let dir, dirPath; // set up a temp dir beforeEach(() => { dir = fs.mkdtempSync("__tmp__"); dirPath = `${process.cwd()}/${dir}`; }); // clean up our temp dir afterEach(() => { if (dir) deleteFolderRecursive(dir); dir = dirPath = undefined; }); describe("finding files", () => { it("loads with client defaults from different dir", async () => { writeFilesToDir(dir, { "my.config.js": ` module.exports = { client: { service: 'hello' } } `, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.rawConfig).toMatchInlineSnapshot(` Object { "client": Object { "addTypename": true, "clientOnlyDirectives": Array [ "connection", "type", ], "clientSchemaDirectives": Array [ "client", "rest", ], "excludes": Array [ "**/node_modules", "**/__tests__", ], "includes": Array [ "src/**/*.{ts,tsx,js,jsx,graphql,gql}", ], "service": "hello", "statsWindow": Object { "from": -86400, "to": -0, }, "tagName": "gql", }, "engine": Object { "endpoint": "https://graphql.api.apollographql.com/api/graphql", }, } `); }); it("loads with service defaults from different dir", async () => { writeFilesToDir(dir, { "my.config.js": ` module.exports = { service: { name: 'hello' } } `, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.rawConfig).toMatchInlineSnapshot(` Object { "engine": Object { "endpoint": "https://graphql.api.apollographql.com/api/graphql", }, "service": Object { "endpoint": Object { "url": "http://localhost:4000/graphql", }, "excludes": Array [ "**/node_modules", "**/__tests__", ], "includes": Array [ "src/**/*.{ts,tsx,js,jsx,graphql,gql}", ], "name": "hello", }, } `); }); it("[deprecated] loads config from package.json", async () => { writeFilesToDir(dir, { "package.json": `{"apollo":{"client": {"service": "hello"}} }`, }); // silence the warning const spy = jest.spyOn(console, "warn"); spy.mockImplementationOnce(() => {}); const config = await loadConfig({ configPath: dirPath }); spy.mockRestore(); expect(config.client.service).toEqual("hello"); }); it("loads config from a ts file", async () => { writeFilesToDir(dir, { "apollo.config.ts": `module.exports = {"client": {"service": "hello"}`, }); const config = await loadConfig({ configPath: dirPath }); expect(config.client.service).toEqual("hello"); }); }); describe("errors", () => { it("throws when config file is empty", async () => { writeFilesToDir(dir, { "my.config.js": `` }); const spy = jest.spyOn(console, "error"); // use this to keep the log quiet spy.mockImplementation(); await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/config file failed to load/i) ); spy.mockRestore(); }); it("throws when explorer.search fails", async () => { writeFilesToDir(dir, { "my.config.js": `* 98375^%*&^ its lit` }); const spy = jest.spyOn(console, "error"); // use this to keep the log quiet spy.mockImplementation(); await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/config file failed to load/i) ); spy.mockRestore(); }); it("issues a deprecation warning when loading config from package.json", async () => { const spy = jest.spyOn(console, "warn"); spy.mockImplementation(); writeFilesToDir(dir, { "package.json": `{"apollo":{"client": {"service": "hello"}} }`, }); await loadConfig({ configPath: dirPath, configFileName: "package.json", }); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/The "apollo" package.json configuration/i) ); spy.mockRestore(); }); it("throws if a config file was expected but not found", async () => { const spy = jest.spyOn(console, "error"); spy.mockImplementation(); writeFilesToDir(dir, { "my.config.js": `module.exports = {}` }); await loadConfig({ configFileName: "my.TYPO.js", requireConfig: true, // this is what we're testing }); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/no apollo config/i) ); spy.mockRestore(); }); it("throws if project type cant be resolved", async () => { const spy = jest.spyOn(console, "error"); spy.mockImplementation(); writeFilesToDir(dir, { "my.config.js": `module.exports = {}`, }); await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/unable to resolve/i) ); spy.mockRestore(); }); }); describe("env loading", () => { it("finds .env in config path & parses for key", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env": `APOLLO_KEY=service:harambe:54378950jn`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.client.service).toEqual("harambe"); }); it("finds .env.local in config path & parses for key", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env.local": `APOLLO_KEY=service:harambe:54378950jn`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.client.service).toEqual("harambe"); }); it("finds .env and .env.local in config path & parses for key, preferring .env.local", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env": `APOLLO_KEY=service:hamato:54378950jn`, ".env.local": `APOLLO_KEY=service:yoshi:65489061ko`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.client.service).toEqual("yoshi"); }); it("Allows setting ENGINE_API_KEY with a deprecation warning", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env.local": `ENGINE_API_KEY=service:yoshi:65489061ko`, }); const spy = jest.spyOn(Debug, "warning"); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.client.service).toEqual("yoshi"); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/Deprecation warning/i) ); }); it("Uses new key when .env defined both legacy and new key", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env.local": `ENGINE_API_KEY=service:yoshi:65489061ko\nAPOLLO_KEY=service:yoshi:65489061ko`, }); const spy = jest.spyOn(Debug, "warning"); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.engine.apiKey).toEqual("service:yoshi:65489061ko"); expect(spy).toHaveBeenCalledWith( expect.stringMatching(/Both ENGINE_API_KEY and APOLLO_KEY were found/i) ); }); // this doesn't work right now :) xit("finds .env in cwd & parses for key", async () => { writeFilesToDir(dir, { "dir/my.config.js": `module.exports = { client: { name: 'hello' } }`, ".env": `APOLLO_KEY=service:harambe:54378950jn`, }); process.chdir(dir); const config = await loadConfig({ configPath: "dir/", configFileName: "my.config.js", }); process.chdir("../"); expect(config.client.service).toEqual("harambe"); }); }); describe("project type", () => { it("uses passed in type when config doesnt have client/service", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { engine: { endpoint: 'http://a.a' } }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", type: "client", }); expect(config.isClient).toEqual(true); }); it("infers client projects from config", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { service: 'hello' } }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.isClient).toEqual(true); }); it("infers service projects from config", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { service: 'wow' }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.isService).toEqual(true); }); }); describe("service name", () => { it("lets config service name take precedence for client project", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { service: 'hello' } }`, ".env": `APOLLO_KEY=service:harambe:54378950jn`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", name: "not-it", }); expect(config.client.service).toEqual("hello"); }); it("lets name passed in take precedence over env var", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { } }`, ".env": `APOLLO_KEY=service:harambe:54378950jn`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", name: "hello", }); expect(config.client.service).toEqual("hello"); }); it("uses env var to determine service name when no other options", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { } }`, ".env": `APOLLO_KEY=service:harambe:54378950jn`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.client.service).toEqual("harambe"); }); }); describe("default merging", () => { it("merges service name and default config for client projects", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { service: 'hello' } }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.rawConfig.client.includes).toEqual( DefaultClientConfig.includes ); }); it("merges service name and default config for service projects", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { service: { name: 'wow' } }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.rawConfig.service.includes).toEqual( DefaultServiceConfig.includes ); }); it("merges engine config defaults", async () => { writeFilesToDir(dir, { "my.config.js": `module.exports = { client: { service: 'wow' } }`, }); const config = await loadConfig({ configPath: dirPath, configFileName: "my.config.js", }); expect(config.rawConfig.engine.endpoint).toEqual( DefaultEngineConfig.endpoint ); }); }); });