1+ /*
2+ * Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+ * SPDX-License-Identifier: Apache-2.0
4+ */
5+
6+ /**
7+ * Tests for tagPrefix-based tag filtering in changelog generation
8+ */
9+
10+ import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
11+ import { getPreviousTag } from '../utils/github' ;
12+
13+ // Mock execa
14+ vi . mock ( 'execa' , ( ) => ( {
15+ execa : vi . fn ( ) ,
16+ } ) ) ;
17+
18+ import { execa } from 'execa' ;
19+
20+ describe ( 'getPreviousTag with tagPrefix filtering' , ( ) => {
21+ beforeEach ( ( ) => {
22+ vi . clearAllMocks ( ) ;
23+ } ) ;
24+
25+ afterEach ( ( ) => {
26+ vi . restoreAllMocks ( ) ;
27+ } ) ;
28+
29+ it ( 'should filter tags by tagPrefix correctly' , async ( ) => {
30+ // Mock git tag output
31+ const mockTags = [
32+ 'pdk@0.0.6-beta.1' ,
33+ 'pdk@0.0.5' ,
34+ '@agent-tars@0.3.0' ,
35+ 'v0.3.0' ,
36+ 'pdk@0.0.4' ,
37+ ] . join ( '\n' ) ;
38+
39+ vi . mocked ( execa ) . mockResolvedValue ( {
40+ stdout : mockTags ,
41+ } as any ) ;
42+
43+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
44+
45+ expect ( result ) . toBe ( 'pdk@0.0.5' ) ;
46+ expect ( execa ) . toHaveBeenCalledWith ( 'git' , [ 'tag' , '--sort=-creatordate' ] , {
47+ cwd : '/test/cwd' ,
48+ } ) ;
49+ } ) ;
50+
51+ it ( 'should return null when no tags match tagPrefix' , async ( ) => {
52+ const mockTags = [
53+ '@agent-tars@0.3.0' ,
54+ 'v0.3.0' ,
55+ 'v0.2.0' ,
56+ ] . join ( '\n' ) ;
57+
58+ vi . mocked ( execa ) . mockResolvedValue ( {
59+ stdout : mockTags ,
60+ } as any ) ;
61+
62+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
63+
64+ expect ( result ) . toBeNull ( ) ;
65+ } ) ;
66+
67+ it ( 'should handle mixed tag formats correctly' , async ( ) => {
68+ const mockTags = [
69+ 'pdk@0.0.6-beta.1' ,
70+ '@agent-tars@0.3.0' ,
71+ 'v0.3.0' ,
72+ 'pdk@0.0.5' ,
73+ '@agent-tars@0.2.9' ,
74+ 'v0.2.0' ,
75+ 'pdk@0.0.4' ,
76+ ] . join ( '\n' ) ;
77+
78+ vi . mocked ( execa ) . mockResolvedValue ( {
79+ stdout : mockTags ,
80+ } as any ) ;
81+
82+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
83+
84+ expect ( result ) . toBe ( 'pdk@0.0.5' ) ;
85+ } ) ;
86+
87+ it ( 'should filter out canary releases even with tagPrefix' , async ( ) => {
88+ const mockTags = [
89+ 'pdk@0.0.6-beta.1' ,
90+ 'pdk@0.0.5-canary-abc123' ,
91+ 'pdk@0.0.5' ,
92+ 'pdk@0.0.4-canary-def456' ,
93+ 'pdk@0.0.4' ,
94+ ] . join ( '\n' ) ;
95+
96+ vi . mocked ( execa ) . mockResolvedValue ( {
97+ stdout : mockTags ,
98+ } as any ) ;
99+
100+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
101+
102+ expect ( result ) . toBe ( 'pdk@0.0.5' ) ;
103+ } ) ;
104+
105+ it ( 'should return most recent tag when current tag not found' , async ( ) => {
106+ const mockTags = [
107+ 'pdk@0.0.5' ,
108+ 'pdk@0.0.4' ,
109+ 'pdk@0.0.3' ,
110+ ] . join ( '\n' ) ;
111+
112+ vi . mocked ( execa ) . mockResolvedValue ( {
113+ stdout : mockTags ,
114+ } as any ) ;
115+
116+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
117+
118+ expect ( result ) . toBe ( 'pdk@0.0.5' ) ;
119+ } ) ;
120+
121+ it ( 'should work without tagPrefix (backward compatibility)' , async ( ) => {
122+ const mockTags = [
123+ 'v0.3.0' ,
124+ 'v0.2.0' ,
125+ 'v0.1.0' ,
126+ ] . join ( '\n' ) ;
127+
128+ vi . mocked ( execa ) . mockResolvedValue ( {
129+ stdout : mockTags ,
130+ } as any ) ;
131+
132+ const result = await getPreviousTag ( 'v0.3.0' , '/test/cwd' ) ;
133+
134+ expect ( result ) . toBe ( 'v0.2.0' ) ;
135+ } ) ;
136+
137+ it ( 'should handle empty tag list' , async ( ) => {
138+ vi . mocked ( execa ) . mockResolvedValue ( {
139+ stdout : '' ,
140+ } as any ) ;
141+
142+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
143+
144+ expect ( result ) . toBeNull ( ) ;
145+ } ) ;
146+
147+ it ( 'should handle git command errors gracefully' , async ( ) => {
148+ vi . mocked ( execa ) . mockRejectedValue ( new Error ( 'Git command failed' ) ) ;
149+
150+ const result = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
151+
152+ expect ( result ) . toBeNull ( ) ;
153+ } ) ;
154+
155+ it ( 'should handle complex real-world scenario' , async ( ) => {
156+ // Simulate the real tag list from the repository
157+ const mockTags = [
158+ 'pdk@0.0.6-beta.1' ,
159+ 'pdk@0.0.5-canary-7d05b7ce-20251213170600' ,
160+ 'pdk@0.0.4-canary-156ae14f-20251213163615-canary-156ae14f-20251213163717' ,
161+ 'pdk@0.0.4-canary-598a6df1e-20251202172337' ,
162+ '@agent-tars@0.3.0-beta.1' ,
163+ '@agent-tars@0.3.0-beta.0' ,
164+ '@agent-tars@0.2.9' ,
165+ 'v0.3.0' ,
166+ 'v0.2.0' ,
167+ 'pdk@0.0.5' ,
168+ 'pdk@0.0.4' ,
169+ 'v0.1.0' ,
170+ ] . join ( '\n' ) ;
171+
172+ vi . mocked ( execa ) . mockResolvedValue ( {
173+ stdout : mockTags ,
174+ } as any ) ;
175+
176+ // Test case 1: Current version is 0.0.5, releasing 0.0.6-beta.1
177+ const result1 = await getPreviousTag ( 'pdk@0.0.6-beta.1' , '/test/cwd' , 'pdk@' ) ;
178+ expect ( result1 ) . toBe ( 'pdk@0.0.5' ) ;
179+
180+ // Test case 2: Current version is 0.0.6-beta.1, releasing 0.0.6-beta.2
181+ const result2 = await getPreviousTag ( 'pdk@0.0.6-beta.2' , '/test/cwd' , 'pdk@' ) ;
182+ expect ( result2 ) . toBe ( 'pdk@0.0.6-beta.1' ) ;
183+ } ) ;
184+
185+ it ( 'should handle @agent-tars@ tagPrefix correctly' , async ( ) => {
186+ const mockTags = [
187+ '@agent-tars@0.3.0-beta.1' ,
188+ '@agent-tars@0.3.0-beta.0' ,
189+ '@agent-tars@0.2.9' ,
190+ '@agent-tars@0.2.8' ,
191+ 'pdk@0.0.6-beta.1' ,
192+ 'v0.3.0' ,
193+ ] . join ( '\n' ) ;
194+
195+ vi . mocked ( execa ) . mockResolvedValue ( {
196+ stdout : mockTags ,
197+ } as any ) ;
198+
199+ const result = await getPreviousTag ( '@agent-tars@0.3.0-beta.1' , '/test/cwd' , '@agent-tars@' ) ;
200+
201+ expect ( result ) . toBe ( '@agent-tars@0.3.0-beta.0' ) ;
202+ } ) ;
203+
204+ it ( 'should handle v tagPrefix correctly' , async ( ) => {
205+ const mockTags = [
206+ 'v0.3.0' ,
207+ 'v0.2.0' ,
208+ 'v0.1.0' ,
209+ '@agent-tars@0.3.0-beta.1' ,
210+ 'pdk@0.0.6-beta.1' ,
211+ ] . join ( '\n' ) ;
212+
213+ vi . mocked ( execa ) . mockResolvedValue ( {
214+ stdout : mockTags ,
215+ } as any ) ;
216+
217+ const result = await getPreviousTag ( 'v0.3.0' , '/test/cwd' , 'v' ) ;
218+
219+ expect ( result ) . toBe ( 'v0.2.0' ) ;
220+ } ) ;
221+ } ) ;
0 commit comments