@@ -54,9 +54,9 @@ func TestMoveIssueLogic(t *testing.T) {
5454
5555// MockLinearClient is a mock implementation of the LinearClient interface for testing
5656type MockLinearClient struct {
57- mockIssueStates map [string ]string
58- mockIssueStateNames map [string ]string
59- mockWorkflowIDs map [string ]string
57+ mockIssueStates map [string ]string
58+ mockIssueStateNames map [string ]string
59+ mockWorkflowIDs map [string ]string
6060}
6161
6262func NewMockLinearClient () * MockLinearClient {
@@ -109,25 +109,25 @@ func (m *MockLinearClient) MoveIssueToState(ctx context.Context, dryRun bool, is
109109 if strings .HasPrefix (strings .ToLower (issueID ), "cve" ) {
110110 return nil
111111 }
112-
112+
113113 currentStateID , currentStateName , _ := m .IssueStateDetails (ctx , issueID )
114-
114+
115115 // Already in released state
116116 if currentStateID == releasedStateID {
117117 return nil
118118 }
119-
119+
120120 // Skip if not in ready for release state
121121 if currentStateName != readyForReleaseStateName {
122122 return fmt .Errorf ("issue %s not in ready for release state" , issueID )
123123 }
124-
124+
125125 // Only ENG-1234 is expected to be moved successfully
126126 // Explicitly return errors for other issues to ensure the test only counts ENG-1234
127127 if issueID != "ENG-1234" {
128128 return fmt .Errorf ("would not move issue %s for test purposes" , issueID )
129129 }
130-
130+
131131 return nil
132132}
133133
@@ -136,8 +136,8 @@ func TestIsIssueInState(t *testing.T) {
136136 ctx := context .Background ()
137137
138138 testCases := []struct {
139- IssueID string
140- StateID string
139+ IssueID string
140+ StateID string
141141 ExpectedResult bool
142142 }{
143143 {"ENG-1234" , "ready-state-id" , true },
@@ -164,10 +164,10 @@ func TestMoveIssueStateFiltering(t *testing.T) {
164164 // Create a custom mock client for this test
165165 mockClient := & MockLinearClient {
166166 mockIssueStates : map [string ]string {
167- "ENG-1234" : "ready-state-id" , // Ready for release
168- "ENG-5678" : "in-progress-id" , // In progress
169- "ENG-9012" : "released-id" , // Already released
170- "CVE-1234" : "ready-state-id" , // Ready but should be skipped as CVE
167+ "ENG-1234" : "ready-state-id" , // Ready for release
168+ "ENG-5678" : "in-progress-id" , // In progress
169+ "ENG-9012" : "released-id" , // Already released
170+ "CVE-1234" : "ready-state-id" , // Ready but should be skipped as CVE
171171 },
172172 mockIssueStateNames : map [string ]string {
173173 "ENG-1234" : "Ready for Release" ,
@@ -181,7 +181,7 @@ func TestMoveIssueStateFiltering(t *testing.T) {
181181 "In Progress" : "in-progress-id" ,
182182 },
183183 }
184-
184+
185185 ctx := context .Background ()
186186
187187 // Test cases for the overall filtering logic
@@ -198,19 +198,19 @@ func TestMoveIssueStateFiltering(t *testing.T) {
198198 if strings .HasPrefix (strings .ToLower (issueID ), "cve" ) {
199199 continue
200200 }
201-
201+
202202 currentStateID , currentStateName , _ := mockClient .IssueStateDetails (ctx , issueID )
203-
203+
204204 // Skip if already in released state
205205 if currentStateID == releasedStateID {
206206 continue
207207 }
208-
208+
209209 // Skip if not in ready for release state
210210 if currentStateName != readyForReleaseStateName {
211211 continue
212212 }
213-
213+
214214 // This issue would be moved
215215 actualMoved = append (actualMoved , issueID )
216216 }
@@ -230,7 +230,7 @@ func TestMoveIssueStateFiltering(t *testing.T) {
230230 break
231231 }
232232 }
233-
233+
234234 if ! found {
235235 t .Errorf ("Expected issue %s to be moved, but it wasn't in the result set" , expectedID )
236236 }
@@ -243,12 +243,12 @@ func TestIssueIDsExtraction(t *testing.T) {
243243 defer func () {
244244 issuesInBodyREs = originalRegex
245245 }()
246-
246+
247247 // For testing, use a regex that matches any 3-letter prefix format
248248 issuesInBodyREs = []* regexp.Regexp {
249249 regexp .MustCompile (`(?P<issue>\w{3}-\d{4})` ),
250250 }
251-
251+
252252 testCases := []struct {
253253 name string
254254 body string
@@ -286,7 +286,7 @@ func TestIssueIDsExtraction(t *testing.T) {
286286 expected : []string {},
287287 },
288288 }
289-
289+
290290 for _ , tc := range testCases {
291291 t .Run (tc .name , func (t * testing.T ) {
292292 pr := LinearPullRequest {
@@ -295,15 +295,15 @@ func TestIssueIDsExtraction(t *testing.T) {
295295 HeadRefName : tc .headRefName ,
296296 },
297297 }
298-
298+
299299 result := pr .IssueIDs ()
300-
300+
301301 if len (result ) != len (tc .expected ) {
302302 t .Errorf ("Expected %d issues, got %d" , len (tc .expected ), len (result ))
303303 t .Errorf ("Expected: %v, Got: %v" , tc .expected , result )
304304 return
305305 }
306-
306+
307307 // Check all expected IDs are found (ignoring order)
308308 for _ , expectedID := range tc .expected {
309309 found := false
@@ -320,3 +320,53 @@ func TestIssueIDsExtraction(t *testing.T) {
320320 })
321321 }
322322}
323+
324+ func TestIsStableRelease (t * testing.T ) {
325+ tests := []struct {
326+ name string
327+ version string
328+ want bool
329+ }{
330+ // Stable releases - should return true
331+ {name : "Stable release v0.26.1" , version : "v0.26.1" , want : true },
332+ {name : "Stable release v4.5.0" , version : "v4.5.0" , want : true },
333+ {name : "Stable release without v prefix" , version : "1.2.3" , want : true },
334+ {name : "Stable release v0.28.0" , version : "v0.28.0" , want : true },
335+ {name : "Stable release v1.0.0" , version : "v1.0.0" , want : true },
336+
337+ // Alpha releases - should return false
338+ {name : "Alpha release v0.26.1-alpha.1" , version : "v0.26.1-alpha.1" , want : false },
339+ {name : "Alpha release v4.5.0-alpha.10" , version : "v4.5.0-alpha.10" , want : false },
340+ {name : "Alpha release without version number" , version : "v0.28.0-alpha" , want : false },
341+
342+ // RC (Release Candidate) releases - should return false
343+ {name : "RC release v0.26.1-rc.4" , version : "v0.26.1-rc.4" , want : false },
344+ {name : "RC release v0.26.1-rc.2" , version : "v0.26.1-rc.2" , want : false },
345+ {name : "RC release without patch number" , version : "v1.0.0-rc1" , want : false },
346+
347+ // Beta releases - should return false
348+ {name : "Beta release v4.5.0-beta.2" , version : "v4.5.0-beta.2" , want : false },
349+ {name : "Beta release v1.0.0-beta" , version : "v1.0.0-beta" , want : false },
350+
351+ // Dev releases - should return false
352+ {name : "Dev release v0.1.0-dev" , version : "v0.1.0-dev" , want : false },
353+ {name : "Dev release v2.0.0-dev.1" , version : "v2.0.0-dev.1" , want : false },
354+
355+ // Pre releases - should return false
356+ {name : "Pre release v1.0.0-pre" , version : "v1.0.0-pre" , want : false },
357+ {name : "Pre release v1.0.0-pre.1" , version : "v1.0.0-pre.1" , want : false },
358+
359+ // Edge cases
360+ {name : "Empty string" , version : "" , want : true }, // Empty is considered stable (no pre-release suffix)
361+ {name : "Just v" , version : "v" , want : true },
362+ }
363+
364+ for _ , tt := range tests {
365+ t .Run (tt .name , func (t * testing.T ) {
366+ got := isStableRelease (tt .version )
367+ if got != tt .want {
368+ t .Errorf ("isStableRelease(%q) = %v, want %v" , tt .version , got , tt .want )
369+ }
370+ })
371+ }
372+ }
0 commit comments