CommandCollectionTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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. public function testAddInvalidInstance()
  121. {
  122. $this->expectException(\InvalidArgumentException::class);
  123. $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\'. It is not a subclass of Cake\Console\Shell');
  124. $collection = new CommandCollection();
  125. $shell = new stdClass();
  126. $collection->add('routes', $shell);
  127. }
  128. /**
  129. * Provider for invalid names.
  130. *
  131. * @return array
  132. */
  133. public function invalidNameProvider()
  134. {
  135. return [
  136. // Empty
  137. [''],
  138. // Leading spaces
  139. [' spaced'],
  140. // Trailing spaces
  141. ['spaced '],
  142. // Too many words
  143. ['one two three four'],
  144. ];
  145. }
  146. /**
  147. * test adding a command instance.
  148. *
  149. * @dataProvider invalidNameProvider
  150. * @return void
  151. */
  152. public function testAddCommandInvalidName($name)
  153. {
  154. $this->expectException(InvalidArgumentException::class);
  155. $this->expectExceptionMessage("The command name `$name` is invalid.");
  156. $collection = new CommandCollection();
  157. $collection->add($name, DemoCommand::class);
  158. }
  159. /**
  160. * Class names that are not shells should fail
  161. */
  162. public function testInvalidShellClassName()
  163. {
  164. $this->expectException(\InvalidArgumentException::class);
  165. $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\'. It is not a subclass of Cake\Console\Shell');
  166. $collection = new CommandCollection();
  167. $collection->add('routes', stdClass::class);
  168. }
  169. /**
  170. * Test removing a command
  171. *
  172. * @return void
  173. */
  174. public function testRemove()
  175. {
  176. $collection = new CommandCollection();
  177. $collection->add('routes', RoutesShell::class);
  178. $this->assertSame($collection, $collection->remove('routes'));
  179. $this->assertFalse($collection->has('routes'));
  180. }
  181. /**
  182. * Removing an unknown command does not fail
  183. *
  184. * @return void
  185. */
  186. public function testRemoveUnknown()
  187. {
  188. $collection = new CommandCollection();
  189. $this->assertSame($collection, $collection->remove('nope'));
  190. $this->assertFalse($collection->has('nope'));
  191. }
  192. /**
  193. * test getIterator
  194. *
  195. * @return void
  196. */
  197. public function testGetIterator()
  198. {
  199. $in = [
  200. 'i18n' => I18nShell::class,
  201. 'routes' => RoutesShell::class,
  202. ];
  203. $collection = new CommandCollection($in);
  204. $out = [];
  205. foreach ($collection as $key => $value) {
  206. $out[$key] = $value;
  207. }
  208. $this->assertEquals($in, $out);
  209. }
  210. /**
  211. * test autodiscovering app shells
  212. *
  213. * @return void
  214. */
  215. public function testAutoDiscoverApp()
  216. {
  217. $collection = new CommandCollection();
  218. $collection->addMany($collection->autoDiscover());
  219. $this->assertTrue($collection->has('app'));
  220. $this->assertTrue($collection->has('demo'));
  221. $this->assertTrue($collection->has('i18m'));
  222. $this->assertTrue($collection->has('sample'));
  223. $this->assertTrue($collection->has('testing_dispatch'));
  224. $this->assertSame('TestApp\Shell\AppShell', $collection->get('app'));
  225. $this->assertSame('TestApp\Command\DemoCommand', $collection->get('demo'));
  226. $this->assertSame('TestApp\Shell\I18mShell', $collection->get('i18m'));
  227. $this->assertSame('TestApp\Shell\SampleShell', $collection->get('sample'));
  228. }
  229. /**
  230. * test autodiscovering core shells
  231. *
  232. * @return void
  233. */
  234. public function testAutoDiscoverCore()
  235. {
  236. $collection = new CommandCollection();
  237. $collection->addMany($collection->autoDiscover());
  238. $this->assertTrue($collection->has('version'));
  239. $this->assertTrue($collection->has('routes'));
  240. $this->assertTrue($collection->has('i18n'));
  241. $this->assertTrue($collection->has('orm_cache'));
  242. $this->assertTrue($collection->has('server'));
  243. $this->assertTrue($collection->has('cache'));
  244. $this->assertFalse($collection->has('command_list'), 'Hidden commands should stay hidden');
  245. // These have to be strings as ::class uses the local namespace.
  246. $this->assertSame('Cake\Shell\RoutesShell', $collection->get('routes'));
  247. $this->assertSame('Cake\Shell\I18nShell', $collection->get('i18n'));
  248. $this->assertSame('Cake\Command\VersionCommand', $collection->get('version'));
  249. }
  250. /**
  251. * test missing plugin discovery
  252. *
  253. * @return void
  254. */
  255. public function testDiscoverPluginUnknown()
  256. {
  257. $collection = new CommandCollection();
  258. $this->assertSame([], $collection->discoverPlugin('Nope'));
  259. }
  260. /**
  261. * test autodiscovering plugin shells
  262. *
  263. * @return void
  264. */
  265. public function testDiscoverPlugin()
  266. {
  267. $this->loadPlugins(['TestPlugin', 'Company/TestPluginThree']);
  268. $collection = new CommandCollection();
  269. // Add a dupe to test de-duping
  270. $collection->add('sample', DemoCommand::class);
  271. $result = $collection->discoverPlugin('TestPlugin');
  272. $this->assertArrayHasKey(
  273. 'example',
  274. $result,
  275. 'Used short name for unique plugin shell'
  276. );
  277. $this->assertArrayHasKey(
  278. 'test_plugin.example',
  279. $result,
  280. 'Long names are stored for unique shells'
  281. );
  282. $this->assertArrayNotHasKey('sample', $result, 'Existing command not output');
  283. $this->assertArrayHasKey(
  284. 'test_plugin.sample',
  285. $result,
  286. 'Duplicate shell was given a full alias'
  287. );
  288. $this->assertEquals('TestPlugin\Shell\ExampleShell', $result['example']);
  289. $this->assertEquals($result['example'], $result['test_plugin.example']);
  290. $this->assertEquals('TestPlugin\Shell\SampleShell', $result['test_plugin.sample']);
  291. $result = $collection->discoverPlugin('Company/TestPluginThree');
  292. $this->assertArrayHasKey(
  293. 'company',
  294. $result,
  295. 'Used short name for unique plugin shell'
  296. );
  297. $this->assertArrayHasKey(
  298. 'company/test_plugin_three.company',
  299. $result,
  300. 'Long names are stored as well'
  301. );
  302. $this->assertSame($result['company'], $result['company/test_plugin_three.company']);
  303. $this->clearPlugins();
  304. }
  305. }