mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 00:02:10 +00:00
* 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.
151 lines
5.1 KiB
JavaScript
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')
|
|
})
|
|
})
|