specmatic 0.65.2 → 0.67.0

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.
@@ -3,120 +3,207 @@ import fetch from 'node-fetch';
3
3
  import path from 'path';
4
4
  import { ChildProcess } from 'child_process';
5
5
  import { mock as jestMock } from 'jest-mock-extended';
6
- import { Readable } from "stream";
6
+ import { Readable } from 'stream';
7
+ import { copyFileSync, mkdirSync, existsSync } from 'fs';
7
8
 
8
- import * as specmatic from '../';
9
- import { startStub, stopStub, printJarVersion, setExpectations } from '../';
9
+ import * as specmatic from '../../';
10
10
  import { specmaticJarPathLocal } from '../../config';
11
- import mockStub from '../../../mockStub.json';
11
+ import mockStub from '../../../test-resources/sample-mock-stub.json';
12
12
 
13
13
  jest.mock('exec-sh');
14
14
  jest.mock('node-fetch');
15
15
 
16
- const contractsPath = './contracts';
17
- const stubDataPath = './data';
18
- const host = 'localhost';
19
- const port = '8000';
16
+ const STUB_PATH = 'test-resources/sample-mock-stub.json';
17
+ const CONTRACT_YAML_FILE_PATH = './contracts';
18
+ const STUB_DIR_PATH = './data';
19
+ const HOST = 'localhost';
20
+ const PORT = '8000';
21
+
20
22
  const javaProcessMock = jestMock<ChildProcess>();
21
23
  const readableMock = jestMock<Readable>();
22
24
  javaProcessMock.stdout = readableMock;
23
25
  javaProcessMock.stderr = readableMock;
24
26
 
25
27
  beforeEach(() => {
26
- execSh.mockReset();
27
- fetch.mockReset();
28
+ execSh.mockReset();
29
+ fetch.mockReset();
28
30
  });
29
31
 
30
32
  test('startStub method starts the specmatic stub server', async () => {
31
- execSh.mockReturnValue(javaProcessMock);
33
+ execSh.mockReturnValue(javaProcessMock);
32
34
 
33
- startStub(host, port, stubDataPath).then(javaProcess => {
34
- expect(javaProcess).toBe(javaProcessMock);
35
- })
35
+ specmatic.startStub(HOST, PORT, STUB_DIR_PATH).then(javaProcess => {
36
+ expect(javaProcess).toBe(javaProcessMock);
37
+ });
36
38
 
37
- readableMock.on.mock.calls[0][1]("Stub server is running");
39
+ readableMock.on.mock.calls[0][1]('Stub server is running');
38
40
 
39
- expect(execSh).toHaveBeenCalledTimes(1);
40
- expect(execSh.mock.calls[0][0])
41
- .toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} stub --data=${path.resolve(stubDataPath)} --host=${host} --port=${port}`);
41
+ expect(execSh).toHaveBeenCalledTimes(1);
42
+ expect(execSh.mock.calls[0][0]).toBe(
43
+ `java -jar ${path.resolve(specmaticJarPathLocal)} stub --data=${path.resolve(STUB_DIR_PATH)} --host=${HOST} --port=${PORT}`
44
+ );
42
45
  });
43
46
 
44
47
  test('startStub method stubDir is optional', async () => {
45
- execSh.mockReturnValue(javaProcessMock);
48
+ execSh.mockReturnValue(javaProcessMock);
46
49
 
47
- startStub(host, port).then(javaProcess => {
48
- expect(javaProcess).toBe(javaProcessMock);
49
- })
50
+ specmatic.startStub(HOST, PORT).then(javaProcess => {
51
+ expect(javaProcess).toBe(javaProcessMock);
52
+ });
50
53
 
51
- readableMock.on.mock.calls[0][1]("Stub server is running");
54
+ readableMock.on.mock.calls[0][1]('Stub server is running');
52
55
 
53
- expect(execSh).toHaveBeenCalledTimes(1);
54
- expect(execSh.mock.calls[0][0])
55
- .toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} stub --host=${host} --port=${port}`);
56
+ expect(execSh).toHaveBeenCalledTimes(1);
57
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} stub --host=${HOST} --port=${PORT}`);
56
58
  });
57
59
 
58
60
  test('startStub method host and port are optional', async () => {
59
- execSh.mockReturnValue(javaProcessMock);
61
+ execSh.mockReturnValue(javaProcessMock);
60
62
 
61
- startStub().then(javaProcess => {
62
- expect(javaProcess).toBe(javaProcessMock);
63
- })
63
+ specmatic.startStub().then(javaProcess => {
64
+ expect(javaProcess).toBe(javaProcessMock);
65
+ });
64
66
 
65
- readableMock.on.mock.calls[0][1]("Stub server is running");
67
+ readableMock.on.mock.calls[0][1]('Stub server is running');
66
68
 
67
- expect(execSh).toHaveBeenCalledTimes(1);
68
- expect(execSh.mock.calls[0][0])
69
- .toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} stub`);
69
+ expect(execSh).toHaveBeenCalledTimes(1);
70
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} stub`);
70
71
  });
71
72
 
72
73
  test('stopStub method stops any running stub server', () => {
73
- stopStub(javaProcessMock);
74
+ specmatic.stopStub(javaProcessMock);
74
75
 
75
- expect(readableMock.removeAllListeners).toHaveBeenCalledTimes(2);
76
- expect(javaProcessMock.removeAllListeners).toHaveBeenCalledTimes(1);
77
- expect(javaProcessMock.kill).toHaveBeenCalledTimes(1);
76
+ expect(readableMock.removeAllListeners).toHaveBeenCalledTimes(2);
77
+ expect(javaProcessMock.removeAllListeners).toHaveBeenCalledTimes(1);
78
+ expect(javaProcessMock.kill).toHaveBeenCalledTimes(1);
78
79
  });
79
80
 
80
81
  test('test runs the contract tests', function (done) {
81
- specmatic.test(contractsPath, host, port).then(result => {
82
- expect(result).toBeTruthy();
83
- done();
82
+ specmatic.test(HOST, PORT, CONTRACT_YAML_FILE_PATH).then(result => {
83
+ expect(result).toBeTruthy();
84
+ done();
85
+ });
86
+ copyReportFile();
87
+ execSh.mock.calls[0][2](); //Execute the callback
88
+ expect(execSh).toHaveBeenCalledTimes(1);
89
+ expect(execSh.mock.calls[0][0]).toBe(
90
+ `java -jar ${path.resolve(specmaticJarPathLocal)} test ${path.resolve(
91
+ CONTRACT_YAML_FILE_PATH
92
+ )} --junitReportDir=dist/test-report --host=${HOST} --port=${PORT}`
93
+ );
94
+ });
95
+
96
+ test('test runs the contract tests with host and port optional', function (done) {
97
+ specmatic.test().then(result => {
98
+ expect(result).toBeTruthy();
99
+ done();
100
+ });
101
+ copyReportFile();
102
+ execSh.mock.calls[0][2](); //Execute the callback
103
+ expect(execSh).toHaveBeenCalledTimes(1);
104
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} test --junitReportDir=dist/test-report`);
105
+ });
106
+
107
+ test('test runs the contract tests with contracts path optional', function (done) {
108
+ specmatic.test(HOST, PORT).then(result => {
109
+ expect(result).toBeTruthy();
110
+ done();
84
111
  });
112
+ copyReportFile();
85
113
  execSh.mock.calls[0][2](); //Execute the callback
86
114
  expect(execSh).toHaveBeenCalledTimes(1);
87
- expect(execSh.mock.calls[0][0])
88
- .toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} test ${path.resolve(contractsPath)} --host=${host} --port=${port}`);
89
- });
115
+ expect(execSh.mock.calls[0][0]).toBe(
116
+ `java -jar ${path.resolve(specmaticJarPathLocal)} test --junitReportDir=dist/test-report --host=${HOST} --port=${PORT}`
117
+ );
118
+ });
119
+
120
+ test('test runs the contract tests and get summary', function (done) {
121
+ specmatic.test().then(result => {
122
+ expect(result).not.toBeUndefined();
123
+ expect(result!.count).toBe(5);
124
+ expect(result!.success).toBe(3);
125
+ expect(result!.failure).toBe(2);
126
+ done();
127
+ });
128
+ copyReportFile();
129
+ execSh.mock.calls[0][2](); //Execute the callback
130
+ expect(execSh).toHaveBeenCalledTimes(1);
131
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} test --junitReportDir=dist/test-report`);
132
+ });
133
+
134
+ test('test runs the contract tests and get summary when there is just one test', function (done) {
135
+ specmatic.test().then(result => {
136
+ expect(result).not.toBeUndefined();
137
+ expect(result!.count).toBe(1);
138
+ expect(result!.success).toBe(1);
139
+ expect(result!.failure).toBe(0);
140
+ done();
141
+ });
142
+ copyReportFileWithName('sample-junit-result-single.xml');
143
+ execSh.mock.calls[0][2](); //Execute the callback
144
+ expect(execSh).toHaveBeenCalledTimes(1);
145
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} test --junitReportDir=dist/test-report`);
146
+ });
147
+
148
+ test('test invocation makes sure previous junit report if any is deleted', function (done) {
149
+ copyReportFileWithName('sample-junit-result-single.xml');
150
+ specmatic.test(HOST, PORT).then(result => {
151
+ expect(result).toBeTruthy();
152
+ done();
153
+ });
154
+ expect(existsSync(path.resolve('dist/test-report'))).toBeFalsy();
155
+ copyReportFileWithName('sample-junit-result-single.xml');
156
+ execSh.mock.calls[0][2](); //Execute the callback
157
+ });
90
158
 
91
159
  test('printJarVersion', () => {
92
- printJarVersion();
160
+ specmatic.printJarVersion();
93
161
 
94
- expect(execSh).toHaveBeenCalledTimes(1);
95
- expect(execSh.mock.calls[0][0])
96
- .toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} --version`);
162
+ expect(execSh).toHaveBeenCalledTimes(1);
163
+ expect(execSh.mock.calls[0][0]).toBe(`java -jar ${path.resolve(specmaticJarPathLocal)} --version`);
97
164
  });
98
165
 
99
- test('setExpectations with default baseUrl', () => {
100
- fetch.mockReturnValue(Promise.resolve("{}"));
101
- setExpectations(path.resolve('./mockStub.json'));
166
+ test('setExpectations with default baseUrl', done => {
167
+ fetch.mockReturnValue(Promise.resolve('{}'));
168
+ specmatic.setExpectations(path.resolve(STUB_PATH)).then(result => {
169
+ expect(result).toBeTruthy();
170
+ done();
171
+ });
102
172
 
103
- expect(fetch).toHaveBeenCalledTimes(1);
104
- expect(fetch.mock.calls[0][0]).toBe('http://localhost:9000/_specmatic/expectations');
105
- expect(fetch.mock.calls[0][1]).toMatchObject({
106
- method: 'POST',
107
- body: JSON.stringify(mockStub)
108
- });
173
+ expect(fetch).toHaveBeenCalledTimes(1);
174
+ expect(fetch.mock.calls[0][0]).toBe('http://localhost:9000/_specmatic/expectations');
175
+ expect(fetch.mock.calls[0][1]).toMatchObject({
176
+ method: 'POST',
177
+ body: JSON.stringify(mockStub),
178
+ });
109
179
  });
110
180
 
111
- test('setExpectations with a different baseUrl for the stub server', () => {
112
- fetch.mockReturnValue(Promise.resolve("{}"));
113
- const stubServerBaseUrl = 'http://localhost:8000/'
114
- setExpectations(path.resolve('./mockStub.json'), stubServerBaseUrl);
115
-
116
- expect(fetch).toHaveBeenCalledTimes(1);
117
- expect(fetch.mock.calls[0][0]).toBe(`${stubServerBaseUrl}_specmatic/expectations`);
118
- expect(fetch.mock.calls[0][1]).toMatchObject({
119
- method: 'POST',
120
- body: JSON.stringify(mockStub)
121
- });
181
+ test('setExpectations with a different baseUrl for the stub server', done => {
182
+ fetch.mockReturnValue(Promise.resolve('{}'));
183
+ const stubServerBaseUrl = 'http://localhost:8000/';
184
+ specmatic.setExpectations(path.resolve(STUB_PATH), stubServerBaseUrl).then(result => {
185
+ expect(result).toBeTruthy();
186
+ done();
187
+ });
188
+
189
+ expect(fetch).toHaveBeenCalledTimes(1);
190
+ expect(fetch.mock.calls[0][0]).toBe(`${stubServerBaseUrl}_specmatic/expectations`);
191
+ expect(fetch.mock.calls[0][1]).toMatchObject({
192
+ method: 'POST',
193
+ body: JSON.stringify(mockStub),
194
+ });
122
195
  });
196
+
197
+ function copyReportFile() {
198
+ copyReportFileWithName('sample-junit-result-multiple.xml');
199
+ }
200
+
201
+ function copyReportFileWithName(fileName: string) {
202
+ const destDir = path.resolve('dist/test-report');
203
+ if (!existsSync(destDir)) {
204
+ mkdirSync(destDir, { recursive: true });
205
+ }
206
+ const srcPath = path.resolve('test-resources', fileName);
207
+ const destPath = path.resolve(destDir, 'TEST-junit-jupiter.xml');
208
+ copyFileSync(srcPath, destPath);
209
+ }
@@ -0,0 +1,18 @@
1
+ import execSh from 'exec-sh';
2
+ import path from 'path';
3
+ import { specmaticJarPathLocal } from '../config';
4
+
5
+ const callSpecmaticCli = (args?: string[]) => {
6
+ const specmaticJarPath = path.resolve(specmaticJarPathLocal);
7
+ const cliArgs = (args || process.argv).slice(2).join(' ');
8
+
9
+ console.log('starting specmatic server', cliArgs);
10
+ execSh(`java -jar ${specmaticJarPath} ${cliArgs}`, {}, (err: any) => {
11
+ if (err) {
12
+ console.log('Exit code: ', err.code);
13
+ process.exit(err.code);
14
+ }
15
+ });
16
+ };
17
+
18
+ export default callSpecmaticCli;
package/src/lib/index.ts CHANGED
@@ -3,17 +3,19 @@ import path from 'path';
3
3
  import execSh from 'exec-sh';
4
4
  import { specmaticJarPathLocal, specmatic } from '../config';
5
5
  import { ChildProcess } from 'child_process';
6
+ import { XMLParser } from 'fast-xml-parser';
7
+ import fs from 'fs';
6
8
 
7
9
  const specmaticJarPath = path.resolve(specmaticJarPathLocal);
8
10
 
9
- const startStub = (host?: string, port?: string, stubDir?: string) : Promise<ChildProcess> => {
11
+ const startStub = (host?: string, port?: string, stubDir?: string): Promise<ChildProcess> => {
10
12
  const stubs = path.resolve(stubDir + '');
11
13
 
12
14
  var cmd = `java -jar ${specmaticJarPath} stub`;
13
15
  if (stubDir) cmd += ` --data=${stubs}`;
14
16
  if (host) cmd += ` --host=${host}`;
15
17
  if (port) cmd += ` --port=${port}`;
16
- console.log(cmd);
18
+ // console.log(cmd);
17
19
 
18
20
  console.log('Starting specmatic stub server');
19
21
  return new Promise((resolve, _reject) => {
@@ -23,9 +25,10 @@ const startStub = (host?: string, port?: string, stubDir?: string) : Promise<Chi
23
25
  }
24
26
  });
25
27
  javaProcess.stdout.on('data', function (data: String) {
26
- console.log('STDOUT: ' + data);
28
+ // console.log('STDOUT: ' + data);
27
29
  if (data.indexOf('Stub server is running') > -1) {
28
- resolve(javaProcess);
30
+ console.log('STDOUT: ' + data);
31
+ resolve(javaProcess);
29
32
  }
30
33
  });
31
34
  javaProcess.stderr.on('data', function (data: String) {
@@ -35,42 +38,65 @@ const startStub = (host?: string, port?: string, stubDir?: string) : Promise<Chi
35
38
  };
36
39
 
37
40
  const stopStub = (javaProcess: ChildProcess) => {
38
- console.log(`Stopping specmatic server`);
41
+ console.log(`Stopping specmatic stub server`);
39
42
  javaProcess.stdout?.removeAllListeners();
40
43
  javaProcess.stderr?.removeAllListeners();
41
44
  javaProcess.removeAllListeners('close');
42
45
  javaProcess.kill();
43
46
  };
44
47
 
45
- const test = (specs?: string, host?: string, port?: string): Promise<boolean> => {
48
+ const test = (host?: string, port?: string, specs?: string): Promise<TestResult | undefined> => {
46
49
  const specsPath = path.resolve(specs + '');
47
50
 
48
51
  var cmd = `java -jar ${specmaticJarPath} test`;
49
52
  if (specs) cmd += ` ${specsPath}`;
53
+ cmd += ' --junitReportDir=dist/test-report';
50
54
  if (host) cmd += ` --host=${host}`;
51
55
  if (port) cmd += ` --port=${port}`;
52
- console.log(cmd);
56
+ // console.log(cmd);
53
57
 
54
58
  console.log('Running specmatic tests');
55
59
 
60
+ const reportDir = path.resolve('dist/test-report');
61
+ fs.rmSync(reportDir, { recursive: true, force: true });
62
+
56
63
  return new Promise((resolve, _reject) => {
57
- execSh(cmd, {}, (err: any) => {
58
- if (err) {
59
- console.error('Specmatic test run failed with error', err);
60
- }
61
- resolve(err == null);
64
+ execSh(cmd, { stdio: 'pipe', stderr: 'pipe' }, (err: any) => {
65
+ // if (err) {
66
+ // console.error('Specmatic test run failed with error', err);
67
+ // }
68
+ var testCases = parseJunitXML();
69
+ const total = testCases.length;
70
+ const success = testCases.filter((testcase: { [id: string]: any }) => testcase['system-out'] && !testcase['failure']).length;
71
+ const failure = testCases.filter((testcase: { [id: string]: any }) => testcase['failure']).length;
72
+ var result = new TestResult(total, success, failure);
73
+ resolve(result);
62
74
  });
63
75
  });
64
76
  };
65
77
 
66
- const setExpectations = (stubPath: string, stubServerBaseUrl?: string) => {
78
+ const showTestResults = (cb: (name: string, result: boolean) => {}) => {
79
+ var testCases = parseJunitXML();
80
+ testCases.map(function (testcase: { [id: string]: any }) {
81
+ var name = testcase['system-out'].trim().replaceAll('\n', '').split('display-name: Scenario: ')[1].trim();
82
+ cb(name, !testcase.failure);
83
+ });
84
+ };
85
+
86
+ const setExpectations = (stubPath: string, stubServerBaseUrl?: string): Promise<boolean> => {
67
87
  const stubResponse = require(path.resolve(stubPath));
68
88
 
69
89
  console.log('Setting expectations');
70
- fetch(`${stubServerBaseUrl ? stubServerBaseUrl : `http://localhost:9000/`}_specmatic/expectations`, {
71
- method: 'POST',
72
- body: JSON.stringify(stubResponse),
73
- }).then(json => console.log(json));
90
+
91
+ return new Promise((resolve, _reject) => {
92
+ fetch(`${stubServerBaseUrl ? stubServerBaseUrl : `http://localhost:9000/`}_specmatic/expectations`, {
93
+ method: 'POST',
94
+ body: JSON.stringify(stubResponse),
95
+ }).then(json => {
96
+ console.log('Setting expectations complete');
97
+ resolve(true);
98
+ });
99
+ });
74
100
  };
75
101
 
76
102
  const printJarVersion = () => {
@@ -81,4 +107,25 @@ const printJarVersion = () => {
81
107
  });
82
108
  };
83
109
 
84
- export { startStub, stopStub, test, setExpectations, printJarVersion };
110
+ const parseJunitXML = () => {
111
+ const reportPath = path.resolve('dist/test-report/TEST-junit-jupiter.xml');
112
+ var data = fs.readFileSync(reportPath);
113
+ const parser = new XMLParser();
114
+ var resultXml = parser.parse(data);
115
+ resultXml.testsuite.testcase = Array.isArray(resultXml.testsuite.testcase) ? resultXml.testsuite.testcase : [resultXml.testsuite.testcase];
116
+ return resultXml.testsuite.testcase;
117
+ };
118
+
119
+ class TestResult {
120
+ count: number;
121
+ success: number;
122
+ failure: number;
123
+
124
+ constructor(count: number, success: number, failure: number) {
125
+ this.count = count;
126
+ this.success = success;
127
+ this.failure = failure;
128
+ }
129
+ }
130
+
131
+ export { startStub, stopStub, test, setExpectations, printJarVersion, showTestResults };