Skip to content

Commit 7787116

Browse files
committed
Implement CI process to validate deployment including test run.
1 parent e9d6d7f commit 7787116

9 files changed

+870
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Deployment Verification
2+
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
7+
on:
8+
pull_request:
9+
branches:
10+
- develop
11+
types:
12+
- opened
13+
- synchronize
14+
- reopened
15+
16+
jobs:
17+
validate_scratch_deploy:
18+
runs-on: ubuntu-latest
19+
steps:
20+
# Install Salesforce CLI
21+
- name: Install Salesforce CLI
22+
run: |
23+
npm install @salesforce/cli --location=global
24+
nodeInstallPath=$(npm config get prefix)
25+
echo "$nodeInstallPath/bin" >> $GITHUB_PATH
26+
sf --version
27+
28+
# Checkout the code in the pull request
29+
- name: Checkout source code
30+
uses: actions/checkout@v3
31+
32+
# Load secret for dev hub
33+
- name: Populate auth file with SFDX_URL secret
34+
shell: bash
35+
run: "echo ${{ secrets.SFDX_AUTH_URL }} > ./SFDX_URL_STORE.txt"
36+
37+
# Authenticate with dev hub
38+
- name: Authenticate with dev hub
39+
run: sf org login sfdx-url -f ./SFDX_URL_STORE.txt -a LibakDevHub -d
40+
41+
# Run Apex Tests
42+
- name: Deployment Validation
43+
id: run_tests
44+
run: |
45+
sf project deploy start --test-level RunLocalTests --dry-run --json --target-org LibakDevHub > validation_result.json || true
46+
47+
# Post Comment on PR if Tests Fail or Coverage is Low
48+
- name: Post Comment on PR
49+
uses: actions/github-script@v6
50+
with:
51+
script: |
52+
const fs = require('fs');
53+
const deploymentResult = JSON.parse(fs.readFileSync('./validation_result.json', 'utf8'));
54+
55+
const message = require('./CI/scripts/validation_comment.js')(deploymentResult);
56+
57+
await github.rest.issues.createComment({
58+
...context.repo,
59+
issue_number: context.payload.pull_request?.number,
60+
body: message
61+
});
62+
63+
if(deploymentResult.status === 1) {
64+
throw new Error('Deployment Validation was failed. See Pull Request comments.')
65+
}
66+

CI/scripts/validation_comment.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const deploymentValidation = ({
2+
status,
3+
result,
4+
warnings
5+
}) => {
6+
const testFailureMessage = buildTestFailureString(
7+
result.details.runTestResult.failures
8+
);
9+
const componentsFailureMessage = buildComponentsFailureString(
10+
result.details.componentFailures
11+
);
12+
13+
return buildMessageString(
14+
status,
15+
testFailureMessage,
16+
componentsFailureMessage
17+
);
18+
};
19+
20+
const buildTestFailureString = testFailures => {
21+
return testFailures && testFailures.length && testFailures.reduce((result, failure) => {
22+
result += `
23+
\t\tTest Class: ${failure.name}
24+
\t\tTest Method: ${failure.methodName}
25+
\t\tMessage: ${failure.message}
26+
\t\tStackTrace: ${failure.stackTrace.replace(/\n/g, '\n\t\t')}
27+
`;
28+
return result;
29+
}, '\n')
30+
}
31+
32+
const buildComponentsFailureString = componentFailures => {
33+
return componentFailures && componentFailures.length && componentFailures.reduce((result, failure) => {
34+
result += `
35+
\t\tType: ${failure.componentType}
36+
\t\tName: ${failure.fullName}
37+
\t\t${failure.problemType}: ${failure.problem}
38+
\t\tLine: ${failure.lineNumber}
39+
`;
40+
return result;
41+
}, '\n')
42+
}
43+
44+
const buildMessageString = (statusCode, testResultStr, componentsFailureMessage) => {
45+
let message = `### ${!statusCode ? '💚' : '💔'} Deployment Validation Results:`;
46+
message += `\n- **Status**: ${!statusCode && 'Success' || 'Failed'}`;
47+
message += testResultStr
48+
? '\n- **Test Failures**:' + testResultStr
49+
: '';
50+
message += componentsFailureMessage
51+
? '\n- **Component Failures**:' + componentsFailureMessage
52+
: '';
53+
return message
54+
}
55+
56+
module.exports = deploymentValidation;
57+
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const deploymentResult = {
2+
"status": 1,
3+
"result": {
4+
"checkOnly": true,
5+
"completedDate": "2024-12-20T08:15:35.000Z",
6+
"createdBy": "0052y000000XAvZ",
7+
"createdByName": "Andrew Kohanovskij",
8+
"createdDate": "2024-12-20T08:15:34.000Z",
9+
"details": {
10+
"componentFailures": [
11+
{
12+
"changed": false,
13+
"columnNumber": 5,
14+
"componentType": "ApexClass",
15+
"created": false,
16+
"createdDate": "2024-12-20T08:15:34.000Z",
17+
"deleted": false,
18+
"fileName": "classes/SomeClass.cls",
19+
"fullName": "SomeClass",
20+
"lineNumber": 4,
21+
"problem": "Missing ';' at '}'",
22+
"problemType": "Error",
23+
"success": false
24+
},{
25+
"changed": false,
26+
"columnNumber": 5,
27+
"componentType": "ApexClass",
28+
"created": false,
29+
"createdDate": "2024-12-20T08:15:34.000Z",
30+
"deleted": false,
31+
"fileName": "classes/SomeClass.cls",
32+
"fullName": "SomeClass",
33+
"lineNumber": 4,
34+
"problem": "Missing ';' at '}'",
35+
"problemType": "Error",
36+
"success": false
37+
}
38+
],
39+
"componentSuccesses": [
40+
{
41+
"changed": true,
42+
"componentType": "",
43+
"created": false,
44+
"createdDate": "2024-12-20T08:15:34.000Z",
45+
"deleted": false,
46+
"fileName": "package.xml",
47+
"fullName": "package.xml",
48+
"success": true
49+
},
50+
{
51+
"changed": true,
52+
"componentType": "ApexClass",
53+
"created": true,
54+
"createdDate": "2024-12-20T08:15:34.000Z",
55+
"deleted": false,
56+
"fileName": "classes/SomeClassTest.cls",
57+
"fullName": "SomeClassTest",
58+
"id": "01pIi000000Gtz3IAC",
59+
"success": true
60+
}
61+
],
62+
"runTestResult": {
63+
"numFailures": 0,
64+
"numTestsRun": 0,
65+
"totalTime": 0,
66+
"codeCoverage": [],
67+
"codeCoverageWarnings": [],
68+
"failures": [],
69+
"flowCoverage": [],
70+
"flowCoverageWarnings": [],
71+
"successes": []
72+
}
73+
},
74+
"done": true,
75+
"id": "0AfIi000000oo38KAA",
76+
"ignoreWarnings": false,
77+
"lastModifiedDate": "2024-12-20T08:15:35.000Z",
78+
"numberComponentErrors": 1,
79+
"numberComponentsDeployed": 1,
80+
"numberComponentsTotal": 2,
81+
"numberTestErrors": 0,
82+
"numberTestsCompleted": 0,
83+
"numberTestsTotal": 0,
84+
"rollbackOnError": true,
85+
"runTestsEnabled": true,
86+
"startDate": "2024-12-20T08:15:34.000Z",
87+
"status": "Failed",
88+
"success": false,
89+
"files": [
90+
{
91+
"fullName": "SomeClass",
92+
"type": "ApexClass",
93+
"state": "Failed",
94+
"problemType": "Error",
95+
"filePath": "/Users/andreikakhanouski/Job/test-git-actions/force-app/main/default/classes/SomeClass.cls",
96+
"lineNumber": 4,
97+
"columnNumber": 5,
98+
"error": "Missing ';' at '}' (4:5)"
99+
},
100+
{
101+
"fullName": "SomeClassTest",
102+
"type": "ApexClass",
103+
"state": "Created",
104+
"filePath": "/Users/andreikakhanouski/Job/test-git-actions/force-app/main/default/classes/SomeClassTest.cls"
105+
},
106+
{
107+
"fullName": "SomeClassTest",
108+
"type": "ApexClass",
109+
"state": "Created",
110+
"filePath": "/Users/andreikakhanouski/Job/test-git-actions/force-app/main/default/classes/SomeClassTest.cls-meta.xml"
111+
}
112+
],
113+
"zipSize": 1376,
114+
"zipFileCount": 5,
115+
"deployUrl": "https://libak-dev-ed.my.salesforce.com/lightning/setup/DeployStatus/page?address=%2Fchangemgmt%2FmonitorDeploymentsDetails.apexp%3FasyncId%3D0AfIi000000oo38KAA%26retURL%3D%252Fchangemgmt%252FmonitorDeployment.apexp"
116+
},
117+
"warnings": []
118+
}
119+
120+
const fs = require('fs');
121+
fs.writeFileSync(
122+
"CI/scripts/validation_comment_tests/test_validation_result_mock_components_fail.md",
123+
require('../validation_comment.js')(deploymentResult)
124+
);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
### 💔 Deployment Validation Results:
2+
- **Status**: Failed
3+
- **Component Failures**:
4+
5+
Type: ApexClass
6+
Name: SomeClass
7+
Error: Missing ';' at '}'
8+
Line: 4
9+
10+
Type: ApexClass
11+
Name: SomeClass
12+
Error: Missing ';' at '}'
13+
Line: 4

0 commit comments

Comments
 (0)