Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CSR-2066] feat: added full test suite generation for convert command #139

Merged
merged 17 commits into from
Feb 10, 2025
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
2 changes: 1 addition & 1 deletion examples/jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": true,
"scripts": {
"test": "jest",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=2gT26j --key=nHw5cTjgW7f1rvL5",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=xW2Ijf --key=9bqJY1huXL2l3ONF",
"build": "echo \"No build specified\" && exit 0"
},
"devDependencies": {
Expand Down
21 changes: 0 additions & 21 deletions examples/jest/src/basic/basic2.spec.ts

This file was deleted.

16 changes: 10 additions & 6 deletions packages/cmd/src/commands/convert/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const inputFormatOption = new Option(
).choices(Object.values(REPORT_INPUT_FORMATS));

export const inputFileOption = new Option(
'--input-file <pattern>',
'the pattern to search for test reports'
'--input-file <patterns>',
'comma-separated glob patterns to match report file paths (e.g., "report1.xml,report2.xml")'
).argParser(validateGlobPattern);

export const outputDirOption = new Option(
Expand All @@ -40,9 +40,13 @@ export const frameworkVersionOption = new Option(
);

function validateGlobPattern(value: string) {
const result = glob.globSync(value);
if (result.length === 0) {
throw new InvalidArgumentError('No files found with the provided pattern');
const patterns = value.split(',').map((pattern) => pattern.trim());

const allResults = glob.globSync(patterns);

if (allResults.length === 0) {
throw new InvalidArgumentError('No files found with the provided patterns');
}
return result;

return allResults;
}
21 changes: 21 additions & 0 deletions packages/cmd/src/lib/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ export async function writeFileAsync(filePath: string, content: string) {
}
}

export async function writeFileAsyncIfNotExists(
filePath: string,
content: string
) {
try {
// Check if file exists
try {
await fs.access(filePath);
// If we reach here, file exists
return filePath;
} catch {
// File doesn't exist, create it
await fs.writeFile(filePath, content, 'utf8');
return filePath;
}
} catch (err) {
error(`Error writing file at ${filePath}:`, err);
throw err;
}
}

export async function ensurePathExists(
filePath: string,
isDirectory?: boolean
Expand Down
17 changes: 9 additions & 8 deletions packages/cmd/src/services/convert/__tests__/instances.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { parseStringPromise } from 'xml2js';
import { InstanceReport } from '../../../types';
import { createSuiteJson, getInstanceMap } from '../postman/instances';
import { timeToMilliseconds } from '../utils';
import { mockDate } from './fixtures';

describe('getInstanceMap', () => {
it('should return an empty Map when the XML input is empty', async () => {
expect(await getInstanceMap('')).toEqual(new Map());
expect(await getInstanceMap([])).toEqual(new Map());
});

it('should return an empty Map when the test suite array is empty', async () => {
expect(await getInstanceMap('<testsuites></testsuites>')).toEqual(
new Map()
);
expect(await getInstanceMap([])).toEqual(new Map());
});

it('should return an empty Map when instances have no tests', async () => {
expect(
await getInstanceMap('<testsuites><testsuite></testsuite></testsuites>')
).toEqual(new Map());
expect(await getInstanceMap([])).toEqual(new Map());
});

const xmlInput = `
Expand Down Expand Up @@ -46,7 +43,11 @@ describe('getInstanceMap', () => {
}

beforeEach(async () => {
instanceMap = await getInstanceMap(xmlInput);
const parsedXMLInput = await parseStringPromise(xmlInput, {
explicitArray: false,
mergeAttrs: true,
});
instanceMap = await getInstanceMap([parsedXMLInput]);
});

it('returns a map of instances', () => {
Expand Down
64 changes: 0 additions & 64 deletions packages/cmd/src/services/convert/combineInputFiles.ts

This file was deleted.

48 changes: 48 additions & 0 deletions packages/cmd/src/services/convert/createFullTestSuite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
FullSuiteProject,
FullSuiteTest,
FullTestSuite,
} from '../upload/discovery';
import { TestCase, TestSuite, TestSuites } from './types';
import {
ensureArray,
generateTestId,
getSuiteName,
getTestTitle,
} from './utils';

export function createFullTestSuite(parsedXMLInputs: TestSuites[]) {
const fullTestSuite: FullTestSuite = [];

parsedXMLInputs.forEach((item) => {
const testsuites = ensureArray<TestSuite>(item.testsuites?.testsuite);

const fullSuiteProject: FullSuiteProject = {
name: item.testsuites?.name ?? 'No name',
tags: [],
tests: [],
};

testsuites?.forEach((suite) => {
const suiteName = getSuiteName(suite, testsuites);
const testcases = ensureArray<TestCase>(suite?.testcase);

testcases?.forEach((testcase) => {
const fullSuiteTest: FullSuiteTest = {
title: getTestTitle(testcase.name, suiteName),
spec: suiteName,
tags: [],
testId: generateTestId(
getTestTitle(testcase.name, suiteName).join(', '),
suiteName
),
};

fullSuiteProject.tests.push(fullSuiteTest);
});
});
fullTestSuite.push(fullSuiteProject);
});

return fullTestSuite;
}
28 changes: 7 additions & 21 deletions packages/cmd/src/services/convert/getInstanceMap.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,37 @@
import { warn } from '@logger';
import { readFile } from 'fs-extra';
import {
REPORT_FRAMEWORKS,
REPORT_INPUT_FORMATS,
} from '../../commands/convert/options';
import { InstanceReport } from '../../types';
import { combineInputFiles, saveXMLInput } from './combineInputFiles';
import { getInstanceMap as getInstanceMapForPostman } from './postman/instances';
import { TestSuites } from './types';

export async function getInstanceMap({
inputFormat,
inputFiles,
outputDir,
framework,
parsedXMLArray,
}: {
inputFormat: REPORT_INPUT_FORMATS;
inputFiles: string[];
outputDir: string;
framework: REPORT_FRAMEWORKS;
parsedXMLArray: TestSuites[];
}): Promise<Map<string, InstanceReport>> {
if (inputFormat === REPORT_INPUT_FORMATS.junit) {
let xmlInput = '';
if (inputFiles.length > 1) {
xmlInput = await combineInputFiles(inputFiles);
} else if (inputFiles.length === 1) {
xmlInput = await readFile(inputFiles[0], 'utf-8');
}

const trimmedXMLInput = xmlInput.trim();
if (trimmedXMLInput) {
await saveXMLInput(outputDir, trimmedXMLInput);
return getInstanceMapByFramework(framework, trimmedXMLInput);
}
return getInstanceMapByFramework(framework, parsedXMLArray);
}

return new Map();
}

async function getInstanceMapByFramework(
framework: REPORT_FRAMEWORKS,
xmlInput: string
parsedXMLArray: TestSuites[]
) {
switch (framework) {
case 'postman':
return getInstanceMapForPostman(xmlInput);
return getInstanceMapForPostman(parsedXMLArray);
case 'vitest':
return getInstanceMapForPostman(xmlInput);
return getInstanceMapForPostman(parsedXMLArray);
default:
warn('Unsupported framework: %s', framework);
return new Map<string, InstanceReport>();
Expand Down
41 changes: 41 additions & 0 deletions packages/cmd/src/services/convert/getParsedXMLArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { debug } from '@debug';
import { warn } from '@logger';
import { readFile } from 'fs-extra';
import { parseStringPromise } from 'xml2js';
import { TestSuites } from './types';

export async function getParsedXMLArray(
inputFiles: string[]
): Promise<TestSuites[]> {
const filesData: string[] = await Promise.all(
inputFiles.map((item) => readFile(item, 'utf-8'))
);

const parsedXMLInputs = (
await Promise.all(filesData.map(getParsedXMLInput))
).filter(Boolean);

if (filesData.length !== parsedXMLInputs.length) {
warn(
'Some files could not be parsed. Enable debug logging for more details'
);
}

return parsedXMLInputs;
}

async function getParsedXMLInput(XMLString: string) {
const trimmedXMLString = XMLString.trim();
if (!trimmedXMLString) return null;

try {
const parsedXMLInput = await parseStringPromise(trimmedXMLString, {
explicitArray: false,
mergeAttrs: true,
});
return parsedXMLInput || null;
} catch (e) {
debug('Error parsing XML input', e);
return null;
}
}
Loading
Loading