@@ -50,13 +50,189 @@ jest.mock('@actions/github', () => ({
5050 } ) ,
5151} ) ) ;
5252
53+ /** Build Labels with optional currentIssueLabels (for isHotfix, containsBranchedLabel, etc.). */
54+ function makeLabels ( overrides : { currentIssueLabels ?: string [ ] } = { } ) : Labels {
55+ const labels = new Labels (
56+ 'launch' ,
57+ 'bug' ,
58+ 'bugfix' ,
59+ 'hotfix' ,
60+ 'enhancement' ,
61+ 'feature' ,
62+ 'release' ,
63+ 'question' ,
64+ 'help' ,
65+ 'deploy' ,
66+ 'deployed' ,
67+ 'docs' ,
68+ 'documentation' ,
69+ 'chore' ,
70+ 'maintenance' ,
71+ 'priority-high' ,
72+ 'priority-medium' ,
73+ 'priority-low' ,
74+ 'priority-none' ,
75+ 'xxl' ,
76+ 'xl' ,
77+ 'l' ,
78+ 'm' ,
79+ 's' ,
80+ 'xs'
81+ ) ;
82+ if ( overrides . currentIssueLabels ) {
83+ labels . currentIssueLabels = overrides . currentIssueLabels ;
84+ }
85+ return labels ;
86+ }
87+
5388describe ( 'IssueRepository' , ( ) => {
5489 const repo = new IssueRepository ( ) ;
5590
5691 beforeEach ( ( ) => {
5792 jest . clearAllMocks ( ) ;
5893 } ) ;
5994
95+ describe ( 'updateTitleIssueFormat' , ( ) => {
96+ it ( 'updates title with emoji when hotfix and branched' , async ( ) => {
97+ const labels = makeLabels ( { currentIssueLabels : [ 'hotfix' , 'launch' ] } ) ;
98+ mockRest . issues . update . mockResolvedValue ( undefined ) ;
99+ const result = await repo . updateTitleIssueFormat (
100+ 'o' ,
101+ 'r' ,
102+ '' ,
103+ 'Fix login' ,
104+ 1 ,
105+ false ,
106+ 'x' ,
107+ labels ,
108+ 'token'
109+ ) ;
110+ expect ( result ) . toBe ( '🔥x - Fix login' ) ;
111+ expect ( mockRest . issues . update ) . toHaveBeenCalledWith ( {
112+ owner : 'o' ,
113+ repo : 'r' ,
114+ issue_number : 1 ,
115+ title : '🔥x - Fix login' ,
116+ } ) ;
117+ } ) ;
118+
119+ it ( 'returns undefined when formatted title equals current title' , async ( ) => {
120+ const labels = makeLabels ( ) ;
121+ const result = await repo . updateTitleIssueFormat (
122+ 'o' ,
123+ 'r' ,
124+ '' ,
125+ '🤖 - Clean title' ,
126+ 1 ,
127+ false ,
128+ 'x' ,
129+ labels ,
130+ 'token'
131+ ) ;
132+ expect ( result ) . toBeUndefined ( ) ;
133+ expect ( mockRest . issues . update ) . not . toHaveBeenCalled ( ) ;
134+ } ) ;
135+
136+ it ( 'includes version in title when version length > 0' , async ( ) => {
137+ const labels = makeLabels ( { currentIssueLabels : [ 'feature' , 'launch' ] } ) ;
138+ mockRest . issues . update . mockResolvedValue ( undefined ) ;
139+ const result = await repo . updateTitleIssueFormat (
140+ 'o' ,
141+ 'r' ,
142+ 'v1.0' ,
143+ 'Add API' ,
144+ 2 ,
145+ true ,
146+ 'y' ,
147+ labels ,
148+ 'token'
149+ ) ;
150+ expect ( result ) . toContain ( 'v1.0' ) ;
151+ expect ( result ) . toContain ( 'Add API' ) ;
152+ } ) ;
153+
154+ it ( 'returns undefined and setFailed when update throws' , async ( ) => {
155+ const labels = makeLabels ( { currentIssueLabels : [ 'bug' ] } ) ;
156+ mockRest . issues . update . mockRejectedValue ( new Error ( 'API error' ) ) ;
157+ const result = await repo . updateTitleIssueFormat (
158+ 'o' ,
159+ 'r' ,
160+ '' ,
161+ 'Broken' ,
162+ 1 ,
163+ false ,
164+ 'x' ,
165+ labels ,
166+ 'token'
167+ ) ;
168+ expect ( result ) . toBeUndefined ( ) ;
169+ expect ( mockSetFailed ) . toHaveBeenCalled ( ) ;
170+ } ) ;
171+ } ) ;
172+
173+ describe ( 'updateTitlePullRequestFormat' , ( ) => {
174+ it ( 'updates PR title with [#N] and emoji when title differs' , async ( ) => {
175+ const labels = makeLabels ( { currentIssueLabels : [ 'feature' ] } ) ;
176+ mockRest . issues . update . mockResolvedValue ( undefined ) ;
177+ const result = await repo . updateTitlePullRequestFormat (
178+ 'o' ,
179+ 'r' ,
180+ 'Old PR title' ,
181+ 'Add feature' ,
182+ 42 ,
183+ 10 ,
184+ false ,
185+ 'x' ,
186+ labels ,
187+ 'token'
188+ ) ;
189+ expect ( result ) . toBe ( '[#42] ✨ - Add feature' ) ;
190+ expect ( mockRest . issues . update ) . toHaveBeenCalledWith ( {
191+ owner : 'o' ,
192+ repo : 'r' ,
193+ issue_number : 10 ,
194+ title : '[#42] ✨ - Add feature' ,
195+ } ) ;
196+ } ) ;
197+
198+ it ( 'returns undefined when formatted title equals pullRequestTitle' , async ( ) => {
199+ const labels = makeLabels ( ) ;
200+ const result = await repo . updateTitlePullRequestFormat (
201+ 'o' ,
202+ 'r' ,
203+ '[#1] 🤖 - Same' ,
204+ 'Same' ,
205+ 1 ,
206+ 5 ,
207+ false ,
208+ 'x' ,
209+ labels ,
210+ 'token'
211+ ) ;
212+ expect ( result ) . toBeUndefined ( ) ;
213+ expect ( mockRest . issues . update ) . not . toHaveBeenCalled ( ) ;
214+ } ) ;
215+
216+ it ( 'returns undefined and setFailed when update throws' , async ( ) => {
217+ const labels = makeLabels ( { currentIssueLabels : [ 'hotfix' ] } ) ;
218+ mockRest . issues . update . mockRejectedValue ( new Error ( 'API error' ) ) ;
219+ const result = await repo . updateTitlePullRequestFormat (
220+ 'o' ,
221+ 'r' ,
222+ 'PR' ,
223+ 'Fix' ,
224+ 1 ,
225+ 1 ,
226+ false ,
227+ 'x' ,
228+ labels ,
229+ 'token'
230+ ) ;
231+ expect ( result ) . toBeUndefined ( ) ;
232+ expect ( mockSetFailed ) . toHaveBeenCalled ( ) ;
233+ } ) ;
234+ } ) ;
235+
60236 describe ( 'getDescription' , ( ) => {
61237 it ( 'returns undefined when issueNumber is -1' , async ( ) => {
62238 const result = await repo . getDescription ( 'o' , 'r' , - 1 , 'token' ) ;
@@ -531,6 +707,68 @@ describe('IssueRepository', () => {
531707 } ) ;
532708 } ) ;
533709
710+ describe ( 'setIssueType' , ( ) => {
711+ const issueTypes = new IssueTypes (
712+ 'Task' , 'Task desc' , 'BLUE' ,
713+ 'Bug' , 'Bug desc' , 'RED' ,
714+ 'Feature' , 'Feature desc' , 'GREEN' ,
715+ 'Docs' , 'Docs desc' , 'GREY' ,
716+ 'Maintenance' , 'Maint desc' , 'GREY' ,
717+ 'Hotfix' , 'Hotfix desc' , 'RED' ,
718+ 'Release' , 'Release desc' , 'BLUE' ,
719+ 'Question' , 'Q desc' , 'PURPLE' ,
720+ 'Help' , 'Help desc' , 'PURPLE'
721+ ) ;
722+
723+ it ( 'sets issue type when type exists in organization' , async ( ) => {
724+ const labels = makeLabels ( { currentIssueLabels : [ 'bug' ] } ) ;
725+ mockGraphql
726+ . mockResolvedValueOnce ( { repository : { issue : { id : 'I_1' } } } )
727+ . mockResolvedValueOnce ( {
728+ organization : {
729+ id : 'O_1' ,
730+ issueTypes : { nodes : [ { id : 'T_BUG' , name : 'Bug' } ] } ,
731+ } ,
732+ } )
733+ . mockResolvedValueOnce ( { updateIssueIssueType : { issue : { id : 'I_1' , issueType : { id : 'T_BUG' , name : 'Bug' } } } } ) ;
734+ await repo . setIssueType ( 'org' , 'repo' , 1 , labels , issueTypes , 'token' ) ;
735+ expect ( mockGraphql ) . toHaveBeenCalledTimes ( 3 ) ;
736+ } ) ;
737+
738+ it ( 'creates issue type when not found then updates issue' , async ( ) => {
739+ const labels = makeLabels ( { currentIssueLabels : [ 'hotfix' ] } ) ;
740+ mockGraphql
741+ . mockResolvedValueOnce ( { repository : { issue : { id : 'I_1' } } } )
742+ . mockResolvedValueOnce ( {
743+ organization : { id : 'O_1' , issueTypes : { nodes : [ ] } } ,
744+ } )
745+ . mockResolvedValueOnce ( { createIssueType : { issueType : { id : 'T_NEW' } } } )
746+ . mockResolvedValueOnce ( { updateIssueIssueType : { issue : { id : 'I_1' } } } ) ;
747+ await repo . setIssueType ( 'org' , 'repo' , 1 , labels , issueTypes , 'token' ) ;
748+ expect ( mockGraphql ) . toHaveBeenCalledTimes ( 4 ) ;
749+ } ) ;
750+
751+ it ( 'returns early when createIssueType throws' , async ( ) => {
752+ const labels = makeLabels ( { currentIssueLabels : [ 'release' ] } ) ;
753+ mockGraphql
754+ . mockResolvedValueOnce ( { repository : { issue : { id : 'I_1' } } } )
755+ . mockResolvedValueOnce ( {
756+ organization : { id : 'O_1' , issueTypes : { nodes : [ ] } } ,
757+ } )
758+ . mockRejectedValueOnce ( new Error ( 'Create failed' ) ) ;
759+ await repo . setIssueType ( 'org' , 'repo' , 1 , labels , issueTypes , 'token' ) ;
760+ expect ( mockGraphql ) . toHaveBeenCalledTimes ( 3 ) ;
761+ } ) ;
762+
763+ it ( 'throws when getId or organization query fails' , async ( ) => {
764+ const labels = makeLabels ( { currentIssueLabels : [ 'feature' ] } ) ;
765+ mockGraphql . mockRejectedValue ( new Error ( 'GraphQL error' ) ) ;
766+ await expect (
767+ repo . setIssueType ( 'org' , 'repo' , 1 , labels , issueTypes , 'token' )
768+ ) . rejects . toThrow ( 'GraphQL error' ) ;
769+ } ) ;
770+ } ) ;
771+
534772 describe ( 'ensureLabels' , ( ) => {
535773 it ( 'ensures all required labels and returns counts' , async ( ) => {
536774 const labels = new Labels (
@@ -566,6 +804,20 @@ describe('IssueRepository', () => {
566804 expect ( result . errors ) . toEqual ( [ ] ) ;
567805 expect ( result . created ) . toBeGreaterThan ( 0 ) ;
568806 } ) ;
807+
808+ it ( 'collects errors when one ensureLabel throws' , async ( ) => {
809+ const labels = makeLabels ( ) ;
810+ mockRest . issues . listLabelsForRepo . mockResolvedValue ( { data : [ ] } ) ;
811+ let callCount = 0 ;
812+ mockRest . issues . createLabel . mockImplementation ( ( ) => {
813+ callCount ++ ;
814+ if ( callCount === 2 ) return Promise . reject ( new Error ( 'Label exists' ) ) ;
815+ return Promise . resolve ( undefined ) ;
816+ } ) ;
817+ const result = await repo . ensureLabels ( 'o' , 'r' , labels , 'token' ) ;
818+ expect ( result . errors . length ) . toBeGreaterThan ( 0 ) ;
819+ expect ( result . errors . some ( ( e ) => e . includes ( 'Label exists' ) ) ) . toBe ( true ) ;
820+ } ) ;
569821 } ) ;
570822
571823 describe ( 'listIssueTypes' , ( ) => {
@@ -662,6 +914,35 @@ describe('IssueRepository', () => {
662914 expect ( result . created ) . toBe ( 0 ) ;
663915 expect ( result . errors ) . toEqual ( [ ] ) ;
664916 } ) ;
917+
918+ it ( 'collects errors when one ensureIssueType throws' , async ( ) => {
919+ const issueTypesForTest = new IssueTypes (
920+ 'Task' , 'Task desc' , 'BLUE' ,
921+ 'Bug' , 'Bug desc' , 'RED' ,
922+ 'Feature' , 'Feature desc' , 'GREEN' ,
923+ 'Docs' , 'Docs desc' , 'GREY' ,
924+ 'Maintenance' , 'Maint desc' , 'GREY' ,
925+ 'Hotfix' , 'Hotfix desc' , 'RED' ,
926+ 'Release' , 'Release desc' , 'BLUE' ,
927+ 'Question' , 'Q desc' , 'PURPLE' ,
928+ 'Help' , 'Help desc' , 'PURPLE'
929+ ) ;
930+ let callCount = 0 ;
931+ mockGraphql . mockImplementation ( ( ) => {
932+ callCount ++ ;
933+ if ( callCount === 1 ) {
934+ return Promise . resolve ( {
935+ organization : { id : 'O_1' , issueTypes : { nodes : [ { id : 'T1' , name : 'Task' } ] } } ,
936+ } ) ;
937+ }
938+ if ( callCount <= 3 ) {
939+ return Promise . resolve ( { organization : { id : 'O_1' , issueTypes : { nodes : [ ] } } } ) ;
940+ }
941+ return Promise . reject ( new Error ( 'Create failed' ) ) ;
942+ } ) ;
943+ const result = await repo . ensureIssueTypes ( 'org' , issueTypesForTest , 'token' ) ;
944+ expect ( result . errors . length ) . toBeGreaterThan ( 0 ) ;
945+ } ) ;
665946 } ) ;
666947} ) ;
667948
0 commit comments