Files
orca/config/scripts/release-policy.test.mjs
T
Jinjing 95a16e3f67 fix(release): stop the release policy from deleting pipeline-cut releases (#23669)
* fix(release): stop the release policy from deleting pipeline-cut releases

The policy judged a release by who created the release object. Cut Release
reuses an existing draft, so a CI-built v1.4.216 whose draft a person had
created was deleted (tag included) when its notes were edited, and Latest
fell back to v1.4.214 because v1.4.215 was also published by a person.

- Authorize a desktop release when its annotated tag was created by the
  release pipeline and points at its `release: vX` commit, not only by author.
- Only delete on `published`; an edit never deletes a release or tag.
- Pick Latest from the highest authorized stable using the same check.
- Move the policy into config/scripts/release-policy.mjs with tests.

* fix(release): load the policy module from the tagged commit

Release events run the workflow file from the tag's commit, so checking out
the default branch could pair an old workflow with a newer module.
2026-09-28 12:03:58 -07:00

151 lines
5.1 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest'
import { enforceReleasePolicy, restoreLatestStable } from './release-policy.mjs'
const BOT_EMAIL = '41898282+github-actions[bot]@users.noreply.github.com'
const repoRef = { owner: 'stablyai', repo: 'orca' }
function notFound() {
return Object.assign(new Error('Not Found'), { status: 404 })
}
// tags: { [tag]: { tagger, subject } } for annotated tags; missing tags 404.
function createGithub({ releases = [], tags = {} } = {}) {
const rest = {
repos: {
listReleases: vi.fn(),
updateRelease: vi.fn(async () => ({ data: {} })),
deleteRelease: vi.fn(async () => ({ data: {} }))
},
git: {
getRef: vi.fn(async ({ ref }) => {
const tag = ref.replace(/^tags\//, '')
if (!tags[tag]) {
throw notFound()
}
return { data: { object: { type: 'tag', sha: `tag-${tag}` } } }
}),
getTag: vi.fn(async ({ tag_sha }) => {
const tag = tag_sha.replace(/^tag-/, '')
return {
data: {
tagger: { email: tags[tag].tagger ?? BOT_EMAIL },
object: { type: 'commit', sha: `commit-${tag}` }
}
}
}),
getCommit: vi.fn(async ({ commit_sha }) => {
const tag = commit_sha.replace(/^commit-/, '')
return { data: { message: `${tags[tag].subject ?? `release: ${tag}`}\n` } }
}),
deleteRef: vi.fn(async () => ({ data: {} }))
}
}
return { rest, paginate: vi.fn(async () => releases) }
}
function release(tag, author, overrides = {}) {
return {
id: Number(tag.replace(/\D/g, '')),
tag_name: tag,
author: { login: author },
draft: false,
prerelease: false,
...overrides
}
}
function run(github, payloadRelease, action) {
const core = { warning: vi.fn() }
const context = { payload: { release: payloadRelease, action }, repo: repoRef }
return enforceReleasePolicy({ github, context, core }).then(() => core)
}
describe('release policy', () => {
it('keeps a pipeline-cut release whose draft a person created', async () => {
const r216 = release('v1.4.216', 'Jinwoo-H')
const github = createGithub({
releases: [r216, release('v1.4.214', 'github-actions[bot]')],
tags: { 'v1.4.216': {}, 'v1.4.214': {} }
})
await run(github, r216, 'edited')
expect(github.rest.repos.deleteRelease).not.toHaveBeenCalled()
expect(github.rest.git.deleteRef).not.toHaveBeenCalled()
expect(github.rest.repos.updateRelease).toHaveBeenCalledWith({
...repoRef,
release_id: r216.id,
make_latest: 'true'
})
})
it('never deletes an unauthorized release on edit', async () => {
const qa = release('v1.4.300', 'someone')
const github = createGithub({ releases: [qa] })
const core = await run(github, qa, 'edited')
expect(github.rest.repos.deleteRelease).not.toHaveBeenCalled()
expect(github.rest.git.deleteRef).not.toHaveBeenCalled()
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('left as-is'))
})
it('deletes a published release whose tag was not cut by the pipeline', async () => {
const qa = release('v1.4.300', 'someone')
const github = createGithub({
releases: [qa, release('v1.4.215', 'nwparker')],
tags: { 'v1.4.300': { tagger: 'someone@example.com' }, 'v1.4.215': {} }
})
await run(github, qa, 'published')
expect(github.rest.repos.deleteRelease).toHaveBeenCalledWith({ ...repoRef, release_id: qa.id })
expect(github.rest.git.deleteRef).toHaveBeenCalledWith({ ...repoRef, ref: 'tags/v1.4.300' })
})
it('rejects a bot-tagged tag whose commit is not a release commit', async () => {
const qa = release('v1.4.300', 'someone')
const github = createGithub({ tags: { 'v1.4.300': { subject: 'fix: something' } } })
await run(github, qa, 'published')
expect(github.rest.repos.deleteRelease).toHaveBeenCalled()
})
it('accepts scheduled RC release commits', async () => {
const rc = release('v1.4.217-rc.1', 'someone', { prerelease: true })
const github = createGithub({
tags: { 'v1.4.217-rc.1': { subject: 'release: v1.4.217-rc.1 [rc-slot:2026-09-28-15]' } }
})
await run(github, rc, 'published')
expect(github.rest.repos.deleteRelease).not.toHaveBeenCalled()
})
it('restores Latest to the highest pipeline-cut stable, not only bot-authored ones', async () => {
const github = createGithub({
releases: [
release('v1.4.214', 'github-actions[bot]'),
release('v1.4.215', 'nwparker'),
release('v1.4.216-rc.0', 'github-actions[bot]', { prerelease: true })
],
tags: { 'v1.4.214': {}, 'v1.4.215': {} }
})
await expect(restoreLatestStable(github, repoRef)).resolves.toBe('v1.4.215')
expect(github.rest.repos.updateRelease).toHaveBeenCalledTimes(1)
})
it('compares versions numerically when picking Latest', async () => {
const github = createGithub({
releases: [
release('v1.4.99', 'github-actions[bot]'),
release('v1.4.100', 'github-actions[bot]')
]
})
await expect(restoreLatestStable(github, repoRef)).resolves.toBe('v1.4.100')
})
})