Skip to content

Commit 76ae24f

Browse files
feat: SL-2035 cli url spec (#200)
* feat: SL-2035 passing url as spec in cli * feat: SL-2035 various fixes * fix: SL-2035 fixed test * fix: SL-2035 post review changes * test: SL-2035 added few tests for CLI to increase coverage
1 parent 3fc6bd0 commit 76ae24f

File tree

17 files changed

+218
-83
lines changed

17 files changed

+218
-83
lines changed

packages/cli/src/commands/__tests__/__snapshots__/serve.func.ts.snap

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/cli/src/commands/__tests__/serve.func.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { httpLoaderInstance } from '@stoplight/prism-core';
2+
import { createServer } from '@stoplight/prism-http-server';
3+
import Serve from '../serve';
4+
5+
const listenMock = jest.fn();
6+
7+
jest.mock('@stoplight/prism-core');
8+
jest.mock('@stoplight/prism-http-server', () => ({
9+
createServer: jest.fn(() => ({ listen: listenMock })),
10+
}));
11+
12+
describe('serve command', () => {
13+
beforeEach(() => {
14+
(createServer as jest.Mock).mockClear();
15+
});
16+
17+
test('starts filesystem server variant', async () => {
18+
await Serve.run(['-s', '/path/to']);
19+
20+
expect(createServer).toHaveBeenLastCalledWith(
21+
{ path: '/path/to' },
22+
{ components: { config: { mock: false } } }
23+
);
24+
25+
expect(listenMock).toHaveBeenLastCalledWith(4010);
26+
});
27+
28+
test('starts http server variant', async () => {
29+
await Serve.run(['-s', 'http://path.to/spec.oas2.yaml']);
30+
31+
expect(createServer).toHaveBeenLastCalledWith(
32+
{ url: 'http://path.to/spec.oas2.yaml' },
33+
{ components: { config: { mock: false }, loader: httpLoaderInstance } }
34+
);
35+
36+
expect(listenMock).toHaveBeenLastCalledWith(4010);
37+
});
38+
});

packages/cli/src/commands/serve.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Command, flags as oflags } from '@oclif/command';
2-
import { TPrismHttpComponents } from '@stoplight/prism-http';
2+
import { httpLoaderInstance } from '@stoplight/prism-core';
33
import { createServer } from '@stoplight/prism-http-server';
4-
import { existsSync, statSync } from 'fs';
54

65
export default class Serve extends Command {
76
public static description = 'Start a server with the given spec file';
@@ -23,35 +22,24 @@ export default class Serve extends Command {
2322
};
2423

2524
public async run() {
26-
const { flags } = this.parse(Serve);
27-
const { port, spec, mock } = flags;
25+
const {
26+
flags: { spec, mock, port },
27+
} = this.parse(Serve);
28+
29+
const server =
30+
spec && isHttp(spec)
31+
? createServer(
32+
{ url: spec },
33+
{ components: { config: { mock }, loader: httpLoaderInstance } }
34+
)
35+
: createServer({ path: spec }, { components: { config: { mock } } });
2836

29-
await this.validateSpecPath(spec);
30-
31-
const components: TPrismHttpComponents<any> = !mock ? { config: { mock: false } } : {};
32-
const server = createServer({ path: spec }, { components });
3337
const address = await server.listen(port as number);
3438

3539
this.log(address);
3640
}
41+
}
3742

38-
private validateSpecPath(path?: string) {
39-
if (!path) {
40-
return;
41-
}
42-
43-
if (!existsSync(path)) {
44-
this.error(`Non-existing path to spec supplied: ${path}`);
45-
this.exit(0x01);
46-
return;
47-
}
48-
49-
const stats = statSync(path);
50-
51-
if (stats.isDirectory()) {
52-
this.error(`Supplied spec path points to directory. Only files are supported.`);
53-
this.exit(0x02);
54-
return;
55-
}
56-
}
43+
function isHttp(spec: string) {
44+
return !!spec.match(/^https?:\/\//);
5745
}

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
},
1818
"dependencies": {
1919
"@stoplight/graphite": "3.5.x",
20+
"axios": "0.x.x",
2021
"lodash": "4.x.x",
2122
"tslib": "1.9.x"
2223
},

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './types';
22
export * from './factory';
33
export * from './loaders/filesystem';
4+
export * from './loaders/http';
45
export * from './utils';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`filesystemLoader throws error when supplied with a directory 1`] = `"Non-existing path to spec supplied: a path"`;
4+
5+
exports[`filesystemLoader throws error when supplied with non-existing path 1`] = `"Supplied spec path points to directory. Only files are supported."`;

packages/core/src/loaders/filesystem/__tests__/filesystemLoader.spec.ts renamed to packages/core/src/loaders/filesystem/__tests__/filesystemLoader.unit.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { FilesystemLoader } from '../../../../src';
2-
jest.mock('../../../../src/utils/graphFacade');
3-
import { GraphFacade } from '../../../../src/utils/graphFacade';
1+
import * as fs from 'fs';
2+
import { FilesystemLoader } from '../';
3+
import { GraphFacade } from '../../../utils/graphFacade';
4+
jest.mock('../../../utils/graphFacade');
5+
jest.mock('fs');
46

57
describe('filesystemLoader', () => {
68
const fakeHttpOperations = ['a', 'b', 'c'];
@@ -11,12 +13,15 @@ describe('filesystemLoader', () => {
1113
graphFacadeMock = new GraphFacade();
1214
createFileSystemNodeMock = graphFacadeMock.createFilesystemNode as jest.Mock;
1315
createFileSystemNodeMock.mockResolvedValue(true);
16+
1417
Object.defineProperty(graphFacadeMock, 'httpOperations', {
1518
get: jest.fn().mockReturnValue(fakeHttpOperations),
1619
});
1720
});
1821

1922
test('given no opts should delegate to graph facade for http operations', async () => {
23+
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true);
24+
jest.spyOn(fs, 'statSync').mockReturnValueOnce({ isDirectory: () => false });
2025
const filesystemLoader = new FilesystemLoader(graphFacadeMock);
2126

2227
const operations = await filesystemLoader.load();
@@ -26,11 +31,27 @@ describe('filesystemLoader', () => {
2631
});
2732

2833
test('given opts should delegate to graph facade for http operations', async () => {
34+
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true);
35+
jest.spyOn(fs, 'statSync').mockReturnValueOnce({ isDirectory: () => false });
36+
2937
const filesystemLoader = new FilesystemLoader(graphFacadeMock);
3038

3139
const operations = await filesystemLoader.load({ path: 'a path' });
3240

3341
expect(createFileSystemNodeMock).toHaveBeenCalledWith('a path');
3442
expect(operations).toBe(fakeHttpOperations);
3543
});
44+
45+
it('throws error when supplied with non-existing path', async () => {
46+
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true);
47+
jest.spyOn(fs, 'statSync').mockReturnValueOnce({ isDirectory: () => true });
48+
const filesystemLoader = new FilesystemLoader(graphFacadeMock);
49+
return expect(filesystemLoader.load({ path: 'a path' })).rejects.toThrowErrorMatchingSnapshot();
50+
});
51+
52+
it('throws error when supplied with a directory', async () => {
53+
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false);
54+
const filesystemLoader = new FilesystemLoader(graphFacadeMock);
55+
return expect(filesystemLoader.load({ path: 'a path' })).rejects.toThrowErrorMatchingSnapshot();
56+
});
3657
});

packages/core/src/loaders/filesystem/filesystemLoader.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IHttpOperation } from '@stoplight/types';
2+
import { existsSync, statSync } from 'fs';
23
import { IFilesystemLoaderOpts } from '../../types';
34
import { GraphFacade } from '../../utils/graphFacade';
45

@@ -12,8 +13,20 @@ export class FilesystemLoader {
1213
* There is no way to filter by a type in runtime :(
1314
*/
1415
public async load(/*<Resource>*/ _opts?: IFilesystemLoaderOpts): Promise<IHttpOperation[]> {
15-
const fsPath = _opts ? _opts.path : DEFAULT_PATH;
16+
const fsPath = _opts && _opts.path ? _opts.path : DEFAULT_PATH;
17+
18+
if (!existsSync(fsPath)) {
19+
throw new Error(`Non-existing path to spec supplied: ${fsPath}`);
20+
}
21+
22+
const stats = statSync(fsPath);
23+
24+
if (stats.isDirectory()) {
25+
throw new Error(`Supplied spec path points to directory. Only files are supported.`);
26+
}
27+
1628
await this.graphFacade.createFilesystemNode(fsPath);
29+
1730
return this.graphFacade.httpOperations;
1831
}
1932
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { HttpLoader } from '../';
2+
jest.mock('../../../utils/graphFacade');
3+
jest.mock('axios');
4+
import axios from 'axios';
5+
import { GraphFacade } from '../../../utils/graphFacade';
6+
7+
describe('httpLoader', () => {
8+
const fakeHttpOperations = ['a', 'b', 'c'];
9+
let graphFacadeMock: any;
10+
11+
beforeEach(() => {
12+
graphFacadeMock = new GraphFacade();
13+
Object.defineProperty(graphFacadeMock, 'httpOperations', {
14+
get: jest.fn().mockReturnValue(fakeHttpOperations),
15+
});
16+
((axios as unknown) as jest.Mock).mockResolvedValue({ data: 'a data' });
17+
});
18+
19+
test('given no opts should delegate to graph facade for http operations', async () => {
20+
const httpLoader = new HttpLoader(graphFacadeMock);
21+
22+
const operations = await httpLoader.load({ url: 'a url' });
23+
24+
expect(axios).toHaveBeenCalledWith({ url: 'a url', transformResponse: expect.any(Function) });
25+
expect(operations).toBe(fakeHttpOperations);
26+
});
27+
});

0 commit comments

Comments
 (0)