Skip to content

Commit ba3af59

Browse files
authored
Merge pull request #391 from github/fixup/commit-safety-checks
Clean up / improve: `commit-safety-checks.js`
2 parents f5b98aa + eb2a50e commit ba3af59

File tree

4 files changed

+150
-21
lines changed

4 files changed

+150
-21
lines changed

__tests__/functions/commit-safety-checks.test.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import {commitSafetyChecks} from '../../src/functions/commit-safety-checks'
1+
import {
2+
commitSafetyChecks,
3+
isTimestampOlder
4+
} from '../../src/functions/commit-safety-checks'
25
import {COLORS} from '../../src/functions/colors'
36
import * as core from '@actions/core'
47

@@ -164,3 +167,89 @@ test('raises an error if the date format is invalid', async () => {
164167
'Invalid date format. Please ensure the dates are valid UTC timestamps.'
165168
)
166169
})
170+
171+
test('throws if context.payload.comment.created_at is missing', async () => {
172+
const brokenContext = {payload: {comment: {}}}
173+
await expect(commitSafetyChecks(brokenContext, data)).rejects.toThrow(
174+
'Missing context.payload.comment.created_at'
175+
)
176+
})
177+
178+
test('throws if commit.author.date is missing', async () => {
179+
const brokenData = JSON.parse(JSON.stringify(data))
180+
delete brokenData.commit.author.date
181+
await expect(commitSafetyChecks(context, brokenData)).rejects.toThrow(
182+
'Missing commit.author.date'
183+
)
184+
})
185+
186+
test('rejects a deployment if commit.verification.verified_at is null and commit_verification is true', async () => {
187+
data.inputs.commit_verification = true
188+
data.commit.verification = {
189+
verified: true,
190+
reason: 'valid',
191+
signature: 'SOME_SIGNATURE',
192+
payload: 'SOME_PAYLOAD',
193+
verified_at: null
194+
}
195+
196+
await expect(commitSafetyChecks(context, data)).resolves.toEqual({
197+
message: `### ⚠️ Cannot proceed with deployment\n\n- commit: \`${sha}\`\n- verification failed reason: \`valid\`\n\n> The commit signature is not valid as there is no valid \`verified_at\` date. Please ensure the commit has been properly signed and try again.`,
198+
status: false,
199+
isVerified: true
200+
})
201+
})
202+
203+
test('rejects a deployment if commit.verification.verified_at is missing and commit_verification is true', async () => {
204+
data.inputs.commit_verification = true
205+
data.commit.verification = {
206+
verified: true,
207+
reason: 'valid',
208+
signature: 'SOME_SIGNATURE',
209+
payload: 'SOME_PAYLOAD'
210+
}
211+
212+
await expect(commitSafetyChecks(context, data)).resolves.toEqual({
213+
message: `### ⚠️ Cannot proceed with deployment\n\n- commit: \`${sha}\`\n- verification failed reason: \`valid\`\n\n> The commit signature is not valid as there is no valid \`verified_at\` date. Please ensure the commit has been properly signed and try again.`,
214+
status: false,
215+
isVerified: true
216+
})
217+
})
218+
219+
test('isTimestampOlder throws if timestampA is missing', () => {
220+
expect(() => isTimestampOlder(undefined, '2024-10-15T11:00:00Z')).toThrow(
221+
'One or both timestamps are missing or empty.'
222+
)
223+
})
224+
225+
test('isTimestampOlder throws if timestampB is missing', () => {
226+
expect(() => isTimestampOlder('2024-10-15T11:00:00Z', undefined)).toThrow(
227+
'One or both timestamps are missing or empty.'
228+
)
229+
})
230+
231+
test('isTimestampOlder throws if both timestamps are invalid', () => {
232+
expect(() => isTimestampOlder('bad', 'bad')).toThrow(/Invalid date format/)
233+
})
234+
235+
test('isTimestampOlder covers else branch (not older)', async () => {
236+
const context = {payload: {comment: {created_at: '2024-10-15T12:00:00Z'}}}
237+
const data = {
238+
sha: 'abc123',
239+
commit: {
240+
author: {date: '2024-10-15T11:00:00Z'},
241+
verification: {
242+
verified: false,
243+
reason: 'unsigned',
244+
signature: null,
245+
payload: null,
246+
verified_at: null
247+
}
248+
},
249+
inputs: {commit_verification: false}
250+
}
251+
await commitSafetyChecks(context, data)
252+
expect(debugMock).toHaveBeenCalledWith(
253+
'2024-10-15T12:00:00Z is not older than 2024-10-15T11:00:00Z'
254+
)
255+
})

dist/index.js

Lines changed: 29 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/functions/commit-safety-checks.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,26 @@ export async function commitSafetyChecks(context, data) {
99
const commit = data.commit
1010
const inputs = data.inputs
1111
const sha = data.sha
12+
const comment_created_at = context?.payload?.comment?.created_at
13+
const commit_created_at = commit?.author?.date // fetch the timestamp that the commit was authored (format: "2024-10-21T19:10:24Z" - String)
14+
const verified_at = commit?.verification?.verified_at
15+
core.debug(`comment_created_at: ${comment_created_at}`)
16+
core.debug(`commit_created_at: ${commit_created_at}`)
17+
core.debug(`verified_at: ${verified_at}`)
18+
19+
// Defensive: Ensure required fields exist
20+
if (!comment_created_at) {
21+
throw new Error('Missing context.payload.comment.created_at')
22+
}
23+
if (!commit_created_at) {
24+
throw new Error('Missing commit.author.date')
25+
}
1226

1327
const isVerified = commit?.verification?.verified === true ? true : false
1428
core.debug(`isVerified: ${isVerified}`)
1529
core.setOutput('commit_verified', isVerified)
1630
core.saveState('commit_verified', isVerified)
1731

18-
const comment_created_at = context.payload.comment.created_at
19-
core.debug(`comment_created_at: ${comment_created_at}`)
20-
21-
// fetch the timestamp that the commit was authored (format: "2024-10-21T19:10:24Z" - String)
22-
const commit_created_at = commit.author.date
23-
core.debug(`commit_created_at: ${commit_created_at}`)
24-
2532
// check to ensure that the commit was authored before the comment was created
2633
if (isTimestampOlder(comment_created_at, commit_created_at)) {
2734
return {
@@ -52,11 +59,20 @@ export async function commitSafetyChecks(context, data) {
5259
}
5360
}
5461

62+
// if commit_verification is enabled and the verified_at timestamp is not present, throw an error
63+
if (inputs.commit_verification === true && !verified_at) {
64+
return {
65+
message: `### ⚠️ Cannot proceed with deployment\n\n- commit: \`${sha}\`\n- verification failed reason: \`${commit?.verification?.reason}\`\n\n> The commit signature is not valid as there is no valid \`verified_at\` date. Please ensure the commit has been properly signed and try again.`,
66+
status: false,
67+
isVerified: isVerified
68+
}
69+
}
70+
5571
// check to ensure that the commit signature was authored before the comment was created
5672
// even if the commit signature is valid, we still want to reject it if it was authored after the comment was created
5773
if (
5874
inputs.commit_verification === true &&
59-
isTimestampOlder(comment_created_at, commit?.verification?.verified_at)
75+
isTimestampOlder(comment_created_at, verified_at)
6076
) {
6177
return {
6278
message: `### ⚠️ Cannot proceed with deployment\n\nThe latest commit is not safe for deployment. The commit signature was verified after the trigger comment was created. Please try again if you recently pushed a new commit.`,
@@ -77,15 +93,19 @@ export async function commitSafetyChecks(context, data) {
7793
// :param timestampA: The first timestamp to compare (String - format: "2024-10-21T19:10:24Z")
7894
// :param timestampB: The second timestamp to compare (String - format: "2024-10-21T19:10:24Z")
7995
// :returns: true if timestampA is older than timestampB, false otherwise
80-
function isTimestampOlder(timestampA, timestampB) {
96+
export function isTimestampOlder(timestampA, timestampB) {
97+
// Defensive: handle null/undefined/empty
98+
if (!timestampA || !timestampB) {
99+
throw new Error('One or both timestamps are missing or empty.')
100+
}
81101
// Parse the date strings into Date objects
82102
const timestampADate = new Date(timestampA)
83103
const timestampBDate = new Date(timestampB)
84104

85105
// Check if the parsed dates are valid
86106
if (isNaN(timestampADate) || isNaN(timestampBDate)) {
87107
throw new Error(
88-
'Invalid date format. Please ensure the dates are valid UTC timestamps.'
108+
`Invalid date format. Please ensure the dates are valid UTC timestamps. Received: '${timestampA}', '${timestampB}'`
89109
)
90110
}
91111

0 commit comments

Comments
 (0)