mirror of
https://github.com/logos-co/logos-web.git
synced 2026-08-27 12:11:14 +00:00
225 lines
6.3 KiB
TypeScript
225 lines
6.3 KiB
TypeScript
/**
|
|
* End-to-end smoke test for the GitHub mutation primitives.
|
|
*
|
|
* Runs the full create-branch → commit → open-PR → query-PR → close-PR →
|
|
* delete-branch lifecycle against a real repo so regressions in `github/*`
|
|
* surface immediately. Safe to re-run — every iteration uses a unique
|
|
* timestamped branch name, and cleanup runs in `finally`.
|
|
*
|
|
* Authentication: uses GitHub App installation credentials only. Configure
|
|
* the target repo via `GITHUB_OWNER` / `GITHUB_REPO`; defaults to
|
|
* acid-info/logos-co.
|
|
*/
|
|
import { getOctokit, __resetOctokitForTests } from '../src/github/client'
|
|
import {
|
|
__resetGithubConfigForTests,
|
|
loadGithubConfigFromEnv,
|
|
setGithubConfig,
|
|
} from '../src/github/config'
|
|
import { findOpenPullRequestsTouchingPath } from '../src/github/locks'
|
|
import {
|
|
branchExists,
|
|
commitFiles,
|
|
commitTextFile,
|
|
createBranch,
|
|
getBranchSha,
|
|
} from '../src/github/mutations'
|
|
import {
|
|
createOrGetPullRequest,
|
|
findPullRequestByBranch,
|
|
} from '../src/github/pull-requests'
|
|
|
|
type Step = {
|
|
name: string
|
|
run: () => Promise<unknown>
|
|
}
|
|
|
|
const config = loadGithubConfigFromEnv()
|
|
const { owner, repo, prBaseBranch: baseBranch } = config
|
|
|
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-').replace(/-Z$/, 'Z')
|
|
const suffix = Math.random().toString(36).slice(2, 8)
|
|
const branchName = `content/smoke-test-${stamp}-${suffix}`
|
|
const filePath = `tmp/github-smoke/${stamp}-${suffix}.json`
|
|
const fileContent = JSON.stringify(
|
|
{ stamp, suffix, note: 'smoke test artifact — safe to delete' },
|
|
null,
|
|
2
|
|
)
|
|
|
|
const main = async (): Promise<void> => {
|
|
__resetGithubConfigForTests()
|
|
__resetOctokitForTests()
|
|
setGithubConfig(config)
|
|
|
|
console.log(`target : ${owner}/${repo}`)
|
|
console.log(`base : ${baseBranch}`)
|
|
console.log(`branch : ${branchName}`)
|
|
console.log(`file : ${filePath}`)
|
|
console.log('')
|
|
|
|
const steps: Step[] = [
|
|
{
|
|
name: 'getBranchSha(base)',
|
|
run: async () => {
|
|
const sha = await getBranchSha(baseBranch)
|
|
return `base sha=${sha.slice(0, 7)}`
|
|
},
|
|
},
|
|
{
|
|
name: 'createBranch',
|
|
run: () =>
|
|
createBranch({ newBranch: branchName, fromBranch: baseBranch }),
|
|
},
|
|
{
|
|
name: 'branchExists',
|
|
run: async () => {
|
|
const exists = await branchExists(branchName)
|
|
if (!exists) throw new Error('branch should exist after create')
|
|
return 'true'
|
|
},
|
|
},
|
|
{
|
|
name: 'commitTextFile (single)',
|
|
run: () =>
|
|
commitTextFile({
|
|
branch: branchName,
|
|
path: filePath,
|
|
content: fileContent,
|
|
message: `chore(smoke): commit single file ${suffix}`,
|
|
}),
|
|
},
|
|
{
|
|
name: 'commitFiles (atomic multi)',
|
|
run: async () => {
|
|
const result = await commitFiles({
|
|
branch: branchName,
|
|
message: `chore(smoke): commit multi files ${suffix}`,
|
|
changes: [
|
|
{
|
|
path: `${filePath.replace('.json', '')}-second.json`,
|
|
content: JSON.stringify({ batch: 'second' }, null, 2),
|
|
},
|
|
{
|
|
path: `${filePath.replace('.json', '')}-third.json`,
|
|
content: JSON.stringify({ batch: 'third' }, null, 2),
|
|
},
|
|
],
|
|
})
|
|
return `commitSha=${result.commitSha.slice(0, 7)}`
|
|
},
|
|
},
|
|
{
|
|
name: 'createOrGetPullRequest',
|
|
run: async () => {
|
|
const pr = await createOrGetPullRequest({
|
|
branchName,
|
|
title: `chore(smoke): GitHub primitives smoke test ${suffix}`,
|
|
body:
|
|
'Automated smoke test of the `@repo/content` GitHub mutation ' +
|
|
'primitives. Closed automatically by the smoke script — if ' +
|
|
'this PR lingers in your inbox it means the cleanup phase ' +
|
|
'failed and the branch needs manual deletion.',
|
|
draft: true,
|
|
})
|
|
return `#${pr.number} ${pr.htmlUrl}`
|
|
},
|
|
},
|
|
{
|
|
name: 'findPullRequestByBranch (idempotency)',
|
|
run: async () => {
|
|
const pr = await findPullRequestByBranch(branchName)
|
|
if (!pr) throw new Error('expected to find the PR by branch')
|
|
return `#${pr.number}`
|
|
},
|
|
},
|
|
{
|
|
name: 'createOrGetPullRequest (returns existing, not duplicate)',
|
|
run: async () => {
|
|
const a = await createOrGetPullRequest({
|
|
branchName,
|
|
title: 'should not create a duplicate',
|
|
body: 'should not create a duplicate',
|
|
})
|
|
return `#${a.number}`
|
|
},
|
|
},
|
|
{
|
|
name: 'findOpenPullRequestsTouchingPath',
|
|
run: async () => {
|
|
const matches = await findOpenPullRequestsTouchingPath(filePath)
|
|
const ours = matches.find((pr) => pr.branchName === branchName)
|
|
if (!ours) {
|
|
throw new Error(
|
|
`expected to find our smoke PR in lock query for ${filePath}; got [${matches
|
|
.map((pr) => `#${pr.number}`)
|
|
.join(', ')}]`
|
|
)
|
|
}
|
|
return `${matches.length} PR(s) touch the path; ours #${ours.number}`
|
|
},
|
|
},
|
|
]
|
|
|
|
let failed = 0
|
|
for (const step of steps) {
|
|
try {
|
|
const result = await step.run()
|
|
console.log(`ok ${step.name}${result ? ' — ' + result : ''}`)
|
|
} catch (err) {
|
|
failed++
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
console.error(`FAIL ${step.name}: ${message}`)
|
|
break
|
|
}
|
|
}
|
|
|
|
console.log('')
|
|
console.log('cleanup phase')
|
|
await cleanup()
|
|
|
|
if (failed > 0) process.exit(1)
|
|
}
|
|
|
|
const cleanup = async (): Promise<void> => {
|
|
const octokit = getOctokit()
|
|
|
|
// Close PR if it exists.
|
|
try {
|
|
const pr = await findPullRequestByBranch(branchName)
|
|
if (pr) {
|
|
await octokit.pulls.update({
|
|
owner,
|
|
repo,
|
|
pull_number: pr.number,
|
|
state: 'closed',
|
|
})
|
|
console.log(`ok closed PR #${pr.number}`)
|
|
}
|
|
} catch (err) {
|
|
console.warn(
|
|
`warn PR close skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
)
|
|
}
|
|
|
|
// Delete the branch.
|
|
try {
|
|
await octokit.git.deleteRef({
|
|
owner,
|
|
repo,
|
|
ref: `heads/${branchName}`,
|
|
})
|
|
console.log(`ok deleted branch ${branchName}`)
|
|
} catch (err) {
|
|
console.warn(
|
|
`warn branch delete skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
)
|
|
}
|
|
}
|
|
|
|
main().catch(async (err) => {
|
|
console.error(err)
|
|
await cleanup()
|
|
process.exit(1)
|
|
})
|