specmatic 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.babelrc +8 -12
  2. package/.github/dependabot.yml +18 -0
  3. package/.github/workflows/publish.yml +22 -0
  4. package/.github/workflows/test.yml +21 -0
  5. package/CONTRIBUTING.MD +17 -41
  6. package/README.md +152 -22
  7. package/dist/app.d.js +2 -1
  8. package/dist/bin/command.line.d.ts +2 -0
  9. package/dist/bin/command.line.js +36 -0
  10. package/dist/bin/index.d.ts +2 -0
  11. package/dist/bin/index.js +4 -5
  12. package/dist/common/logger.d.ts +3 -0
  13. package/dist/common/logger.js +62 -0
  14. package/dist/common/runner.d.ts +4 -0
  15. package/dist/common/runner.js +53 -0
  16. package/dist/config.d.ts +2 -0
  17. package/dist/config.js +6 -7
  18. package/dist/core/index.d.ts +24 -0
  19. package/dist/core/index.js +297 -0
  20. package/dist/core/shutdownUtils.d.ts +2 -0
  21. package/dist/core/shutdownUtils.js +42 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +100 -9
  24. package/dist/kafka/index.d.ts +14 -0
  25. package/dist/kafka/index.js +158 -0
  26. package/global.d.ts +1 -0
  27. package/jest.config.json +5 -0
  28. package/package.json +50 -23
  29. package/specmatic.jar +0 -0
  30. package/src/app.d.ts +1 -1
  31. package/src/bin/__tests__/command.line.ts +40 -0
  32. package/src/bin/command.line.ts +35 -0
  33. package/src/bin/index.ts +2 -2
  34. package/src/common/__tests__/logger.ts +85 -0
  35. package/src/common/logger.ts +49 -0
  36. package/src/common/runner.ts +44 -0
  37. package/src/config.ts +2 -3
  38. package/src/core/__tests__/end.points.api.ts +103 -0
  39. package/src/core/__tests__/print.jar.version.ts +28 -0
  40. package/src/core/__tests__/set.expectation.ts +54 -0
  41. package/src/core/__tests__/set.test.results.ts +62 -0
  42. package/src/core/__tests__/stub.ts +136 -0
  43. package/src/core/__tests__/test.ts +176 -0
  44. package/src/core/index.ts +261 -0
  45. package/src/core/shutdownUtils.ts +21 -0
  46. package/src/downloadSpecmaticJar.js +30 -0
  47. package/src/index.ts +19 -2
  48. package/src/kafka/index.ts +140 -0
  49. package/test-resources/sample-junit-result-corrupt.xml +175 -0
  50. package/test-resources/sample-junit-result-generative.xml +389 -0
  51. package/test-resources/sample-junit-result-multiple.xml +304 -0
  52. package/test-resources/sample-junit-result-no-testname.xml +179 -0
  53. package/test-resources/sample-junit-result-single.xml +92 -0
  54. package/test-resources/sample-junit-result-skipped.xml +198 -0
  55. package/tsconfig.json +106 -20
  56. package/.github/workflows/npm-publish.yml +0 -25
  57. package/.vscode/settings.json +0 -7
  58. package/dist/bin/core.js +0 -30
  59. package/dist/lib/index.js +0 -107
  60. package/src/bin/__tests__/core.ts +0 -13
  61. package/src/bin/core.ts +0 -22
  62. package/src/lib/__tests__/index.ts +0 -122
  63. package/src/lib/index.ts +0 -84
  64. /package/{mockStub.json → test-resources/sample-mock-stub.json} +0 -0
  65. /package/{specmatic.json → test-resources/sample-specmatic.json} +0 -0
@@ -0,0 +1,85 @@
1
+ import { initLogger } from '../logger'
2
+ import { when } from 'jest-when'
3
+
4
+ import fs from 'fs'
5
+
6
+ beforeEach(() => {
7
+ jest.resetAllMocks()
8
+ delete process.env.SPECMATIC_LOG_LEVEL
9
+ })
10
+
11
+ test('logging level is debug as configured in package.json', () => {
12
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
13
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {"logLevel": "debug"}}')
14
+ const logger = initLogger()
15
+ expect(logger.level).toBe('debug')
16
+ })
17
+
18
+ test('logging level is info as configured in package.json', () => {
19
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
20
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {"logLevel": "info"}}')
21
+ const logger = initLogger()
22
+ expect(logger.level).toBe('info')
23
+ })
24
+
25
+ test('defaults logging level to warn when logLevel setting does not exist with specmatic configuration', () => {
26
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
27
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {}}')
28
+ const logger = initLogger()
29
+ expect(logger.level).toBe('warn')
30
+ })
31
+
32
+ test('defaults logging level to warn when specmatic configuration does not exist', () => {
33
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
34
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{}')
35
+ const logger = initLogger()
36
+ expect(logger.level).toBe('warn')
37
+ })
38
+
39
+ test('defaults logging level to warn when package.json does not exist', () => {
40
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
41
+ when(packageJsonFileReadSpy)
42
+ .calledWith('./package.json')
43
+ .mockImplementation(() => {
44
+ throw new Error()
45
+ })
46
+ const logger = initLogger()
47
+ expect(logger.level).toBe('warn')
48
+ })
49
+
50
+ test('log level is read from environment variable', () => {
51
+ process.env.SPECMATIC_LOG_LEVEL = 'debug'
52
+ const logger = initLogger()
53
+ expect(logger.level).toBe('debug')
54
+ })
55
+
56
+ test('log level set in environment variable takes precendece over specmatic.json', () => {
57
+ process.env.SPECMATIC_LOG_LEVEL = 'error'
58
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
59
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {"logLevel": "debug"}}')
60
+ const logger = initLogger()
61
+ expect(logger.level).toBe('error')
62
+ })
63
+
64
+ test('log level set in environment variable is used instead of default', () => {
65
+ process.env.SPECMATIC_LOG_LEVEL = 'error'
66
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
67
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{}')
68
+ const logger = initLogger()
69
+ expect(logger.level).toBe('error')
70
+ })
71
+
72
+ test('uses default log level "warn" when log level configured in specmatic.json is invalid', () => {
73
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
74
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {"logLevel": "silly"}}')
75
+ const logger = initLogger()
76
+ expect(logger.level).toBe('warn')
77
+ })
78
+
79
+ test('ignores log level configured in environment variable when invalid', () => {
80
+ process.env.SPECMATIC_LOG_LEVEL = 'silly'
81
+ const packageJsonFileReadSpy = jest.spyOn(fs, 'readFileSync')
82
+ when(packageJsonFileReadSpy).calledWith('./package.json').mockReturnValue('{"specmatic": {"logLevel": "debug"}}')
83
+ const logger = initLogger()
84
+ expect(logger.level).toBe('debug')
85
+ })
@@ -0,0 +1,49 @@
1
+ import { config, createLogger, format, transports } from 'winston'
2
+ import fs from 'fs'
3
+
4
+ const logFormat = format.printf(({ level, message, label, timestamp }) => {
5
+ return `[${label}] ${timestamp} ${level}: ${message}`
6
+ })
7
+
8
+ function getSpecmaticConfig() {
9
+ const packageJsonPath = './package.json'
10
+ var specmaticConfig
11
+ if (fs.existsSync(packageJsonPath)) {
12
+ let packageJsonContent
13
+ try {
14
+ packageJsonContent = fs.readFileSync(packageJsonPath)
15
+ } catch (error) {
16
+ packageJsonContent = '{}'
17
+ }
18
+ const packageConfig = JSON.parse(packageJsonContent as unknown as string)
19
+ specmaticConfig = packageConfig.specmatic
20
+ }
21
+ return specmaticConfig || {}
22
+ }
23
+
24
+ function getLogLevel() {
25
+ let logLevel
26
+ if (process.env.SPECMATIC_LOG_LEVEL) {
27
+ logLevel = process.env.SPECMATIC_LOG_LEVEL
28
+ }
29
+ if (!logLevel || !config.syslog.levels[logLevel]) {
30
+ const specmaticConfig = getSpecmaticConfig()
31
+ logLevel = specmaticConfig.logLevel
32
+ }
33
+ if (!logLevel || !config.syslog.levels[logLevel]) {
34
+ logLevel = 'warn'
35
+ }
36
+ return logLevel
37
+ }
38
+
39
+ export function initLogger() {
40
+ let logLevel = getLogLevel()
41
+ const logger = createLogger({
42
+ level: logLevel,
43
+ format: format.combine(format.label({ label: 'specmatic' }), format.timestamp({ format: 'DD-MM-YYYY HH:mm:ss' }), logFormat),
44
+ transports: [new transports.Console()],
45
+ })
46
+ return logger
47
+ }
48
+
49
+ export default initLogger()
@@ -0,0 +1,44 @@
1
+ import execSh from 'exec-sh';
2
+ import path from 'path';
3
+ import { specmaticCoreJarName, specmaticKafkaJarName } from '../config';
4
+ import logger from '../common/logger';
5
+ import { ChildProcess, spawn, SpawnOptions } from 'child_process';
6
+
7
+ const callCore = (args: string, done: (error: any) => void, onOutput: (message: string, error: boolean) => void): ChildProcess => {
8
+ const rootPath = path.resolve(__dirname, '..', '..');
9
+ const specmaticJarPath = path.resolve(rootPath, specmaticCoreJarName);
10
+ logger.debug(`CLI: Specmatic jar path: ${specmaticJarPath}`);
11
+ return callJar(specmaticJarPath, args, done, onOutput);
12
+ };
13
+
14
+ const callKafka = (args: string, done: (error: any) => void, onOutput: (message: string, error: boolean) => void): ChildProcess => {
15
+ const rootPath = path.resolve(__dirname, '..', '..', '..', 'specmatic-beta', 'kafka');
16
+ const specmaticJarPath = path.resolve(rootPath, specmaticKafkaJarName);
17
+ logger.debug(`CLI: Specmatic jar path: ${specmaticJarPath}`);
18
+ return callJar(specmaticJarPath, args, done, onOutput);
19
+ };
20
+
21
+ function callJar(jarPath: string, args: string, done: (error: any) => void, onOutput: (message: string, error: boolean) => void) {
22
+ let argsList = [];
23
+ if (process.env['endpointsAPI']) {
24
+ argsList.push(`-DendpointsAPI="${process.env['endpointsAPI']}"`);
25
+ }
26
+ argsList = argsList.concat(['-jar', `"${jarPath}"`, args]);
27
+ const javaProcess: ChildProcess = spawn('java', argsList, { stdio: 'pipe', stderr: 'pipe', shell: true } as SpawnOptions);
28
+ javaProcess.stdout?.on('data', function (data: String) {
29
+ onOutput(`${data}`, false);
30
+ });
31
+ javaProcess.stderr?.on('data', function (data: String) {
32
+ onOutput(`${data}`, true);
33
+ });
34
+ javaProcess.on('close', function (code:number) {
35
+ if (code) {
36
+ done(new Error('Command exited with non zero code: ' + code))
37
+ } else {
38
+ done(null);
39
+ }
40
+ });
41
+ return javaProcess;
42
+ }
43
+
44
+ export { callCore, callKafka };
package/src/config.ts CHANGED
@@ -1,3 +1,2 @@
1
- export const specmaticJarPathLocal = './node_modules/specmatic/specmatic.jar';
2
- export const specmaticJarPathRemote = 'https://github.com/znsio/specmatic/releases/download/0.24.2/specmatic.jar';
3
- export const specmatic = 'specmatic.json'
1
+ export const specmaticCoreJarName = 'specmatic.jar';
2
+ export const specmaticKafkaJarName = 'specmatic-kafka-all.jar';
@@ -0,0 +1,103 @@
1
+ import * as specmatic from '..'
2
+ import express from 'express'
3
+ import request from 'supertest'
4
+
5
+ let server
6
+ var app
7
+
8
+ beforeEach(async () => {
9
+ app = express()
10
+ server = await specmatic.startApiCoverageServer(app)
11
+ })
12
+
13
+ afterEach(done => {
14
+ server.close(() => {
15
+ done()
16
+ })
17
+ })
18
+
19
+ test('adds an environment variable indicating api endpoits route is configured', async () => {
20
+ app.get('/', () => {})
21
+ expect(process.env.endpointsAPI).toMatch(/http:\/\/.+?:[0-9]+?/)
22
+ })
23
+
24
+ test('gives list of end points for a single route defined on express app', async () => {
25
+ app.get('/', () => {})
26
+ const res = await request(server).get('/').accept('application/json').expect(200)
27
+ const response = generateResponseObject({ '/': ['GET'] })
28
+ expect(res.body).toStrictEqual(response)
29
+ })
30
+
31
+ test('gives list of end points for multiple routes defined on express app', async () => {
32
+ app.get('/', () => {})
33
+ app.post('/', () => {})
34
+ app.get('/ping', () => {})
35
+ const res = await request(server).get('/').accept('application/json').expect(200)
36
+ const response = generateResponseObject({ '/': ['GET', 'POST'], '/ping': ['GET'] })
37
+ expect(res.body).toStrictEqual(response)
38
+ })
39
+
40
+ test('gives list of end points for a multiple routes defined on multiple routers', async () => {
41
+ const userRouter = express.Router()
42
+ userRouter.get('/', function (req, res) {})
43
+ userRouter.post('/', function (req, res) {})
44
+ userRouter.delete('/', function (req, res) {})
45
+ userRouter.put('/', function (req, res) {})
46
+
47
+ const productRouter = express.Router()
48
+ productRouter.get('/', function (req, res) {})
49
+ productRouter.post('/', function (req, res) {})
50
+
51
+ app.use('/user', userRouter)
52
+ app.use('/product', productRouter)
53
+
54
+ const res = await request(server).get('/').accept('application/json').expect(200)
55
+ const response = generateResponseObject({ '/product': ['GET', 'POST'], '/user': ['DELETE', 'GET', 'POST', 'PUT'] })
56
+ expect(res.body).toStrictEqual(response)
57
+ })
58
+
59
+ test('coverts express route variable syntax to spring request mapping syntax', async () => {
60
+ app.get('/:id', () => {})
61
+ const res = await request(server).get('/').accept('application/json').expect(200)
62
+ const response = generateResponseObject({ '/{id}': ['GET'] })
63
+ expect(res.body).toStrictEqual(response)
64
+ })
65
+
66
+ test('coverts express route multiple variable syntax to spring request mapping syntax', async () => {
67
+ app.get('/:store/:id', () => {})
68
+ const res = await request(server).get('/').accept('application/json').expect(200)
69
+ const response = generateResponseObject({ '/{store}/{id}': ['GET'] })
70
+ expect(res.body).toStrictEqual(response)
71
+ })
72
+
73
+ test('removes escaped dot and backslash in url', async () => {
74
+ app.get('/v1\\.0/:store/:id', () => {})
75
+ const res = await request(server).get('/').accept('application/json').expect(200)
76
+ const response = generateResponseObject({ '/v1.0/{store}/{id}': ['GET'] })
77
+ expect(res.body).toStrictEqual(response)
78
+ })
79
+
80
+ function generateResponseObject(endPoints: { [key: string]: string[] }) {
81
+ const structure = {
82
+ contexts: {
83
+ application: {
84
+ mappings: {
85
+ dispatcherServlets: {
86
+ dispatcherServlet: [],
87
+ },
88
+ },
89
+ },
90
+ },
91
+ }
92
+ Object.keys(endPoints).map(key => {
93
+ structure.contexts.application.mappings.dispatcherServlets.dispatcherServlet.push({
94
+ details: {
95
+ requestMappingConditions: {
96
+ methods: endPoints[key],
97
+ patterns: [key],
98
+ },
99
+ },
100
+ } as never)
101
+ })
102
+ return structure
103
+ }
@@ -0,0 +1,28 @@
1
+ import path from 'path';
2
+ import { ChildProcess, spawn } from 'child_process';
3
+ import { mock as jestMock, mockReset } from 'jest-mock-extended';
4
+ import { Readable } from 'stream';
5
+ import * as specmatic from '../..';
6
+ import { specmaticCoreJarName } from '../../config';
7
+
8
+ jest.mock('child_process');
9
+
10
+ const SPECMATIC_JAR_PATH = path.resolve(__dirname, '..', '..', '..', specmaticCoreJarName);
11
+
12
+ const javaProcessMock = jestMock<ChildProcess>();
13
+ const readableMock = jestMock<Readable>();
14
+ javaProcessMock.stdout = readableMock;
15
+ javaProcessMock.stderr = readableMock;
16
+
17
+ beforeEach(() => {
18
+ jest.resetAllMocks();
19
+ });
20
+
21
+ test('prints bundled jar version', () => {
22
+ spawn.mockReturnValue(javaProcessMock);
23
+
24
+ specmatic.printJarVersion();
25
+
26
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
27
+ expect(spawn.mock.calls[0][1][2]).toBe('--version');
28
+ });
@@ -0,0 +1,54 @@
1
+ import axios from 'axios';
2
+ import path from 'path';
3
+ import * as specmatic from '../..';
4
+ import mockStub from '../../../test-resources/sample-mock-stub.json';
5
+ import { mockReset } from 'jest-mock-extended';
6
+
7
+ jest.mock('axios');
8
+
9
+ const STUB_PATH = 'test-resources/sample-mock-stub.json';
10
+
11
+ beforeEach(() => {
12
+ mockReset(axios);
13
+ });
14
+
15
+ test('setExpectations with default baseUrl', async () => {
16
+ axios.post.mockResolvedValue({ status: 200 });
17
+ await expect(specmatic.setExpectations(path.resolve(STUB_PATH))).toResolve();
18
+
19
+ expect(axios.post).toHaveBeenCalledTimes(1);
20
+ expect(axios.post.mock.calls[0][0]).toBe('http://localhost:9000/_specmatic/expectations');
21
+ expect(axios.post.mock.calls[0][1]).toMatchObject(mockStub);
22
+ });
23
+
24
+ test('setExpectations with a different baseUrl for the stub server', async () => {
25
+ axios.post.mockResolvedValue({ status: 200 });
26
+ const stubServerBaseUrl = 'http://localhost:8000';
27
+
28
+ await expect(specmatic.setExpectations(path.resolve(STUB_PATH), stubServerBaseUrl)).toResolve();
29
+
30
+ expect(axios.post).toHaveBeenCalledTimes(1);
31
+ expect(axios.post.mock.calls[0][0]).toBe(`${stubServerBaseUrl}/_specmatic/expectations`);
32
+ expect(axios.post.mock.calls[0][1]).toMatchObject(mockStub);
33
+ });
34
+
35
+ test('setExpectations notifies when it fails', async () => {
36
+ axios.post.mockReturnValue(Promise.reject());
37
+
38
+ await expect(specmatic.setExpectations(path.resolve(STUB_PATH))).toReject();
39
+
40
+ expect(axios.post).toHaveBeenCalledTimes(1);
41
+ expect(axios.post.mock.calls[0][0]).toBe('http://localhost:9000/_specmatic/expectations');
42
+ expect(axios.post.mock.calls[0][1]).toMatchObject(mockStub);
43
+ });
44
+
45
+ test('setExpectations notifies as failure when status code is not 200', async () => {
46
+ axios.post.mockReturnValue(Promise.reject('Cannot set expectations'));
47
+ const stubServerBaseUrl = 'http://localhost:8000';
48
+
49
+ await expect(specmatic.setExpectations(path.resolve(STUB_PATH), stubServerBaseUrl)).toReject();
50
+
51
+ expect(axios.post).toHaveBeenCalledTimes(1);
52
+ expect(axios.post.mock.calls[0][0]).toBe(`${stubServerBaseUrl}/_specmatic/expectations`);
53
+ expect(axios.post.mock.calls[0][1]).toMatchObject(mockStub);
54
+ });
@@ -0,0 +1,62 @@
1
+ import path from 'path';
2
+ import { copyFileSync, mkdirSync, existsSync } from 'fs';
3
+ import * as specmatic from '../..';
4
+
5
+ test('invokes the test function', () => {
6
+ const cb = jest.fn();
7
+ copyReportFile();
8
+ specmatic.showTestResults(cb);
9
+ expect(cb).toHaveBeenCalledTimes(5);
10
+ expect(() => cb.mock.calls[0][1]()).not.toThrow();
11
+ expect(() => cb.mock.calls[1][1]()).not.toThrow();
12
+ expect(() => cb.mock.calls[2][1]()).not.toThrow();
13
+ expect(() => cb.mock.calls[3][1]()).toThrow();
14
+ expect(() => cb.mock.calls[4][1]()).toThrow();
15
+ });
16
+
17
+ test('works with junit report from generative tests mode', () => {
18
+ const cb = jest.fn();
19
+ copyReportFileWithName('sample-junit-result-generative.xml');
20
+ specmatic.showTestResults(cb);
21
+ expect(cb).toHaveBeenCalledTimes(4);
22
+ });
23
+
24
+ test('shows "No Name" when test name cannot be found within system-out tag', () => {
25
+ const cb = jest.fn();
26
+ copyReportFileWithName('sample-junit-result-no-testname.xml');
27
+ specmatic.showTestResults(cb);
28
+ expect(cb).toHaveBeenCalledTimes(1);
29
+ expect(cb).toHaveBeenCalledWith('No Name', expect.any(Function));
30
+ });
31
+
32
+ test('shows "No Name" where system-out tag does not exist', () => {
33
+ const cb = jest.fn();
34
+ copyReportFileWithName('sample-junit-result-corrupt.xml');
35
+ specmatic.showTestResults(cb);
36
+ expect(cb).toHaveBeenCalledTimes(1);
37
+ expect(cb).toHaveBeenCalledWith('No Name', expect.any(Function));
38
+ });
39
+
40
+ test('handles report having skipped tests', () => {
41
+ const cb = jest.fn();
42
+ copyReportFileWithName('sample-junit-result-skipped.xml');
43
+ specmatic.showTestResults(cb);
44
+ expect(cb).toHaveBeenCalledTimes(3);
45
+ expect(() => cb.mock.calls[0][1]()).toThrow();
46
+ expect(() => cb.mock.calls[1][1]()).not.toThrow();
47
+ expect(() => cb.mock.calls[2][1]()).not.toThrow();
48
+ });
49
+
50
+ function copyReportFile() {
51
+ copyReportFileWithName('sample-junit-result-multiple.xml');
52
+ }
53
+
54
+ function copyReportFileWithName(fileName: string) {
55
+ const destDir = path.resolve('dist/test-report');
56
+ if (!existsSync(destDir)) {
57
+ mkdirSync(destDir, { recursive: true });
58
+ }
59
+ const srcPath = path.resolve('test-resources', fileName);
60
+ const destPath = path.resolve(destDir, 'TEST-junit-jupiter.xml');
61
+ copyFileSync(srcPath, destPath);
62
+ }
@@ -0,0 +1,136 @@
1
+ import path from 'path';
2
+ import { ChildProcess, spawn } from 'child_process';
3
+ import { mock as jestMock, mockReset } from 'jest-mock-extended';
4
+ import { Readable } from 'stream';
5
+ import terminate from 'terminate/promise';
6
+
7
+ import * as specmatic from '../..';
8
+ import { specmaticCoreJarName } from '../../config';
9
+ import { Stub } from '..';
10
+ import * as shutDown from '../shutdownUtils'
11
+
12
+
13
+ jest.mock('child_process');
14
+ jest.mock('terminate');
15
+ jest.mock('../shutdownUtils')
16
+
17
+ const SPECMATIC_JAR_PATH = path.resolve(__dirname, '..', '..', '..', specmaticCoreJarName);
18
+ const HOST = 'localhost';
19
+ const PORT = 8000;
20
+
21
+ const javaProcessMock = jestMock<ChildProcess>();
22
+ const readableMock = jestMock<Readable>();
23
+ javaProcessMock.stdout = readableMock;
24
+ javaProcessMock.stderr = readableMock;
25
+
26
+ const stubUrl = `http://${HOST}:${PORT}`;
27
+ const stub = new Stub(HOST, PORT, stubUrl, javaProcessMock);
28
+
29
+ beforeEach(() => {
30
+ jest.resetAllMocks();
31
+ });
32
+
33
+ test('starts the specmatic stub server', async () => {
34
+ spawn.mockReturnValue(javaProcessMock);
35
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
36
+
37
+ await expect(specmatic.startStub(HOST, PORT)).resolves.toStrictEqual(stub);
38
+
39
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
40
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
41
+ });
42
+
43
+ test('notifies when start fails due to port not available', async () => {
44
+ spawn.mockReturnValue(javaProcessMock);
45
+ setTimeout(() => readableMock.on.mock.calls[0][1]('Address already in use'), 0);
46
+
47
+ await expect(specmatic.startStub(HOST, PORT)).toReject();
48
+
49
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
50
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
51
+ });
52
+
53
+ test('returns host, port and stub url', async () => {
54
+ spawn.mockReturnValue(javaProcessMock);
55
+ const randomPort = 62269;
56
+ const stubUrl = `http://${HOST}:${randomPort}`;
57
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}. Ctrl + C to stop.`), 0);
58
+
59
+ const stub = new Stub(HOST, randomPort, stubUrl, javaProcessMock);
60
+
61
+ await expect(specmatic.startStub(HOST, PORT)).resolves.toStrictEqual(stub);
62
+
63
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
64
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
65
+ });
66
+
67
+ test('fails if stub url is not available in start up message', async () => {
68
+ spawn.mockReturnValue(javaProcessMock);
69
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running`), 0);
70
+
71
+ await expect(specmatic.startStub(HOST, PORT)).toReject();
72
+
73
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
74
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
75
+ });
76
+
77
+ test('fails if host info is not available in start up message', async () => {
78
+ spawn.mockReturnValue(javaProcessMock);
79
+ const stubUrl = `http://`;
80
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
81
+
82
+ await expect(specmatic.startStub(HOST, PORT)).toReject();
83
+
84
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
85
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
86
+ });
87
+
88
+ test('fails if port info is not available in start up message', async () => {
89
+ spawn.mockReturnValue(javaProcessMock);
90
+ const stubUrl = `http://${HOST}`;
91
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
92
+
93
+ await expect(specmatic.startStub(HOST, PORT)).toReject();
94
+
95
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
96
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT}`);
97
+ });
98
+
99
+ test('host and port are optional', async () => {
100
+ spawn.mockReturnValue(javaProcessMock);
101
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
102
+
103
+ await expect(specmatic.startStub()).resolves.toStrictEqual(stub);
104
+
105
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
106
+ expect(spawn.mock.calls[0][1][2]).toBe('stub');
107
+ });
108
+
109
+ test('takes additional pass through arguments', async () => {
110
+ spawn.mockReturnValue(javaProcessMock);
111
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
112
+
113
+ await expect(specmatic.startStub(HOST, PORT, ['p1', 'p2'])).resolves.toStrictEqual(stub);
114
+
115
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
116
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT} p1 p2`);
117
+ });
118
+
119
+ test('additional pass through arguments can be string or number', async () => {
120
+ spawn.mockReturnValue(javaProcessMock);
121
+ setTimeout(() => readableMock.on.mock.calls[0][1](`Stub server is running on ${stubUrl}`), 0);
122
+
123
+ await expect(specmatic.startStub(HOST, PORT, ['p1', 123])).resolves.toStrictEqual(stub);
124
+
125
+ expect(spawn.mock.calls[0][1][1]).toBe(`"${path.resolve(SPECMATIC_JAR_PATH)}"`);
126
+ expect(spawn.mock.calls[0][1][2]).toBe(`stub --host=${HOST} --port=${PORT} p1 123`);
127
+ });
128
+
129
+ test('stopStub method stops any running stub server', async () => {
130
+ shutDown.gracefulShutdown.mockResolvedValue(true);
131
+ specmatic.stopStub(stub);
132
+
133
+ expect(readableMock.removeAllListeners).toHaveBeenCalledTimes(2);
134
+ expect(javaProcessMock.removeAllListeners).toHaveBeenCalledTimes(1);
135
+ expect(shutDown.gracefulShutdown).toHaveBeenCalledTimes(1);
136
+ });