Skip to content

Commit

Permalink
fix: switch to promises/async and refactor imports
Browse files Browse the repository at this point in the history
  • Loading branch information
mcarvin8 committed Apr 9, 2024
1 parent 40934fb commit 2577692
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 90 deletions.
24 changes: 8 additions & 16 deletions src/commands/apex-code-coverage/transformer/transform.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

import * as fs from 'node:fs';
import * as path from 'node:path';
import { resolve } from 'node:path';
import { writeFile, readFile } from 'node:fs/promises';

import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
Expand Down Expand Up @@ -48,11 +48,11 @@ export default class TransformerTransform extends SfCommand<TransformerTransform
let jsonFilePath = flags['coverage-json'];
let xmlFilePath = flags['xml'];
let sfdxConfigFile = flags['sfdx-configuration'];
jsonFilePath = path.resolve(jsonFilePath);
xmlFilePath = path.resolve(xmlFilePath);
sfdxConfigFile = path.resolve(sfdxConfigFile);
jsonFilePath = resolve(jsonFilePath);
xmlFilePath = resolve(xmlFilePath);
sfdxConfigFile = resolve(sfdxConfigFile);

const jsonData = fs.readFileSync(jsonFilePath, 'utf-8');
const jsonData = await readFile(jsonFilePath, 'utf-8');
const coverageData = JSON.parse(jsonData) as CoverageData;
const {
xml: xmlData,
Expand All @@ -71,16 +71,8 @@ export default class TransformerTransform extends SfCommand<TransformerTransform
this.error('None of the files listed in the coverage JSON were processed.');
}

// Write the XML data to the XML file
try {
fs.writeFileSync(xmlFilePath, xmlData);
this.log(`The XML data has been written to ${xmlFilePath}`);
} catch (error) {
if (error instanceof Error) {
this.error(`Error writing XML data to file: ${error.message}`);
}
}

await writeFile(xmlFilePath, xmlData);
this.log(`The XML data has been written to ${xmlFilePath}`);
return { path: xmlFilePath };
}
}
28 changes: 2 additions & 26 deletions src/helpers/convertToGenericCoverageReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
/* eslint-disable no-await-in-loop */

import { CoverageData } from './types.js';
import { getTotalLines } from './getTotalLines.js';
import { findFilePath } from './findFilePath.js';
import { setCoveredLines } from './setCoveredLines.js';

export async function convertToGenericCoverageReport(
data: CoverageData,
Expand Down Expand Up @@ -37,34 +37,10 @@ export async function convertToGenericCoverageReport(
}

// this function is only needed until Salesforce fixes the API to correctly return covered lines
xml += setCoveredLines(coveredLines, uncoveredLines, filePath);
xml += await setCoveredLines(coveredLines, uncoveredLines, filePath);
filesProcessed++;
xml += '\t</file>\n';
}
xml += '</coverage>';
return { xml, warnings, filesProcessed };
}

function setCoveredLines(coveredLines: number[], uncoveredLines: number[], filePath: string): string {
let formattedCoveredLines: string = '';
const randomLines: number[] = [];
const totalLines = getTotalLines(filePath);
for (const coveredLine of coveredLines) {
if (coveredLine > totalLines) {
for (let randomLineNumber = 1; randomLineNumber <= totalLines; randomLineNumber++) {
if (
!uncoveredLines.includes(randomLineNumber) &&
!coveredLines.includes(randomLineNumber) &&
!randomLines.includes(randomLineNumber)
) {
formattedCoveredLines += `\t\t<lineToCover lineNumber="${randomLineNumber}" covered="true"/>\n`;
randomLines.push(randomLineNumber);
break;
}
}
} else {
formattedCoveredLines += `\t\t<lineToCover lineNumber="${coveredLine}" covered="true"/>\n`;
}
}
return formattedCoveredLines;
}
10 changes: 5 additions & 5 deletions src/helpers/findFilePath.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
/* eslint-disable no-await-in-loop */

import * as fs from 'node:fs';
import * as path from 'node:path';
import { readdir, stat } from 'node:fs/promises';
import { join } from 'node:path';

import { getPackageDirectories } from './getPackageDirectories.js';

Expand All @@ -20,10 +20,10 @@ export async function findFilePath(fileName: string, dxConfigFile: string): Prom
}

async function searchRecursively(fileName: string, dxDirectory: string): Promise<string | undefined> {
const files = await fs.promises.readdir(dxDirectory);
const files = await readdir(dxDirectory);
for (const file of files) {
const filePath = path.join(dxDirectory, file);
const stats = await fs.promises.stat(filePath);
const filePath = join(dxDirectory, file);
const stats = await stat(filePath);
if (stats.isDirectory()) {
const result = await searchRecursively(fileName, filePath);
if (result) {
Expand Down
8 changes: 4 additions & 4 deletions src/helpers/getPackageDirectories.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use strict';
/* eslint-disable no-await-in-loop */

import * as fs from 'node:fs';
import * as promises from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { SfdxProject } from './types.js';

export async function getPackageDirectories(dxConfigFile: string): Promise<string[]> {
if (!fs.existsSync(dxConfigFile)) {
if (!existsSync(dxConfigFile)) {
throw Error(`Salesforce DX Config File does not exist in this path: ${dxConfigFile}`);
}

const sfdxProjectRaw: string = await promises.readFile(dxConfigFile, 'utf-8');
const sfdxProjectRaw: string = await readFile(dxConfigFile, 'utf-8');
const sfdxProject: SfdxProject = JSON.parse(sfdxProjectRaw) as SfdxProject;
const packageDirectories = sfdxProject.packageDirectories.map((directory) => directory.path);
return packageDirectories;
Expand Down
7 changes: 4 additions & 3 deletions src/helpers/getTotalLines.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';
import * as fs from 'node:fs';

export function getTotalLines(filePath: string): number {
const fileContent = fs.readFileSync(filePath, 'utf8');
import { readFile } from 'node:fs/promises';

export async function getTotalLines(filePath: string): Promise<number> {
const fileContent = await readFile(filePath, 'utf8');
return fileContent.split(/\r\n|\r|\n/).length;
}
31 changes: 31 additions & 0 deletions src/helpers/setCoveredLines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

import { getTotalLines } from './getTotalLines.js';

export async function setCoveredLines(
coveredLines: number[],
uncoveredLines: number[],
filePath: string
): Promise<string> {
let formattedCoveredLines: string = '';
const randomLines: number[] = [];
const totalLines = await getTotalLines(filePath);
for (const coveredLine of coveredLines) {
if (coveredLine > totalLines) {
for (let randomLineNumber = 1; randomLineNumber <= totalLines; randomLineNumber++) {
if (
!uncoveredLines.includes(randomLineNumber) &&
!coveredLines.includes(randomLineNumber) &&
!randomLines.includes(randomLineNumber)
) {
formattedCoveredLines += `\t\t<lineToCover lineNumber="${randomLineNumber}" covered="true"/>\n`;
randomLines.push(randomLineNumber);
break;
}
}
} else {
formattedCoveredLines += `\t\t<lineToCover lineNumber="${coveredLine}" covered="true"/>\n`;
}
}
return formattedCoveredLines;
}
77 changes: 41 additions & 36 deletions test/commands/transformer/unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as fs from 'node:fs';
import * as assert from 'node:assert';
import * as path from 'node:path';
'use strict';

import { copyFile, readFile, writeFile, rm, mkdir } from 'node:fs/promises';
import { strictEqual } from 'node:assert';
import { resolve } from 'node:path';

import { TestContext } from '@salesforce/core/lib/testSetup.js';
import { expect } from 'chai';
Expand All @@ -17,15 +19,14 @@ describe('transform the code coverage json', () => {
let testXmlPath1 = 'coverage1.xml';
let testXmlPath2 = 'coverage2.xml';
let sfdxConfigFile = 'sfdx-project.json';
baselineClassPath = path.resolve(baselineClassPath);
baselineTriggerPath = path.resolve(baselineTriggerPath);
coverageJsonPathNoExts = path.resolve(coverageJsonPathNoExts);
coverageJsonPathWithExts = path.resolve(coverageJsonPathWithExts);
testXmlPath1 = path.resolve(testXmlPath1);
testXmlPath2 = path.resolve(testXmlPath2);
sfdxConfigFile = path.resolve(sfdxConfigFile);
baselineClassPath = resolve(baselineClassPath);
baselineTriggerPath = resolve(baselineTriggerPath);
coverageJsonPathNoExts = resolve(coverageJsonPathNoExts);
coverageJsonPathWithExts = resolve(coverageJsonPathWithExts);
testXmlPath1 = resolve(testXmlPath1);
testXmlPath2 = resolve(testXmlPath2);
sfdxConfigFile = resolve(sfdxConfigFile);

// Mock file contents
const configFile = {
packageDirectories: [{ path: 'force-app', default: true }, { path: 'packaged' }],
namespace: '',
Expand All @@ -34,13 +35,12 @@ describe('transform the code coverage json', () => {
};
const configJsonString = JSON.stringify(configFile, null, 2);

// Create mock files
before(() => {
fs.mkdirSync('force-app/main/default/classes', { recursive: true });
fs.mkdirSync('packaged/triggers', { recursive: true });
fs.copyFileSync(baselineClassPath, 'force-app/main/default/classes/AccountProfile.cls');
fs.copyFileSync(baselineTriggerPath, 'packaged/triggers/AccountTrigger.trigger');
fs.writeFileSync(sfdxConfigFile, configJsonString);
before(async () => {
await mkdir('force-app/main/default/classes', { recursive: true });
await mkdir('packaged/triggers', { recursive: true });
await copyFile(baselineClassPath, 'force-app/main/default/classes/AccountProfile.cls');
await copyFile(baselineTriggerPath, 'packaged/triggers/AccountTrigger.trigger');
await writeFile(sfdxConfigFile, configJsonString);
});

beforeEach(() => {
Expand All @@ -51,40 +51,45 @@ describe('transform the code coverage json', () => {
$$.restore();
});

// Cleanup mock files
after(() => {
fs.unlinkSync('force-app/main/default/classes/AccountProfile.cls');
fs.unlinkSync('packaged/triggers/AccountTrigger.trigger');
fs.rmdirSync('force-app', { recursive: true });
fs.rmdirSync('packaged', { recursive: true });
fs.rmSync(testXmlPath1);
fs.rmSync(testXmlPath2);
fs.rmSync(sfdxConfigFile);
after(async () => {
await rm('force-app/main/default/classes/AccountProfile.cls');
await rm('packaged/triggers/AccountTrigger.trigger');
await rm('force-app', { recursive: true });
await rm('packaged', { recursive: true });
await rm(testXmlPath1);
await rm(testXmlPath2);
await rm(sfdxConfigFile);
});

it('transform the test JSON file without file extensions into the generic test coverage format', async () => {
it('transform the test JSON file without file extensions into the generic test coverage format without any warnings', async () => {
await TransformerTransform.run(['--coverage-json', coverageJsonPathNoExts, '--xml', testXmlPath1]);
const output = sfCommandStubs.log
.getCalls()
.flatMap((c) => c.args)
.join('\n');
expect(output).to.include(`The XML data has been written to ${testXmlPath1}`);
const warnings = sfCommandStubs.warn
.getCalls()
.flatMap((c) => c.args)
.join('\n');
expect(warnings).to.include('');
});
it('transform the test JSON file with file extensions into the generic test coverage format', async () => {
it('transform the test JSON file with file extensions into the generic test coverage format without any warnings', async () => {
await TransformerTransform.run(['--coverage-json', coverageJsonPathWithExts, '--xml', testXmlPath2]);
const output = sfCommandStubs.log
.getCalls()
.flatMap((c) => c.args)
.join('\n');
expect(output).to.include(`The XML data has been written to ${testXmlPath2}`);
const warnings = sfCommandStubs.warn
.getCalls()
.flatMap((c) => c.args)
.join('\n');
expect(warnings).to.include('');
});
it('confirm the 2 XML files created in the previous tests match', async () => {
const xmlContent1 = fs.readFileSync(testXmlPath1, 'utf-8');
const xmlContent2 = fs.readFileSync(testXmlPath2, 'utf-8');
assert.strictEqual(
xmlContent1,
xmlContent2,
`File content is different between ${testXmlPath1} and ${testXmlPath2}`
);
const xmlContent1 = await readFile(testXmlPath1, 'utf-8');
const xmlContent2 = await readFile(testXmlPath2, 'utf-8');
strictEqual(xmlContent1, xmlContent2, `File content is different between ${testXmlPath1} and ${testXmlPath2}`);
});
});

0 comments on commit 2577692

Please sign in to comment.