Skip to content

Commit

Permalink
feat: add --tag support to cli (#693)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnWeber committed May 20, 2024
1 parent bac8071 commit 6b818a1
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 76 deletions.
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
},
{
"type": "node",
"request": "launch",
"name": "debug files",
"skipFiles": [
"<node_internals>/**"
],
"cwd": "${workspaceFolder:httpyac.github.io}/examples/",
"console": "integratedTerminal",
"program": "${workspaceFolder}/bin/httpyac.js",
"args": [
"send",
"**/*.http",
"-o",
"short"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
}
]
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [unreleased]
### Features
- add `--tag` support to cli to only execute httpRegion with defined tag (#693)

## [6.13.3]
### Fix
- Aws Signing use query params in signing request (#684)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"prepack": "npm run build",
"prepare": "husky install",
"start": "npm run watch",
"test": "jest",
"test": "node --experimental-vm-modules ./node_modules/.bin/jest",
"tsc-watch": "tsc --watch --project tsconfig.build.json",
"tsc:check": "tsc --noEmit --project tsconfig.json",
"tsc": "tsc --declaration --emitDeclarationOnly --project tsconfig.build.json",
Expand Down
3 changes: 2 additions & 1 deletion src/cli/send/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export interface SendOptions {
repeatMode?: 'sequential' | 'parallel';
repeat?: number;
parallel?: number;
timeout?: number;
silent?: boolean;
tag?: Array<string>;
timeout?: number;
var?: Array<string>;
verbose?: boolean;
}
Expand Down
112 changes: 112 additions & 0 deletions src/cli/send/selectHttpFiles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { QuestionCollection } from 'inquirer';
import { HttpFile, HttpRegion } from '../../store';
import { selectHttpFiles } from './selectHttpFiles';

function createHttpFile(name: string, httpRegions: Array<Record<string, string>>) {
const httpFile = new HttpFile(name);
httpRegions.forEach((metaData, index) => {
const httpRegion = new HttpRegion(httpFile, index);
httpFile.httpRegions.push(httpRegion);
Object.assign(httpRegion.metaData, metaData);
httpRegion.symbol.name = metaData.name;
});
return httpFile;
}

describe('selectHttpFiles', () => {
const defaultHttpFiles: Array<HttpFile> = [
createHttpFile('test1', [
{
name: 'foo1',
tag: 'foo',
},
{
name: 'foo2',
tag: 'foo',
},
{
name: 'foo3',
tag: 'bar',
},
{
name: 'foo4',
tag: 'bar',
},
{
name: 'foo5',
tag: 'fuu',
},
]),
createHttpFile('test2', [
{
name: 'test1',
tag: 'test',
},
{
name: 'test2',
tag: 'test',
},
]),
];
it('should export', () => {
expect(selectHttpFiles).toBeDefined();
});

it('should return all values', async () => {
const result = await selectHttpFiles(defaultHttpFiles, { all: true });

expect(result.length).toBe(2);
expect(result.map(h => h.httpFile.fileName)).toEqual(['test1', 'test2']);
expect(result.map(h => h.httpRegions)).toEqual([undefined, undefined]);
});
it('should return values by name', async () => {
const result = await selectHttpFiles(defaultHttpFiles, { name: 'foo1' });

expect(result.length).toBe(1);
expect(result.map(h => h.httpFile.fileName)).toEqual(['test1']);
expect(result.map(h => h.httpRegions?.map(hr => hr.metaData.name))).toEqual([['foo1']]);
});
it('should return values by tag', async () => {
const result = await selectHttpFiles(defaultHttpFiles, { tag: ['foo', 'fuu'] });

expect(result.length).toBe(1);
expect(result.map(h => h.httpFile.fileName)).toEqual(['test1']);
expect(result.map(h => h.httpRegions?.map(hr => hr.metaData.name))).toEqual([['foo1', 'foo2', 'foo5']]);
});
it('should return values by line', async () => {
const result = await selectHttpFiles(defaultHttpFiles, { line: 1 });

expect(result.length).toBe(2);
expect(result.map(h => h.httpFile.fileName)).toEqual(['test1', 'test2']);
expect(result.map(h => h.httpRegions?.map(hr => hr.metaData.name))).toEqual([['foo2'], ['test2']]);
});
it('should return values by manual input', async () => {
const inquirer = await import('inquirer');
Object.assign(inquirer.default, {
prompt(questions: QuestionCollection) {
const q = questions[0];
return {
region: q.choices[1],
};
},
});
const result = await selectHttpFiles(defaultHttpFiles, {});

expect(result.length).toBe(1);
expect(result.map(h => h.httpFile.fileName)).toEqual(['test1']);
expect(result.map(h => h.httpRegions?.map(hr => hr.metaData.name))).toEqual([['foo1']]);
});
it('should return empty on invalid manual input', async () => {
const inquirer = await import('inquirer');
Object.assign(inquirer.default, {
prompt() {
return {
region: 'fii',
};
},
});
const result = await selectHttpFiles(defaultHttpFiles, {});

expect(result.length).toBe(0);
});
});
103 changes: 103 additions & 0 deletions src/cli/send/selectHttpFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as models from '../../models';
import * as utils from '../../utils';
import { SendOptions } from './options';

type SelectActionResult = Array<{ httpRegions?: Array<models.HttpRegion>; httpFile: models.HttpFile }>;

export async function selectHttpFiles(
httpFiles: Array<models.HttpFile>,
cliOptions: SendOptions
): Promise<SelectActionResult> {
if (cliOptions.all) {
return httpFiles.map(httpFile => ({
httpFile,
}));
}
const resultWithArgs = selectHttpFilesWithArgs(httpFiles, cliOptions);
if (resultWithArgs.length > 0) {
return resultWithArgs;
}
return await selectManualHttpFiles(httpFiles);
}

function selectHttpFilesWithArgs(httpFiles: Array<models.HttpFile>, cliOptions: SendOptions) {
const result: SelectActionResult = [];

for (const httpFile of httpFiles) {
const httpRegions = httpFile.httpRegions.filter(h => {
if (hasName(h, cliOptions.name)) {
return true;
}
if (hasTag(h, cliOptions.tag)) {
return true;
}
if (isLine(h, cliOptions.line)) {
return true;
}
return false;
});
if (httpRegions.length > 0) {
result.push({
httpFile,
httpRegions,
});
}
}
return result;
}

function hasName(httpRegion: models.HttpRegion, name: string | undefined) {
if (name) {
return httpRegion.metaData?.name === name;
}
return false;
}

function isLine(httpRegion: models.HttpRegion, line: number | undefined) {
if (line !== undefined) {
return line && httpRegion.symbol.startLine <= line && httpRegion.symbol.endLine >= line;
}
return false;
}

function hasTag(httpRegion: models.HttpRegion, tags: Array<string> | undefined) {
if (tags && utils.isString(httpRegion.metaData?.tag)) {
return tags.includes(httpRegion.metaData.tag);
}
return false;
}

async function selectManualHttpFiles(httpFiles: Array<models.HttpFile>): Promise<SelectActionResult> {
const httpRegionMap: Record<string, SelectActionResult> = {};
const hasManyFiles = httpFiles.length > 1;
const cwd = `${process.cwd()}`;
for (const httpFile of httpFiles) {
const fileName = utils.ensureString(httpFile.fileName)?.replace(cwd, '.');
httpRegionMap[hasManyFiles ? `${fileName}: all` : 'all'] = [{ httpFile }];

for (const httpRegion of httpFile.httpRegions) {
if (!httpRegion.isGlobal()) {
const name = httpRegion.symbol.name;
httpRegionMap[hasManyFiles ? `${fileName}: ${name}` : name] = [
{
httpRegions: [httpRegion],
httpFile,
},
];
}
}
}
const inquirer = await import('inquirer');
const answer = await inquirer.default.prompt([
{
type: 'list',
name: 'region',
message: 'please choose which region to use',
choices: Object.entries(httpRegionMap).map(([key]) => key),
},
]);
if (answer.region && httpRegionMap[answer.region]) {
return httpRegionMap[answer.region];
}
return [];
}
87 changes: 13 additions & 74 deletions src/cli/send/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { toSendJsonOutput } from './jsonOutput';
import { getLogLevel, OutputType, SendFilterOptions, SendOptions } from './options';
import { createCliPluginRegister } from './plugin';
import { transformToJunit } from './junitUtils';
import { selectHttpFiles } from './selectHttpFiles';

export function sendCommand() {
const program = new Command('send')
Expand Down Expand Up @@ -41,6 +42,7 @@ export function sendCommand() {
.option('--repeat-mode <mode>', 'repeat mode: sequential, parallel (default)')
.option('--parallel <count>', 'send parallel requests', utils.toNumber)
.option('-s, --silent', 'log only request')
.option('-t, --tag <tag...>', 'list of tags to execute')
.option('--timeout <timeout>', 'maximum time allowed for connections', utils.toNumber)
.option('--var <variables...>', 'list of variables')
.option('-v, --verbose', 'make the operation more talkative')
Expand All @@ -58,24 +60,21 @@ async function execute(fileNames: Array<string>, options: SendOptions): Promise<
let isFirstRequest = true;
while (options.interactive || isFirstRequest) {
isFirstRequest = false;
const selection = await selectAction(httpFiles, options);
const selection = await selectHttpFiles(httpFiles, options);

context.processedHttpRegions = [];

if (selection) {
await send(Object.assign({}, context, selection));
} else {
const sendFuncs = httpFiles.map(
httpFile =>
async function sendHttpFile() {
if (!options.junit && !options.json && context.scriptConsole && httpFiles.length > 1) {
context.scriptConsole.info(`--------------------- ${httpFile.fileName} --`);
}
await send(Object.assign({}, context, { httpFile }));
const sendFuncs = selection.map(
({ httpFile, httpRegions }) =>
async function sendHttpFile() {
if (!options.junit && !options.json && context.scriptConsole && selection.length > 1) {
context.scriptConsole.info(`--------------------- ${httpFile.fileName} --`);
}
);
await utils.promiseQueue(options.parallel || 1, ...sendFuncs);
}
await send(Object.assign({}, context, { httpFile, httpRegions }));
}
);
await utils.promiseQueue(options.parallel || 1, ...sendFuncs);

reportOutput(context, options);
}
} else {
Expand Down Expand Up @@ -198,66 +197,6 @@ async function queryGlobbyPattern(fileName: string) {
return await globby(fileName.replace(/\\/gu, '/'), globOptions);
}

type SelectActionResult = { httpRegion?: models.HttpRegion | undefined; httpFile: models.HttpFile } | false;

async function selectAction(httpFiles: models.HttpFile[], cliOptions: SendOptions): Promise<SelectActionResult> {
if (httpFiles.length === 1) {
const httpFile = httpFiles[0];
const httpRegion = getHttpRegion(httpFile, cliOptions);
if (httpRegion) {
return {
httpFile,
httpRegion,
};
}
}

if (!cliOptions.all) {
const httpRegionMap: Record<string, { httpRegion?: models.HttpRegion | undefined; httpFile: models.HttpFile }> = {};
const hasManyFiles = httpFiles.length > 1;
for (const httpFile of httpFiles) {
httpRegionMap[hasManyFiles ? `${httpFile.fileName}: all` : 'all'] = { httpFile };

for (const httpRegion of httpFile.httpRegions) {
if (httpRegion.request) {
const name = httpRegion.symbol.name;
httpRegionMap[hasManyFiles ? `${httpFile.fileName}: ${name}` : name] = {
httpRegion,
httpFile,
};
}
}
}
const answer = await (
await import('inquirer')
).default.prompt([
{
type: 'list',
name: 'region',
message: 'please choose which region to use',
choices: Object.entries(httpRegionMap).map(([key]) => key),
},
]);
if (answer.region && httpRegionMap[answer.region]) {
return httpRegionMap[answer.region];
}
}
return false;
}

function getHttpRegion(httpFile: models.HttpFile, cliOptions: SendOptions): models.HttpRegion | false {
let httpRegion: models.HttpRegion | false = false;
if (cliOptions.name) {
httpRegion = httpFile.httpRegions.find(obj => obj.metaData.name === cliOptions.name) || false;
} else {
httpRegion =
httpFile.httpRegions.find(
obj => cliOptions.line && obj.symbol.startLine <= cliOptions.line && obj.symbol.endLine >= cliOptions.line
) || false;
}
return httpRegion;
}

function getStreamLogger(options: SendOptions): models.StreamLogger | undefined {
if (options.output !== 'none') {
return async function logStream(type, response) {
Expand Down
3 changes: 3 additions & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from './getEnvironmentConfig';
export * from './httpFile';
export * from './httpFileStore';
export * from './httpRegion';
export * from './parser';
export * from './pluginStore';
export * from './userSessionStore';

0 comments on commit 6b818a1

Please sign in to comment.