ReviewOS

also looking at this

stacks/buddy

refactor: one update-type classifier, with the edge cases decided instead of accidental

#1439
Merged glennmichael123 wants to merge refactor/one-update-type into main
4 files +108 -116
test/version-classification.test.tsmodified+51-0
Changes to test/version-classification.test.ts
@@ -1,5 +1,6 @@
11import { describe, expect, it } from 'bun:test'
22import { getUpdateType } from '../src/utils/helpers'
3import { VersionResolver } from '../src/version/version-resolver'
34
45describe('Version Classification', () => {
56 describe('getUpdateType', () => {
@@ -52,3 +53,53 @@ describe('Version Classification', () => {
5253 })
5354 })
5455})
56
57/**
58 * Three parallel implementations of this classification existed — helpers,
59 * a private Buddy method, and VersionResolver — and disagreed on short
60 * actions tags, prereleases, suffixed Docker tags and non-semver input.
61 * These pin the decided semantics of the one that remains.
62 */
63describe('one classifier across the codebase', () => {
64 it('success case - a short actions tag reads as the bump it is', () => {
65 // Bun.semver.order('4.2.2', '4') is -1 — without numeric padding the
66 // upgrade guard misread this real upgrade as a downgrade.
67 expect(getUpdateType('v4', 'v4.2.2')).toBe('minor')
68 expect(getUpdateType('v4.2.2', 'v5')).toBe('major')
69 })
70
71 it('success case - prerelease movement under an unchanged triple is a patch', () => {
72 // These fell through to 'major', letting an excludeMajor filter block a
73 // beta graduation as if it were a breaking change.
74 expect(getUpdateType('1.0.0-beta.1', '1.0.0')).toBe('patch')
75 expect(getUpdateType('1.0.0-beta.1', '1.0.0-beta.2')).toBe('patch')
76 })
77
78 it('success case - a prerelease of the next major is still major', () => {
79 expect(getUpdateType('1.0.0', '2.0.0-rc.1')).toBe('major')
80 })
81
82 it('success case - suffixed docker tags classify by their numeric core', () => {
83 expect(getUpdateType('3.19-alpine', '3.20-alpine')).toBe('minor')
84 expect(getUpdateType('3.19-alpine', '4.0-alpine')).toBe('major')
85 })
86
87 it('edge case - non-semver input degrades to patch, never a fabricated major', () => {
88 expect(getUpdateType('latest', '1.0.0')).toBe('patch')
89 expect(getUpdateType('workspace:*', '1.0.0')).toBe('patch')
90 expect(getUpdateType('main', '1.0.0')).toBe('patch')
91 })
92
93 it('success case - VersionResolver answers identically', () => {
94 const cases: Array<[string, string]> = [
95 ['v4', 'v4.2.2'],
96 ['v1.0.0', 'v2.0.0'],
97 ['1.0.0-beta.1', '1.0.0'],
98 ['latest', '1.0.0'],
99 ['1.2.3', '1.3.0'],
100 ]
101
102 for (const [from, to] of cases)
103 expect(VersionResolver.getUpdateType(from, to)).toBe(getUpdateType(from, to))
104 })
105})