Files
stablyai--orca/config/scripts/create-draft-release.test.mjs
T
2026-07-13 13:05:33 +08:00

254 lines
7.6 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest'
import {
createDraftRelease,
latestPreviousPublishedDesktopReleaseTag,
parseDesktopReleaseTag,
truncateReleaseBody
} from './create-draft-release.mjs'
function release(tag, options = {}) {
return {
draft: false,
tag_name: tag,
...options
}
}
function jsonResponse(body, init = {}) {
return {
ok: init.ok ?? true,
status: init.status ?? 200,
statusText: init.statusText ?? 'OK',
json: vi.fn(async () => body),
text: vi.fn(async () => (typeof body === 'string' ? body : JSON.stringify(body)))
}
}
describe('truncateReleaseBody', () => {
it('leaves short release notes unchanged', () => {
expect(truncateReleaseBody('short notes', 120_000)).toBe('short notes')
})
it('caps long release notes and appends an explanation', () => {
const body = truncateReleaseBody('a'.repeat(130_000), 1_000)
expect(body).toHaveLength(1_000)
expect(body).toContain('Release notes were truncated')
})
})
describe('parseDesktopReleaseTag', () => {
it('parses stable and rc desktop release tags only', () => {
expect(parseDesktopReleaseTag('v1.4.36')).toMatchObject({
tag: 'v1.4.36',
major: 1,
minor: 4,
patch: 36,
rc: null
})
expect(parseDesktopReleaseTag('v1.4.36-rc.2')).toMatchObject({
tag: 'v1.4.36-rc.2',
major: 1,
minor: 4,
patch: 36,
rc: 2
})
expect(parseDesktopReleaseTag('mobile-v0.0.12')).toBeNull()
})
})
describe('latestPreviousPublishedDesktopReleaseTag', () => {
it('bounds stable notes to the previous stable release when rcs exist', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.35'), release('v1.4.36-rc.0'), release('v1.4.36')],
'v1.4.36'
)
).toBe('v1.4.35')
})
it('does not collapse a stable changelog to its rc-to-stable version bump', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[
release('v1.4.120'),
release('v1.4.121-rc.0'),
release('v1.4.121-rc.6'),
release('v1.4.121')
],
'v1.4.121'
)
).toBe('v1.4.120')
})
it('bounds the first rc notes to the previous stable release', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.35'), release('v1.4.36-rc.0'), release('mobile-v0.0.12')],
'v1.4.36-rc.0'
)
).toBe('v1.4.35')
})
it('bounds later rc notes to the prior rc', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.36-rc.0'), release('v1.4.36-rc.1')],
'v1.4.36-rc.1'
)
).toBe('v1.4.36-rc.0')
})
it('ignores draft releases as public changelog boundaries', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.35'), release('v1.4.36-rc.0', { draft: true }), release('v1.4.36-rc.1')],
'v1.4.36-rc.1'
)
).toBe('v1.4.35')
})
it('returns empty string for the first desktop release when no earlier tag exists', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.36'), release('mobile-v0.0.12')],
'v1.4.36'
)
).toBe('')
expect(latestPreviousPublishedDesktopReleaseTag([], 'v1.4.36')).toBe('')
})
it('returns empty string when the current tag is not a desktop release tag', () => {
expect(
latestPreviousPublishedDesktopReleaseTag(
[release('v1.4.35'), release('v1.4.36')],
'mobile-v0.0.12'
)
).toBe('')
})
})
describe('createDraftRelease', () => {
it('creates a draft release with bounded generated notes', async () => {
const fetchImpl = vi
.fn()
.mockResolvedValueOnce(jsonResponse([release('v1.4.35'), release('v1.4.36')]))
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'a'.repeat(130_000) }))
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36', draft: true }))
await createDraftRelease({
repo: 'stablyai/orca',
tag: 'v1.4.36',
token: 'token',
fetchImpl,
log: vi.fn()
})
expect(fetchImpl).toHaveBeenNthCalledWith(
1,
'https://api.github.com/repos/stablyai/orca/releases?per_page=100&page=1',
expect.any(Object)
)
expect(fetchImpl).toHaveBeenNthCalledWith(
2,
'https://api.github.com/repos/stablyai/orca/releases/generate-notes',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
tag_name: 'v1.4.36',
target_commitish: 'v1.4.36',
previous_tag_name: 'v1.4.35'
})
})
)
expect(fetchImpl).toHaveBeenNthCalledWith(
3,
'https://api.github.com/repos/stablyai/orca/releases',
expect.objectContaining({
method: 'POST',
body: expect.any(String)
})
)
const createBody = JSON.parse(fetchImpl.mock.calls[2][1].body)
expect(createBody).toMatchObject({
tag_name: 'v1.4.36',
name: 'v1.4.36',
draft: true,
prerelease: false
})
expect(createBody.body).toHaveLength(120_000)
expect(createBody.body).toContain('Release notes were truncated')
})
it('marks rc tags as prereleases', async () => {
const fetchImpl = vi
.fn()
.mockResolvedValueOnce(jsonResponse([release('v1.4.36'), release('v1.4.36-rc.1')]))
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36-rc.1', body: 'notes' }))
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36-rc.1', draft: true }))
await createDraftRelease({
repo: 'stablyai/orca',
tag: 'v1.4.36-rc.1',
token: 'token',
fetchImpl,
log: vi.fn()
})
const createBody = JSON.parse(fetchImpl.mock.calls[2][1].body)
expect(createBody.prerelease).toBe(true)
})
it('omits previous_tag_name for the first desktop release so notes fall back to the GitHub default', async () => {
const fetchImpl = vi
.fn()
.mockResolvedValueOnce(jsonResponse([release('v1.4.36'), release('mobile-v0.0.12')]))
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'notes' }))
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36', draft: true }))
await createDraftRelease({
repo: 'stablyai/orca',
tag: 'v1.4.36',
token: 'token',
fetchImpl,
log: vi.fn()
})
const generateNotesBody = JSON.parse(fetchImpl.mock.calls[1][1].body)
expect(generateNotesBody).toEqual({ tag_name: 'v1.4.36', target_commitish: 'v1.4.36' })
expect(generateNotesBody).not.toHaveProperty('previous_tag_name')
})
it('paginates through every release page before choosing the previous release', async () => {
const firstPage = Array.from({ length: 100 }, (_, index) => release(`mobile-v0.0.${index}`))
const fetchImpl = vi
.fn()
.mockResolvedValueOnce(jsonResponse(firstPage))
.mockResolvedValueOnce(jsonResponse([release('v1.4.35')]))
.mockResolvedValueOnce(jsonResponse({ name: 'v1.4.36', body: 'notes' }))
.mockResolvedValueOnce(jsonResponse({ tag_name: 'v1.4.36', draft: true }))
await createDraftRelease({
repo: 'stablyai/orca',
tag: 'v1.4.36',
token: 'token',
fetchImpl,
log: vi.fn()
})
expect(fetchImpl).toHaveBeenNthCalledWith(
1,
'https://api.github.com/repos/stablyai/orca/releases?per_page=100&page=1',
expect.any(Object)
)
expect(fetchImpl).toHaveBeenNthCalledWith(
2,
'https://api.github.com/repos/stablyai/orca/releases?per_page=100&page=2',
expect.any(Object)
)
const generateNotesBody = JSON.parse(fetchImpl.mock.calls[2][1].body)
expect(generateNotesBody.previous_tag_name).toBe('v1.4.35')
})
})