Skip to content

Commit 1448155

Browse files
authored
Merge pull request #124 from qtomlinson/qt/cleanup
Add cleanup for integration tests
2 parents 57679ae + ca50dcb commit 1448155

File tree

7 files changed

+406
-6
lines changed

7 files changed

+406
-6
lines changed

.github/workflows/integration-test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ jobs:
5555
continue-on-error: true
5656
run: DYNAMIC_COORDINATES=${{ matrix.dynamicCoordinates }} npm run e2e-test-service
5757

58+
- name: Clean up
59+
run: GITHUB_TOKEN=${{ secrets.CLEARLYDEFINED_GITHUB_PAT }} npm run e2e-test-service-cleanup
60+
5861
- name: Generate structured diffs
5962
run: DYNAMIC_COORDINATES=${{ matrix.dynamicCoordinates }} npm run definitions-diff ${{ github.event.inputs.baseFolderPath || 'diffs' }}
6063

tools/integration/lib/cleanupPR.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// (c) Copyright 2025, SAP SE and ClearlyDefined contributors. Licensed under the MIT license.
2+
// SPDX-License-Identifier: MIT
3+
4+
/** @type {string | undefined} */
5+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
6+
7+
/** @type {string} */
8+
const REPO_OWNER = 'clearlydefined'
9+
10+
/** @type {string} */
11+
const REPO_NAME = 'curated-data-dev'
12+
13+
/** @type {string} */
14+
const TARGET_TITLE = 'test maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16'
15+
16+
/** @type {string} */
17+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
18+
19+
/**
20+
* @typedef {Object} Options
21+
* @property {string} title - The title to search for.
22+
* @property {string} repoOwner - The owner of the repository.
23+
* @property {string} repoName - The name of the repository.
24+
*/
25+
26+
/**
27+
* Creates an authenticated Octokit instance.
28+
* @returns {Promise<import('@octokit/rest').Octokit>}
29+
* @throws {Error} If GITHUB_TOKEN is not set.
30+
*/
31+
const createOctokit = () =>
32+
import('@octokit/rest').then(({ Octokit }) => {
33+
if (!GITHUB_TOKEN) {
34+
throw new Error('GITHUB_TOKEN is not set')
35+
}
36+
return new Octokit({
37+
auth: GITHUB_TOKEN
38+
})
39+
})
40+
41+
/**
42+
* Finds pull requests matching the given title and created before the specified date.
43+
* @param {import('@octokit/rest').Octokit} octokit - The Octokit instance.
44+
* @param {Options} options - The options for cleanup.
45+
* @param {string} dateSince - The ISO date string to filter PRs created before this date.
46+
* @returns {Promise<{prNumber: number, prTitle: string}[]>} The list of matching pull requests.
47+
*/
48+
const findPullRequests = async (octokit, options, dateSince) => {
49+
const { title: givenTitle, repoOwner, repoName } = options
50+
/** @type {{prNumber: number, prTitle: string}[]} */
51+
const result = []
52+
try {
53+
const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, {
54+
owner: repoOwner,
55+
repo: repoName,
56+
state: 'open',
57+
sort: 'created',
58+
direction: 'desc'
59+
})
60+
61+
for await (const { data: openPrs } of iterator) {
62+
const foundPrs = findInBatch(openPrs, givenTitle)
63+
result.push(...foundPrs)
64+
if (checkIsDone(openPrs, dateSince)) break
65+
}
66+
return result
67+
} catch (error) {
68+
const errorMessage = error instanceof Error ? error.message : String(error)
69+
console.error(`Failed to fetch pull requests for repo ${repoOwner}/${repoName}: ${errorMessage}`)
70+
throw error
71+
}
72+
}
73+
74+
/**
75+
* Filters pull requests by title.
76+
* @param {{number: number, title: string}[]} openPrs - The list of open pull requests.
77+
* @param {string} givenTitle - The title to search for.
78+
* @returns {{prNumber: number, prTitle: string}[]} The list of matching pull requests.
79+
*/
80+
const findInBatch = (openPrs, givenTitle) => {
81+
const found = (openPrs || [])
82+
.map(({ number, title }) => {
83+
console.debug(`Checking PR #${number}: ${title}`)
84+
return { prNumber: number, prTitle: title }
85+
})
86+
.filter(({ prTitle }) => prTitle === givenTitle)
87+
88+
console.info(`Found ${found.length} PRs with title: ${givenTitle}`)
89+
return found
90+
}
91+
92+
/**
93+
* Checks if the iteration is done based on the date of the earliest PR.
94+
* @param {{created_at: string}[]} prsByDateDesc - The list of PRs sorted by date in descending order.
95+
* @param {string} dateSince - The ISO date string to compare against.
96+
* @returns {boolean} True if the iteration is done, false otherwise.
97+
*/
98+
const checkIsDone = (prsByDateDesc, dateSince) => {
99+
if (!prsByDateDesc.length) return true
100+
const earliestPr = prsByDateDesc[prsByDateDesc.length - 1]
101+
console.debug(`${earliestPr.created_at} < ${dateSince} ?`)
102+
return earliestPr.created_at < dateSince
103+
}
104+
105+
/**
106+
* Closes a pull request.
107+
* @param {import('@octokit/rest').Octokit} octokit - The Octokit instance.
108+
* @param {Options} options - The options for cleanup.
109+
* @param {number} prNumber - The pull request number.
110+
* @returns {Promise<void>}
111+
* @throws {Error} If the pull request cannot be closed.
112+
*/
113+
const closePullRequest = async (octokit, options, prNumber) => {
114+
const { repoOwner, repoName } = options
115+
try {
116+
await octokit.pulls.update({
117+
owner: repoOwner,
118+
repo: repoName,
119+
pull_number: prNumber,
120+
state: 'closed'
121+
})
122+
console.info(`PR #${prNumber} closed successfully.`)
123+
} catch (error) {
124+
const errorMessage = error instanceof Error ? error.message : String(error)
125+
console.error(`Failed to close PR #${prNumber}: ${errorMessage}`)
126+
throw error
127+
}
128+
}
129+
130+
/**
131+
* Cleans up pull requests with the specified title created before the given date.
132+
* @param {Object} opts - The options for cleanup.
133+
* @param {string} [opts.title=TARGET_TITLE] - The title to search for.
134+
* @param {string} [opts.repoOwner=REPO_OWNER] - The owner of the repository.
135+
* @param {string} [opts.repoName=REPO_NAME] - The name of the repository.
136+
* @param {string} [dateSince=oneDayAgo] - The ISO date string to filter PRs created before this date.
137+
* @returns {Promise<void>}
138+
*/
139+
const cleanupPR = async (opts = {}, dateSince = oneDayAgo) => {
140+
const { title = TARGET_TITLE, repoOwner = REPO_OWNER, repoName = REPO_NAME } = opts
141+
const options = { title, repoOwner, repoName }
142+
console.info(`Cleanup options: ${JSON.stringify(options)}`)
143+
console.info(`Searching for PRs with title: ${title}`)
144+
145+
const octokit = await createOctokit()
146+
const found = await findPullRequests(octokit, options, dateSince)
147+
console.info(`Found ${found.length} PRs with title: ${title} before ${dateSince}`)
148+
149+
for (const { prTitle, prNumber } of found) {
150+
console.debug(`Found PR #${prNumber} with title: ${prTitle}`)
151+
await closePullRequest(octokit, options, prNumber)
152+
}
153+
}
154+
155+
module.exports = { cleanupPR }

0 commit comments

Comments
 (0)