diff --git a/src/cli/services/__tests__/linter.test.ts b/src/cli/services/__tests__/linter.test.ts index 212e71a8b..2fcc83b66 100644 --- a/src/cli/services/__tests__/linter.test.ts +++ b/src/cli/services/__tests__/linter.test.ts @@ -5,6 +5,10 @@ import { ValidationError } from '../../../ruleset/validation'; import { ILintConfig } from '../../../types/config'; import lintCommand from '../../commands/lint'; import { lint } from '../linter'; +import * as http from 'http'; +import * as url from 'url'; +import { DEFAULT_REQUEST_OPTIONS } from '../../../request'; +import * as ProxyAgent from 'proxy-agent'; jest.mock('../output'); @@ -803,4 +807,59 @@ describe('Linter service', () => { ); }); }); + + describe('proxy', () => { + let server: http.Server; + const PORT = 4001; + + beforeAll(() => { + // nock cannot mock proxied requests + server = http + .createServer((req, res) => { + const { pathname } = url.parse(String(req.url)); + if (pathname === '/custom-ruleset') { + res.writeHead(403); + } else if (pathname === '/ok.json') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.write( + JSON.stringify({ + info: { + title: '', + description: 'Foo', + }, + }), + ); + } else { + res.writeHead(404); + } + + res.end(); + }) + .listen(PORT, '0.0.0.0'); + }); + + afterAll(() => { + server.close(); + }); + + describe('when agent is set', () => { + beforeEach(() => { + DEFAULT_REQUEST_OPTIONS.agent = new ProxyAgent(`http://localhost:${PORT}`) as any; + }); + + afterEach(() => { + delete DEFAULT_REQUEST_OPTIONS.agent; + }); + + describe('loading a ruleset', () => { + it('proxies the request', async () => { + await expect( + run(`lint --ruleset http://localhost:4000/custom-ruleset src/__tests__/__fixtures__/petstore.oas3.json`), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Could not parse http://localhost:4000/custom-ruleset: Forbidden"`, + ); + }); + }); + }); + }); }); diff --git a/src/cli/services/linter/linter.ts b/src/cli/services/linter/linter.ts index 38ea2b0b3..fff15d716 100644 --- a/src/cli/services/linter/linter.ts +++ b/src/cli/services/linter/linter.ts @@ -7,13 +7,19 @@ import { ILintConfig } from '../../../types/config'; import { getRuleset, listFiles, skipRules, segregateEntriesPerKind, readFileDescriptor } from './utils'; import { getResolver } from './utils/getResolver'; import { YamlParserResult } from '@stoplight/yaml'; +import { DEFAULT_REQUEST_OPTIONS } from '../../../request'; +import type { Agent } from 'https'; export async function lint(documents: Array, flags: ILintConfig): Promise { const spectral = new Spectral({ resolver: getResolver(flags.resolver), + proxyUri: process.env.PROXY, + }); + + const ruleset = await getRuleset(flags.ruleset, { + agent: DEFAULT_REQUEST_OPTIONS.agent as Agent, }); - const ruleset = await getRuleset(flags.ruleset); spectral.setRuleset(ruleset); for (const [format, lookup, prettyName] of KNOWN_FORMATS) { diff --git a/src/cli/services/linter/utils/getRuleset.ts b/src/cli/services/linter/utils/getRuleset.ts index 224297b0d..fab02d5fe 100644 --- a/src/cli/services/linter/utils/getRuleset.ts +++ b/src/cli/services/linter/utils/getRuleset.ts @@ -1,11 +1,11 @@ import { isAbsolute, resolve } from '@stoplight/path'; import { Optional } from '@stoplight/types'; -import { readRuleset } from '../../../../ruleset'; +import { IRulesetReadOptions, readRuleset } from '../../../../ruleset'; import { getDefaultRulesetFile } from '../../../../ruleset/utils'; import { IRuleset } from '../../../../types/ruleset'; import { KNOWN_RULESETS } from '../../../../formats'; -async function loadRulesets(cwd: string, rulesetFiles: string[]): Promise { +async function loadRulesets(cwd: string, rulesetFiles: string[], opts: IRulesetReadOptions): Promise { if (rulesetFiles.length === 0) { return { functions: {}, @@ -14,13 +14,16 @@ async function loadRulesets(cwd: string, rulesetFiles: string[]): Promise (isAbsolute(file) ? file : resolve(cwd, file)))); + return readRuleset( + rulesetFiles.map(file => (isAbsolute(file) ? file : resolve(cwd, file))), + opts, + ); } -export async function getRuleset(rulesetFile: Optional): Promise { +export async function getRuleset(rulesetFile: Optional, opts: IRulesetReadOptions): Promise { const rulesetFiles = rulesetFile ?? (await getDefaultRulesetFile(process.cwd())); return await (rulesetFiles !== null - ? loadRulesets(process.cwd(), Array.isArray(rulesetFiles) ? rulesetFiles : [rulesetFiles]) - : readRuleset(KNOWN_RULESETS)); + ? loadRulesets(process.cwd(), Array.isArray(rulesetFiles) ? rulesetFiles : [rulesetFiles], opts) + : readRuleset(KNOWN_RULESETS, opts)); }