Skip to content

Commit

Permalink
Expand & improve the testing module (#1149)
Browse files Browse the repository at this point in the history
* Add a throwing test stub to the testing module

* Improve Stubscape

Update the Stubscape implementation so that the behavior of `quote` and
`quoteAll` is more similar to the real implementation of Shescape. Tests
are updated accordingly, mostly resulting in cleaner integration tests
(with more coverage) at the minor cost of more specific unit tests (that
actually more accurately reflect expected behavior).

Also update the Stubscape JSDoc to be up-to-date, both with respect to
the changes seen here as well as the changes of v3.0.0.
  • Loading branch information
ericcornelissen authored Oct 11, 2023
1 parent 3e99c28 commit 4f03fd8
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 42 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Versioning].

## [Unreleased]

- _No changes yet_
- Improve alignment between `Shescape` and `Stubscape`. ([#1149])
- Add a failing `Shescape` stub to the testing module. ([#1149])

## [2.0.0] - 2023-09-07

Expand Down Expand Up @@ -312,6 +313,7 @@ Versioning].
[#1094]: https://github.com/ericcornelissen/shescape/pull/1094
[#1137]: https://github.com/ericcornelissen/shescape/pull/1137
[#1142]: https://github.com/ericcornelissen/shescape/pull/1142
[#1149]: https://github.com/ericcornelissen/shescape/pull/1149
[552e8ea]: https://github.com/ericcornelissen/shescape/commit/552e8eab56861720b1d4e5474fb65741643358f9
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
[semantic versioning]: https://semver.org/spec/v2.0.0.html
7 changes: 6 additions & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ mocking ([for example with Jest][jest-module-mock]).
// my-module.test.js

import assert from "node:assert";
import { Shescape as Stubscape } from "shescape/testing";
import { Shescape as Stubscape, Throwscape } from "shescape/testing";
import { functionUnderTest } from "./my-module.js";

// Test good conditions
const stubscape = new Stubscape();
assert.ok(functionUnderTest(stubscape));

// Test bad conditions
const throwscape = new Throwscape();
assert.ok(functionUnderTest(throwscape));
```

### Why Stubs
Expand Down
87 changes: 77 additions & 10 deletions test/integration/testing/commonjs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,34 @@
*/

import { testProp } from "@fast-check/ava";
import test from "ava";
import * as fc from "fast-check";

import { arbitrary } from "../_.js";

import { Shescape as Stubscape } from "shescape/testing";
import { Shescape as StubscapeCjs } from "../../../testing.cjs";
import {
injectionStrings,
Shescape as Stubscape,
Throwscape,
} from "shescape/testing";
import {
injectionStrings as injectionStringsCjs,
Shescape as StubscapeCjs,
Throwscape as ThrowscapeCjs,
} from "../../../testing.cjs";

test("injection strings", (t) => {
for (const injectionStringCjs of injectionStringsCjs) {
t.true(injectionStrings.includes(injectionStringCjs));
}

for (const injectionString of injectionStrings) {
t.true(injectionStringsCjs.includes(injectionString));
}
});

testProp(
"escape (esm === cjs)",
"Stubscape#escape (esm === cjs)",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
(t, arg, options) => {
const stubscape = new Stubscape(options);
Expand All @@ -26,7 +45,7 @@ testProp(
);

testProp(
"escapeAll (esm === cjs)",
"Stubscape#escapeAll (esm === cjs)",
[fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
(t, args, options) => {
const stubscape = new Stubscape(options);
Expand All @@ -39,27 +58,75 @@ testProp(
);

testProp(
"quote (esm === cjs)",
"Stubscape#quote (esm === cjs)",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
(t, arg, options) => {
let resultEsm, resultCjs, erroredEsm, erroredCjs;

const stubscape = new Stubscape(options);
const stubscapeCjs = new StubscapeCjs(options);

const resultEsm = stubscape.quote(arg);
const resultCjs = stubscapeCjs.quote(arg);
try {
resultEsm = stubscape.quote(arg);
} catch (_) {
erroredEsm = true;
}

try {
resultCjs = stubscapeCjs.quote(arg);
} catch (_) {
erroredCjs = true;
}

t.is(erroredEsm, erroredCjs);
t.is(resultEsm, resultCjs);
},
);

testProp(
"quoteAll (esm === cjs)",
"Stubscape#quoteAll (esm === cjs)",
[fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
(t, args, options) => {
let resultEsm, resultCjs, erroredEsm, erroredCjs;

const stubscape = new Stubscape(options);
const stubscapeCjs = new StubscapeCjs(options);

const resultEsm = stubscape.quoteAll(args);
const resultCjs = stubscapeCjs.quoteAll(args);
try {
resultEsm = stubscape.quoteAll(args);
} catch (_) {
erroredEsm = true;
}

try {
resultCjs = stubscapeCjs.quoteAll(args);
} catch (_) {
erroredCjs = true;
}

t.is(erroredEsm, erroredCjs);
t.deepEqual(resultEsm, resultCjs);
},
);

testProp(
"Throwscape#constructor (esm === cjs)",
[arbitrary.shescapeOptions()],
(t, options) => {
let erroredEsm, erroredCjs;

try {
new Throwscape(options);
} catch (_) {
erroredEsm = true;
}

try {
new ThrowscapeCjs(options);
} catch (_) {
erroredCjs = true;
}

t.deepEqual(erroredEsm, erroredCjs);
},
);
35 changes: 22 additions & 13 deletions test/integration/testing/functional.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import * as fc from "fast-check";
import { arbitrary } from "../_.js";

import { Shescape } from "shescape";
import { injectionStrings, Shescape as Stubscape } from "shescape/testing";
import {
injectionStrings,
Shescape as Stubscape,
Throwscape,
} from "shescape/testing";

test("injection strings", (t) => {
t.true(Array.isArray(injectionStrings));
Expand All @@ -24,7 +28,7 @@ test("injection strings", (t) => {
});

testProp(
"escape (stubscape ~ shescape)",
"Stubscape#escape (stubscape =~ shescape)",
[fc.anything(), arbitrary.shescapeOptions()],
(t, arg, options) => {
let result, stubResult, errored, stubErrored;
Expand Down Expand Up @@ -56,7 +60,7 @@ testProp(
);

testProp(
"escapeAll (stubscape ~ shescape)",
"Stubscape#escapeAll (stubscape =~ shescape)",
[fc.anything(), arbitrary.shescapeOptions()],
(t, args, options) => {
let result, stubResult, errored, stubErrored;
Expand Down Expand Up @@ -88,11 +92,8 @@ testProp(
);

testProp(
"quote with shell (stubscape ~ shescape)",
[
fc.anything(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
"Stubscape#quote, with shell (stubscape =~ shescape)",
[fc.anything(), arbitrary.shescapeOptions()],
(t, arg, options) => {
let result, stubResult, errored, stubErrored;

Expand Down Expand Up @@ -123,11 +124,8 @@ testProp(
);

testProp(
"quoteAll with shell (stubscape ~ shescape)",
[
fc.anything(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
"Stubscape#quoteAll, with shell (stubscape =~ shescape)",
[fc.anything(), arbitrary.shescapeOptions()],
(t, args, options) => {
let result, stubResult, errored, stubErrored;

Expand Down Expand Up @@ -156,3 +154,14 @@ testProp(
t.is(typeof result, typeof stubResult);
},
);

testProp(
"Throwscape#constructor",
[arbitrary.shescapeOptions()],
(t, options) => {
t.throws(() => new Throwscape(options), {
instanceOf: Error,
message: "Can't be instantiated",
});
},
);
44 changes: 40 additions & 4 deletions test/unit/testing/stubscape.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ test("escapeAll invalid arguments", (t) => {

testProp(
"quote valid arguments",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
const result = stubscape.quote(arg);
Expand All @@ -87,9 +90,26 @@ test("quote invalid arguments", (t) => {
}
});

testProp(
"quote without a shell",
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell === false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quote(arg), {
instanceOf: Error,
});
},
);

testProp(
"quoteAll valid arguments",
[fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
[
fc.array(arbitrary.shescapeArg()),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, args, options) => {
const stubscape = new Stubscape(options);
const result = stubscape.quoteAll(args);
Expand All @@ -100,12 +120,14 @@ testProp(

testProp(
"quoteAll non-array arguments",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quoteAll(arg), {
instanceOf: TypeError,
message: "args.map is not a function",
});
},
);
Expand All @@ -120,3 +142,17 @@ test("quoteAll invalid arguments", (t) => {
});
}
});

testProp(
"quoteAll without a shell",
[
fc.array(arbitrary.shescapeArg(), { minLength: 1 }),
arbitrary.shescapeOptions().filter((options) => options?.shell === false),
],
(t, args, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quoteAll(args), {
instanceOf: Error,
});
},
);
17 changes: 17 additions & 0 deletions test/unit/testing/throwscape.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @overview Contains unit tests for the throwing test stub of shescape.
* @license MIT
*/

import { testProp } from "@fast-check/ava";

import { arbitrary } from "./_.js";

import { Throwscape } from "../../../testing.js";

testProp("throws", [arbitrary.shescapeOptions()], (t, options) => {
t.throws(() => new Throwscape(options), {
instanceOf: Error,
message: "Can't be instantiated",
});
});
15 changes: 8 additions & 7 deletions testing.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import shescape from "shescape";
import type { Shescape as ShescapeType } from "shescape";

/**
* A list of example shell injection strings to test whether or not a function
Expand All @@ -21,9 +21,10 @@ export const injectionStrings: string[];
* - Errors on non-stringable inputs.
* - Converts non-array inputs to single-item arrays where necessary.
*/
export const shescape: {
escape: shescape.escape;
escapeAll: shescape.escapeAll;
quote: shescape.quote;
quoteAll: shescape.quoteAll;
};
export const Shescape: ShescapeType;

/**
* A test stub of Shescape that can't be instantiated. This can be used to
* simulate a failure to instantiate Shescape in your code.
*/
export const Throwscape: ShescapeType;
Loading

0 comments on commit 4f03fd8

Please sign in to comment.