Skip to content

Commit 7f32c78

Browse files
committed
Refactor to support diffCommand test
1 parent 6033dad commit 7f32c78

File tree

4 files changed

+143
-44
lines changed

4 files changed

+143
-44
lines changed

src/diff/diffCommand.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@
77
"use strict";
88

99
import { expect, test } from "@oclif/test";
10-
import { writeSync } from "fs-extra";
10+
import * as fs from "fs-extra";
1111
import { FileResult, fileSync, dirSync, DirResult } from "tmp";
1212
import { Rule } from "json-rules-engine";
1313
import chai from "chai";
1414
import chaiFs from "chai-fs";
1515

16-
import { DiffCommand as cmd } from "./diffCommand";
16+
import { DiffCommand as cmd, DiffCommand } from "./diffCommand";
1717
import * as diffDirectories from "./diffDirectories";
1818
import { NodeChanges } from "./changes/nodeChanges";
1919
import { ApiChanges } from "./changes/apiChanges";
2020
import { ApiCollectionChanges } from "./changes/apiCollectionChanges";
2121
import { ApiDifferencer } from "./apiDifferencer";
2222
import { CategorizedChange } from "./changes/categorizedChange";
2323
import { RuleCategory } from "./ruleCategory";
24+
import * as oasDiff from "./oasDiff";
25+
26+
import proxyquire from "proxyquire";
27+
import sinon from "sinon";
2428

2529
chai.use(chaiFs);
30+
const pq = proxyquire.noCallThru();
2631

2732
const nodeChanges = new NodeChanges("test-id", ["test:type"]);
2833
nodeChanges.added = { "core:name": "oldName" };
@@ -40,7 +45,7 @@ apiCollectionChanges.changed["file.raml"] = apiChanges;
4045

4146
const createTempFile = (content: string): FileResult => {
4247
const tempFile = fileSync();
43-
writeSync(tempFile.fd, content);
48+
fs.writeSync(tempFile.fd, content);
4449
return tempFile;
4550
};
4651

@@ -71,6 +76,43 @@ const ramlAdded = createTempFile(
7176
`
7277
);
7378

79+
const oasAdded = createTempFile(
80+
`
81+
"openapi": "3.0.0",
82+
"info": {
83+
"title": "Test API",
84+
"version": "1.0.0"
85+
},
86+
"paths": {
87+
"/resource": {
88+
"get": {
89+
"summary": "Get resource"
90+
},
91+
"post": {
92+
"summary": "Create resource"
93+
}
94+
}
95+
}
96+
`
97+
);
98+
99+
const oasOld = createTempFile(
100+
`
101+
"openapi": "3.0.0",
102+
"info": {
103+
"title": "Test API",
104+
"version": "1.0.0"
105+
},
106+
"paths": {
107+
"/resource": {
108+
"get": {
109+
"summary": "Get resource"
110+
}
111+
}
112+
}
113+
`
114+
);
115+
74116
const operationRemovedRule = new Rule(
75117
`{
76118
"name": "Rule to detect operation removal",
@@ -376,4 +418,15 @@ describe("raml-toolkit cli diff command", () => {
376418
)
377419
.exit(2)
378420
.it("does not allow format to be specified for diff only");
421+
422+
// TODO: The stub does not apply. The actual oasDiffChangelog is called and it will throw
423+
// an error because we are using test data and not actual oas files
424+
test
425+
.stderr({ print: true })
426+
.stdout({ print: true })
427+
.stub(oasDiff, "oasDiffChangelog", async () => 0)
428+
.do(() => {
429+
cmd.run([oasOld.name, oasAdded.name, "-s", "oas"]);
430+
})
431+
.it("invokes oasdiff when spec flag is set to oas");
379432
});

src/diff/diffCommand.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ import { ApiDifferencer } from "./apiDifferencer";
1111
import { ApiChanges } from "./changes/apiChanges";
1212
import { ApiCollectionChanges } from "./changes/apiCollectionChanges";
1313
import { diffRamlDirectories } from "./diffDirectories";
14+
import { oasDiffChangelog } from "./oasDiff";
1415
import { allCommonFlags } from "../common/flags";
1516
import { execSync } from "child_process";
1617
import fs from "fs-extra";
1718

18-
import { oasDiffChangelog } from "./oasDiff";
19-
2019
export class DiffCommand extends Command {
2120
// `raml-toolkit --help` only uses the first line, `raml-toolkit diff --help` skips it
2221
static description = `Compute the difference between two API specifications
@@ -200,37 +199,40 @@ Exit statuses:
200199
}
201200
}
202201

202+
/**
203+
* Find the differences between two OAS files.
204+
*
205+
* Requires oasDiff to be installed to work. Will return an error if oasDiff is not installed
206+
*
207+
* Otherwise, returns the exit code of oasDiff.oasDiffChangelog
208+
*
209+
* @param baseApis - Path to a base OAS file
210+
* @param newApis - Path to a new OAS file
211+
* @param flags - Parsed CLI flags passed to the command
212+
*/
213+
protected async _diffOasFiles(
214+
baseApis: string,
215+
newApis: string,
216+
flags: OutputFlags<typeof DiffCommand.flags>
217+
): Promise<number> {
218+
// Diff two files (we do not have a custom ruleset defined for OAS
219+
// By default, checks are all 'diff-only'
220+
return await oasDiffChangelog(baseApis, newApis, flags);
221+
}
222+
203223
async run(): Promise<void> {
204224
const { args, flags } = this.parse(DiffCommand);
205225
const baseApis = args.base;
206226
const newApis = args.new;
207-
227+
let exitCode = 0;
208228
if (!(await fs.pathExists(baseApis))) {
209-
this.error(`File or directory not found: ${baseApis}`);
229+
this.error(`File or directory not found: ${baseApis}`, { exit: 2 });
210230
}
211231
if (!(await fs.pathExists(newApis))) {
212-
this.error(`File or directory not found: ${newApis}`);
232+
this.error(`File or directory not found: ${newApis}`, { exit: 2 });
213233
}
214-
215234
if (flags.spec === "oas") {
216-
// check if oasdiff is installed
217-
try {
218-
execSync(`oasdiff --version`);
219-
} catch (err) {
220-
this.error(
221-
"oasdiff is not installed. Install oasdiff according to https://github.com/oasdiff/oasdiff#installation"
222-
);
223-
}
224-
225-
if (flags.dir) {
226-
const baseApisYamlGlob = '"' + baseApis + "/**/*.yaml" + '"';
227-
const newApisYamlGlob = '"' + newApis + "/**/*.yaml" + '"';
228-
await oasDiffChangelog(baseApisYamlGlob, newApisYamlGlob, flags);
229-
} else {
230-
// Diff two files (we do not have a custom ruleset defined for OAS
231-
// By default, checks are all 'diff-only'
232-
await oasDiffChangelog(baseApis, newApis, flags);
233-
}
235+
exitCode = await this._diffOasFiles(baseApis, newApis, flags);
234236
} else {
235237
if (flags.dir) {
236238
await this._diffDirs(baseApis, newApis, flags);
@@ -240,6 +242,6 @@ Exit statuses:
240242
await this._diffFilesUsingRuleset(baseApis, newApis, flags);
241243
}
242244
}
243-
this.exit();
245+
this.exit(exitCode);
244246
}
245247
}

src/diff/oasDiff.test.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ const pq = proxyquire.noCallThru();
1212

1313
describe("oasDiffChangelog", () => {
1414
it("should execute oasdiff command with correct parameters", async () => {
15-
const stub = sinon.stub().returns("");
15+
const stub = sinon.stub();
16+
stub.onCall(0).returns("version 1.0.0");
17+
stub.onCall(1).returns("");
1618

1719
const oasDiff = pq("./oasDiff", {
1820
child_process: {
@@ -26,12 +28,14 @@ describe("oasDiffChangelog", () => {
2628
const flags = {};
2729
const result = await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
2830

29-
expect(stub.calledOnce).to.be.true;
31+
expect(stub.called).to.be.true;
3032
expect(result).to.equal(0);
3133
});
3234

3335
it("should return 1 when oasdiff returns a non-empty string", async () => {
34-
const execSyncStub = sinon.stub().returns("mock oasdiff change");
36+
const execSyncStub = sinon.stub();
37+
execSyncStub.onCall(0).returns("version 1.0.0");
38+
execSyncStub.onCall(1).returns("mock oasdiff change");
3539

3640
const oasDiff = pq("./oasDiff", {
3741
child_process: {
@@ -45,12 +49,14 @@ describe("oasDiffChangelog", () => {
4549
const flags = {};
4650
const result = await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
4751

48-
expect(execSyncStub.calledOnce).to.be.true;
52+
expect(execSyncStub.called).to.be.true;
4953
expect(result).to.equal(1);
5054
});
5155

5256
it("should return 2 when oasdiff throws an error", async () => {
53-
const execSyncStub = sinon.stub().throws(new Error("mock oasdiff error"));
57+
const execSyncStub = sinon.stub();
58+
execSyncStub.onCall(0).returns("version 1.0.0");
59+
execSyncStub.onCall(1).throws(new Error("mock oasdiff error"));
5460

5561
const oasDiff = pq("./oasDiff", {
5662
child_process: {
@@ -64,12 +70,14 @@ describe("oasDiffChangelog", () => {
6470
const flags = {};
6571
const result = await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
6672

67-
expect(execSyncStub.calledOnce).to.be.true;
73+
expect(execSyncStub.called).to.be.true;
6874
expect(result).to.equal(2);
6975
});
7076

7177
it("should run oasdiff in directory mode when the --dir flag is provided", async () => {
72-
const execSyncStub = sinon.stub().returns("a change");
78+
const execSyncStub = sinon.stub();
79+
execSyncStub.onCall(0).returns("version 1.0.0");
80+
execSyncStub.onCall(1).returns("a change");
7381

7482
const oasDiff = pq("./oasDiff", {
7583
child_process: {
@@ -84,14 +92,16 @@ describe("oasDiffChangelog", () => {
8492
};
8593
await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
8694

87-
expect(execSyncStub.calledOnce).to.be.true;
88-
expect(execSyncStub.args[0][0]).to.equal(
89-
"oasdiff changelog --composed base.yaml new.yaml"
95+
expect(execSyncStub.called).to.be.true;
96+
expect(execSyncStub.args[1][0]).to.equal(
97+
'oasdiff changelog --composed "base.yaml/**/*.yaml" "new.yaml/**/*.yaml"'
9098
);
9199
});
92100

93101
it("should save the changes to a file when the --out-file flag is provided", async () => {
94-
const execSyncStub = sinon.stub().returns("a change");
102+
const execSyncStub = sinon.stub();
103+
execSyncStub.onCall(0).returns("version 1.0.0");
104+
execSyncStub.onCall(1).returns("a change");
95105
const fsStub = sinon.stub();
96106

97107
const oasDiff = pq("./oasDiff", {
@@ -111,11 +121,13 @@ describe("oasDiffChangelog", () => {
111121
};
112122

113123
await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
114-
expect(fsStub.calledOnce).to.be.true;
124+
expect(fsStub.called).to.be.true;
115125
});
116126

117127
it("should save the changes to a jsonfile when the --out-file flag is provided and format is json", async () => {
118-
const execSyncStub = sinon.stub().returns('{"change": "a change"}');
128+
const execSyncStub = sinon.stub();
129+
execSyncStub.onCall(0).returns("version 1.0.0");
130+
execSyncStub.onCall(1).returns('{"change": "a change"}');
119131
const fsStub = sinon.stub();
120132

121133
const oasDiff = pq("./oasDiff", {
@@ -136,6 +148,18 @@ describe("oasDiffChangelog", () => {
136148
};
137149

138150
await oasDiff.oasDiffChangelog(baseApi, newApi, flags);
139-
expect(fsStub.calledOnce).to.be.true;
151+
expect(fsStub.called).to.be.true;
152+
});
153+
154+
it("should throw an error if oasdiff is not installed", () => {
155+
const oasDiff = pq("./oasDiff", {
156+
child_process: {
157+
execSync: sinon.stub().throws(new Error("oasdiff not installed")),
158+
},
159+
});
160+
161+
expect(() => oasDiff.checkOasDiffIsInstalled()).to.throw(
162+
"oasdiff is not installed. Install oasdiff according to https://github.com/oasdiff/oasdiff#installation"
163+
);
140164
});
141165
});

src/diff/oasDiff.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,34 @@ async function _saveOrLogOas(changes: string, flags): Promise<void> {
3333
/**
3434
* Wrapper for oasdiff changelog command.
3535
*
36-
* @param baseApi - The base API file
36+
* @param baseApi - The base API file or directory
3737
* @param newApi - The new API file
3838
* @param flags - Parsed CLI flags passed to the command
3939
* @returns 0 if no changes are reported, 1 if changes are reported, and 2 if an error occurs
4040
*/
4141
export async function oasDiffChangelog(baseApi: string, newApi: string, flags) {
4242
try {
43+
checkOasDiffIsInstalled();
4344
console.log("Starting oasdiff");
45+
4446
const jsonMode = flags.format === "json" ? "-f json" : "";
4547
const directoryMode = flags.dir ? "--composed" : "";
4648

49+
// If the user is diffing directories, we need to pass the glob pattern to oasdiff
50+
let baseApiTarget = baseApi;
51+
let newApiTarget = newApi;
52+
if (flags.dir) {
53+
baseApiTarget = '"' + baseApi + "/**/*.yaml" + '"';
54+
newApiTarget = '"' + newApi + "/**/*.yaml" + '"';
55+
}
56+
4757
// TODO: Do we want to support the other output formats?
4858
// See https://github.com/oasdiff/oasdiff/blob/main/docs/BREAKING-CHANGES.md#output-formats
4959

5060
// TODO: Do we want to support customizing severity levels?
5161
// This would be akin to the raml rulesets
5262
const oasdiffOutput = execSync(
53-
`oasdiff changelog ${jsonMode} ${directoryMode} ${baseApi} ${newApi}`
63+
`oasdiff changelog ${jsonMode} ${directoryMode} ${baseApiTarget} ${newApiTarget}`
5464
).toString();
5565

5666
if (oasdiffOutput.trim().length === 0) {
@@ -65,3 +75,13 @@ export async function oasDiffChangelog(baseApi: string, newApi: string, flags) {
6575
return 2;
6676
}
6777
}
78+
79+
export function checkOasDiffIsInstalled() {
80+
try {
81+
execSync(`oasdiff --version`).toString();
82+
} catch (err) {
83+
throw new Error(
84+
"oasdiff is not installed. Install oasdiff according to https://github.com/oasdiff/oasdiff#installation"
85+
);
86+
}
87+
}

0 commit comments

Comments
 (0)