77use Hypernode \Api \Exception \HypernodeApiClientException ;
88use Hypernode \Api \HypernodeClient ;
99use Hypernode \Api \Resource \Logbook \Flow ;
10+ use Hypernode \Api \Service \BrancherApp ;
1011use Hypernode \Api \Service \Logbook ;
1112use Hypernode \Deploy \Brancher \BrancherHypernodeManager ;
1213use Hypernode \Deploy \Exception \CreateBrancherHypernodeFailedException ;
@@ -20,6 +21,7 @@ class BrancherHypernodeManagerTest extends TestCase
2021{
2122 private LoggerInterface &MockObject $ logger ;
2223 private HypernodeClient &MockObject $ hypernodeClient ;
24+ private BrancherApp &MockObject $ brancherApp ;
2325 private Logbook &MockObject $ logbook ;
2426 private TestSshPoller $ sshPoller ;
2527 private BrancherHypernodeManager $ manager ;
@@ -28,7 +30,9 @@ protected function setUp(): void
2830 {
2931 $ this ->logger = $ this ->createMock (LoggerInterface::class);
3032 $ this ->hypernodeClient = $ this ->createMock (HypernodeClient::class);
33+ $ this ->brancherApp = $ this ->createMock (BrancherApp::class);
3134 $ this ->logbook = $ this ->createMock (Logbook::class);
35+ $ this ->hypernodeClient ->brancherApp = $ this ->brancherApp ;
3236 $ this ->hypernodeClient ->logbook = $ this ->logbook ;
3337 $ this ->sshPoller = new TestSshPoller ();
3438 $ this ->sshPoller ->setMicrotime (1000.0 );
@@ -258,4 +262,88 @@ private function createFlow(string $name, string $state): Flow
258262 'state ' => $ state ,
259263 ]);
260264 }
265+
266+ public function testCancelSucceeds (): void
267+ {
268+ $ this ->brancherApp ->expects ($ this ->once ())
269+ ->method ('cancel ' )
270+ ->with ('test-brancher ' );
271+
272+ $ this ->manager ->cancel ('test-brancher ' );
273+ }
274+
275+ public function testCancelAlreadyCancelledBrancherContinues (): void
276+ {
277+ $ response = $ this ->createMock (ResponseInterface::class);
278+ $ response ->method ('getStatusCode ' )->willReturn (400 );
279+ $ response ->method ('getBody ' )->willReturn ('["Brancher app test-brancher has already been cancelled."] ' );
280+ $ exception = new HypernodeApiClientException ($ response );
281+
282+ $ this ->brancherApp ->expects ($ this ->once ())
283+ ->method ('cancel ' )
284+ ->with ('test-brancher ' )
285+ ->willThrowException ($ exception );
286+
287+ $ this ->logger ->expects ($ this ->exactly (2 ))
288+ ->method ('info ' );
289+
290+ // Should not throw
291+ $ this ->manager ->cancel ('test-brancher ' );
292+ }
293+
294+ public function testCancelNotFoundBrancherContinues (): void
295+ {
296+ $ response = $ this ->createMock (ResponseInterface::class);
297+ $ response ->method ('getStatusCode ' )->willReturn (404 );
298+ $ response ->method ('getBody ' )->willReturn ('Not Found ' );
299+ $ exception = new HypernodeApiClientException ($ response );
300+
301+ $ this ->brancherApp ->expects ($ this ->once ())
302+ ->method ('cancel ' )
303+ ->with ('test-brancher ' )
304+ ->willThrowException ($ exception );
305+
306+ $ this ->logger ->expects ($ this ->exactly (2 ))
307+ ->method ('info ' );
308+
309+ // Should not throw
310+ $ this ->manager ->cancel ('test-brancher ' );
311+ }
312+
313+ public function testCancelOtherClientErrorPropagates (): void
314+ {
315+ $ response = $ this ->createMock (ResponseInterface::class);
316+ $ response ->method ('getStatusCode ' )->willReturn (403 );
317+ $ response ->method ('getBody ' )->willReturn ('Forbidden ' );
318+ $ exception = new HypernodeApiClientException ($ response );
319+
320+ $ this ->brancherApp ->expects ($ this ->once ())
321+ ->method ('cancel ' )
322+ ->with ('test-brancher ' )
323+ ->willThrowException ($ exception );
324+
325+ $ this ->expectException (HypernodeApiClientException::class);
326+
327+ $ this ->manager ->cancel ('test-brancher ' );
328+ }
329+
330+ public function testCancelMultipleBranchersPartialFailure (): void
331+ {
332+ $ response = $ this ->createMock (ResponseInterface::class);
333+ $ response ->method ('getStatusCode ' )->willReturn (400 );
334+ $ response ->method ('getBody ' )->willReturn ('["Brancher app brancher-1 has already been cancelled."] ' );
335+ $ exception = new HypernodeApiClientException ($ response );
336+
337+ $ this ->brancherApp ->expects ($ this ->exactly (2 ))
338+ ->method ('cancel ' )
339+ ->willReturnCallback (function (string $ name ) use ($ exception ) {
340+ if ($ name === 'brancher-1 ' ) {
341+ throw $ exception ;
342+ }
343+ // brancher-2 succeeds
344+ });
345+
346+ // Should not throw, should continue to second brancher
347+ $ this ->manager ->cancel ('brancher-1 ' , 'brancher-2 ' );
348+ }
261349}
0 commit comments