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

feat: add covered lines, renumbering out-of-range lines numbers #6

Merged
merged 4 commits into from
Mar 21, 2024
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
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