@freshworks/shiftleft-tools 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +351 -0
- package/bin/shiftleft.js +95 -0
- package/package.json +57 -0
- package/src/commands/doctor.js +208 -0
- package/src/commands/init-postman.js +298 -0
- package/src/commands/init-rules.js +78 -0
- package/src/commands/link.js +172 -0
- package/src/commands/protect.js +61 -0
- package/src/commands/run-tests.js +182 -0
- package/src/commands/setup-pipeline.js +209 -0
- package/src/commands/update.js +203 -0
- package/src/index.js +4 -0
- package/src/utils/copy-tree.js +98 -0
- package/src/utils/gitignore.js +26 -0
- package/src/utils/logger.js +9 -0
- package/src/utils/manifest.js +145 -0
- package/src/utils/stack.js +80 -0
- package/src/utils/template.js +135 -0
- package/templates/AGENTS.md +109 -0
- package/templates/CLAUDE.md +3 -0
- package/templates/jenkins/Jenkinsfile-java.groovy +432 -0
- package/templates/jenkins/Jenkinsfile-node.groovy +450 -0
- package/templates/postman/.husky/pre-commit +19 -0
- package/templates/postman/.prettierrc.json +5 -0
- package/templates/postman/README.md.ejs +147 -0
- package/templates/postman/collections/01-core.json.ejs +91 -0
- package/templates/postman/config/local.json.ejs +12 -0
- package/templates/postman/config/staging.json.ejs +26 -0
- package/templates/postman/environments/local.postman_environment.json.ejs +31 -0
- package/templates/postman/environments/staging.postman_environment.json.ejs +31 -0
- package/templates/postman/gitignore +16 -0
- package/templates/postman/npmrc +31 -0
- package/templates/postman/package.json.ejs +66 -0
- package/templates/postman/run-all-shim.sh +16 -0
- package/templates/postman/scripts/auth/generate-jwt.sh +113 -0
- package/templates/postman/scripts/auth/get-issuer-secret.sh +140 -0
- package/templates/postman/scripts/infra/start-mocks.sh +138 -0
- package/templates/postman/scripts/infra/stop-mocks.sh +43 -0
- package/templates/postman/scripts/lib/api_coverage.py +1122 -0
- package/templates/postman/scripts/lib/cleanup-reports.sh +101 -0
- package/templates/postman/scripts/lib/cleanup-stryker.sh +44 -0
- package/templates/postman/scripts/lib/report_combined.py +527 -0
- package/templates/postman/scripts/lib/report_consolidated.py +363 -0
- package/templates/postman/scripts/lib/report_generator.py +121 -0
- package/templates/postman/scripts/lib/report_migration.py +156 -0
- package/templates/postman/scripts/lib/report_mutation.py +110 -0
- package/templates/postman/scripts/lib/report_unit.py +353 -0
- package/templates/postman/scripts/lib/report_utils.py +973 -0
- package/templates/postman/scripts/report-generators/generate-consolidated-report.sh +445 -0
- package/templates/postman/scripts/report-generators/java-api-coverage-matrix.sh +257 -0
- package/templates/postman/scripts/report-generators/mutation-report.sh +672 -0
- package/templates/postman/scripts/report-generators/node-api-coverage-matrix.sh +167 -0
- package/templates/postman/scripts/report-generators/stage-report-artifacts.sh +27 -0
- package/templates/postman/scripts/run-all.sh +452 -0
- package/templates/postman/scripts/runners/run-mutation-tests.sh +113 -0
- package/templates/postman/scripts/runners/run-tests-local.sh +936 -0
- package/templates/postman/scripts/runners/run-tests-staging.sh +741 -0
- package/templates/postman-node/README.md.ejs +26 -0
- package/templates/postman-node/collections/crud/01-bootstrap.json.ejs +34 -0
- package/templates/postman-node/config/local.json.ejs +46 -0
- package/templates/postman-node/config/staging.json.ejs +31 -0
- package/templates/postman-node/local.test.env.ejs +3 -0
- package/templates/postman-node/mocks/external.js +14 -0
- package/templates/postman-node/package.json.ejs +39 -0
- package/templates/postman-node/requirements.txt +1 -0
- package/templates/postman-node/scripts/database/cleanup-mysql.sh +12 -0
- package/templates/postman-node/scripts/database/run-migrations.js +29 -0
- package/templates/postman-node/scripts/database/start-mysql.sh +34 -0
- package/templates/postman-node/scripts/database/wait-for-mysql.sh +36 -0
- package/templates/postman-node/scripts/lib/api_coverage_node.py +1137 -0
- package/templates/postman-node/scripts/lib/fetch-jwt.sh +86 -0
- package/templates/postman-node/scripts/lib/run-newman.sh +104 -0
- package/templates/postman-node/scripts/lib/setup-database.sh +55 -0
- package/templates/postman-node/scripts/lib/start-app.sh +48 -0
- package/templates/postman-node/scripts/lib/utils.sh +114 -0
- package/templates/postman-node/scripts/report-generators/stage-report-artifacts.sh +26 -0
- package/templates/postman-node/scripts/run-all.sh +303 -0
- package/templates/postman-node/scripts/runners/run-tests.sh +123 -0
- package/templates/postman-node/scripts/setup-mocks.js.ejs +29 -0
- package/templates/postman-node/stryker.config.js.ejs +51 -0
- package/templates/rules/local-test-setup.mdc +420 -0
- package/templates/rules/testing-node.mdc +66 -0
- package/templates/rules/testing.mdc +248 -0
- package/templates/skills/_shared/postman-standards.md +380 -0
- package/templates/skills/enhance-test-pipeline/SKILL-java.md +483 -0
- package/templates/skills/enhance-test-pipeline/SKILL-node.md +431 -0
- package/templates/skills/enhance-test-pipeline/SKILL.md +9 -0
- package/templates/skills/review-test-suite/SKILL-java.md +137 -0
- package/templates/skills/review-test-suite/SKILL-node.md +78 -0
- package/templates/skills/review-test-suite/SKILL.md +9 -0
- package/templates/skills/run-test-suite/SKILL-java.md +186 -0
- package/templates/skills/run-test-suite/SKILL-node.md +191 -0
- package/templates/skills/run-test-suite/SKILL.md +9 -0
- package/templates/skills/setup-api-tests/SKILL-java.md +1094 -0
- package/templates/skills/setup-api-tests/SKILL-node.md +141 -0
- package/templates/skills/setup-api-tests/SKILL.md +9 -0
- package/templates/skills/setup-mutation-tests/SKILL-java.md +303 -0
- package/templates/skills/setup-mutation-tests/SKILL-node.md +408 -0
- package/templates/skills/setup-mutation-tests/SKILL.md +9 -0
- package/templates/skills/setup-test-pipeline/SKILL-java.md +454 -0
- package/templates/skills/setup-test-pipeline/SKILL-node.md +318 -0
- package/templates/skills/setup-test-pipeline/SKILL.md +9 -0
- package/templates/skills/write-api-tests/SKILL-java.md +115 -0
- package/templates/skills/write-api-tests/SKILL-node.md +83 -0
- package/templates/skills/write-api-tests/SKILL.md +9 -0
- package/templates/stryker.config.js +50 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!groovy
|
|
2
|
+
|
|
3
|
+
utilObj = new Utils()
|
|
4
|
+
|
|
5
|
+
pipeline {
|
|
6
|
+
|
|
7
|
+
agent {
|
|
8
|
+
kubernetes {
|
|
9
|
+
yaml k8sPodTemplate(pod: 'jnlp+node+dind24')
|
|
10
|
+
defaultContainer 'node-base'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Artifacts will be retained 10 days or 50 builds
|
|
15
|
+
options {
|
|
16
|
+
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '50'))
|
|
17
|
+
disableConcurrentBuilds()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
environment {
|
|
21
|
+
NODE_VERSION = utilObj.getNodeVersionFromPackageJSON()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parameters {
|
|
25
|
+
choice(choices: 'dev\nstaging\nproduction', description: '', name: 'deployTo')
|
|
26
|
+
string(name: 'image_version', defaultValue: '', description: 'Image version for Staging/Production deployment [Recommended]')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stages {
|
|
30
|
+
stage('Checkout & Setup') {
|
|
31
|
+
when {
|
|
32
|
+
expression {
|
|
33
|
+
params.deployTo == 'dev'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
steps {
|
|
37
|
+
checkoutCode(env.NODE_VERSION)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stage ('Code Sanity') {
|
|
42
|
+
when {
|
|
43
|
+
expression {
|
|
44
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
steps {
|
|
48
|
+
doCodeSanity(env.NODE_VERSION)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stage ('Unit Tests') {
|
|
53
|
+
when {
|
|
54
|
+
expression {
|
|
55
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
steps {
|
|
59
|
+
runUnitTests(env.NODE_VERSION)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stage ('Mutation Tests') {
|
|
64
|
+
when {
|
|
65
|
+
expression {
|
|
66
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
steps {
|
|
70
|
+
script {
|
|
71
|
+
// Full scan (all scoped files) for CI — local dev uses "since" mode (changed files only)
|
|
72
|
+
utilObj.runCmd('yarn install --ignore-engines && yarn mutation-tests:full', env.NODE_VERSION)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
post {
|
|
76
|
+
always {
|
|
77
|
+
publishHTML([
|
|
78
|
+
allowMissing: true,
|
|
79
|
+
alwaysLinkToLastBuild: true,
|
|
80
|
+
keepAll: true,
|
|
81
|
+
reportDir: 'coverage/mutation',
|
|
82
|
+
reportFiles: 'index.html',
|
|
83
|
+
reportName: 'Stryker Mutation Report',
|
|
84
|
+
reportTitles: 'Mutation testing (full scan)'
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
stage ('Integration Tests') {
|
|
91
|
+
when {
|
|
92
|
+
expression {
|
|
93
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
steps {
|
|
97
|
+
runIntegrationTests(env.NODE_VERSION)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
stage('SonarQube analysis') {
|
|
102
|
+
when {
|
|
103
|
+
expression {
|
|
104
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
steps {
|
|
108
|
+
runSonarQubeAnalysis()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Shift-left Postman (isoforge + Newman), API coverage matrix, and combined quality report.
|
|
113
|
+
stage('Staging Postman, API Coverage & Quality Report') {
|
|
114
|
+
when {
|
|
115
|
+
expression {
|
|
116
|
+
params.deployTo == 'dev' && !params.deployOnly
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
steps {
|
|
120
|
+
script {
|
|
121
|
+
runShiftLeftStagingPostman()
|
|
122
|
+
}
|
|
123
|
+
script {
|
|
124
|
+
sh """
|
|
125
|
+
set -e
|
|
126
|
+
# Library scripts are staged from the shiftleft-tools package (gitignored cache)
|
|
127
|
+
shiftleft stage-scripts
|
|
128
|
+
# Adjust routes dir to match your actual route/controller dir (src/controllers, src/routes, etc.)
|
|
129
|
+
./postman/scripts/report-generators/node-api-coverage-matrix.sh ./src/controllers ./postman
|
|
130
|
+
"""
|
|
131
|
+
}
|
|
132
|
+
script {
|
|
133
|
+
generateQualityReport()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
post {
|
|
137
|
+
always {
|
|
138
|
+
publishHTML([
|
|
139
|
+
allowMissing: true,
|
|
140
|
+
alwaysLinkToLastBuild: true,
|
|
141
|
+
keepAll: true,
|
|
142
|
+
reportDir: 'postman/reports',
|
|
143
|
+
reportFiles: 'api-coverage-matrix-latest.html',
|
|
144
|
+
reportName: 'API Coverage Matrix',
|
|
145
|
+
reportTitles: 'Postman vs Express route coverage'
|
|
146
|
+
])
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
stage ('Build') {
|
|
152
|
+
when {
|
|
153
|
+
branch 'master'
|
|
154
|
+
expression {
|
|
155
|
+
params.deployTo == 'dev' && params.image_version == ''
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
steps {
|
|
159
|
+
buildProject(env.NODE_VERSION, '', false)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
stage('Security Tests') {
|
|
164
|
+
when {
|
|
165
|
+
expression {
|
|
166
|
+
params.deployTo == 'dev'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
steps {
|
|
170
|
+
runSecurityTests('SERVICE_NAME')
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
stage ('Promote Image') {
|
|
175
|
+
when {
|
|
176
|
+
branch 'master'
|
|
177
|
+
expression {
|
|
178
|
+
params.deployTo == 'production'
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
steps {
|
|
182
|
+
promote('SERVICE_NAME', false, params.image_version)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
stage ('Deploy Image') {
|
|
187
|
+
when {
|
|
188
|
+
branch 'master'
|
|
189
|
+
}
|
|
190
|
+
steps {
|
|
191
|
+
deployArgo(params.deployTo, 'SERVICE_NAME', params.image_version)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
post {
|
|
197
|
+
always {
|
|
198
|
+
sendEmail()
|
|
199
|
+
deleteDir()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Shift-left: build PR image (tag from shiftleftBuildLabels), isoforge shiftleft_feature_stack for SERVICE_NAME,
|
|
206
|
+
* run Postman against staging with routing header to isolation pod, publish reports, teardown.
|
|
207
|
+
* Gate: PR comment body must start with "shiftleft"
|
|
208
|
+
* Replace ISO_HEADER and ISO_HEADER_VALUE with values from isoforge header_map for this service.
|
|
209
|
+
*/
|
|
210
|
+
// Ensure the shiftleft CLI is installed. `shiftleft stage-scripts` / run-all.sh
|
|
211
|
+
// (which execs `shiftleft test`) stage the library scripts from the package, so
|
|
212
|
+
// CI agents need the CLI. Idempotent. Requires a Jenkins "Secret text" credential
|
|
213
|
+
// (id below) holding the Nexus npm basic-auth token, i.e. the value for
|
|
214
|
+
// //nexuscentral.runwayci.com/repository/npm-group/:_auth= (base64 of user:pass).
|
|
215
|
+
def ensureShiftleftCli() {
|
|
216
|
+
withCredentials([string(credentialsId: 'nexus-npm-auth', variable: 'NEXUS_NPM_AUTH')]) {
|
|
217
|
+
sh '''#!/bin/bash --login
|
|
218
|
+
set -e
|
|
219
|
+
if ! command -v shiftleft >/dev/null 2>&1; then
|
|
220
|
+
echo "//nexuscentral.runwayci.com/repository/npm-group/:_auth=${NEXUS_NPM_AUTH}" >> "$HOME/.npmrc"
|
|
221
|
+
npm install -g shiftleft-tools \
|
|
222
|
+
--registry=https://nexuscentral.runwayci.com/repository/npm-group/ --prefer-online
|
|
223
|
+
fi
|
|
224
|
+
'''
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
def runShiftLeftStagingPostman() {
|
|
229
|
+
ensureShiftleftCli()
|
|
230
|
+
def buildCause = ''
|
|
231
|
+
def latestArgoSha = ''
|
|
232
|
+
def serviceName = utilObj.getK8ServiceName(utilObj.getRepoName())
|
|
233
|
+
// Replace with your service's isoforge routing header and account ID
|
|
234
|
+
def isoRouteHeader = 'ISO_HEADER'
|
|
235
|
+
def isoRouteValue = 'ISO_HEADER_VALUE'
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
def causes = currentBuild.rawBuild.getCauses()
|
|
239
|
+
if (causes && causes.size() > 0) {
|
|
240
|
+
def cause = causes[0]
|
|
241
|
+
if (cause?.class?.toString()?.contains('GitHubPullRequestCommentCause')) {
|
|
242
|
+
buildCause = cause.getCommentBody() ?: ''
|
|
243
|
+
}
|
|
244
|
+
cause = null
|
|
245
|
+
}
|
|
246
|
+
} catch (Exception e) {
|
|
247
|
+
echo "Could not get build cause: ${e.message}"
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
echo "Build Cause = ${buildCause}"
|
|
251
|
+
echo "K8s service (isoforge/Argo): ${serviceName}"
|
|
252
|
+
if (!buildCause.startsWith('shiftleft')) {
|
|
253
|
+
echo 'Branch build with no intention of shift left integration test.'
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
lock('SERVICE_NAME-postman-stage') {
|
|
258
|
+
def sl = shiftleftBuildLabels()
|
|
259
|
+
def branchNameSetup = "${sl.gitBranchBase}/${serviceName}-shiftleftFeatureStack"
|
|
260
|
+
def branchNameTeardown = "${sl.gitBranchBase.replace('iso-setup-', 'iso-teardown-')}/${serviceName}-shiftleftFeatureStack"
|
|
261
|
+
def imageTag = sl.imageTag
|
|
262
|
+
|
|
263
|
+
echo "gitBranchBase: ${sl.gitBranchBase} imageTag: ${imageTag}"
|
|
264
|
+
echo "Branch setup: ${branchNameSetup} teardown: ${branchNameTeardown}"
|
|
265
|
+
echo "Building image for isolation (tag: ${imageTag})"
|
|
266
|
+
buildProject(env.NODE_VERSION, '', false, imageTag)
|
|
267
|
+
|
|
268
|
+
// SECURITY: GH_TOKEN only exposed to git/gh commands
|
|
269
|
+
withCredentials([string(credentialsId: 'GITHUB_RUNWAYCI_TOKEN', variable: 'GH_TOKEN')]) {
|
|
270
|
+
utilObj.runCmd("""
|
|
271
|
+
set -euo pipefail
|
|
272
|
+
git clone git@github.com:freshdesk/freshapps-k8s.git
|
|
273
|
+
cd freshapps-k8s
|
|
274
|
+
isoforge version
|
|
275
|
+
git checkout -b ${branchNameSetup}
|
|
276
|
+
|
|
277
|
+
isoforge setup \\
|
|
278
|
+
--service ${serviceName} \\
|
|
279
|
+
--namespace shiftleft_feature_stack \\
|
|
280
|
+
--imageTag ${imageTag} \\
|
|
281
|
+
--header ${isoRouteHeader} \\
|
|
282
|
+
--headerValue ${isoRouteValue}
|
|
283
|
+
|
|
284
|
+
if [[ -n "\$(git status --porcelain)" ]]; then
|
|
285
|
+
git add .
|
|
286
|
+
git commit -m "Isolation setup for ${serviceName} in shiftleftFeatureStack"
|
|
287
|
+
git push origin ${branchNameSetup}
|
|
288
|
+
else
|
|
289
|
+
echo "No changes to commit, check previous steps"
|
|
290
|
+
exit 1
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
gh pr create \\
|
|
294
|
+
--title "Staging Isoforge-${BUILD_NUMBER} ${serviceName} shiftleftFeatureStack" \\
|
|
295
|
+
--body "Isolation setup for ${serviceName} in shiftleftFeatureStack" \\
|
|
296
|
+
--base main \\
|
|
297
|
+
--head ${branchNameSetup}
|
|
298
|
+
|
|
299
|
+
gh pr merge ${branchNameSetup} \\
|
|
300
|
+
--squash \\
|
|
301
|
+
--admin
|
|
302
|
+
|
|
303
|
+
git checkout main
|
|
304
|
+
git pull origin main
|
|
305
|
+
""", NODE_VERSION)
|
|
306
|
+
|
|
307
|
+
latestArgoSha = sh(returnStdout: true, script: '''
|
|
308
|
+
cd freshapps-k8s && git rev-parse HEAD
|
|
309
|
+
''').trim()
|
|
310
|
+
}
|
|
311
|
+
echo "Latest SHA (freshapps-k8s) after setup = ${latestArgoSha}"
|
|
312
|
+
|
|
313
|
+
withCredentials([string(credentialsId: 'ARGO_API_TOKEN', variable: 'ARGO_API_TOKEN')]) {
|
|
314
|
+
utilObj.runCmd("""
|
|
315
|
+
argoapi application --revision ${latestArgoSha} --service ${serviceName} \\
|
|
316
|
+
--namespace NAMESPACE
|
|
317
|
+
""", NODE_VERSION)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// IRSA on the pod supplies AWS for Secrets Manager access
|
|
321
|
+
updateShiftLeftStatus('pending')
|
|
322
|
+
try {
|
|
323
|
+
utilObj.runCmd("""
|
|
324
|
+
set -euo pipefail
|
|
325
|
+
cd postman
|
|
326
|
+
npm ci --ignore-scripts
|
|
327
|
+
npm audit --audit-level=critical 2>&1 || echo "WARNING: audit found issues"
|
|
328
|
+
npm audit signatures 2>&1 || echo "WARNING: signature verification failed"
|
|
329
|
+
npm audit --json > ../audit-report.json 2>&1 || true
|
|
330
|
+
# Library scripts are staged from the shiftleft-tools package (gitignored cache)
|
|
331
|
+
shiftleft stage-scripts
|
|
332
|
+
./scripts/run-tests.sh staging
|
|
333
|
+
""", NODE_VERSION)
|
|
334
|
+
echo 'Shift left Postman tests completed'
|
|
335
|
+
updateShiftLeftStatus('success', '/PostmanTestReport-ShiftLeft/')
|
|
336
|
+
} catch (Exception e) {
|
|
337
|
+
updateShiftLeftStatus('failure', '/PostmanTestReport-ShiftLeft/')
|
|
338
|
+
echo "Staging Postman tests failed: ${e.getMessage()}"
|
|
339
|
+
currentBuild.result = 'UNSTABLE'
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
archiveArtifacts artifacts: 'audit-report.json', allowEmptyArchive: true
|
|
343
|
+
|
|
344
|
+
if (fileExists('postman/reports')) {
|
|
345
|
+
publishHTML([
|
|
346
|
+
allowMissing: true,
|
|
347
|
+
alwaysLinkToLastBuild: true,
|
|
348
|
+
keepAll: true,
|
|
349
|
+
reportDir: 'postman/reports',
|
|
350
|
+
reportFiles: 'consolidated-staging-*.html',
|
|
351
|
+
reportName: 'PostmanTestReport-ShiftLeft',
|
|
352
|
+
reportTitles: 'SERVICE_NAME Staging (Isoforge) Postman'
|
|
353
|
+
])
|
|
354
|
+
} else {
|
|
355
|
+
echo 'postman/reports missing, skipping HTML report publication'
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
withCredentials([string(credentialsId: 'GITHUB_RUNWAYCI_TOKEN', variable: 'GH_TOKEN')]) {
|
|
359
|
+
utilObj.runCmd("""
|
|
360
|
+
set -euo pipefail
|
|
361
|
+
cd freshapps-k8s
|
|
362
|
+
git pull origin main
|
|
363
|
+
git checkout -b ${branchNameTeardown}
|
|
364
|
+
|
|
365
|
+
isoforge teardown \\
|
|
366
|
+
--service ${serviceName} \\
|
|
367
|
+
--namespace shiftleft_feature_stack
|
|
368
|
+
|
|
369
|
+
if [[ -n "\$(git status --porcelain)" ]]; then
|
|
370
|
+
git add .
|
|
371
|
+
git commit -m "Teardown ${serviceName} in shiftleftFeatureStack"
|
|
372
|
+
git push origin ${branchNameTeardown}
|
|
373
|
+
else
|
|
374
|
+
echo "No changes to commit for teardown, check previous steps"
|
|
375
|
+
exit 1
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
gh pr create \\
|
|
379
|
+
--title "Staging Isoforge-Teardown-${BUILD_NUMBER} ${serviceName}" \\
|
|
380
|
+
--body "Isolation teardown for ${serviceName} in shiftleftFeatureStack" \\
|
|
381
|
+
--base main \\
|
|
382
|
+
--head ${branchNameTeardown}
|
|
383
|
+
|
|
384
|
+
gh pr merge ${branchNameTeardown} \\
|
|
385
|
+
--squash \\
|
|
386
|
+
--admin
|
|
387
|
+
""", NODE_VERSION)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
withCredentials([string(credentialsId: 'ARGO_API_TOKEN', variable: 'ARGO_API_TOKEN')]) {
|
|
391
|
+
utilObj.runCmd("""
|
|
392
|
+
argoapi sync
|
|
393
|
+
""", NODE_VERSION)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
def generateQualityReport() {
|
|
399
|
+
ensureShiftleftCli()
|
|
400
|
+
echo "Generating Combined Quality Report..."
|
|
401
|
+
try {
|
|
402
|
+
utilObj.runCmd("""
|
|
403
|
+
set -euo pipefail
|
|
404
|
+
cd postman/scripts
|
|
405
|
+
|
|
406
|
+
# Stage Stryker/nyc HTML under postman/reports/artifacts for relative links in quality report
|
|
407
|
+
./stage-report-artifacts.sh "\${WORKSPACE}" "\${WORKSPACE}/postman/reports" || true
|
|
408
|
+
|
|
409
|
+
# Generate combined report — reuses existing results from prior stages:
|
|
410
|
+
# --skip-unit reuses unit test results from Unit Tests stage
|
|
411
|
+
# --skip-mutation reuses Stryker results from Mutation Tests stage
|
|
412
|
+
# --skip-postman reuses Newman JSON from ShiftLeft stage
|
|
413
|
+
# --skip-coverage reuses API coverage matrix already generated
|
|
414
|
+
./run-all.sh --skip-unit --skip-mutation --skip-postman --skip-coverage --no-delay
|
|
415
|
+
""", NODE_VERSION)
|
|
416
|
+
} catch (Exception e) {
|
|
417
|
+
echo "Quality report generation failed: ${e.getMessage()}"
|
|
418
|
+
currentBuild.result = 'UNSTABLE'
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (fileExists('postman/reports')) {
|
|
422
|
+
def qualityReport = sh(
|
|
423
|
+
returnStdout: true,
|
|
424
|
+
script: "ls -t postman/reports/quality-report-*.html 2>/dev/null | head -1 | xargs -r basename"
|
|
425
|
+
).trim()
|
|
426
|
+
if (qualityReport) {
|
|
427
|
+
publishHTML([
|
|
428
|
+
allowMissing: true,
|
|
429
|
+
alwaysLinkToLastBuild: true,
|
|
430
|
+
keepAll: true,
|
|
431
|
+
reportDir: 'postman/reports',
|
|
432
|
+
reportFiles: qualityReport,
|
|
433
|
+
includes: '**/*',
|
|
434
|
+
reportName: 'QualityReport',
|
|
435
|
+
reportTitles: 'Combined Quality Report'
|
|
436
|
+
])
|
|
437
|
+
}
|
|
438
|
+
archiveArtifacts artifacts: 'postman/reports/**', allowEmptyArchive: true
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
def updateShiftLeftStatus(String state, String targetUrlPath = '') {
|
|
443
|
+
withCredentials([string(credentialsId: 'GITHUB_RUNWAYCI_TOKEN', variable: 'GITHUB_RUNWAYCI_TOKEN')]) {
|
|
444
|
+
def targetUrl = targetUrlPath ? "${env.BUILD_URL}${targetUrlPath}" : "${env.BUILD_URL}"
|
|
445
|
+
utilObj.runCmd("""
|
|
446
|
+
octocat status --context shiftleft-SERVICE_NAME-postman --description "ShiftLeft SERVICE_NAME Postman" --state ${state} --target_url "${targetUrl}"
|
|
447
|
+
octocat label --label shifted-left
|
|
448
|
+
""", NODE_VERSION)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Auto-format staged JSON files in postman folder before commit
|
|
3
|
+
|
|
4
|
+
# Get list of staged JSON files in postman folder
|
|
5
|
+
STAGED_JSON=$(git diff --cached --name-only --diff-filter=ACM -- 'postman/**/*.json')
|
|
6
|
+
|
|
7
|
+
if [ -n "$STAGED_JSON" ]; then
|
|
8
|
+
echo "Formatting staged Postman JSON files..."
|
|
9
|
+
|
|
10
|
+
# Format each staged file
|
|
11
|
+
echo "$STAGED_JSON" | while read -r file; do
|
|
12
|
+
if [ -f "$file" ]; then
|
|
13
|
+
npx --prefix postman prettier --write "$file"
|
|
14
|
+
git add "$file"
|
|
15
|
+
fi
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
echo "Done formatting."
|
|
19
|
+
fi
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# <%= serviceName %> Postman Tests
|
|
2
|
+
|
|
3
|
+
API integration tests using Postman/Newman.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- npm 8.4+
|
|
9
|
+
- Application running locally (for local tests)
|
|
10
|
+
<% if (includeStaging) { %>- AWS CLI configured with staging profile (for staging tests)
|
|
11
|
+
- `jq` installed (`brew install jq`)<% } %>
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd postman
|
|
17
|
+
npm install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running Tests
|
|
21
|
+
|
|
22
|
+
### Local Environment
|
|
23
|
+
|
|
24
|
+
Start the application first, then:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd postman/scripts
|
|
28
|
+
./runners/run-tests-local.sh
|
|
29
|
+
```
|
|
30
|
+
<% if (includeStaging) { %>
|
|
31
|
+
### Staging Environment
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd postman/scripts
|
|
35
|
+
AWS_PROFILE=staging ./runners/run-tests-staging.sh
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Staging config is in `postman/config/staging.json` — set your AWS secret name, JWT parameters, and base URL there.
|
|
39
|
+
<% } %>
|
|
40
|
+
### All Tests + Quality Report (Unit + Mutation + Postman + Coverage)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd postman/scripts
|
|
44
|
+
./run-all.sh # Run everything (local)
|
|
45
|
+
./run-all.sh --skip-mutation # Skip slow mutation tests
|
|
46
|
+
./run-all.sh --env staging # Run Postman against staging
|
|
47
|
+
./run-all.sh --skip-unit --skip-mutation # Postman + coverage only
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Test Reports
|
|
51
|
+
|
|
52
|
+
Reports are generated in `postman/reports/`:
|
|
53
|
+
- `quality-report-*.html` — Combined quality report (unit + mutation + Postman + coverage)
|
|
54
|
+
- `consolidated-*.html` — Postman test summary across all collections
|
|
55
|
+
- `api-coverage-matrix-*.html` — API endpoint coverage matrix
|
|
56
|
+
- `test-local-*.html` / `test-staging-*.html` — Per-collection Newman reports
|
|
57
|
+
|
|
58
|
+
## API Coverage
|
|
59
|
+
|
|
60
|
+
Check that all endpoints have 2xx test coverage:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd postman/scripts
|
|
64
|
+
./report-generators/java-api-coverage-matrix.sh
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Mutation Testing
|
|
68
|
+
|
|
69
|
+
Verify unit test quality with PIT:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd postman/scripts
|
|
73
|
+
./report-generators/mutation-report.sh
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Directory Structure
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
postman/
|
|
80
|
+
collections/ # Newman collection JSON files
|
|
81
|
+
environments/ # Postman environment files
|
|
82
|
+
config/ # Environment config (staging.json etc.)
|
|
83
|
+
reports/ # Generated test reports (gitignored)
|
|
84
|
+
scripts/
|
|
85
|
+
run-all.sh # Unified runner (all phases)
|
|
86
|
+
runners/
|
|
87
|
+
run-tests-local.sh # Local Postman tests
|
|
88
|
+
run-tests-staging.sh # Staging Postman tests
|
|
89
|
+
report-generators/
|
|
90
|
+
java-api-coverage-matrix.sh # API coverage analysis
|
|
91
|
+
mutation-report.sh # PIT mutation report
|
|
92
|
+
generate-consolidated-report.sh
|
|
93
|
+
stage-report-artifacts.sh
|
|
94
|
+
auth/ # JWT generation scripts
|
|
95
|
+
infra/ # WireMock start/stop scripts
|
|
96
|
+
lib/ # Python report generators
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Adding New Tests
|
|
100
|
+
|
|
101
|
+
1. Create or update collection in `collections/`
|
|
102
|
+
2. Follow naming: `XX-description.json`
|
|
103
|
+
3. Add to `runners/run-tests-local.sh`<% if (includeStaging) { %> and `runners/run-tests-staging.sh`<% } %>
|
|
104
|
+
4. Run coverage report to verify 100% 2xx coverage
|
|
105
|
+
|
|
106
|
+
## Collection Naming
|
|
107
|
+
|
|
108
|
+
- `01-core.json` - Core CRUD operations
|
|
109
|
+
- `02-authorization.json` - Auth tests
|
|
110
|
+
- `03-validation.json` - Invalid input tests
|
|
111
|
+
- `XX-feature.json` - Feature-specific tests
|
|
112
|
+
|
|
113
|
+
## Test Naming Conventions
|
|
114
|
+
|
|
115
|
+
- `GET - Endpoint - 200 valid request`
|
|
116
|
+
- `POST - Endpoint - 201 created`
|
|
117
|
+
- `GET - Endpoint - 404 not found`
|
|
118
|
+
- `POST - Endpoint - 400 invalid input`
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
Key variables in `environments/`:
|
|
123
|
+
- `base_url` - API base URL
|
|
124
|
+
- `auth_token` - JWT token
|
|
125
|
+
- `environment` - `local`<% if (includeStaging) { %> or `staging`<% } %>
|
|
126
|
+
<% if (includeStaging) { %>
|
|
127
|
+
## Staging Config (`config/staging.json`)
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"base_url": "https://your-service-staging.freshworks.com/api/v1",
|
|
132
|
+
"aws": {
|
|
133
|
+
"secretName": "your-secret-name",
|
|
134
|
+
"client": "your_client",
|
|
135
|
+
"issuer": "your_issuer",
|
|
136
|
+
"region": "us-west-2"
|
|
137
|
+
},
|
|
138
|
+
"jwt": {
|
|
139
|
+
"api_issuer": "your_client",
|
|
140
|
+
"api_account_id": "1",
|
|
141
|
+
"api_account_domain": "freshworks.com"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
AWS credentials are fetched via CLI (`AWS_PROFILE=staging`) and cleared before npm runs to prevent supply chain exposure.
|
|
147
|
+
<% } %>
|