@@ -10,6 +10,7 @@ import (
1010 "github.com/compozy/releasepr/internal/config"
1111 "github.com/compozy/releasepr/internal/domain"
1212 "github.com/compozy/releasepr/internal/logger"
13+ "github.com/compozy/releasepr/internal/usecase"
1314 "github.com/spf13/afero"
1415 "github.com/stretchr/testify/assert"
1516 "github.com/stretchr/testify/mock"
@@ -208,6 +209,96 @@ func TestPRReleaseOrchestrator_releaseArtifactCommands(t *testing.T) {
208209 require .NoError (t , existsErr )
209210 assert .False (t , exists )
210211 })
212+ t .Run ("Should restore archived release notes from newly produced rollback data" , func (t * testing.T ) {
213+ ctx := testReleaseContext (t )
214+ fsRepo := afero .NewMemMapFs ()
215+ activePath := ".release-notes/a.md"
216+ archivedPath := ".release-notes/archive/v1.2.3/a.md"
217+ require .NoError (t , fsRepo .MkdirAll (".release-notes/archive/v1.2.3" , 0755 ))
218+ require .NoError (t , afero .WriteFile (fsRepo , archivedPath , []byte ("note" ), 0644 ))
219+ require .NoError (t , afero .WriteFile (fsRepo , ReleaseNotesGitKeepPath , nil , 0644 ))
220+ gitRepo := new (mockGitExtendedRepository )
221+ gitRepo .On ("MoveFile" , mock .Anything , archivedPath , activePath ).
222+ Run (func (mock.Arguments ) {
223+ require .NoError (t , fsRepo .Rename (archivedPath , activePath ))
224+ }).
225+ Return (nil ).
226+ Once ()
227+ compensator := NewCompensatingActions (gitRepo , new (mockGithubExtendedRepository ), fsRepo )
228+ rollbackData := usecase.ArchiveReleaseNotesResult {
229+ Moves : []usecase.ArchivedReleaseNoteMove {
230+ {
231+ From : activePath ,
232+ To : archivedPath ,
233+ },
234+ },
235+ GitKeepCreated : true ,
236+ }.ToRollbackData ()
237+
238+ err := compensator .RestoreArchivedReleaseNotes (ctx , rollbackData )
239+
240+ require .NoError (t , err )
241+ activeExists , activeErr := afero .Exists (fsRepo , activePath )
242+ require .NoError (t , activeErr )
243+ assert .True (t , activeExists )
244+ archivedExists , archivedErr := afero .Exists (fsRepo , archivedPath )
245+ require .NoError (t , archivedErr )
246+ assert .False (t , archivedExists )
247+ gitKeepExists , gitKeepErr := afero .Exists (fsRepo , ReleaseNotesGitKeepPath )
248+ require .NoError (t , gitKeepErr )
249+ assert .False (t , gitKeepExists )
250+ gitRepo .AssertExpectations (t )
251+ })
252+ }
253+
254+ func TestPRReleaseOrchestrator_createPullRequest (t * testing.T ) {
255+ t .Run ("Should stop after one permanent GitHub client error" , func (t * testing.T ) {
256+ t .Parallel ()
257+ githubRepo := new (mockGithubExtendedRepository )
258+ githubRepo .On (
259+ "CreateOrUpdatePR" ,
260+ mock .Anything ,
261+ "release/v1.0.0" ,
262+ "main" ,
263+ "build: release v1.0.0" ,
264+ mock .Anything ,
265+ mock .Anything ,
266+ ).Return (githubStatusError (422 )).Once ()
267+ orchestrator := & PRReleaseOrchestrator {githubRepo : githubRepo }
268+
269+ err := orchestrator .createPullRequest (t .Context (), "v1.0.0" , "changelog" , "" , "release/v1.0.0" )
270+
271+ require .Error (t , err )
272+ githubRepo .AssertExpectations (t )
273+ })
274+ t .Run ("Should retry a transient GitHub server error" , func (t * testing.T ) {
275+ t .Parallel ()
276+ githubRepo := new (mockGithubExtendedRepository )
277+ githubRepo .On (
278+ "CreateOrUpdatePR" ,
279+ mock .Anything ,
280+ "release/v1.0.0" ,
281+ "main" ,
282+ "build: release v1.0.0" ,
283+ mock .Anything ,
284+ mock .Anything ,
285+ ).Return (githubStatusError (502 )).Once ()
286+ githubRepo .On (
287+ "CreateOrUpdatePR" ,
288+ mock .Anything ,
289+ "release/v1.0.0" ,
290+ "main" ,
291+ "build: release v1.0.0" ,
292+ mock .Anything ,
293+ mock .Anything ,
294+ ).Return (nil ).Once ()
295+ orchestrator := & PRReleaseOrchestrator {githubRepo : githubRepo }
296+
297+ err := orchestrator .createPullRequest (t .Context (), "v1.0.0" , "changelog" , "" , "release/v1.0.0" )
298+
299+ require .NoError (t , err )
300+ githubRepo .AssertExpectations (t )
301+ })
211302}
212303
213304func TestPRReleaseOrchestrator_ExecuteReleaseArtifacts (t * testing.T ) {
@@ -671,10 +762,8 @@ func TestPRReleaseOrchestrator_Execute(t *testing.T) {
671762 gitRepo .On ("Commit" , mock .Anything , mock .Anything ).Return (nil ).Once ()
672763 gitRepo .On ("PushBranch" , mock .Anything , branchName ).Return (nil ).Once ()
673764
674- // Fail on PR creation (use mock.Anything for context)
675- // Note: The retry might not be happening for non-retryable errors
676765 githubRepo .On ("CreateOrUpdatePR" , mock .Anything , mock .Anything , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).
677- Return (errors . New ( "GitHub API error" )).
766+ Return (githubStatusError ( 422 )).
678767 Once ()
679768
680769 orch := NewPRReleaseOrchestrator (gitRepo , githubRepo , fsRepo , cliffSvc , npmSvc )
@@ -1086,8 +1175,10 @@ func TestPRReleaseOrchestrator_RollbackOnFailure(t *testing.T) {
10861175 // For file status checks during rollback
10871176 gitRepo .On ("ListLocalBranches" , mock .Anything ).
10881177 Return ([]string {"main" , branchName }, nil ).
1178+ Once ()
1179+ gitRepo .On ("ListLocalBranches" , mock .Anything ).
1180+ Return ([]string {"main" }, nil ).
10891181 Maybe ()
1090- // Check if branch exists
10911182 gitRepo .On ("RemoteBranchExists" , mock .Anything , branchName ).
10921183 Return (true , nil ).
10931184 Maybe ()
0 commit comments