Skip to content

Commit

Permalink
Merge pull request #6 from mcarvin8/beta
Browse files Browse the repository at this point in the history
feat: add covered lines, renumbering out-of-range lines numbers
  • Loading branch information
mcarvin8 authored Mar 21, 2024
2 parents 583a8fc + 516c280 commit 41e47ad
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 15 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [1.6.0-beta.1](https://github.com/mcarvin8/apex-code-coverage-transformer/compare/v1.5.0...v1.6.0-beta.1) (2024-03-20)

### Features

- add `covered` lines, renumbering out-of-range lines numbers ([1733b09](https://github.com/mcarvin8/apex-code-coverage-transformer/commit/1733b09063eac28f0e627e66080dcd24d7c74bf9))

# [1.5.0](https://github.com/mcarvin8/apex-code-coverage-transformer/compare/v1.4.0...v1.5.0) (2024-03-20)

### Features
Expand Down
85 changes: 83 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This will create a coverage JSON in this relative path - `coverage/coverage/cove

This JSON isn't accepted by SonarQube automatically and needs to be converted using this plugin.

**Note**: This has been tested and confirmed on code which meets 100% coverage.
**Disclaimer**: Due to existing bugs with how the Salesforce CLI reports `covered` lines (see [5511](https://github.com/forcedotcom/salesforcedx-vscode/issues/5511) and [1568](https://github.com/forcedotcom/cli/issues/1568)), to add support for `covered` lines in this plugin, I had to add a function to re-number out-of-range `covered` lines the CLI may report (ex: line 100 in a 98-line Apex Class is reported back as `covered` by the Salesforce CLI deploy command). Once Salesforce updates the API to correctly return `covered` lines in the deploy command, this function will be removed.

## Install

Expand Down Expand Up @@ -65,18 +65,99 @@ This [code coverage JSON file](https://raw.githubusercontent.com/mcarvin8/apex-c
<lineToCover lineNumber="53" covered="false"/>
<lineToCover lineNumber="59" covered="false"/>
<lineToCover lineNumber="60" covered="false"/>
<lineToCover lineNumber="1" covered="true"/>
<lineToCover lineNumber="2" covered="true"/>
<lineToCover lineNumber="3" covered="true"/>
<lineToCover lineNumber="4" covered="true"/>
<lineToCover lineNumber="5" covered="true"/>
<lineToCover lineNumber="6" covered="true"/>
<lineToCover lineNumber="7" covered="true"/>
<lineToCover lineNumber="8" covered="true"/>
<lineToCover lineNumber="9" covered="true"/>
<lineToCover lineNumber="10" covered="true"/>
<lineToCover lineNumber="11" covered="true"/>
<lineToCover lineNumber="12" covered="true"/>
<lineToCover lineNumber="13" covered="true"/>
<lineToCover lineNumber="14" covered="true"/>
<lineToCover lineNumber="15" covered="true"/>
<lineToCover lineNumber="16" covered="true"/>
<lineToCover lineNumber="17" covered="true"/>
<lineToCover lineNumber="18" covered="true"/>
<lineToCover lineNumber="19" covered="true"/>
<lineToCover lineNumber="20" covered="true"/>
<lineToCover lineNumber="21" covered="true"/>
<lineToCover lineNumber="22" covered="true"/>
<lineToCover lineNumber="23" covered="true"/>
<lineToCover lineNumber="24" covered="true"/>
<lineToCover lineNumber="25" covered="true"/>
<lineToCover lineNumber="26" covered="true"/>
<lineToCover lineNumber="27" covered="true"/>
</file>
<file path="force-app\main\default\classes\AccountProfile.cls">
<lineToCover lineNumber="52" covered="false"/>
<lineToCover lineNumber="53" covered="false"/>
<lineToCover lineNumber="59" covered="false"/>
<lineToCover lineNumber="60" covered="false"/>
<lineToCover lineNumber="54" covered="true"/>
<lineToCover lineNumber="55" covered="true"/>
<lineToCover lineNumber="56" covered="true"/>
<lineToCover lineNumber="57" covered="true"/>
<lineToCover lineNumber="58" covered="true"/>
<lineToCover lineNumber="61" covered="true"/>
<lineToCover lineNumber="62" covered="true"/>
<lineToCover lineNumber="63" covered="true"/>
<lineToCover lineNumber="64" covered="true"/>
<lineToCover lineNumber="65" covered="true"/>
<lineToCover lineNumber="66" covered="true"/>
<lineToCover lineNumber="67" covered="true"/>
<lineToCover lineNumber="68" covered="true"/>
<lineToCover lineNumber="69" covered="true"/>
<lineToCover lineNumber="70" covered="true"/>
<lineToCover lineNumber="71" covered="true"/>
<lineToCover lineNumber="72" covered="true"/>
<lineToCover lineNumber="1" covered="true"/>
<lineToCover lineNumber="2" covered="true"/>
<lineToCover lineNumber="3" covered="true"/>
<lineToCover lineNumber="4" covered="true"/>
<lineToCover lineNumber="5" covered="true"/>
<lineToCover lineNumber="6" covered="true"/>
<lineToCover lineNumber="7" covered="true"/>
<lineToCover lineNumber="8" covered="true"/>
<lineToCover lineNumber="9" covered="true"/>
<lineToCover lineNumber="10" covered="true"/>
</file>
<file path="force-app\main\default\flows\Get_Info.flow-meta.xml">
<file path="packaged\flows\Get_Info.flow-meta.xml">
<lineToCover lineNumber="52" covered="false"/>
<lineToCover lineNumber="53" covered="false"/>
<lineToCover lineNumber="59" covered="false"/>
<lineToCover lineNumber="60" covered="false"/>
<lineToCover lineNumber="54" covered="true"/>
<lineToCover lineNumber="55" covered="true"/>
<lineToCover lineNumber="56" covered="true"/>
<lineToCover lineNumber="57" covered="true"/>
<lineToCover lineNumber="58" covered="true"/>
<lineToCover lineNumber="61" covered="true"/>
<lineToCover lineNumber="62" covered="true"/>
<lineToCover lineNumber="63" covered="true"/>
<lineToCover lineNumber="64" covered="true"/>
<lineToCover lineNumber="65" covered="true"/>
<lineToCover lineNumber="66" covered="true"/>
<lineToCover lineNumber="67" covered="true"/>
<lineToCover lineNumber="68" covered="true"/>
<lineToCover lineNumber="69" covered="true"/>
<lineToCover lineNumber="70" covered="true"/>
<lineToCover lineNumber="71" covered="true"/>
<lineToCover lineNumber="72" covered="true"/>
<lineToCover lineNumber="73" covered="true"/>
<lineToCover lineNumber="74" covered="true"/>
<lineToCover lineNumber="75" covered="true"/>
<lineToCover lineNumber="76" covered="true"/>
<lineToCover lineNumber="77" covered="true"/>
<lineToCover lineNumber="78" covered="true"/>
<lineToCover lineNumber="79" covered="true"/>
<lineToCover lineNumber="80" covered="true"/>
<lineToCover lineNumber="81" covered="true"/>
<lineToCover lineNumber="82" covered="true"/>
</file>
</coverage>
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "apex-code-coverage-transformer",
"description": "Transforms the Apex Code Coverage JSON into the Generic Test Data Report.",
"version": "1.5.0",
"version": "1.6.0-beta.1",
"dependencies": {
"@oclif/core": "^3.18.1",
"@salesforce/core": "^6.4.7",
Expand Down
38 changes: 32 additions & 6 deletions src/helpers/convertToGenericCoverageReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable no-await-in-loop */

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

export async function convertToGenericCoverageReport(data: CoverageData, dxConfigFile: string): Promise<string> {
Expand All @@ -15,15 +16,40 @@ export async function convertToGenericCoverageReport(data: CoverageData, dxConfi
if (filePath === undefined) {
throw Error(`The file name ${formattedFileName} was not found in any package directory.`);
}
// Extract the "uncovered lines" from the JSON data
const uncoveredLines = Object.keys(fileInfo.s)
.filter(lineNumber => fileInfo.s[lineNumber] === 0)
.map(Number);
const coveredLines = Object.keys(fileInfo.s)
.filter(lineNumber => fileInfo.s[lineNumber] === 1)
.map(Number);
const randomLines: number[] = [];
const totalLines = getTotalLines(filePath);

xml += `\t<file path="${filePath}">\n`;

for (const lineNumber in fileInfo.s) {
if (!Object.hasOwn(fileInfo.s, lineNumber)) continue;
const count = fileInfo.s[lineNumber];
const covered = count > 0 ? 'true' : 'false';
// only add uncovered lines
if (covered === 'false') xml += `\t\t<lineToCover lineNumber="${lineNumber}" covered="${covered}"/>\n`;
for (const uncoveredLine of uncoveredLines) {
xml += `\t\t<lineToCover lineNumber="${uncoveredLine}" covered="false"/>\n`;
}

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)
) {
xml += `\t\t<lineToCover lineNumber="${randomLineNumber}" covered="true"/>\n`;
randomLines.push(randomLineNumber);
break;
}
}
} else {
xml += `\t\t<lineToCover lineNumber="${coveredLine}" covered="true"/>\n`;
}
}

xml += '\t</file>\n';
}
xml += '</coverage>';
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/getTotalLines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';
import * as fs from 'node:fs';

export function getTotalLines(filePath: string): number {
const fileContent = fs.readFileSync(filePath, 'utf8');
return fileContent.split(/\r\n|\r|\n/).length;
}
72 changes: 72 additions & 0 deletions test/baselines/classes/AccountProfile.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
global class PrepareMySandbox implements SandboxPostCopy {
global PrepareMySandbox() {
// Implementations of SandboxPostCopy must have a no-arg constructor.
// This constructor is used during the sandbox copy process.
}

global void runApexClass(SandboxContext context) {
System.debug('Org ID: ' + context.organizationId());
System.debug('Sandbox ID: ' + context.sandboxId());
System.debug('Sandbox Name: ' + context.sandboxName());

updateProfilesAndResetPasswordsForPublicGroupMembers();
// Additional logic to prepare the sandbox for use can be added here.
}

public void updateProfilesAndResetPasswordsForPublicGroupMembers() {
String publicGroupId = '00G5a000003ji0R';
String newProfileId = '00e0b000001KWuY';

Group publicGroup = getPublicGroup(publicGroupId);

if (publicGroup != null) {
List<User> usersToUpdate = getUsersToUpdate(publicGroup, newProfileId);

if (!usersToUpdate.isEmpty()) {
update usersToUpdate;
System.debug('Profile updated for ' + usersToUpdate.size() + ' users.');

// Reset passwords for updated users
resetPasswords(usersToUpdate);
} else {
System.debug('No eligible active users found in the Public Group.');
}
} else {
System.debug('Public Group not found.');
}
}

private Group getPublicGroup(String groupId) {
return [SELECT Id FROM Group WHERE Id = :groupId LIMIT 1];
}

private List<User> getUsersToUpdate(Group publicGroup, String newProfileId) {
List<User> usersToUpdate = new List<User>();
Set<Id> userIds = new Set<Id>();

// Get the current running User's Id
Id currentUserId = UserInfo.getUserId();

for (GroupMember member : [SELECT UserOrGroupId FROM GroupMember WHERE GroupId = :publicGroup.Id]) {
Id userOrGroupId = member.UserOrGroupId;
if (userOrGroupId != null && userOrGroupId.getSObjectType() == User.SObjectType && userOrGroupId != currentUserId) {
userIds.add(userOrGroupId);
}
}

// Query and update active User profiles
for (User user : [SELECT Id, ProfileId FROM User WHERE Id IN :userIds AND IsActive = true]) {
user.ProfileId = newProfileId;
usersToUpdate.add(user);
}

return usersToUpdate;
}

private void resetPasswords(List<User> users) {
for (User u : users) {
System.resetPassword(u.Id, true); // The second parameter generates a new password and sends an email
}
System.debug('Passwords reset for ' + users.size() + ' users.');
}
}
Loading

0 comments on commit 41e47ad

Please sign in to comment.