CommandTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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 3.6.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Console;
  17. use AssertionError;
  18. use Cake\Command\Command;
  19. use Cake\Console\CommandFactory;
  20. use Cake\Console\CommandFactoryInterface;
  21. use Cake\Console\CommandInterface;
  22. use Cake\Console\ConsoleIo;
  23. use Cake\Console\ConsoleOptionParser;
  24. use Cake\Console\Exception\StopException;
  25. use Cake\Console\TestSuite\StubConsoleOutput;
  26. use Cake\Core\Container;
  27. use Cake\ORM\Locator\TableLocator;
  28. use Cake\ORM\Table;
  29. use Cake\TestSuite\TestCase;
  30. use Mockery;
  31. use stdClass;
  32. use TestApp\Command\AbortCommand;
  33. use TestApp\Command\AutoLoadModelCommand;
  34. use TestApp\Command\DemoCommand;
  35. use TestApp\Command\DependencyCommand;
  36. use TestApp\Command\NonInteractiveCommand;
  37. /**
  38. * Test case for Console\Command
  39. */
  40. class CommandTest extends TestCase
  41. {
  42. /**
  43. * test orm locator is setup
  44. */
  45. public function testConstructorSetsLocator(): void
  46. {
  47. $command = new Command();
  48. $result = $command->getTableLocator();
  49. $this->assertInstanceOf(TableLocator::class, $result);
  50. }
  51. /**
  52. * test loadModel is configured properly
  53. */
  54. public function testConstructorAutoLoadModel(): void
  55. {
  56. // No deprecation as AutoLoadModelCommand class defines Posts property
  57. $command = new AutoLoadModelCommand();
  58. $this->assertInstanceOf(Table::class, $command->fetchTable());
  59. }
  60. /**
  61. * Test name
  62. */
  63. public function testSetName(): void
  64. {
  65. $command = new Command();
  66. $this->assertSame($command, $command->setName('routes show'));
  67. $this->assertSame('routes show', $command->getName());
  68. $this->assertSame('routes', $command->getRootName());
  69. }
  70. /**
  71. * Test invalid name
  72. */
  73. public function testSetNameInvalid(): void
  74. {
  75. $this->expectException(AssertionError::class);
  76. $this->expectExceptionMessage("The name 'routes_show' is missing a space. Names should look like `cake routes`");
  77. $command = new Command();
  78. $command->setName('routes_show');
  79. }
  80. /**
  81. * Test invalid name
  82. */
  83. public function testSetNameInvalidLeadingSpace(): void
  84. {
  85. $this->expectException(AssertionError::class);
  86. $command = new Command();
  87. $command->setName(' routes_show');
  88. }
  89. /**
  90. * Test option parser fetching
  91. */
  92. public function testGetOptionParser(): void
  93. {
  94. $command = new Command();
  95. $command->setName('cake routes show');
  96. $parser = $command->getOptionParser();
  97. $this->assertInstanceOf(ConsoleOptionParser::class, $parser);
  98. $this->assertSame('routes show', $parser->getCommand());
  99. }
  100. /**
  101. * Test that initialize is called.
  102. */
  103. public function testRunCallsInitialize(): void
  104. {
  105. $command = new class extends Command {
  106. public bool $initializeCalled = false;
  107. public function initialize(): void
  108. {
  109. $this->initializeCalled = true;
  110. }
  111. };
  112. $command->setName('cake example');
  113. $command->run([], $this->getMockIo(new StubConsoleOutput()));
  114. $this->assertTrue($command->initializeCalled);
  115. }
  116. /**
  117. * Test run() outputs help
  118. */
  119. public function testRunOutputHelp(): void
  120. {
  121. $command = new Command();
  122. $command->setName('cake demo');
  123. $output = new StubConsoleOutput();
  124. $this->assertSame(
  125. CommandInterface::CODE_SUCCESS,
  126. $command->run(['-h'], $this->getMockIo($output))
  127. );
  128. $messages = implode("\n", $output->messages());
  129. $this->assertStringNotContainsString('Demo', $messages);
  130. $this->assertStringContainsString('cake demo [-h]', $messages);
  131. }
  132. /**
  133. * Test run() outputs help
  134. */
  135. public function testRunOutputHelpLongOption(): void
  136. {
  137. $command = new Command();
  138. $command->setName('cake demo');
  139. $output = new StubConsoleOutput();
  140. $this->assertSame(
  141. CommandInterface::CODE_SUCCESS,
  142. $command->run(['--help'], $this->getMockIo($output))
  143. );
  144. $messages = implode("\n", $output->messages());
  145. $this->assertStringNotContainsString('Demo', $messages);
  146. $this->assertStringContainsString('cake demo [-h]', $messages);
  147. }
  148. /**
  149. * Test run() sets output level
  150. */
  151. public function testRunVerboseOption(): void
  152. {
  153. $command = new DemoCommand();
  154. $command->setName('cake demo');
  155. $output = new StubConsoleOutput();
  156. $this->assertNull($command->run(['--verbose'], $this->getMockIo($output)));
  157. $messages = implode("\n", $output->messages());
  158. $this->assertStringContainsString('Verbose!', $messages);
  159. $this->assertStringContainsString('Demo Command!', $messages);
  160. $this->assertStringContainsString('Quiet!', $messages);
  161. $this->assertStringNotContainsString('cake demo [-h]', $messages);
  162. }
  163. /**
  164. * Test run() sets output level
  165. */
  166. public function testRunQuietOption(): void
  167. {
  168. $command = new DemoCommand();
  169. $command->setName('cake demo');
  170. $output = new StubConsoleOutput();
  171. $this->assertNull($command->run(['--quiet'], $this->getMockIo($output)));
  172. $messages = implode("\n", $output->messages());
  173. $this->assertStringContainsString('Quiet!', $messages);
  174. $this->assertStringNotContainsString('Verbose!', $messages);
  175. $this->assertStringNotContainsString('Demo Command!', $messages);
  176. }
  177. /**
  178. * Test run() sets option parser failure
  179. */
  180. public function testRunOptionParserFailure(): void
  181. {
  182. $command = new class extends Command {
  183. public function getOptionParser(): ConsoleOptionParser
  184. {
  185. $parser = new ConsoleOptionParser('cake example');
  186. $parser->addArgument('name', ['required' => true]);
  187. return $parser;
  188. }
  189. };
  190. $output = new StubConsoleOutput();
  191. $result = $command->run([], $this->getMockIo($output));
  192. $this->assertSame(CommandInterface::CODE_ERROR, $result);
  193. $messages = implode("\n", $output->messages());
  194. $this->assertStringContainsString(
  195. 'Error: Missing required argument. The `name` argument is required',
  196. $messages
  197. );
  198. }
  199. /**
  200. * Test abort()
  201. */
  202. public function testAbort(): void
  203. {
  204. $this->expectException(StopException::class);
  205. $this->expectExceptionCode(1);
  206. $command = new Command();
  207. $command->abort();
  208. }
  209. /**
  210. * Test abort()
  211. */
  212. public function testAbortCustomCode(): void
  213. {
  214. $this->expectException(StopException::class);
  215. $this->expectExceptionCode(99);
  216. $command = new Command();
  217. $command->abort(99);
  218. }
  219. /**
  220. * test executeCommand with a string class
  221. */
  222. public function testExecuteCommandString(): void
  223. {
  224. $output = new StubConsoleOutput();
  225. $command = new Command();
  226. $result = $command->executeCommand(DemoCommand::class, [], $this->getMockIo($output));
  227. $this->assertNull($result);
  228. $this->assertEquals(['Quiet!', 'Demo Command!'], $output->messages());
  229. }
  230. /**
  231. * test executeCommand with arguments
  232. */
  233. public function testExecuteCommandArguments(): void
  234. {
  235. $output = new StubConsoleOutput();
  236. $command = new Command();
  237. $command->executeCommand(DemoCommand::class, ['Jane'], $this->getMockIo($output));
  238. $this->assertEquals(['Quiet!', 'Demo Command!', 'Jane'], $output->messages());
  239. }
  240. /**
  241. * test executeCommand with arguments
  242. */
  243. public function testExecuteCommandArgumentsOptions(): void
  244. {
  245. $output = new StubConsoleOutput();
  246. $command = new Command();
  247. $command->executeCommand(DemoCommand::class, ['--quiet', 'Jane'], $this->getMockIo($output));
  248. $this->assertEquals(['Quiet!'], $output->messages());
  249. }
  250. /**
  251. * test executeCommand with an instance
  252. */
  253. public function testExecuteCommandInstance(): void
  254. {
  255. $output = new StubConsoleOutput();
  256. $command = new Command();
  257. $result = $command->executeCommand(new DemoCommand(), [], $this->getMockIo($output));
  258. $this->assertNull($result);
  259. $this->assertEquals(['Quiet!', 'Demo Command!'], $output->messages());
  260. }
  261. /**
  262. * test executeCommand with an abort
  263. */
  264. public function testExecuteCommandAbort(): void
  265. {
  266. $output = new StubConsoleOutput();
  267. $command = new Command();
  268. $result = $command->executeCommand(AbortCommand::class, [], $this->getMockIo($output));
  269. $this->assertSame(127, $result);
  270. $this->assertEquals(['<error>Command aborted</error>'], $output->messages());
  271. }
  272. /**
  273. * Test that noninteractive commands use defaults where applicable.
  274. */
  275. public function testExecuteCommandNonInteractive(): void
  276. {
  277. $output = new StubConsoleOutput();
  278. $command = new Command();
  279. $command->executeCommand(NonInteractiveCommand::class, ['--quiet'], $this->getMockIo($output));
  280. $this->assertEquals(['Result: Default!'], $output->messages());
  281. }
  282. public function testExecuteCommandWithDI(): void
  283. {
  284. $output = new StubConsoleOutput();
  285. $container = new Container();
  286. $factory = new CommandFactory($container);
  287. $container->add(CommandFactoryInterface::class, $factory);
  288. $container->add(Command::class)
  289. ->addArgument(CommandFactoryInterface::class);
  290. $container->add(stdClass::class);
  291. $container->add(DependencyCommand::class)
  292. ->addArgument(stdClass::class);
  293. $command = $factory->create(Command::class);
  294. $result = $command->executeCommand(DependencyCommand::class, [], $this->getMockIo($output));
  295. $this->assertSame(Command::CODE_SUCCESS, $result);
  296. $this->assertEquals(['Dependency Command', 'constructor inject: {}'], $output->messages());
  297. }
  298. /**
  299. * @param \Cake\Console\ConsoleOutput $output
  300. * @return \Cake\Console\ConsoleIo|\Mockery\MockInterface
  301. */
  302. protected function getMockIo($output)
  303. {
  304. return Mockery::mock(ConsoleIo::class, [$output, $output, null, null])->makePartial();
  305. }
  306. }