Skip to content

Commit dee7a89

Browse files
committed
test(coverage) More work on code coverage
1 parent e8ba1ca commit dee7a89

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

tests/TestCase/Command/ServerCommandTest.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
77
use Cake\Core\Configure;
88
use Cake\TestSuite\TestCase;
9+
use ReflectionMethod;
10+
use Synapse\Command\ServerCommand;
11+
use TestApp\Application;
912

1013
/**
1114
* ServerCommand Test Case
@@ -308,4 +311,86 @@ public function testInspectOptionWithOtherOptions(): void
308311
$this->exec('synapse server -i -v -n -h');
309312
$this->assertExitSuccess();
310313
}
314+
315+
// =========================================================================
316+
// findExecutable Tests (via reflection)
317+
// =========================================================================
318+
319+
/**
320+
* Test findExecutable finds php binary
321+
*/
322+
public function testFindExecutableFindsPhp(): void
323+
{
324+
$command = $this->createServerCommand();
325+
$method = new ReflectionMethod(ServerCommand::class, 'findExecutable');
326+
327+
$result = $method->invoke($command, 'php');
328+
329+
$this->assertNotNull($result);
330+
$this->assertFileExists($result);
331+
$this->assertTrue(is_executable($result));
332+
}
333+
334+
/**
335+
* Test findExecutable returns null for nonexistent executable
336+
*/
337+
public function testFindExecutableReturnsNullForNonexistent(): void
338+
{
339+
$command = $this->createServerCommand();
340+
$method = new ReflectionMethod(ServerCommand::class, 'findExecutable');
341+
342+
$result = $method->invoke($command, 'definitely_not_a_real_executable_12345');
343+
344+
$this->assertNull($result);
345+
}
346+
347+
/**
348+
* Test findExecutable finds common executables
349+
*/
350+
public function testFindExecutableFindsCommonExecutables(): void
351+
{
352+
$command = $this->createServerCommand();
353+
$method = new ReflectionMethod(ServerCommand::class, 'findExecutable');
354+
355+
// Test with 'which' or 'ls' - common on Unix systems
356+
$result = $method->invoke($command, 'ls');
357+
358+
if ($result !== null) {
359+
$this->assertFileExists($result);
360+
$this->assertTrue(is_executable($result));
361+
}
362+
363+
// If null, the test passes - executable simply wasn't found
364+
}
365+
366+
/**
367+
* Test defaultName returns correct command name
368+
*/
369+
public function testDefaultName(): void
370+
{
371+
$this->assertEquals('synapse server', ServerCommand::defaultName());
372+
}
373+
374+
/**
375+
* Test getDescription returns appropriate description
376+
*/
377+
public function testGetDescription(): void
378+
{
379+
$description = ServerCommand::getDescription();
380+
381+
$this->assertStringContainsString('MCP', $description);
382+
$this->assertStringContainsString('server', $description);
383+
}
384+
385+
/**
386+
* Create a ServerCommand instance for testing
387+
*/
388+
private function createServerCommand(): ServerCommand
389+
{
390+
// Get the container from the application
391+
$app = new Application(CONFIG);
392+
$container = $app->getContainer();
393+
394+
return new ServerCommand($container);
395+
}
311396
}

tests/TestCase/Tools/TinkerToolsTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,103 @@ public function testDefaultTimeoutIsThirtySeconds(): void
433433
$this->assertTrue($result['success']);
434434
$this->assertEquals('ok', $result['result']);
435435
}
436+
437+
// =========================================================================
438+
// Error Handling and Edge Cases
439+
// =========================================================================
440+
441+
/**
442+
* Test execution timeout triggers error response
443+
*/
444+
public function testExecuteTimeout(): void
445+
{
446+
// Use a very short timeout with code that sleeps
447+
$result = $this->tinkerTools->execute('sleep(5); return "done";', 1);
448+
449+
$this->assertFalse($result['success']);
450+
$this->assertStringContainsString('timed out', $result['error']);
451+
$this->assertEquals('RuntimeException', $result['type']);
452+
}
453+
454+
/**
455+
* Test empty stdout from subprocess returns error with exit code
456+
*/
457+
public function testEmptyStdoutReturnsError(): void
458+
{
459+
// Code that exits without output
460+
$result = $this->tinkerTools->execute('exit(0);');
461+
462+
$this->assertFalse($result['success']);
463+
$this->assertArrayHasKey('error', $result);
464+
$this->assertArrayHasKey('exit_code', $result);
465+
}
466+
467+
/**
468+
* Test null PHP binary returns error
469+
*/
470+
public function testNullPhpBinaryReturnsError(): void
471+
{
472+
// Use reflection to set phpBinary to a value that will make getPhpBinary return null
473+
$tinkerTools = new TinkerTools();
474+
475+
// Mock by setting an invalid configured path and clearing cached value
476+
$originalConfig = Configure::read('Synapse.tinker.php_binary');
477+
Configure::write('Synapse.tinker.php_binary', '/definitely/not/a/real/path');
478+
479+
// Create fresh instance - the config path isn't executable so it will try other methods
480+
// We need to test the case where getPhpBinary returns null
481+
// This is hard to achieve without modifying system, so test the error message path instead
482+
$tinkerTools->setPhpBinary('/nonexistent/php/binary');
483+
$result = $tinkerTools->execute('return 1;');
484+
485+
$this->assertFalse($result['success']);
486+
$this->assertArrayHasKey('error', $result);
487+
488+
Configure::write('Synapse.tinker.php_binary', $originalConfig);
489+
}
490+
491+
/**
492+
* Test getPhpBinary with non-executable configured path falls back
493+
*/
494+
public function testGetPhpBinaryFallsBackWhenConfiguredPathNotExecutable(): void
495+
{
496+
$originalConfig = Configure::read('Synapse.tinker.php_binary');
497+
498+
// Set a path that exists but isn't executable (or doesn't exist)
499+
Configure::write('Synapse.tinker.php_binary', '/tmp/not-executable-file');
500+
501+
$tinkerTools = new TinkerTools();
502+
$phpBinary = $tinkerTools->getPhpBinary();
503+
504+
// Should fall back to which php or PHP_BINARY
505+
$this->assertNotEquals('/tmp/not-executable-file', $phpBinary);
506+
$this->assertNotNull($phpBinary);
507+
508+
Configure::write('Synapse.tinker.php_binary', $originalConfig);
509+
}
510+
511+
/**
512+
* Test getBinPath uses ROOT constant when available
513+
*/
514+
public function testGetBinPathUsesRootConstant(): void
515+
{
516+
$tinkerTools = new TinkerTools();
517+
$binPath = $tinkerTools->getBinPath();
518+
519+
// ROOT is defined in test environment
520+
$this->assertEquals(ROOT . '/bin', $binPath);
521+
}
522+
523+
/**
524+
* Test process that outputs to stderr
525+
*/
526+
public function testProcessWithStderrOutput(): void
527+
{
528+
// Trigger a PHP warning/notice that goes to stderr
529+
$result = $this->tinkerTools->execute('trigger_error("test warning", E_USER_WARNING); return "ok";');
530+
531+
// Should still succeed, warnings don't stop execution
532+
$this->assertTrue($result['success']);
533+
$this->assertEquals('ok', $result['result']);
534+
}
436535
}

0 commit comments

Comments
 (0)