Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name : Build Inso
run: npm run build -w insomnia-inso

- name: Start test server
run: npm run serve -w insomnia-smoke-test & npx -y wait-on http://localhost:4010

- name: Run Inso bundle tests
run: npm run test:bundle -w insomnia-inso

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,14 @@ exports[`Snapshot for "inso run -h" 1`] = `
Execution utilities

Options:
-h, --help display help for command
-h, --help display help for command

Commands:
test [options] [identifier] Run Insomnia unit test suites, identifier can be
a test suite id or a API Spec id
help [command] display help for command"
test [options] [identifier] Run Insomnia unit test suites, identifier
can be a test suite id or a API Spec id
collection [options] [identifier] Run Insomnia request collection,
identifier can be a workspace id
help [command] display help for command"
`;

exports[`Snapshot for "inso run test -h" 1`] = `
Expand Down
37 changes: 18 additions & 19 deletions packages/insomnia-inso/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,40 @@ import { describe, expect, it } from '@jest/globals';
import { exec, ExecException } from 'child_process';
import path from 'path';

// dev experience
// goals: it should be quick and run in ci and should be easy to debug
// ideas: create a second test.yml easier to reason about the state of node-libcurl it can parallel

// issues: no immeidate feedback as the test is running
// run the test, do you need to know about the libcurl thing or should i be automated?

// should be each to copy and run in local js debug terminal
// and also print which one fails when running all tests
// TODO: move all fixtures to the same folder, and name valid or invalid or whatever
const shouldReturnSuccessCode = [
// help
'$PWD/packages/insomnia-inso/bin/inso -h',
// identifier filepath

// lint spec
// as identifer filepath
'$PWD/packages/insomnia-inso/bin/inso lint spec packages/insomnia-inso/src/commands/fixtures/openapi-spec.yaml',
// identifier filepath with spectral.yaml
// as identifier filepath with spectral.yaml
'$PWD/packages/insomnia-inso/bin/inso lint spec packages/insomnia-inso/src/commands/fixtures/with-ruleset/path-plugin.yaml',
// as working directory and identifier filename
'$PWD/packages/insomnia-inso/bin/inso lint spec -w packages/insomnia-inso/src/commands/fixtures/with-ruleset path-plugin.yaml',
// lint from db
// as working directory containing nedb
'$PWD/packages/insomnia-inso/bin/inso lint spec -w packages/insomnia-inso/src/db/fixtures/nedb spc_46c5a4',
'$PWD/packages/insomnia-inso/bin/inso lint spec -w packages/insomnia-inso/src/db/fixtures/git-repo spc_46c5a4',
'$PWD/packages/insomnia-inso/bin/inso lint spec -w packages/insomnia-inso/src/db/fixtures/insomnia-v4/insomnia_v4.yaml spc_3b2850',
// export from db
// export spec nedb, git-repo, export file
'$PWD/packages/insomnia-inso/bin/inso export spec -w packages/insomnia-inso/src/db/fixtures/nedb spc_46c5a4',
'$PWD/packages/insomnia-inso/bin/inso export spec -w packages/insomnia-inso/src/db/fixtures/git-repo spc_46c5a4',
'$PWD/packages/insomnia-inso/bin/inso export spec -w packages/insomnia-inso/src/db/fixtures/insomnia-v4/insomnia_v4.yaml spc_3b2850',
// test from db

// run test
// nedb, gitrepo, export file
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/db/fixtures/nedb -e env_env_ca046a uts_fe901c',
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/db/fixtures/nedb -e env_env_ca046a --reporter min uts_fe901c',
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/db/fixtures/git-repo -e env_env_ca046a uts_fe901c',
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/db/fixtures/insomnia-v4/insomnia_v4.yaml -e env_env_0e4670 spc_3b2850',
// export file,request can inherit auth headers and variables from folder
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/examples/folder-inheritance-document.yml spc_a8144e --verbose',
// workspace - request from db
// '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/db/fixtures/insomnia-v4/insomnia_v4.yaml -e env_env_0e4670 --requestNamePattern "Example 1" wrk_8ee1e0',
// TODO: request group - request from db, add simple export file pointing at local server
// TODO: add bail option

// run collection
// export file
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-smoke-test/fixtures/simple.yaml -e env_2eecf85b7f wrk_0702a5',
// with regex filter
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-smoke-test/fixtures/simple.yaml -e env_2eecf85b7f --requestNamePattern "example http" wrk_0702a5',
];

const shouldReturnErrorCode = [
Expand Down
95 changes: 95 additions & 0 deletions packages/insomnia-inso/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { loadDb } from './db';
import { loadApiSpec, promptApiSpec } from './db/models/api-spec';
import { loadEnvironment, promptEnvironment } from './db/models/environment';
import { loadTestSuites, promptTestSuites } from './db/models/unit-test-suite';
import { loadWorkspace, promptWorkspace } from './db/models/workspace';

export interface GlobalOptions {
ci: boolean;
Expand Down Expand Up @@ -295,6 +296,100 @@ export const go = (args?: string[]) => {
return process.exit(1);
});

run.command('collection [identifier]')
.description('Run Insomnia request collection, identifier can be a workspace id')
.option('-t, --requestNamePattern <regex>', 'run requests that match the regex', '')
.option('-e, --env <identifier>', 'environment to use', '')
.option('-b, --bail', 'abort ("bail") after first test failure', false)
.option('--disableCertValidation', 'disable certificate validation for requests with SSL', false)
.action(async (identifier, cmd: { env: string; disableCertValidation: true; requestNamePattern: string; bail: boolean }) => {
const globals: { config: string; workingDir: string; exportFile: string; ci: boolean; printOptions: boolean; verbose: boolean } = program.optsWithGlobals();

const commandOptions = { ...globals, ...cmd };
const __configFile = await loadCosmiConfig(commandOptions.config, commandOptions.workingDir);

const options = {
reporter: defaultReporter,
...__configFile?.options || {},
...commandOptions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: just double check that command options will be overridden by the config file? Original I thought command options might have highest priority.

...(__configFile ? { __configFile } : {}),
};
logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info;
options.ci && logger.setReporters([new BasicReporter()]);
options.printOptions && logger.log('Loaded options', options, '\n');
let pathToSearch = '';
const useLocalAppData = !options.workingDir && !options.exportFile;
if (useLocalAppData) {
logger.warn('No working directory or export file provided, using local app data directory.');
pathToSearch = localAppDir;
} else {
pathToSearch = path.resolve(options.workingDir || process.cwd(), options.exportFile || '');
}
if (options.reporter && !reporterTypesSet.has(options.reporter)) {
logger.fatal(`Reporter "${options.reporter}" not unrecognized. Options are [${reporterTypes.join(', ')}].`);
return process.exit(1);
}

const db = await loadDb({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we could be a bit defensive here and check loadDb failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree the db load logic is over complex I'm thinking of a better way to abstract it. Will follow up with a pr soon

pathToSearch,
filterTypes: [],
});

const workspace = identifier ? loadWorkspace(db, identifier) : await promptWorkspace(db, !!options.ci);

if (!workspace) {
logger.fatal('No workspace found; cannot run requests.', identifier);
return process.exit(1);
}

// Find environment
const workspaceId = workspace._id;
const environment = options.env ? loadEnvironment(db, workspaceId, options.env) : await promptEnvironment(db, !!options.ci, workspaceId);

if (!environment) {
logger.fatal('No environment identified; cannot run requests without a valid environment.');
return process.exit(1);
}

const getRequestGroupIdsRecursively = (from: string[]): string[] => {
const parentIds = db.RequestGroup.filter(rg => from.includes(rg.parentId)).map(rg => rg._id);
return [...parentIds, ...(parentIds.length > 0 ? getRequestGroupIdsRecursively(parentIds) : [])];
};
const allRequestGroupIds = getRequestGroupIdsRecursively([workspaceId]);
let requests = db.Request.filter(req => [workspaceId, ...allRequestGroupIds].includes(req.parentId));

if (options.requestNamePattern) {
requests = requests.filter(req => req.name.match(new RegExp(options.requestNamePattern)));

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp

RegExp() called with a `cmd` function argument, this might allow an attacker to cause a Regular Expression Denial-of-Service (ReDoS) within your application as RegExP blocks the main thread. For this reason, it is recommended to use hardcoded regexes instead. If your regex is run on user-controlled input, consider performing input validation or use a regex checking/sanitization library such as https://www.npmjs.com/package/recheck to verify that the regex does not appear vulnerable to ReDoS.
}
if (!requests.length) {
logger.fatal('No requests identified; nothing to run.');
return process.exit(1);
}

try {
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Load lazily when needed, otherwise this require slows down the entire CLI.
const { getSendRequestCallbackMemDb } = require('insomnia-send-request');
const sendRequest = await getSendRequestCallbackMemDb(environment._id, db, { validateSSL: !options.disableCertValidation });
let success = true;
for (const req of requests) {
if (options.bail && !success) {
return;
}
logger.log(`Running request: ${req.name} ${req._id}`);
const res = await sendRequest(req._id);
logger.trace(res);
if (res.status !== 200) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Just a todo item that we might check test results to decide if it is failed in the future.

success = false;
logger.error(`Request failed with status ${res.status}`);
}
}
return process.exit(success ? 0 : 1);
} catch (error) {
logErrorAndExit(error);
}
return process.exit(1);
});

program.command('lint')
.description('Lint a yaml file in the workingDir or the provided file path (with .spectral.yml) or a spec in an Insomnia database directory')
.command('spec [identifier]')
Expand Down