generated from actions/typescript-action
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add initial tests for all functionality (#24)
- write tests for all files - switch `ts-jest` to `@swc/jest` - replace `process.exit(1)` with `throw new Error()` for better testability - improve input validation
- Loading branch information
Showing
14 changed files
with
698 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ branding: | |
icon: git-branch | ||
color: orange | ||
|
||
|
||
inputs: | ||
days-to-delete: | ||
required: false | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,23 @@ | ||
import type { Config } from 'jest' | ||
|
||
const esModules = [ | ||
'chalk', | ||
'node-fetch', | ||
'data-uri-to-buffer', | ||
'fetch-blob', | ||
'formdata-polyfill', | ||
].join('|') | ||
|
||
const config: Config = { | ||
clearMocks: true, | ||
moduleFileExtensions: ['js', 'ts'], | ||
testMatch: ['**/*.test.ts'], | ||
transform: { | ||
'^.+\\.ts$': 'ts-jest', | ||
'^.+\\.(t|j)s$': '@swc/jest', | ||
}, | ||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`], | ||
verbose: true, | ||
testEnvironment: 'node', | ||
} | ||
|
||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { deleteStaleBranches } from './delete-stale-branches.ts' | ||
import { getRepositoryBranches } from './get-branches.ts' | ||
import { getInputs } from './get-inputs.ts' | ||
import { getOctokit } from './get-octokit.ts' | ||
|
||
jest.mock('./get-octokit.ts') | ||
jest.mock('./get-branches.ts') | ||
jest.mock('./get-inputs.ts') | ||
jest.spyOn(global.console, 'log').mockImplementation() | ||
|
||
const mockGetInputs = (override: Partial<ReturnType<typeof getInputs>> = {}) => | ||
( | ||
getInputs as unknown as jest.MockedFunction<typeof getInputs> | ||
).mockReturnValue({ | ||
ghToken: 'random-token', | ||
daysToDelete: 90, | ||
dryRun: true, | ||
repositoryOwner: 'balvajs', | ||
repositoryName: 'delete-stale-branches', | ||
...override, | ||
}) | ||
|
||
const mockGetRepositoryBranches = ( | ||
branches: Pick< | ||
Awaited<ReturnType<typeof getRepositoryBranches>>[number], | ||
'daysDiff' | 'name' | ||
>[] = [], | ||
) => | ||
( | ||
getRepositoryBranches as unknown as jest.MockedFunction< | ||
typeof getRepositoryBranches | ||
> | ||
) | ||
// for the deleteStaleBranches only subset of branch properties is needed | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
.mockResolvedValue(branches as any) | ||
|
||
const requestMock = jest.fn() | ||
const getOctokitMock = getOctokit as unknown as jest.MockedFunction< | ||
typeof getOctokit | ||
> | ||
getOctokitMock | ||
// for the deleteStaleBranches only request property is needed | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
.mockReturnValue({ request: requestMock } as any) | ||
|
||
describe('deleteStaleBranches', () => { | ||
beforeEach(() => { | ||
mockGetInputs() | ||
mockGetRepositoryBranches() | ||
}) | ||
|
||
it('exits early if dry-run is true', async () => { | ||
mockGetInputs({ dryRun: true }) | ||
|
||
await deleteStaleBranches() | ||
|
||
expect(requestMock).not.toBeCalled() | ||
}) | ||
|
||
it('delete stale branches if dry-run is false', async () => { | ||
mockGetInputs({ dryRun: false }) | ||
mockGetRepositoryBranches([ | ||
{ daysDiff: 10, name: 'active-branch' }, | ||
{ daysDiff: 90, name: 'branch-1-day-before-stale' }, | ||
{ daysDiff: 100, name: 'stale-branch' }, | ||
]) | ||
|
||
await deleteStaleBranches() | ||
|
||
expect(requestMock).toBeCalledTimes(1) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { Chalk } from 'chalk' | ||
|
||
import { getOctokit } from './get-octokit.ts' | ||
import { getRepositoryBranches } from './get-branches.ts' | ||
import { getInputs } from './get-inputs.ts' | ||
|
||
// set level to 2, to be able to print colorful messages also in CI | ||
const chalk = new Chalk({ level: 2 }) | ||
|
||
const pluralizeBranches = (count: number) => | ||
count === 1 ? 'branch' : 'branches' | ||
|
||
export const deleteStaleBranches = async () => { | ||
const { ghToken, daysToDelete, dryRun, repositoryName, repositoryOwner } = | ||
getInputs() | ||
|
||
const octokit = getOctokit({ ghToken }) | ||
|
||
const branches = await getRepositoryBranches({ | ||
octokit, | ||
repositoryOwner, | ||
repositoryName, | ||
}) | ||
|
||
console.log( | ||
`Found ${branches.length} ${pluralizeBranches( | ||
branches.length, | ||
)} without associated PR.\n\n`, | ||
) | ||
|
||
let separatorPrinted = false | ||
for (const { name, daysDiff } of branches) { | ||
const willBeDeleted = daysDiff > daysToDelete | ||
|
||
if (!separatorPrinted && willBeDeleted) { | ||
console.log( | ||
`\n====== Following branches are inactive more than ${daysToDelete} days and will be deleted ======\n`, | ||
) | ||
separatorPrinted = true | ||
} | ||
|
||
console.log( | ||
`${name} - `, | ||
chalk[willBeDeleted ? 'red' : 'green'](`${daysDiff} days inactive`), | ||
) | ||
} | ||
|
||
const branchesToDelete = branches.filter( | ||
({ daysDiff }) => daysDiff > daysToDelete, | ||
) | ||
|
||
if (dryRun) { | ||
console.log( | ||
`\n\nDry run. Would delete ${branchesToDelete.length} ${pluralizeBranches( | ||
branchesToDelete.length, | ||
)} that are inactive for more than ${daysToDelete} days.`, | ||
) | ||
|
||
return | ||
} | ||
|
||
if (!branchesToDelete.length) { | ||
console.log('\n\nNo stale branches found.') | ||
|
||
return | ||
} | ||
|
||
console.log( | ||
`\n\nDeleting ${branchesToDelete.length} ${pluralizeBranches( | ||
branchesToDelete.length, | ||
)} that ${ | ||
branchesToDelete.length === 1 ? 'is' : 'are' | ||
} inactive for more than ${daysToDelete} days...`, | ||
) | ||
|
||
const deletionResults = await Promise.allSettled( | ||
branchesToDelete.map(async ({ name }) => { | ||
try { | ||
await octokit.request({ | ||
method: 'DELETE', | ||
url: `/repos/${repositoryOwner}/${repositoryName}/git/refs/heads/${name}`, | ||
}) | ||
console.log(`Deleted ${name}`) | ||
} catch (e) { | ||
console.error(`Failed to delete ${name}: ${e}`) | ||
} | ||
}), | ||
) | ||
|
||
const failedDeletions = deletionResults.filter( | ||
({ status }) => status === 'rejected', | ||
) | ||
|
||
if (failedDeletions.length) { | ||
throw new Error( | ||
`${failedDeletions}/${branchesToDelete.length} branch deletions failed.`, | ||
) | ||
} | ||
|
||
console.log( | ||
`\nSuccessfully deleted ${branchesToDelete.length} ${pluralizeBranches( | ||
branchesToDelete.length, | ||
)}.`, | ||
) | ||
} |
Oops, something went wrong.