scripts/tests/changelog-version-test.mjs
Rama master · réplica de lectura del repositorio original.
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { fixture } from './fixtures.mjs'
import {
MANIFESTS,
latestRelease,
manifestVersion,
nextVersion,
parseVersion,
} from '../changelog-version.mjs'
const PROJECTS = [
'start-os',
'start-cli',
'start-sdk',
'start-tunnel',
'start-registry',
'start-wrt',
]
const TIERS = ['patch', 'minor', 'major']
function* combinations(values, length) {
if (!length) yield []
else
for (const value of values)
for (const rest of combinations(values, length - 1))
yield [value, ...rest]
}
function* permutations(values) {
if (!values.length) yield []
else
for (let i = 0; i < values.length; i++)
for (const rest of permutations(values.filter((_, index) => index !== i)))
yield [values[i], ...rest]
}
test('public parser normalizes StartOS and preserves prerelease', () => {
assert.deepEqual(parseVersion('start-os', '0.4.1'), [[4n, 1n, 0n], null])
assert.deepEqual(parseVersion('start-os', '0.4.0.3-rc.1'), [
[4n, 0n, 3n],
'rc.1',
])
assert.deepEqual(parseVersion('start-sdk', '4.0.3+build.1'), [
[4n, 0n, 3n],
null,
])
})
test('public parser rejects noncanonical versions', () => {
for (const [product, version] of [
['start-os', '9.0.0'],
['start-sdk', '1.2.3.4'],
['start-sdk', '1.2.3-rc.01'],
['other', '1.2.3'],
])
assert.throws(() => parseVersion(product, version))
})
test('all tier combinations and orders', () => {
for (const [project, baseline, results] of [
['start-os', '0.4.0.3', ['0.4.0.4', '0.4.1', '0.5.0']],
['start-sdk', '4.0.3', ['4.0.4', '4.1.0', '5.0.0']],
]) {
for (const [allowed, expected] of [
[['patch'], results[0]],
[['patch', 'minor'], results[1]],
[['patch', 'minor', 'major'], results[2]],
]) {
for (let length = 1; length < 5; length++)
for (const tiers of combinations(allowed, length))
if (tiers.includes(allowed.at(-1)))
assert.equal(
nextVersion(project, baseline, tiers.values()),
expected,
)
}
}
})
test('empty fragments preserve exact version', () => {
for (const project of PROJECTS)
for (const version of project === 'start-os'
? ['0.4.0', '0.4.0.0', '0.4.0.3']
: ['0.0.0', '1.2.3'])
assert.equal(nextVersion(project, version, [].values()), version)
})
test('carry and reset boundaries', () => {
const cases = [
['start-sdk', '0.0.0', ['0.0.1', '0.1.0', '1.0.0']],
['start-cli', '9.9.9', ['9.9.10', '9.10.0', '10.0.0']],
['start-registry', '1.99.999', ['1.99.1000', '1.100.0', '2.0.0']],
['start-tunnel', '99.1.2', ['99.1.3', '99.2.0', '100.0.0']],
['start-wrt', '1.2.3', ['1.2.4', '1.3.0', '2.0.0']],
['start-os', '0.0.0', ['0.0.0.1', '0.0.1', '0.1.0']],
['start-os', '0.4.0', ['0.4.0.1', '0.4.1', '0.5.0']],
['start-os', '0.4.0.0', ['0.4.0.1', '0.4.1', '0.5.0']],
['start-os', '0.9.9.9', ['0.9.9.10', '0.9.10', '0.10.0']],
['start-os', '0.4.99.999', ['0.4.99.1000', '0.4.100', '0.5.0']],
]
for (const [project, baseline, results] of cases)
TIERS.forEach((tier, index) =>
assert.equal(nextVersion(project, baseline, [tier]), results[index]),
)
})
test('invalid tier even after major', () => {
for (const tier of ['', 'PATCH', 'security', 'minor ', null, 1])
assert.throws(() => nextVersion('start-sdk', '1.2.3', ['major', tier]))
})
test('malformed versions', () => {
const invalid = [
'',
'1',
'1.2',
'v1.2.3',
'1.2.3.4',
'01.2.3',
'1.02.3',
'1.2.03',
'-1.2.3',
'1.2.3\n',
' 1.2.3',
'1.2.3-',
'1.2.3+',
'1.2.3-alpha.01',
'1.2.3-a_1',
'1.2.3+a_1',
null,
123,
]
for (const project of PROJECTS) {
for (const value of invalid.concat(
project === 'start-os'
? ['0.4', '0.04.0', '0.4.0.03', '0.4.0.1.2', '1.4.0', '1.4.0.3']
: [],
))
assert.throws(() => nextVersion(project, value, []))
}
})
test('prereleases are not baselines', () => {
for (const [project, version] of [
['start-sdk', '1.2.3-beta.1'],
['start-os', '0.4.0.3-rc.1'],
])
assert.throws(() => nextVersion(project, version, []))
})
test('build metadata is stable for next version', () => {
assert.equal(nextVersion('start-sdk', '1.2.3+build.1', []), '1.2.3+build.1')
assert.equal(nextVersion('start-sdk', '1.2.3+build.1', ['patch']), '1.2.4')
})
test('numeric order independent of tag order', () => {
for (const tags of permutations([
'start-sdk/v9.99.99',
'start-sdk/v10.0.0',
'start-sdk/v9.100.0',
'start-sdk/v2.0.0',
]))
assert.equal(latestRelease('start-sdk', tags.values()), '10.0.0')
assert.equal(
latestRelease('start-sdk', ['start-sdk/v1.9.9', 'start-sdk/v1.10.0']),
'1.10.0',
)
assert.equal(
latestRelease('start-sdk', ['start-sdk/v1.2.9', 'start-sdk/v1.2.10']),
'1.2.10',
)
})
test('exact namespace and stable tags', () => {
for (const project of PROJECTS) {
const version = project === 'start-os' ? '0.4.0.3' : '1.2.3'
const tags = [
'v99.0.0',
'other/v99.0.0',
`${project}-extra/v99.0.0`,
`refs/tags/${project}/v99.0.0`,
`${project}/99.0.0`,
`${project}/V99.0.0`,
...['-alpha.1', '-beta.9', '-rc.1', '/extra'].map(
suffix => `${project}/v${version}${suffix}`,
),
`${project}/v01.2.3`,
`${project}/v1.2.3.4.5`,
`${project}/v${version}`,
]
assert.equal(latestRelease(project, tags), version)
}
})
test('prereleases do not override stable', () => {
assert.equal(
latestRelease('start-wrt', ['start-wrt/v1.0.0', 'start-wrt/v2.0.0-beta.4']),
'1.0.0',
)
assert.equal(
latestRelease('start-os', ['start-os/v0.4.0.3', 'start-os/v0.5.0-rc.1']),
'0.4.0.3',
)
})
test('no stable release', () => {
for (const tags of [
[],
['start-sdk/v1.0.0-beta.1'],
['other/v1.0.0'],
['start-sdk/vbad'],
['start-sdk/v1.2.3.4'],
])
assert.equal(latestRelease('start-sdk', tags), null)
})
test('StartOS normalizes omitted patch for comparison', () => {
for (const [tags, expected] of [
[['0.4.0.99', '0.4.1'], '0.4.1'],
[['0.4.0', '0.4.0.1'], '0.4.0.1'],
[['0.9.99.99', '0.10.0'], '0.10.0'],
[['1.2.3.4', '0.4.0.01'], null],
])
assert.equal(
latestRelease(
'start-os',
tags.map(version => `start-os/v${version}`),
),
expected,
)
})
test('build metadata is stable for latest release', () => {
assert.equal(
latestRelease('start-sdk', ['start-sdk/v1.2.3+build.4']),
'1.2.3+build.4',
)
})
test('canonical manifest paths', t => {
const { root, write } = fixture(t)
write('package.json', '{"version":"0.4.0.3"}')
write('projects/start-os/Cargo.toml', '[package]\nversion = "0.4.0-rev.3"')
write('projects/start-os/package.json', '{"version":"9.9.9"}')
write('projects/start-sdk/package.json', '{"version":"3.0.3"}')
write('projects/start-sdk/Cargo.toml', '[package]\nversion = "9.9.9"')
for (const project of ['start-cli', 'start-tunnel', 'start-registry']) {
write(
`projects/${project}/Cargo.toml`,
'[package]\nversion = "1.2.3" # VERSION_BUMP\n',
)
assert.equal(manifestVersion(root, project), '1.2.3')
}
write('projects/start-wrt/Cargo.toml', '[package]\nversion = "9.9.9"')
write(
'projects/start-wrt/backend/ctrl/Cargo.toml',
'[package]\nversion = "1.3.0"',
)
assert.equal(manifestVersion(root, 'start-os'), '0.4.0.3')
assert.equal(manifestVersion(root, 'start-sdk'), '3.0.3')
assert.equal(manifestVersion(root, 'start-wrt'), '1.3.0')
})
test('package table only and literal quotes', t => {
const { root, write } = fixture(t)
write(
'projects/start-cli/Cargo.toml',
`[dependencies]
version = "9.9.9" # VERSION_BUMP
[ package ] # manifest
name = "start-cli"
version = '1.2.3-beta.1+build' # VERSION_BUMP
[[bin]]
version = "8.8.8"
[package.metadata]
version = "7.7.7"
`,
)
assert.equal(manifestVersion(root, 'start-cli'), '1.2.3-beta.1+build')
})
test('malformed or missing package version', t => {
const { root, write } = fixture(t)
for (const text of [
'[dependencies]\nversion = "1.2.3"',
'[package]\nname = "cli"',
'[package]\nversion.workspace = true',
'[package]\nversion = 123',
'[package]\nversion = "1.2.3"\nversion = "1.2.4"',
'[package]\nversion = "1.2.3.4"',
'[package]\nversion = "1.2.03"',
'[package]\nversion = "1.2.3-alpha.01"',
]) {
write('projects/start-cli/Cargo.toml', text)
assert.throws(() => manifestVersion(root, 'start-cli'))
}
})
test('invalid JSON version', t => {
const { root, write } = fixture(t)
for (const text of [
'{}',
'[]',
'{"version":null}',
'{"version":123}',
'{"version":"1.2.3.4"}',
'{',
]) {
write('projects/start-sdk/package.json', text)
assert.throws(() => manifestVersion(root, 'start-sdk'))
}
})
test('missing manifest', t => {
const { root } = fixture(t)
assert.throws(() => manifestVersion(root, 'start-wrt'), { code: 'ENOENT' })
})
test('unknown project', t => {
const { root } = fixture(t)
for (const project of ['other', '../start-os', 'projects/start-sdk', '']) {
assert.throws(() => manifestVersion(root, project))
assert.throws(() => nextVersion(project, '1.2.3', []))
assert.throws(() => latestRelease(project, []))
}
})
test('arbitrary sized numeric components retain precision', () => {
assert.equal(
nextVersion(
'start-sdk',
'9007199254740993.9007199254740993.9007199254740993',
['patch'],
),
'9007199254740993.9007199254740993.9007199254740994',
)
assert.equal(
nextVersion('start-os', '0.9007199254740993.1', ['major']),
'0.9007199254740994.0',
)
assert.equal(
latestRelease('start-sdk', [
'start-sdk/v9007199254740993.0.0',
'start-sdk/v9007199254740992.0.0',
]),
'9007199254740993.0.0',
)
})
test('manifest UTF-8 decoding is strict', t => {
const { root, write } = fixture(t)
write(MANIFESTS['start-sdk'], Buffer.from([0xff]))
assert.throws(() => manifestVersion(root, 'start-sdk'))
})