PageRenderTime 50ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/TestCase/Console/ShellTest.php

http://github.com/cakephp/cakephp
PHP | 1323 lines | 937 code | 181 blank | 205 comment | 3 complexity | e861d2bacf81500324b1bb79b981e772 MD5 | raw file
Possible License(s): JSON
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP Project
  13. * @since 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Console;
  17. use Cake\Console\ConsoleIo;
  18. use Cake\Console\ConsoleOptionParser;
  19. use Cake\Console\Exception\StopException;
  20. use Cake\Console\Shell;
  21. use Cake\Filesystem\Filesystem;
  22. use Cake\TestSuite\TestCase;
  23. use RuntimeException;
  24. use TestApp\Shell\MergeShell;
  25. use TestApp\Shell\ShellTestShell;
  26. use TestApp\Shell\Task\TestAppleTask;
  27. use TestApp\Shell\Task\TestBananaTask;
  28. use TestApp\Shell\TestingDispatchShell;
  29. use TestPlugin\Model\Table\TestPluginCommentsTable;
  30. class_alias(TestAppleTask::class, 'Cake\Shell\Task\TestAppleTask');
  31. class_alias(TestBananaTask::class, 'Cake\Shell\Task\TestBananaTask');
  32. /**
  33. * ShellTest class
  34. */
  35. class ShellTest extends TestCase
  36. {
  37. /**
  38. * Fixtures used in this test case
  39. *
  40. * @var array<string>
  41. */
  42. protected $fixtures = [
  43. 'core.Articles',
  44. 'core.Tags',
  45. 'core.ArticlesTags',
  46. 'core.Attachments',
  47. 'core.Comments',
  48. 'core.Posts',
  49. 'core.Users',
  50. ];
  51. /**
  52. * @var \Cake\Console\Shell
  53. */
  54. protected $Shell;
  55. /**
  56. * @var \Cake\Console\ConsoleIo|\PHPUnit\Framework\MockObject\MockObject
  57. */
  58. protected $io;
  59. /**
  60. * @var \Cake\Filesystem\Filesystem
  61. */
  62. protected $fs;
  63. /**
  64. * setUp method
  65. */
  66. public function setUp(): void
  67. {
  68. parent::setUp();
  69. $this->io = $this->getMockBuilder('Cake\Console\ConsoleIo')
  70. ->disableOriginalConstructor()
  71. ->getMock();
  72. $this->Shell = new ShellTestShell($this->io, $this->getTableLocator());
  73. $this->fs = new Filesystem();
  74. if (is_dir(TMP . 'shell_test')) {
  75. $this->fs->deleteDir(TMP . 'shell_test');
  76. }
  77. }
  78. /**
  79. * testConstruct method
  80. */
  81. public function testConstruct(): void
  82. {
  83. $this->assertSame('ShellTestShell', $this->Shell->name);
  84. $this->assertInstanceOf(ConsoleIo::class, $this->Shell->getIo());
  85. }
  86. /**
  87. * testInitialize method
  88. */
  89. public function testInitialize(): void
  90. {
  91. static::setAppNamespace();
  92. $this->loadPlugins(['TestPlugin']);
  93. $this->Shell->tasks = ['Sample' => ['one', 'two']];
  94. $this->Shell->plugin = 'TestPlugin';
  95. $this->Shell->initialize();
  96. // TestApp\Shell\ShellTestShell has $modelClass property set to 'TestPlugin.TestPluginComments'
  97. $this->Shell->loadModel();
  98. $this->assertTrue(isset($this->Shell->TestPluginComments));
  99. $this->assertInstanceOf(
  100. TestPluginCommentsTable::class,
  101. $this->Shell->TestPluginComments
  102. );
  103. $this->clearPlugins();
  104. }
  105. /**
  106. * test LoadModel method
  107. */
  108. public function testLoadModel(): void
  109. {
  110. static::setAppNamespace();
  111. $Shell = new MergeShell();
  112. $this->assertInstanceOf(
  113. 'TestApp\Model\Table\ArticlesTable',
  114. $Shell->Articles
  115. );
  116. $this->assertSame('Articles', $Shell->modelClass);
  117. $this->loadPlugins(['TestPlugin']);
  118. $result = $this->Shell->loadModel('TestPlugin.TestPluginComments');
  119. $this->assertInstanceOf(
  120. TestPluginCommentsTable::class,
  121. $result
  122. );
  123. $this->assertInstanceOf(
  124. TestPluginCommentsTable::class,
  125. $this->Shell->TestPluginComments
  126. );
  127. $this->clearPlugins();
  128. }
  129. /**
  130. * testIn method
  131. */
  132. public function testIn(): void
  133. {
  134. $this->io->expects($this->once())
  135. ->method('askChoice')
  136. ->with('Just a test?', ['y', 'n'], 'n')
  137. ->will($this->returnValue('n'));
  138. $this->io->expects($this->once())
  139. ->method('ask')
  140. ->with('Just a test?', 'n')
  141. ->will($this->returnValue('n'));
  142. $result = $this->Shell->in('Just a test?', ['y', 'n'], 'n');
  143. $this->assertSame('n', $result);
  144. $result = $this->Shell->in('Just a test?', null, 'n');
  145. $this->assertSame('n', $result);
  146. }
  147. /**
  148. * Test in() when not interactive.
  149. */
  150. public function testInNonInteractive(): void
  151. {
  152. $this->io->expects($this->never())
  153. ->method('askChoice');
  154. $this->io->expects($this->never())
  155. ->method('ask');
  156. $this->Shell->interactive = false;
  157. $result = $this->Shell->in('Just a test?', 'y/n', 'n');
  158. $this->assertSame('n', $result);
  159. }
  160. /**
  161. * testVerbose method
  162. */
  163. public function testVerbose(): void
  164. {
  165. $this->io->expects($this->once())
  166. ->method('verbose')
  167. ->with('Just a test', 1);
  168. $this->Shell->verbose('Just a test');
  169. }
  170. /**
  171. * testQuiet method
  172. */
  173. public function testQuiet(): void
  174. {
  175. $this->io->expects($this->once())
  176. ->method('quiet')
  177. ->with('Just a test', 1);
  178. $this->Shell->quiet('Just a test');
  179. }
  180. /**
  181. * testOut method
  182. */
  183. public function testOut(): void
  184. {
  185. $this->io->expects($this->once())
  186. ->method('out')
  187. ->with('Just a test', 1);
  188. $this->Shell->out('Just a test');
  189. }
  190. /**
  191. * testErr method
  192. */
  193. public function testErr(): void
  194. {
  195. $this->io->expects($this->once())
  196. ->method('error')
  197. ->with('Just a test', 1);
  198. $this->Shell->err('Just a test');
  199. }
  200. /**
  201. * testErr method with array
  202. */
  203. public function testErrArray(): void
  204. {
  205. $this->io->expects($this->once())
  206. ->method('error')
  207. ->with(['Just', 'a', 'test'], 1);
  208. $this->Shell->err(['Just', 'a', 'test']);
  209. }
  210. /**
  211. * testInfo method
  212. */
  213. public function testInfo(): void
  214. {
  215. $this->io->expects($this->once())
  216. ->method('info')
  217. ->with('Just a test', 1);
  218. $this->Shell->info('Just a test');
  219. }
  220. /**
  221. * testInfo method with array
  222. */
  223. public function testInfoArray(): void
  224. {
  225. $this->io->expects($this->once())
  226. ->method('info')
  227. ->with(['Just', 'a', 'test'], 1);
  228. $this->Shell->info(['Just', 'a', 'test']);
  229. }
  230. /**
  231. * testWarn method
  232. */
  233. public function testWarn(): void
  234. {
  235. $this->io->expects($this->once())
  236. ->method('warning')
  237. ->with('Just a test', 1);
  238. $this->Shell->warn('Just a test');
  239. }
  240. /**
  241. * testWarn method with array
  242. */
  243. public function testWarnArray(): void
  244. {
  245. $this->io->expects($this->once())
  246. ->method('warning')
  247. ->with(['Just', 'a', 'test'], 1);
  248. $this->Shell->warn(['Just', 'a', 'test']);
  249. }
  250. /**
  251. * testSuccess method
  252. */
  253. public function testSuccess(): void
  254. {
  255. $this->io->expects($this->once())
  256. ->method('success')
  257. ->with('Just a test', 1);
  258. $this->Shell->success('Just a test');
  259. }
  260. /**
  261. * testSuccess method with array
  262. */
  263. public function testSuccessArray(): void
  264. {
  265. $this->io->expects($this->once())
  266. ->method('success')
  267. ->with(['Just', 'a', 'test'], 1);
  268. $this->Shell->success(['Just', 'a', 'test']);
  269. }
  270. /**
  271. * testNl
  272. */
  273. public function testNl(): void
  274. {
  275. $this->io->expects($this->once())
  276. ->method('nl')
  277. ->with(2);
  278. $this->Shell->nl(2);
  279. }
  280. /**
  281. * testHr
  282. */
  283. public function testHr(): void
  284. {
  285. $this->io->expects($this->once())
  286. ->method('hr')
  287. ->with(2);
  288. $this->Shell->hr(2);
  289. }
  290. /**
  291. * testAbort
  292. */
  293. public function testAbort(): void
  294. {
  295. $this->expectException(StopException::class);
  296. $this->expectExceptionMessage('Foo Not Found');
  297. $this->expectExceptionCode(1);
  298. $this->io->expects($this->once())
  299. ->method('err')
  300. ->with('<error>Foo Not Found</error>');
  301. $this->Shell->abort('Foo Not Found');
  302. }
  303. /**
  304. * testLoadTasks method
  305. */
  306. public function testLoadTasks(): void
  307. {
  308. $this->assertTrue($this->Shell->loadTasks());
  309. $this->Shell->tasks = null;
  310. $this->assertTrue($this->Shell->loadTasks());
  311. $this->Shell->tasks = false;
  312. $this->assertTrue($this->Shell->loadTasks());
  313. $this->Shell->tasks = true;
  314. $this->assertTrue($this->Shell->loadTasks());
  315. $this->Shell->tasks = [];
  316. $this->assertTrue($this->Shell->loadTasks());
  317. $this->Shell->tasks = ['TestApple'];
  318. $this->assertTrue($this->Shell->loadTasks());
  319. $this->assertInstanceOf('Cake\Shell\Task\TestAppleTask', $this->Shell->TestApple);
  320. $this->Shell->tasks = ['TestBanana'];
  321. $this->assertTrue($this->Shell->loadTasks());
  322. $this->assertInstanceOf('Cake\Shell\Task\TestAppleTask', $this->Shell->TestApple);
  323. $this->assertInstanceOf('Cake\Shell\Task\TestBananaTask', $this->Shell->TestBanana);
  324. unset($this->Shell->ShellTestApple, $this->Shell->TestBanana);
  325. $this->Shell->tasks = ['TestApple', 'TestBanana'];
  326. $this->assertTrue($this->Shell->loadTasks());
  327. $this->assertInstanceOf('Cake\Shell\Task\TestAppleTask', $this->Shell->TestApple);
  328. $this->assertInstanceOf('Cake\Shell\Task\TestBananaTask', $this->Shell->TestBanana);
  329. }
  330. /**
  331. * test that __get() makes args and params references
  332. */
  333. public function testMagicGetArgAndParamReferences(): void
  334. {
  335. $this->Shell->tasks = ['TestApple'];
  336. $this->Shell->args = ['one'];
  337. $this->Shell->params = ['help' => false];
  338. $this->Shell->loadTasks();
  339. $result = $this->Shell->TestApple;
  340. $this->Shell->args = ['one', 'two'];
  341. $this->assertSame($this->Shell->args, $result->args);
  342. $this->assertSame($this->Shell->params, $result->params);
  343. }
  344. /**
  345. * testShortPath method
  346. */
  347. public function testShortPath(): void
  348. {
  349. $path = $expected = DS . 'tmp/ab/cd';
  350. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  351. $path = $expected = DS . 'tmp/ab/cd/';
  352. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  353. $path = $expected = DS . 'tmp/ab/index.php';
  354. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  355. $path = DS . 'tmp/ab/' . DS . 'cd';
  356. $expected = DS . 'tmp/ab/cd';
  357. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  358. $path = 'tmp/ab';
  359. $expected = 'tmp/ab';
  360. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  361. $path = 'tmp/ab';
  362. $expected = 'tmp/ab';
  363. $this->assertPathEquals($expected, $this->Shell->shortPath($path));
  364. $path = APP;
  365. $result = $this->Shell->shortPath($path);
  366. $this->assertStringNotContainsString(ROOT, $result, 'Short paths should not contain ROOT');
  367. }
  368. /**
  369. * testCreateFile method
  370. */
  371. public function testCreateFileNonInteractive(): void
  372. {
  373. $eol = PHP_EOL;
  374. $path = TMP . 'shell_test';
  375. $file = $path . DS . 'file1.php';
  376. $this->fs->mkdir($path);
  377. $contents = "<?php{$eol}echo 'test';${eol}\$te = 'st';{$eol}";
  378. $this->Shell->interactive = false;
  379. $result = $this->Shell->createFile($file, $contents);
  380. $this->assertTrue($result);
  381. $this->assertFileExists($file);
  382. $this->assertStringEqualsFile($file, $contents);
  383. }
  384. /**
  385. * Test that while in non interactive mode it will not overwrite files by default.
  386. */
  387. public function testCreateFileNonInteractiveFileExists(): void
  388. {
  389. $eol = PHP_EOL;
  390. $path = TMP . 'shell_test';
  391. $file = $path . DS . 'file1.php';
  392. if (!is_dir($path)) {
  393. mkdir($path, 0770, true);
  394. }
  395. touch($file);
  396. $this->assertFileExists($file);
  397. $this->fs->mkdir($path);
  398. $contents = "<?php{$eol}echo 'test';${eol}\$te = 'st';{$eol}";
  399. $this->Shell->interactive = false;
  400. $result = $this->Shell->createFile($file, $contents);
  401. $this->assertFalse($result);
  402. }
  403. /**
  404. * Test that files are not changed with a 'n' reply.
  405. */
  406. public function testCreateFileNoReply(): void
  407. {
  408. $path = TMP . 'shell_test';
  409. $file = $path . DS . 'file1.php';
  410. $this->fs->mkdir($path);
  411. $this->io->expects($this->once())
  412. ->method('askChoice')
  413. ->will($this->returnValue('n'));
  414. touch($file);
  415. $this->assertFileExists($file);
  416. $contents = 'My content';
  417. $result = $this->Shell->createFile($file, $contents);
  418. $this->assertFileExists($file);
  419. $this->assertTextEquals('', file_get_contents($file));
  420. $this->assertFalse($result, 'Did not create file.');
  421. }
  422. /**
  423. * Test that files are changed with a 'y' reply.
  424. */
  425. public function testCreateFileOverwrite(): void
  426. {
  427. $path = TMP . 'shell_test';
  428. $file = $path . DS . 'file1.php';
  429. $this->fs->mkdir($path);
  430. $this->io->expects($this->once())
  431. ->method('askChoice')
  432. ->will($this->returnValue('y'));
  433. touch($file);
  434. $this->assertFileExists($file);
  435. $contents = 'My content';
  436. $result = $this->Shell->createFile($file, $contents);
  437. $this->assertFileExists($file);
  438. $this->assertTextEquals($contents, file_get_contents($file));
  439. $this->assertTrue($result, 'Did create file.');
  440. }
  441. /**
  442. * Test that there is no user prompt in non-interactive mode while file already exists
  443. * and if force mode is explicitly enabled.
  444. */
  445. public function testCreateFileOverwriteNonInteractive(): void
  446. {
  447. $path = TMP . 'shell_test';
  448. $file = $path . DS . 'file1.php';
  449. $this->fs->mkdir($path);
  450. touch($file);
  451. $this->assertFileExists($file);
  452. $this->io->expects($this->never())->method('askChoice');
  453. $this->Shell->params['force'] = true;
  454. $this->Shell->interactive = false;
  455. $result = $this->Shell->createFile($file, 'My content');
  456. $this->assertTrue($result);
  457. $this->assertStringEqualsFile($file, 'My content');
  458. }
  459. /**
  460. * Test that all files are changed with a 'a' reply.
  461. */
  462. public function testCreateFileOverwriteAll(): void
  463. {
  464. $path = TMP . 'shell_test';
  465. $files = [
  466. $path . DS . 'file1.php' => 'My first content',
  467. $path . DS . 'file2.php' => 'My second content',
  468. $path . DS . 'file3.php' => 'My third content',
  469. ];
  470. $this->fs->mkdir($path);
  471. $this->io->expects($this->once())
  472. ->method('askChoice')
  473. ->will($this->returnValue('a'));
  474. foreach ($files as $file => $contents) {
  475. touch($file);
  476. $this->assertFileExists($file);
  477. $result = $this->Shell->createFile($file, $contents);
  478. $this->assertFileExists($file);
  479. $this->assertTextEquals($contents, file_get_contents($file));
  480. $this->assertTrue($result, 'Did create file.');
  481. }
  482. }
  483. /**
  484. * Test that you can't create files that aren't writable.
  485. */
  486. public function testCreateFileNoPermissions(): void
  487. {
  488. $this->skipIf(DS === '\\', 'Cant perform operations using permissions on windows.');
  489. $path = TMP . 'shell_test';
  490. $file = $path . DS . 'no_perms';
  491. if (!is_dir($path)) {
  492. mkdir($path);
  493. }
  494. chmod($path, 0444);
  495. $this->Shell->createFile($file, 'testing');
  496. $this->assertFileDoesNotExist($file);
  497. chmod($path, 0744);
  498. rmdir($path);
  499. }
  500. /**
  501. * test hasTask method
  502. */
  503. public function testHasTask(): void
  504. {
  505. $this->setAppNamespace();
  506. $this->Shell->tasks = ['Sample', 'TestApple'];
  507. $this->Shell->loadTasks();
  508. $this->assertTrue($this->Shell->hasTask('sample'));
  509. $this->assertTrue($this->Shell->hasTask('Sample'));
  510. $this->assertFalse($this->Shell->hasTask('random'));
  511. $this->assertTrue($this->Shell->hasTask('testApple'));
  512. $this->assertTrue($this->Shell->hasTask('TestApple'));
  513. }
  514. /**
  515. * test task loading exception
  516. */
  517. public function testMissingTaskException(): void
  518. {
  519. $this->expectException(RuntimeException::class);
  520. $this->expectExceptionMessage('Task `DoesNotExist` not found. Maybe you made a typo or a plugin is missing or not loaded?');
  521. $this->Shell->tasks = ['DoesNotExist'];
  522. $this->Shell->loadTasks();
  523. }
  524. /**
  525. * test the hasMethod
  526. */
  527. public function testHasMethod(): void
  528. {
  529. $this->assertTrue($this->Shell->hasMethod('doSomething'));
  530. $this->assertFalse($this->Shell->hasMethod('hr'), 'hr is callable');
  531. $this->assertFalse($this->Shell->hasMethod('_secret'), '_secret is callable');
  532. $this->assertFalse($this->Shell->hasMethod('no_access'), 'no_access is callable');
  533. }
  534. /**
  535. * test run command calling main.
  536. */
  537. public function testRunCommandMain(): void
  538. {
  539. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  540. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  541. $shell = $this->getMockBuilder('Cake\Console\Shell')
  542. ->onlyMethods(['startup'])
  543. ->addMethods(['main'])
  544. ->setConstructorArgs([$io])
  545. ->getMock();
  546. $shell->expects($this->once())->method('startup');
  547. $shell->expects($this->once())->method('main')
  548. ->with('cakes')
  549. ->will($this->returnValue(true));
  550. $result = $shell->runCommand(['cakes', '--verbose']);
  551. $this->assertTrue($result);
  552. $this->assertSame('main', $shell->command);
  553. }
  554. /**
  555. * test run command calling a real method with no subcommands defined.
  556. */
  557. public function testRunCommandWithMethod(): void
  558. {
  559. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  560. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  561. $shell = $this->getMockBuilder('Cake\Console\Shell')
  562. ->onlyMethods(['startup'])
  563. ->addMethods(['hitMe'])
  564. ->setConstructorArgs([$io])
  565. ->getMock();
  566. $shell->expects($this->once())->method('startup');
  567. $shell->expects($this->once())->method('hitMe')
  568. ->with('cakes')
  569. ->will($this->returnValue(true));
  570. $result = $shell->runCommand(['hit_me', 'cakes', '--verbose'], true);
  571. $this->assertTrue($result);
  572. $this->assertSame('hit_me', $shell->command);
  573. }
  574. /**
  575. * test that a command called with an extra parameter passed merges the extra parameters
  576. * to the shell's one
  577. * Also tests that if an extra `requested` parameter prevents the welcome message from
  578. * being displayed
  579. */
  580. public function testRunCommandWithExtra(): void
  581. {
  582. $Parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  583. ->onlyMethods(['help'])
  584. ->setConstructorArgs(['knife'])
  585. ->getMock();
  586. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  587. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  588. ->onlyMethods(['getOptionParser', '_welcome', 'param'])
  589. ->addMethods(['slice'])
  590. ->setConstructorArgs([$io])
  591. ->getMock();
  592. $Parser->addSubCommand('slice');
  593. $Shell->expects($this->once())
  594. ->method('getOptionParser')
  595. ->will($this->returnValue($Parser));
  596. $Shell->expects($this->once())
  597. ->method('slice')
  598. ->with('cakes');
  599. $Shell->expects($this->never())->method('_welcome');
  600. $Shell->expects($this->once())->method('param')
  601. ->with('requested')
  602. ->will($this->returnValue(true));
  603. $Shell->runCommand(['slice', 'cakes'], false, ['requested' => true]);
  604. }
  605. /**
  606. * Test the dispatchShell() arguments parser
  607. */
  608. public function testDispatchShellArgsParser(): void
  609. {
  610. $Shell = new Shell();
  611. $expected = [['schema', 'create', 'DbAcl'], []];
  612. // Shell::dispatchShell('schema create DbAcl');
  613. $result = $Shell->parseDispatchArguments(['schema create DbAcl']);
  614. $this->assertEquals($expected, $result);
  615. // Shell::dispatchShell('schema', 'create', 'DbAcl');
  616. $result = $Shell->parseDispatchArguments(['schema', 'create', 'DbAcl']);
  617. $this->assertEquals($expected, $result);
  618. // Shell::dispatchShell(['command' => 'schema create DbAcl']);
  619. $result = $Shell->parseDispatchArguments([[
  620. 'command' => 'schema create DbAcl',
  621. ]]);
  622. $this->assertEquals($expected, $result);
  623. // Shell::dispatchShell(['command' => ['schema', 'create', 'DbAcl']]);
  624. $result = $Shell->parseDispatchArguments([[
  625. 'command' => ['schema', 'create', 'DbAcl'],
  626. ]]);
  627. $this->assertEquals($expected, $result);
  628. $expected[1] = ['param' => 'value'];
  629. // Shell::dispatchShell(['command' => 'schema create DbAcl', 'extra' => ['param' => 'value']]);
  630. $result = $Shell->parseDispatchArguments([[
  631. 'command' => 'schema create DbAcl',
  632. 'extra' => ['param' => 'value'],
  633. ]]);
  634. $this->assertEquals($expected, $result);
  635. // Shell::dispatchShell(['command' => ['schema', 'create', 'DbAcl'], 'extra' => ['param' => 'value']]);
  636. $result = $Shell->parseDispatchArguments([[
  637. 'command' => ['schema', 'create', 'DbAcl'],
  638. 'extra' => ['param' => 'value'],
  639. ]]);
  640. $this->assertEquals($expected, $result);
  641. }
  642. /**
  643. * test calling a shell that dispatch another one
  644. */
  645. public function testDispatchShell(): void
  646. {
  647. $Shell = new TestingDispatchShell();
  648. ob_start();
  649. $Shell->runCommand(['test_task'], true);
  650. $result = ob_get_clean();
  651. $expected = <<<TEXT
  652. <info>Welcome to CakePHP Console</info>
  653. I am a test task, I dispatch another Shell
  654. I am a dispatched Shell
  655. TEXT;
  656. $this->assertSame($expected, $result);
  657. ob_start();
  658. $Shell->runCommand(['test_task_dispatch_array'], true);
  659. $result = ob_get_clean();
  660. $this->assertSame($expected, $result);
  661. ob_start();
  662. $Shell->runCommand(['test_task_dispatch_command_string'], true);
  663. $result = ob_get_clean();
  664. $this->assertSame($expected, $result);
  665. ob_start();
  666. $Shell->runCommand(['test_task_dispatch_command_array'], true);
  667. $result = ob_get_clean();
  668. $this->assertSame($expected, $result);
  669. $expected = <<<TEXT
  670. <info>Welcome to CakePHP Console</info>
  671. I am a test task, I dispatch another Shell
  672. I am a dispatched Shell. My param `foo` has the value `bar`
  673. TEXT;
  674. ob_start();
  675. $Shell->runCommand(['test_task_dispatch_with_param'], true);
  676. $result = ob_get_clean();
  677. $this->assertSame($expected, $result);
  678. $expected = <<<TEXT
  679. <info>Welcome to CakePHP Console</info>
  680. I am a test task, I dispatch another Shell
  681. I am a dispatched Shell. My param `foo` has the value `bar`
  682. My param `fooz` has the value `baz`
  683. TEXT;
  684. ob_start();
  685. $Shell->runCommand(['test_task_dispatch_with_multiple_params'], true);
  686. $result = ob_get_clean();
  687. $this->assertSame($expected, $result);
  688. $expected = <<<TEXT
  689. <info>Welcome to CakePHP Console</info>
  690. I am a test task, I dispatch another Shell
  691. <info>Welcome to CakePHP Console</info>
  692. I am a dispatched Shell
  693. TEXT;
  694. ob_start();
  695. $Shell->runCommand(['test_task_dispatch_with_requested_off'], true);
  696. $result = ob_get_clean();
  697. $this->assertSame($expected, $result);
  698. }
  699. /**
  700. * Test that runCommand() doesn't call public methods when the second arg is false.
  701. */
  702. public function testRunCommandAutoMethodOff(): void
  703. {
  704. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  705. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  706. $shell = $this->getMockBuilder('Cake\Console\Shell')
  707. ->onlyMethods(['startup'])
  708. ->addMethods(['hit_me'])
  709. ->setConstructorArgs([$io])
  710. ->getMock();
  711. $shell->expects($this->never())->method('startup');
  712. $shell->expects($this->never())->method('hit_me');
  713. $result = $shell->runCommand(['hit_me', 'baseball'], false);
  714. $this->assertFalse($result);
  715. $result = $shell->runCommand(['hit_me', 'baseball']);
  716. $this->assertFalse($result, 'Default value of runCommand() should be false');
  717. }
  718. /**
  719. * test run command calling a real method with mismatching subcommands defined.
  720. */
  721. public function testRunCommandWithMethodNotInSubcommands(): void
  722. {
  723. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  724. ->onlyMethods(['help'])
  725. ->setConstructorArgs(['knife'])
  726. ->getMock();
  727. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  728. $shell = $this->getMockBuilder('Cake\Console\Shell')
  729. ->onlyMethods(['getOptionParser', 'startup'])
  730. ->addMethods(['roll'])
  731. ->setConstructorArgs([$io])
  732. ->getMock();
  733. $parser->addSubCommand('slice');
  734. $shell->expects($this->any())
  735. ->method('getOptionParser')
  736. ->will($this->returnValue($parser));
  737. $parser->expects($this->once())
  738. ->method('help');
  739. $shell->expects($this->never())->method('startup');
  740. $shell->expects($this->never())->method('roll');
  741. $result = $shell->runCommand(['roll', 'cakes', '--verbose']);
  742. $this->assertFalse($result);
  743. }
  744. /**
  745. * test run command calling a real method with subcommands defined.
  746. */
  747. public function testRunCommandWithMethodInSubcommands(): void
  748. {
  749. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  750. ->onlyMethods(['help'])
  751. ->setConstructorArgs(['knife'])
  752. ->getMock();
  753. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  754. $shell = $this->getMockBuilder('Cake\Console\Shell')
  755. ->onlyMethods(['getOptionParser', 'startup'])
  756. ->addMethods(['slice'])
  757. ->setConstructorArgs([$io])
  758. ->getMock();
  759. $parser->addSubCommand('slice');
  760. $shell->expects($this->any())
  761. ->method('getOptionParser')
  762. ->will($this->returnValue($parser));
  763. $shell->expects($this->once())->method('startup');
  764. $shell->expects($this->once())
  765. ->method('slice')
  766. ->with('cakes');
  767. $shell->runCommand(['slice', 'cakes', '--verbose']);
  768. }
  769. /**
  770. * test run command calling a missing method with subcommands defined.
  771. */
  772. public function testRunCommandWithMissingMethodInSubcommands(): void
  773. {
  774. /** @var \Cake\Console\ConsoleOptionParser|\PHPUnit\Framework\MockObject\MockObject $parser */
  775. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  776. ->onlyMethods(['help'])
  777. ->setConstructorArgs(['knife'])
  778. ->getMock();
  779. $parser->addSubCommand('slice');
  780. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  781. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  782. $shell = $this->getMockBuilder('Cake\Console\Shell')
  783. ->onlyMethods(['getOptionParser', 'startup'])
  784. ->setConstructorArgs([$io])
  785. ->getMock();
  786. $shell->expects($this->any())
  787. ->method('getOptionParser')
  788. ->will($this->returnValue($parser));
  789. $shell->expects($this->never())
  790. ->method('startup');
  791. $parser->expects($this->once())
  792. ->method('help');
  793. $shell->runCommand(['slice', 'cakes', '--verbose']);
  794. }
  795. /**
  796. * test run command causing exception on Shell method.
  797. */
  798. public function testRunCommandBaseClassMethod(): void
  799. {
  800. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  801. $shell = $this->getMockBuilder('Cake\Console\Shell')
  802. ->onlyMethods(['startup', 'getOptionParser', 'hr'])
  803. ->disableOriginalConstructor()
  804. ->getMock();
  805. $shell->setIo(
  806. $this->getMockBuilder('Cake\Console\ConsoleIo')
  807. ->onlyMethods(['err'])
  808. ->getMock()
  809. );
  810. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  811. ->disableOriginalConstructor()
  812. ->getMock();
  813. $parser->expects($this->once())->method('help');
  814. $parser->method('parse')
  815. ->will($this->returnValue([[], []]));
  816. $shell->expects($this->once())->method('getOptionParser')
  817. ->will($this->returnValue($parser));
  818. $shell->expects($this->never())->method('hr');
  819. $shell->_io->expects($this->exactly(2))->method('err');
  820. $shell->runCommand(['hr']);
  821. }
  822. /**
  823. * test run command causing exception on Shell method.
  824. */
  825. public function testRunCommandMissingMethod(): void
  826. {
  827. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  828. $shell = $this->getMockBuilder('Cake\Console\Shell')
  829. ->onlyMethods(['startup', 'getOptionParser', 'hr'])
  830. ->disableOriginalConstructor()
  831. ->getMock();
  832. $shell->setIo(
  833. $this->getMockBuilder('Cake\Console\ConsoleIo')
  834. ->onlyMethods(['err'])
  835. ->getMock()
  836. );
  837. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  838. ->disableOriginalConstructor()
  839. ->getMock();
  840. $parser->expects($this->once())->method('help');
  841. $parser->method('parse')
  842. ->will($this->returnValue([[], []]));
  843. $shell->expects($this->once())->method('getOptionParser')
  844. ->will($this->returnValue($parser));
  845. $shell->_io->expects($this->exactly(2))->method('err');
  846. $result = $shell->runCommand(['idontexist']);
  847. $this->assertFalse($result);
  848. }
  849. /**
  850. * test that a --help causes help to show.
  851. */
  852. public function testRunCommandTriggeringHelp(): void
  853. {
  854. $parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
  855. ->disableOriginalConstructor()
  856. ->getMock();
  857. $parser->expects($this->once())->method('parse')
  858. ->with(['--help'])
  859. ->will($this->returnValue([['help' => true], []]));
  860. $parser->expects($this->once())->method('help');
  861. $shell = $this->getMockBuilder('Cake\Console\Shell')
  862. ->onlyMethods(['getOptionParser', 'out', 'startup', '_welcome'])
  863. ->disableOriginalConstructor()
  864. ->getMock();
  865. $shell->setIo($this->getMockBuilder('Cake\Console\ConsoleIo')->getMock());
  866. $shell->expects($this->once())->method('getOptionParser')
  867. ->will($this->returnValue($parser));
  868. $shell->expects($this->once())->method('out');
  869. $shell->runCommand(['--help']);
  870. }
  871. /**
  872. * test that runCommand will not call runCommand on tasks that are not subcommands.
  873. */
  874. public function testRunCommandNotCallUnexposedTask(): void
  875. {
  876. /** @var \Cake\Console\Shell|\PHPUnit\Framework\MockObject\MockObject $shell */
  877. $shell = $this->getMockBuilder('Cake\Console\Shell')
  878. ->onlyMethods(['startup', 'hasTask'])
  879. ->disableOriginalConstructor()
  880. ->getMock();
  881. $shell->setIo(
  882. $this->getMockBuilder('Cake\Console\ConsoleIo')
  883. ->onlyMethods(['err'])
  884. ->getMock()
  885. );
  886. $task = $this->getMockBuilder('Cake\Console\Shell')
  887. ->onlyMethods(['runCommand'])
  888. ->disableOriginalConstructor()
  889. ->getMock();
  890. $task->expects($this->never())
  891. ->method('runCommand');
  892. $shell->expects($this->any())
  893. ->method('hasTask')
  894. ->will($this->returnValue(true));
  895. $shell->expects($this->never())->method('startup');
  896. $shell->_io->expects($this->exactly(2))->method('err');
  897. $shell->RunCommand = $task;
  898. $result = $shell->runCommand(['run_command', 'one']);
  899. $this->assertFalse($result);
  900. }
  901. /**
  902. * test that runCommand will call runCommand on the task.
  903. */
  904. public function testRunCommandHittingTaskInSubcommand(): void
  905. {
  906. $parser = new ConsoleOptionParser('knife');
  907. $parser->addSubcommand('slice');
  908. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  909. $shell = $this->getMockBuilder('Cake\Console\Shell')
  910. ->onlyMethods(['hasTask', 'startup', 'getOptionParser'])
  911. ->disableOriginalConstructor()
  912. ->getMock();
  913. $shell->setIo($io);
  914. $task = $this->getMockBuilder('Cake\Console\Shell')
  915. ->onlyMethods(['runCommand'])
  916. ->addMethods(['main'])
  917. ->disableOriginalConstructor()
  918. ->getMock();
  919. $task->setIo($io);
  920. $task->expects($this->once())
  921. ->method('runCommand')
  922. ->with(['one'], false, ['requested' => true]);
  923. $shell->expects($this->once())->method('getOptionParser')
  924. ->will($this->returnValue($parser));
  925. $shell->expects($this->once())->method('startup');
  926. $shell->expects($this->any())
  927. ->method('hasTask')
  928. ->will($this->returnValue(true));
  929. $shell->Slice = $task;
  930. $shell->runCommand(['slice', 'one']);
  931. }
  932. /**
  933. * test that runCommand will invoke a task
  934. */
  935. public function testRunCommandInvokeTask(): void
  936. {
  937. $parser = new ConsoleOptionParser('knife');
  938. $parser->addSubcommand('slice');
  939. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  940. $shell = $this->getMockBuilder('Cake\Console\Shell')
  941. ->onlyMethods(['hasTask', 'getOptionParser'])
  942. ->setConstructorArgs([$io])
  943. ->getMock();
  944. $task = $this->getMockBuilder('Cake\Console\Shell')
  945. ->onlyMethods(['_welcome'])
  946. ->addMethods(['main'])
  947. ->setConstructorArgs([$io])
  948. ->getMock();
  949. $shell->expects($this->once())
  950. ->method('getOptionParser')
  951. ->will($this->returnValue($parser));
  952. $shell->expects($this->any())
  953. ->method('hasTask')
  954. ->will($this->returnValue(true));
  955. $task->expects($this->never())
  956. ->method('_welcome');
  957. $shell->Slice = $task;
  958. $shell->runCommand(['slice', 'one']);
  959. $this->assertTrue($task->params['requested'], 'Task is requested, no welcome.');
  960. }
  961. /**
  962. * test run command missing parameters
  963. */
  964. public function testRunCommandMainMissingArgument(): void
  965. {
  966. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  967. $shell = $this->getMockBuilder('Cake\Console\Shell')
  968. ->onlyMethods(['startup', 'getOptionParser'])
  969. ->addMethods(['main'])
  970. ->setConstructorArgs([$io])
  971. ->getMock();
  972. $parser = new ConsoleOptionParser('test');
  973. $parser->addArgument('filename', [
  974. 'required' => true,
  975. 'help' => 'a file',
  976. ]);
  977. $shell->expects($this->once())
  978. ->method('getOptionParser')
  979. ->will($this->returnValue($parser));
  980. $shell->expects($this->never())->method('main');
  981. $io->expects($this->once())
  982. ->method('error')
  983. ->with('Error: Missing required argument. The `filename` argument is required.');
  984. $result = $shell->runCommand([]);
  985. $this->assertFalse($result, 'Shell should fail');
  986. }
  987. /**
  988. * test wrapBlock wrapping text.
  989. */
  990. public function testWrapText(): void
  991. {
  992. $text = 'This is the song that never ends. This is the song that never ends. This is the song that never ends.';
  993. $result = $this->Shell->wrapText($text, ['width' => 33]);
  994. $expected = <<<TEXT
  995. This is the song that never ends.
  996. This is the song that never ends.
  997. This is the song that never ends.
  998. TEXT;
  999. $this->assertTextEquals($expected, $result, 'Text not wrapped.');
  1000. $result = $this->Shell->wrapText($text, ['indent' => ' ', 'width' => 33]);
  1001. $expected = <<<TEXT
  1002. This is the song that never ends.
  1003. This is the song that never ends.
  1004. This is the song that never ends.
  1005. TEXT;
  1006. $this->assertTextEquals($expected, $result, 'Text not wrapped.');
  1007. }
  1008. /**
  1009. * Testing camel cased naming of tasks
  1010. */
  1011. public function testShellNaming(): void
  1012. {
  1013. $this->Shell->tasks = ['TestApple'];
  1014. $this->Shell->loadTasks();
  1015. $expected = 'TestApple';
  1016. $this->assertSame($expected, $this->Shell->TestApple->name);
  1017. }
  1018. /**
  1019. * Test reading params
  1020. *
  1021. * @dataProvider paramReadingDataProvider
  1022. * @param mixed $expected
  1023. */
  1024. public function testParamReading(string $toRead, $expected): void
  1025. {
  1026. $this->Shell->params = [
  1027. 'key' => 'value',
  1028. 'help' => false,
  1029. 'emptykey' => '',
  1030. 'truthy' => true,
  1031. ];
  1032. $this->assertSame($expected, $this->Shell->param($toRead));
  1033. }
  1034. /**
  1035. * Data provider for testing reading values with Shell::param()
  1036. *
  1037. * @return array
  1038. */
  1039. public function paramReadingDataProvider(): array
  1040. {
  1041. return [
  1042. [
  1043. 'key',
  1044. 'value',
  1045. ],
  1046. [
  1047. 'help',
  1048. false,
  1049. ],
  1050. [
  1051. 'emptykey',
  1052. '',
  1053. ],
  1054. [
  1055. 'truthy',
  1056. true,
  1057. ],
  1058. [
  1059. 'does_not_exist',
  1060. null,
  1061. ],
  1062. ];
  1063. }
  1064. /**
  1065. * Test that option parsers are created with the correct name/command.
  1066. */
  1067. public function testGetOptionParser(): void
  1068. {
  1069. $this->Shell->name = 'test';
  1070. $this->Shell->plugin = 'plugin';
  1071. $parser = $this->Shell->getOptionParser();
  1072. $this->assertSame('plugin.test', $parser->getCommand());
  1073. }
  1074. /**
  1075. * Test file and console and logging quiet output
  1076. */
  1077. public function testQuietLog(): void
  1078. {
  1079. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')
  1080. ->disableOriginalConstructor()
  1081. ->getMock();
  1082. $io->expects($this->once())
  1083. ->method('level')
  1084. ->with(Shell::QUIET);
  1085. $io->expects($this->exactly(2))
  1086. ->method('setLoggers')
  1087. ->withConsecutive([true], [ConsoleIo::QUIET]);
  1088. $this->Shell = $this->getMockBuilder(ShellTestShell::class)
  1089. ->addMethods(['welcome'])
  1090. ->setConstructorArgs([$io])
  1091. ->getMock();
  1092. $this->Shell->runCommand(['foo', '--quiet']);
  1093. }
  1094. /**
  1095. * Test getIo() and setIo() methods
  1096. */
  1097. public function testGetSetIo(): void
  1098. {
  1099. $this->Shell->setIo($this->io);
  1100. $this->assertSame($this->Shell->getIo(), $this->io);
  1101. }
  1102. /**
  1103. * Test setRootName filters into the option parser help text.
  1104. */
  1105. public function testSetRootNamePropagatesToHelpText(): void
  1106. {
  1107. $this->assertSame($this->Shell, $this->Shell->setRootName('tool'), 'is chainable');
  1108. $this->assertStringContainsString('tool shell_test_shell [-h]', $this->Shell->getOptionParser()->help());
  1109. }
  1110. /**
  1111. * Tests __debugInfo
  1112. */
  1113. public function testDebugInfo(): void
  1114. {
  1115. $expected = [
  1116. 'name' => 'ShellTestShell',
  1117. 'plugin' => null,
  1118. 'command' => null,
  1119. 'tasks' => [],
  1120. 'params' => [],
  1121. 'args' => [],
  1122. 'interactive' => true,
  1123. ];
  1124. $result = $this->Shell->__debugInfo();
  1125. $this->assertEquals($expected, $result);
  1126. }
  1127. }