Skip to content

Commit

Permalink
Add logic to create/update PR comments and implement initial main logic
Browse files Browse the repository at this point in the history
Also refactors some of the previous code to make the final
implementation easier to understand.

Still TODO: tests for `src/comment.ts`
  • Loading branch information
simu committed Nov 1, 2023
1 parent a262b31 commit 656cca8
Show file tree
Hide file tree
Showing 12 changed files with 5,289 additions and 400 deletions.
2 changes: 1 addition & 1 deletion .github/linters/.eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ rules:
'semi': 'off',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/consistent-type-assertions': 'error',
'@typescript-eslint/explicit-member-accessibility':
['error', { 'accessibility': 'no-public' }],
Expand Down
71 changes: 24 additions & 47 deletions __tests__/bump-labels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as github from '@actions/github'
import * as bump_labels from '../src/bump-labels'
import { ReleaseType } from 'semver'
import { expect } from '@jest/globals'
import { makeOctokitMock } from './helpers'
import { makeOctokitMock, populateGitHubContext } from './helpers'

// Mock the GitHub Actions core library
const getInputMock = jest.spyOn(core, 'getInput')
Expand Down Expand Up @@ -65,39 +65,11 @@ describe('readBumpLabels', () => {
})
})

describe('bumpFromLabels', () => {
describe('prBumpLabel', () => {
beforeEach(() => {
jest.clearAllMocks()
// set context for tests
// @ts-ignore
github.context = {
event_name: 'pull_request',
ref: 'refs/pull/123/merge',
workflow: 'Mock workflow',
action: 'mock-action-1',
actor: 'vshn-renovate',
repo: {
owner: 'projectsyn',
repo: 'pr-label-tag-action'
},
payload: {
action: 'synchronize',
number: 123,
pull_request: {
number: 123,
title: 'Mock PR',
user: {
login: 'vshn-renovate'
}
}
},
issue: {
owner: 'projectsyn',
repo: 'pr-label-tag-action',
number: 123
},
sha: ''
}
populateGitHubContext()
})

it('raises an error on non-PR events', async () => {
Expand All @@ -111,7 +83,7 @@ describe('bumpFromLabels', () => {
delete github.context.payload.pull_request

await expect(async () => {
await bump_labels.bumpFromLabels(bumpLabels)
await bump_labels.prBumpLabel(bumpLabels)
}).rejects.toThrow(
new Error(
"Action is running on a 'discussion' event, only 'pull_request' events are supported"
Expand All @@ -136,9 +108,10 @@ describe('bumpFromLabels', () => {
})
getOctokitMock.mockImplementation(makeOctokitMock('bump:patch'))

await expect(bump_labels.bumpFromLabels(bumpLabels)).resolves.toBe(
'patch' as ReleaseType
)
await expect(bump_labels.prBumpLabel(bumpLabels)).resolves.toStrictEqual({
bump: 'patch' as ReleaseType,
labels: ['bump:patch']
})
})

it('identifies bump:minor label', async () => {
Expand All @@ -158,9 +131,10 @@ describe('bumpFromLabels', () => {
})
getOctokitMock.mockImplementation(makeOctokitMock('bump:minor'))

await expect(bump_labels.bumpFromLabels(bumpLabels)).resolves.toBe(
'minor' as ReleaseType
)
await expect(bump_labels.prBumpLabel(bumpLabels)).resolves.toStrictEqual({
bump: 'minor' as ReleaseType,
labels: ['bump:minor']
})
})

it('identifies bump:major label', async () => {
Expand All @@ -180,9 +154,10 @@ describe('bumpFromLabels', () => {
})
getOctokitMock.mockImplementation(makeOctokitMock('bump:major'))

await expect(bump_labels.bumpFromLabels(bumpLabels)).resolves.toBe(
'major' as ReleaseType
)
await expect(bump_labels.prBumpLabel(bumpLabels)).resolves.toStrictEqual({
bump: 'major' as ReleaseType,
labels: ['bump:major']
})
})

it('logs a message when no bump labels are present', async () => {
Expand All @@ -202,9 +177,10 @@ describe('bumpFromLabels', () => {
})
getOctokitMock.mockImplementation(makeOctokitMock())

await expect(async () => {
await bump_labels.bumpFromLabels(bumpLabels)
}).rejects.toThrow(new Error('Unknown version bump null'))
await expect(bump_labels.prBumpLabel(bumpLabels)).resolves.toStrictEqual({
bump: null,
labels: []
})

expect(infoMock).toHaveBeenNthCalledWith(1, 'No bump labels found')
})
Expand All @@ -228,9 +204,10 @@ describe('bumpFromLabels', () => {
makeOctokitMock('bump:patch', 'bump:minor')
)

await expect(bump_labels.bumpFromLabels(bumpLabels)).rejects.toThrow(
new Error('Unknown version bump null')
)
await expect(bump_labels.prBumpLabel(bumpLabels)).resolves.toStrictEqual({
bump: null,
labels: ['bump:patch', 'bump:minor']
})

expect(warningMock).toHaveBeenNthCalledWith(
1,
Expand Down
25 changes: 25 additions & 0 deletions __tests__/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as exec from '@actions/exec'
import * as github from '@actions/github'
import { expect } from '@jest/globals'

export function makeGitExecMock(
Expand Down Expand Up @@ -72,3 +73,27 @@ export function makeOctokitMock(
}
}
}

export function populateGitHubContext(): void {
process.env['GITHUB_REPOSITORY'] = 'projectsyn/pr-label-tag-action'
github.context.eventName = 'pull_request'
github.context.ref = 'refs/pull/123/merge'
github.context.workflow = 'Mock workflow'
github.context.action = 'mock-action-1'
github.context.actor = 'vshn-renovate'
github.context.payload = {
action: 'synchronize',
number: 123,
pull_request: {
number: 123,
title: 'Mock PR',
user: {
login: 'vshn-renovate'
}
}
}
github.context.issue.owner = 'projectsyn'
github.context.issue.repo = 'pr-label-tag-action'
github.context.issue.number = 123
github.context.sha = ''
}
160 changes: 156 additions & 4 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@

import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as github from '@actions/github'
import * as main from '../src/main'
import { makeGitExecMock } from './helpers'
import * as comment from '../src/comment'
import {
makeGitExecMock,
makeOctokitMock,
populateGitHubContext
} from './helpers'

// Mock the GitHub Actions core library
const debugMock = jest.spyOn(core, 'debug')
const getInputMock = jest.spyOn(core, 'getInput')
const setFailedMock = jest.spyOn(core, 'setFailed')
const execMock = jest.spyOn(exec, 'exec')
const getOctokitMock = jest.spyOn(github, 'getOctokit')
const createOrUpdateCommentMock = jest.spyOn(comment, 'createOrUpdateComment')

// Mock the action's main function
const runMock = jest.spyOn(main, 'run')
Expand All @@ -25,30 +33,161 @@ describe('action', () => {
jest.clearAllMocks()
// Mock `git tag --sort=-v:refname`
execMock.mockImplementation(makeGitExecMock('v1.2.3\n'))
// mock our own createOrUpdateComment to do nothing
createOrUpdateCommentMock.mockImplementation(
async (body: string): Promise<void> => {
return new Promise(resolve => {
body
resolve()
})
}
)
// Mock github.getOctokit to return fake api responses for fetching PR
// labels
getOctokitMock.mockImplementation(makeOctokitMock('bump:patch'))
populateGitHubContext()
})

it('parses the bump labels', async () => {
it('creates or updates comment on labeled PR', async () => {
// Set the action's inputs as return values from core.getInput()
getInputMock.mockImplementation((name: string): string => {
switch (name) {
case 'patch-label':
return 'patch'
return 'bump:patch'
case 'minor-label':
return 'bump:minor'
case 'major-label':
return 'bump:major'
case 'github-token':
return 'mock-token'
default:
return ''
}
})

await main.run()
expect(runMock).toHaveReturned()

// Verify that all of the core library functions were called correctly
expect(debugMock).toHaveBeenNthCalledWith(
1,
'Using bump:patch, bump:minor, bump:major to determine SemVer bump ...'
)
expect(createOrUpdateCommentMock).toHaveBeenNthCalledWith(
1,
'🚀 Merging this PR will release `v1.2.4`\n\n' +
'🛠️ _Auto release enabled_ with label `bump:patch`'
)
})

it('creates or updates comment on PR with multiple bump labels', async () => {
// Set the action's inputs as return values from core.getInput()
getInputMock.mockImplementation((name: string): string => {
switch (name) {
case 'patch-label':
return 'bump:patch'
case 'minor-label':
return 'bump:minor'
case 'major-label':
return 'bump:major'
case 'github-token':
return 'mock-token'
default:
return ''
}
})
getOctokitMock.mockImplementation(
makeOctokitMock('bump:patch', 'bump:minor')
)

await main.run()
expect(runMock).toHaveReturned()

// Verify that all of the core library functions were called correctly
expect(debugMock).toHaveBeenNthCalledWith(
1,
'Using patch, bump:minor, bump:major to determine SemVer bump ...'
'Using bump:patch, bump:minor, bump:major to determine SemVer bump ...'
)
expect(createOrUpdateCommentMock).toHaveBeenNthCalledWith(
1,
'Found 2 bump labels, please make sure you only add one bump label.\n\n' +
'🛠️ _Auto release disabled_'
)
})

it('creates or updates comment on closed unmerged PR', async () => {
// Set the action's inputs as return values from core.getInput()
getInputMock.mockImplementation((name: string): string => {
switch (name) {
case 'patch-label':
return 'bump:patch'
case 'minor-label':
return 'bump:minor'
case 'major-label':
return 'bump:major'
case 'github-token':
return 'mock-token'
default:
return ''
}
})
getOctokitMock.mockImplementation(makeOctokitMock('bump:patch'))
github.context.payload.action = 'closed'
expect(github.context.payload.pull_request).toBeDefined()
if (github.context.payload.pull_request) {
github.context.payload.pull_request['merged'] = false
}

await main.run()
expect(runMock).toHaveReturned()

// Verify that all of the core library functions were called correctly
expect(debugMock).toHaveBeenNthCalledWith(
1,
'Using bump:patch, bump:minor, bump:major to determine SemVer bump ...'
)
expect(createOrUpdateCommentMock).toHaveBeenNthCalledWith(
1,
'🚀 This PR has been closed unmerged. No new release will be created for these changes\n\n' +
'🛠️ _Auto release disabled_'
)
})

it('creates or updates comment on merged PR', async () => {
// Set the action's inputs as return values from core.getInput()
getInputMock.mockImplementation((name: string): string => {
switch (name) {
case 'patch-label':
return 'bump:patch'
case 'minor-label':
return 'bump:minor'
case 'major-label':
return 'bump:major'
case 'github-token':
return 'mock-token'
default:
return ''
}
})
getOctokitMock.mockImplementation(makeOctokitMock('bump:patch'))
github.context.payload.action = 'closed'
expect(github.context.payload.pull_request).toBeDefined()
if (github.context.payload.pull_request) {
github.context.payload.pull_request['merged'] = true
}

await main.run()
expect(runMock).toHaveReturned()

// Verify that all of the core library functions were called correctly
expect(debugMock).toHaveBeenNthCalledWith(
1,
'Using bump:patch, bump:minor, bump:major to determine SemVer bump ...'
)
expect(createOrUpdateCommentMock).toHaveBeenNthCalledWith(
1,
'🚀 This PR has been released as [`v1.2.4`](https://github.com/projectsyn/pr-label-tag-action/releases/tag/v1.2.4)\n\n' +
'🛠️ _Auto release enabled_ with label `bump:patch`'
)
})

Expand All @@ -73,4 +212,17 @@ describe('action', () => {
"Empty bump labels aren't supported"
)
})

it('raises an error on non-PR events', async () => {
delete github.context.payload.pull_request
github.context.eventName = 'discussion'

await main.run()
expect(runMock).toHaveReturned()

expect(setFailedMock).toHaveBeenNthCalledWith(
1,
"Action is running for a 'discussion' event. Only 'pull_request' events are supported"
)
})
})
Loading

0 comments on commit 656cca8

Please sign in to comment.