CommandCollectionTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.5.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\Console;
  16. use Cake\Console\CommandCollection;
  17. use Cake\Core\Configure;
  18. use Cake\Core\Plugin;
  19. use Cake\Shell\I18nShell;
  20. use Cake\Shell\RoutesShell;
  21. use Cake\TestSuite\TestCase;
  22. use InvalidArgumentException;
  23. use stdClass;
  24. use TestApp\Command\DemoCommand;
  25. /**
  26. * Test case for the CommandCollection
  27. */
  28. class CommandCollectionTest extends TestCase
  29. {
  30. public function setUp()
  31. {
  32. parent::setUp();
  33. Configure::write('App.namespace', 'TestApp');
  34. }
  35. /**
  36. * Test constructor with valid classnames
  37. *
  38. * @return void
  39. */
  40. public function testConstructor()
  41. {
  42. $collection = new CommandCollection([
  43. 'i18n' => I18nShell::class,
  44. 'routes' => RoutesShell::class
  45. ]);
  46. $this->assertTrue($collection->has('routes'));
  47. $this->assertTrue($collection->has('i18n'));
  48. $this->assertCount(2, $collection);
  49. }
  50. /**
  51. * Constructor with invalid class names should blow up
  52. *
  53. * @return void
  54. */
  55. public function testConstructorInvalidClass()
  56. {
  57. $this->expectException(\InvalidArgumentException::class);
  58. $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'nope\' it is not a subclass of Cake\Console\Shell');
  59. new CommandCollection([
  60. 'i18n' => I18nShell::class,
  61. 'nope' => stdClass::class
  62. ]);
  63. }
  64. /**
  65. * Test basic add/get
  66. *
  67. * @return void
  68. */
  69. public function testAdd()
  70. {
  71. $collection = new CommandCollection();
  72. $this->assertSame($collection, $collection->add('routes', RoutesShell::class));
  73. $this->assertTrue($collection->has('routes'));
  74. $this->assertSame(RoutesShell::class, $collection->get('routes'));
  75. }
  76. /**
  77. * test adding a command instance.
  78. *
  79. * @return void
  80. */
  81. public function testAddCommand()
  82. {
  83. $collection = new CommandCollection();
  84. $this->assertSame($collection, $collection->add('ex', DemoCommand::class));
  85. $this->assertTrue($collection->has('ex'));
  86. $this->assertSame(DemoCommand::class, $collection->get('ex'));
  87. }
  88. /**
  89. * Test that add() replaces.
  90. *
  91. * @return void
  92. */
  93. public function testAddReplace()
  94. {
  95. $collection = new CommandCollection();
  96. $this->assertSame($collection, $collection->add('routes', RoutesShell::class));
  97. $this->assertSame($collection, $collection->add('routes', I18nShell::class));
  98. $this->assertTrue($collection->has('routes'));
  99. $this->assertSame(I18nShell::class, $collection->get('routes'));
  100. }
  101. /**
  102. * Test adding with instances
  103. *
  104. * @return void
  105. */
  106. public function testAddInstance()
  107. {
  108. $collection = new CommandCollection();
  109. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')
  110. ->disableOriginalConstructor()
  111. ->getMock();
  112. $shell = new RoutesShell($io);
  113. $collection->add('routes', $shell);
  114. $this->assertTrue($collection->has('routes'));
  115. $this->assertSame($shell, $collection->get('routes'));
  116. }
  117. /**
  118. * Instances that are not shells should fail.
  119. *
  120. */
  121. public function testAddInvalidInstance()
  122. {
  123. $this->expectException(\InvalidArgumentException::class);
  124. $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\' it is not a subclass of Cake\Console\Shell');
  125. $collection = new CommandCollection();
  126. $shell = new stdClass();
  127. $collection->add('routes', $shell);
  128. }
  129. /**
  130. * Provider for invalid names.
  131. *
  132. * @return array
  133. */
  134. public function invalidNameProvider()
  135. {
  136. return [
  137. // Empty
  138. [''],
  139. // Leading spaces
  140. [' spaced'],
  141. // Trailing spaces
  142. ['spaced '],
  143. // Too many words
  144. ['one two three four'],
  145. ];
  146. }
  147. /**
  148. * test adding a command instance.
  149. *
  150. * @dataProvider invalidNameProvider
  151. * @return void
  152. */
  153. public function testAddCommandInvalidName($name)
  154. {
  155. $this->expectException(InvalidArgumentException::class);
  156. $this->expectExceptionMessage("The command name `$name` is invalid.");
  157. $collection = new CommandCollection();
  158. $collection->add($name, DemoCommand::class);
  159. }
  160. /**
  161. * Class names that are not shells should fail
  162. *
  163. */
  164. public function testInvalidShellClassName()
  165. {
  166. $this->expectException(\InvalidArgumentException::class);
  167. $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\' it is not a subclass of Cake\Console\Shell');
  168. $collection = new CommandCollection();
  169. $collection->add('routes', stdClass::class);
  170. }
  171. /**
  172. * Test removing a command
  173. *
  174. * @return void
  175. */
  176. public function testRemove()
  177. {
  178. $collection = new CommandCollection();
  179. $collection->add('routes', RoutesShell::class);
  180. $this->assertSame($collection, $collection->remove('routes'));
  181. $this->assertFalse($collection->has('routes'));
  182. }
  183. /**
  184. * Removing an unknown command does not fail
  185. *
  186. * @return void
  187. */
  188. public function testRemoveUnknown()
  189. {
  190. $collection = new CommandCollection();
  191. $this->assertSame($collection, $collection->remove('nope'));
  192. $this->assertFalse($collection->has('nope'));
  193. }
  194. /**
  195. * test getIterator
  196. *
  197. * @return void
  198. */
  199. public function testGetIterator()
  200. {
  201. $in = [
  202. 'i18n' => I18nShell::class,
  203. 'routes' => RoutesShell::class
  204. ];
  205. $collection = new CommandCollection($in);
  206. $out = [];
  207. foreach ($collection as $key => $value) {
  208. $out[$key] = $value;
  209. }
  210. $this->assertEquals($in, $out);
  211. }
  212. /**
  213. * test autodiscovering app shells
  214. *
  215. * @return void
  216. */
  217. public function testAutoDiscoverApp()
  218. {
  219. $collection = new CommandCollection();
  220. $collection->addMany($collection->autoDiscover());
  221. $this->assertTrue($collection->has('app'));
  222. $this->assertTrue($collection->has('demo'));
  223. $this->assertTrue($collection->has('i18m'));
  224. $this->assertTrue($collection->has('sample'));
  225. $this->assertTrue($collection->has('testing_dispatch'));
  226. $this->assertSame('TestApp\Shell\AppShell', $collection->get('app'));
  227. $this->assertSame('TestApp\Command\DemoCommand', $collection->get('demo'));
  228. $this->assertSame('TestApp\Shell\I18mShell', $collection->get('i18m'));
  229. $this->assertSame('TestApp\Shell\SampleShell', $collection->get('sample'));
  230. }
  231. /**
  232. * test autodiscovering core shells
  233. *
  234. * @return void
  235. */
  236. public function testAutoDiscoverCore()
  237. {
  238. $collection = new CommandCollection();
  239. $collection->addMany($collection->autoDiscover());
  240. $this->assertTrue($collection->has('version'));
  241. $this->assertTrue($collection->has('routes'));
  242. $this->assertTrue($collection->has('i18n'));
  243. $this->assertTrue($collection->has('orm_cache'));
  244. $this->assertTrue($collection->has('server'));
  245. $this->assertTrue($collection->has('cache'));
  246. $this->assertFalse($collection->has('command_list'), 'Hidden commands should stay hidden');
  247. // These have to be strings as ::class uses the local namespace.
  248. $this->assertSame('Cake\Shell\RoutesShell', $collection->get('routes'));
  249. $this->assertSame('Cake\Shell\I18nShell', $collection->get('i18n'));
  250. $this->assertSame('Cake\Command\VersionCommand', $collection->get('version'));
  251. }
  252. /**
  253. * test missing plugin discovery
  254. *
  255. * @return void
  256. */
  257. public function testDiscoverPluginUnknown()
  258. {
  259. $collection = new CommandCollection();
  260. $this->assertSame([], $collection->discoverPlugin('Nope'));
  261. }
  262. /**
  263. * test autodiscovering plugin shells
  264. *
  265. * @return void
  266. */
  267. public function testDiscoverPlugin()
  268. {
  269. $this->loadPlugins(['TestPlugin', 'Company/TestPluginThree']);
  270. $collection = new CommandCollection();
  271. // Add a dupe to test de-duping
  272. $collection->add('sample', DemoCommand::class);
  273. $result = $collection->discoverPlugin('TestPlugin');
  274. $this->assertArrayHasKey(
  275. 'example',
  276. $result,
  277. 'Used short name for unique plugin shell'
  278. );
  279. $this->assertArrayHasKey(
  280. 'test_plugin.example',
  281. $result,
  282. 'Long names are stored for unique shells'
  283. );
  284. $this->assertArrayNotHasKey('sample', $result, 'Existing command not output');
  285. $this->assertArrayHasKey(
  286. 'test_plugin.sample',
  287. $result,
  288. 'Duplicate shell was given a full alias'
  289. );
  290. $this->assertEquals('TestPlugin\Shell\ExampleShell', $result['example']);
  291. $this->assertEquals($result['example'], $result['test_plugin.example']);
  292. $this->assertEquals('TestPlugin\Shell\SampleShell', $result['test_plugin.sample']);
  293. $result = $collection->discoverPlugin('Company/TestPluginThree');
  294. $this->assertArrayHasKey(
  295. 'company',
  296. $result,
  297. 'Used short name for unique plugin shell'
  298. );
  299. $this->assertArrayHasKey(
  300. 'company/test_plugin_three.company',
  301. $result,
  302. 'Long names are stored as well'
  303. );
  304. $this->assertSame($result['company'], $result['company/test_plugin_three.company']);
  305. $this->clearPlugins();
  306. }
  307. }