ConnectionManagerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * Licensed under The MIT License
  5. * For full copyright and license information, please see the LICENSE.txt
  6. * Redistributions of files must retain the above copyright notice
  7. *
  8. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  9. * @link https://cakephp.org CakePHP(tm) Project
  10. * @since 1.2.0
  11. * @license https://opensource.org/licenses/mit-license.php MIT License
  12. */
  13. namespace Cake\Test\TestCase\Datasource;
  14. use BadMethodCallException;
  15. use Cake\Core\Exception\CakeException;
  16. use Cake\Datasource\ConnectionManager;
  17. use Cake\Datasource\Exception\MissingDatasourceException;
  18. use Cake\TestSuite\TestCase;
  19. use InvalidArgumentException;
  20. use TestApp\Datasource\FakeConnection;
  21. /**
  22. * ConnectionManager Test
  23. */
  24. class ConnectionManagerTest extends TestCase
  25. {
  26. /**
  27. * tearDown method
  28. */
  29. public function tearDown(): void
  30. {
  31. parent::tearDown();
  32. $this->clearPlugins();
  33. ConnectionManager::drop('test_variant');
  34. ConnectionManager::dropAlias('other_name');
  35. }
  36. /**
  37. * Data provider for valid config data sets.
  38. *
  39. * @return array
  40. */
  41. public static function configProvider(): array
  42. {
  43. return [
  44. 'Array of data using classname key.' => [[
  45. 'className' => FakeConnection::class,
  46. 'instance' => 'Sqlite',
  47. 'database' => ':memory:',
  48. ]],
  49. 'Direct instance' => [new FakeConnection()],
  50. ];
  51. }
  52. /**
  53. * Test the various valid config() calls.
  54. *
  55. * @dataProvider configProvider
  56. * @param \Cake\Datasource\ConnectionInterface|array $settings
  57. */
  58. public function testConfigVariants($settings): void
  59. {
  60. $this->assertNotContains('test_variant', ConnectionManager::configured(), 'test_variant config should not exist.');
  61. ConnectionManager::setConfig('test_variant', $settings);
  62. $ds = ConnectionManager::get('test_variant');
  63. $this->assertInstanceOf(FakeConnection::class, $ds);
  64. $this->assertContains('test_variant', ConnectionManager::configured());
  65. }
  66. /**
  67. * Test invalid classes cause exceptions
  68. */
  69. public function testConfigInvalidOptions(): void
  70. {
  71. $this->expectException(MissingDatasourceException::class);
  72. ConnectionManager::setConfig('test_variant', [
  73. 'className' => 'Herp\Derp',
  74. ]);
  75. ConnectionManager::get('test_variant');
  76. }
  77. /**
  78. * Test for errors on duplicate config.
  79. */
  80. public function testConfigDuplicateConfig(): void
  81. {
  82. $this->expectException(BadMethodCallException::class);
  83. $this->expectExceptionMessage('Cannot reconfigure existing key "test_variant"');
  84. $settings = [
  85. 'className' => FakeConnection::class,
  86. 'database' => ':memory:',
  87. ];
  88. ConnectionManager::setConfig('test_variant', $settings);
  89. ConnectionManager::setConfig('test_variant', $settings);
  90. }
  91. /**
  92. * Test get() failing on missing config.
  93. */
  94. public function testGetFailOnMissingConfig(): void
  95. {
  96. $this->expectException(CakeException::class);
  97. $this->expectExceptionMessage('The datasource configuration "test_variant" was not found.');
  98. ConnectionManager::get('test_variant');
  99. }
  100. /**
  101. * Test loading configured connections.
  102. */
  103. public function testGet(): void
  104. {
  105. $config = ConnectionManager::getConfig('test');
  106. $this->skipIf(empty($config), 'No test config, skipping');
  107. $ds = ConnectionManager::get('test');
  108. $this->assertSame($ds, ConnectionManager::get('test'));
  109. $this->assertInstanceOf('Cake\Database\Connection', $ds);
  110. $this->assertSame('test', $ds->configName());
  111. }
  112. /**
  113. * Test loading connections without aliases
  114. */
  115. public function testGetNoAlias(): void
  116. {
  117. $this->expectException(CakeException::class);
  118. $this->expectExceptionMessage('The datasource configuration "other_name" was not found.');
  119. $config = ConnectionManager::getConfig('test');
  120. $this->skipIf(empty($config), 'No test config, skipping');
  121. ConnectionManager::alias('test', 'other_name');
  122. ConnectionManager::get('other_name', false);
  123. }
  124. /**
  125. * Test that configured() finds configured sources.
  126. */
  127. public function testConfigured(): void
  128. {
  129. ConnectionManager::setConfig('test_variant', [
  130. 'className' => FakeConnection::class,
  131. 'database' => ':memory:',
  132. ]);
  133. $results = ConnectionManager::configured();
  134. $this->assertContains('test_variant', $results);
  135. }
  136. /**
  137. * testGetPluginDataSource method
  138. */
  139. public function testGetPluginDataSource(): void
  140. {
  141. $this->loadPlugins(['TestPlugin']);
  142. $name = 'test_variant';
  143. $config = ['className' => 'TestPlugin.TestSource', 'foo' => 'bar'];
  144. ConnectionManager::setConfig($name, $config);
  145. $connection = ConnectionManager::get($name);
  146. $this->assertInstanceOf('TestPlugin\Datasource\TestSource', $connection);
  147. unset($config['className']);
  148. $this->assertSame($config + ['name' => 'test_variant'], $connection->config());
  149. }
  150. /**
  151. * Tests that a connection configuration can be deleted in runtime
  152. */
  153. public function testDrop(): void
  154. {
  155. ConnectionManager::setConfig('test_variant', [
  156. 'className' => FakeConnection::class,
  157. 'database' => ':memory:',
  158. ]);
  159. $result = ConnectionManager::configured();
  160. $this->assertContains('test_variant', $result);
  161. $this->assertTrue(ConnectionManager::drop('test_variant'));
  162. $result = ConnectionManager::configured();
  163. $this->assertNotContains('test_variant', $result);
  164. $this->assertFalse(ConnectionManager::drop('probably_does_not_exist'), 'Should return false on failure.');
  165. }
  166. /**
  167. * Test aliasing connections.
  168. */
  169. public function testAlias(): void
  170. {
  171. ConnectionManager::setConfig('test_variant', [
  172. 'className' => FakeConnection::class,
  173. 'database' => ':memory:',
  174. ]);
  175. ConnectionManager::alias('test_variant', 'other_name');
  176. $result = ConnectionManager::get('test_variant');
  177. $this->assertSame($result, ConnectionManager::get('other_name'));
  178. }
  179. /**
  180. * provider for DSN strings.
  181. *
  182. * @return array
  183. */
  184. public function dsnProvider(): array
  185. {
  186. return [
  187. 'no user' => [
  188. 'mysql://localhost:3306/database',
  189. [
  190. 'className' => 'Cake\Database\Connection',
  191. 'driver' => 'Cake\Database\Driver\Mysql',
  192. 'host' => 'localhost',
  193. 'database' => 'database',
  194. 'port' => 3306,
  195. 'scheme' => 'mysql',
  196. ],
  197. ],
  198. 'subdomain host' => [
  199. 'mysql://my.host-name.com:3306/database',
  200. [
  201. 'className' => 'Cake\Database\Connection',
  202. 'driver' => 'Cake\Database\Driver\Mysql',
  203. 'host' => 'my.host-name.com',
  204. 'database' => 'database',
  205. 'port' => 3306,
  206. 'scheme' => 'mysql',
  207. ],
  208. ],
  209. 'user & pass' => [
  210. 'mysql://root:secret@localhost:3306/database?log=1',
  211. [
  212. 'scheme' => 'mysql',
  213. 'className' => 'Cake\Database\Connection',
  214. 'driver' => 'Cake\Database\Driver\Mysql',
  215. 'host' => 'localhost',
  216. 'username' => 'root',
  217. 'password' => 'secret',
  218. 'port' => 3306,
  219. 'database' => 'database',
  220. 'log' => '1',
  221. ],
  222. ],
  223. 'no password' => [
  224. 'mysql://user@localhost:3306/database',
  225. [
  226. 'className' => 'Cake\Database\Connection',
  227. 'driver' => 'Cake\Database\Driver\Mysql',
  228. 'host' => 'localhost',
  229. 'database' => 'database',
  230. 'port' => 3306,
  231. 'scheme' => 'mysql',
  232. 'username' => 'user',
  233. ],
  234. ],
  235. 'empty password' => [
  236. 'mysql://user:@localhost:3306/database',
  237. [
  238. 'className' => 'Cake\Database\Connection',
  239. 'driver' => 'Cake\Database\Driver\Mysql',
  240. 'host' => 'localhost',
  241. 'database' => 'database',
  242. 'port' => 3306,
  243. 'scheme' => 'mysql',
  244. 'username' => 'user',
  245. 'password' => '',
  246. ],
  247. ],
  248. 'sqlite memory' => [
  249. 'sqlite:///:memory:',
  250. [
  251. 'className' => 'Cake\Database\Connection',
  252. 'driver' => 'Cake\Database\Driver\Sqlite',
  253. 'database' => ':memory:',
  254. 'scheme' => 'sqlite',
  255. ],
  256. ],
  257. 'sqlite path' => [
  258. 'sqlite:////absolute/path',
  259. [
  260. 'className' => 'Cake\Database\Connection',
  261. 'driver' => 'Cake\Database\Driver\Sqlite',
  262. 'database' => '/absolute/path',
  263. 'scheme' => 'sqlite',
  264. ],
  265. ],
  266. 'sqlite database query' => [
  267. 'sqlite:///?database=:memory:',
  268. [
  269. 'className' => 'Cake\Database\Connection',
  270. 'driver' => 'Cake\Database\Driver\Sqlite',
  271. 'database' => ':memory:',
  272. 'scheme' => 'sqlite',
  273. ],
  274. ],
  275. 'sqlserver' => [
  276. 'sqlserver://sa:Password12!@.\SQL2012SP1/cakephp?MultipleActiveResultSets=false',
  277. [
  278. 'className' => 'Cake\Database\Connection',
  279. 'driver' => 'Cake\Database\Driver\Sqlserver',
  280. 'host' => '.\SQL2012SP1',
  281. 'MultipleActiveResultSets' => false,
  282. 'password' => 'Password12!',
  283. 'database' => 'cakephp',
  284. 'scheme' => 'sqlserver',
  285. 'username' => 'sa',
  286. ],
  287. ],
  288. 'sqllocaldb' => [
  289. 'sqlserver://username:password@(localdb)\.\DeptSharedLocalDB/database',
  290. [
  291. 'className' => 'Cake\Database\Connection',
  292. 'driver' => 'Cake\Database\Driver\Sqlserver',
  293. 'host' => '(localdb)\.\DeptSharedLocalDB',
  294. 'password' => 'password',
  295. 'database' => 'database',
  296. 'scheme' => 'sqlserver',
  297. 'username' => 'username',
  298. ],
  299. ],
  300. 'classname query arg' => [
  301. 'mysql://localhost/database?className=Custom\Driver',
  302. [
  303. 'className' => 'Cake\Database\Connection',
  304. 'database' => 'database',
  305. 'driver' => 'Custom\Driver',
  306. 'host' => 'localhost',
  307. 'scheme' => 'mysql',
  308. ],
  309. ],
  310. 'classname and port' => [
  311. 'mysql://localhost:3306/database?className=Custom\Driver',
  312. [
  313. 'className' => 'Cake\Database\Connection',
  314. 'database' => 'database',
  315. 'driver' => 'Custom\Driver',
  316. 'host' => 'localhost',
  317. 'scheme' => 'mysql',
  318. 'port' => 3306,
  319. ],
  320. ],
  321. 'custom connection class' => [
  322. 'Cake\Database\Connection://localhost:3306/database?driver=Cake\Database\Driver\Mysql',
  323. [
  324. 'className' => 'Cake\Database\Connection',
  325. 'database' => 'database',
  326. 'driver' => 'Cake\Database\Driver\Mysql',
  327. 'host' => 'localhost',
  328. 'scheme' => 'Cake\Database\Connection',
  329. 'port' => 3306,
  330. ],
  331. ],
  332. 'complex password' => [
  333. 'mysql://user:/?#][{}$%20@!@localhost:3306/database?log=1&quoteIdentifiers=1',
  334. [
  335. 'className' => 'Cake\Database\Connection',
  336. 'database' => 'database',
  337. 'driver' => 'Cake\Database\Driver\Mysql',
  338. 'host' => 'localhost',
  339. 'password' => '/?#][{}$%20@!',
  340. 'port' => 3306,
  341. 'scheme' => 'mysql',
  342. 'username' => 'user',
  343. 'log' => 1,
  344. 'quoteIdentifiers' => 1,
  345. ],
  346. ],
  347. ];
  348. }
  349. /**
  350. * Test parseDsn method.
  351. *
  352. * @dataProvider dsnProvider
  353. */
  354. public function testParseDsn(string $dsn, array $expected): void
  355. {
  356. $result = ConnectionManager::parseDsn($dsn);
  357. $this->assertEquals($expected, $result);
  358. }
  359. /**
  360. * Test parseDsn invalid.
  361. */
  362. public function testParseDsnInvalid(): void
  363. {
  364. $this->expectException(InvalidArgumentException::class);
  365. $this->expectExceptionMessage('The DSN string \'bagof:nope\' could not be parsed.');
  366. $result = ConnectionManager::parseDsn('bagof:nope');
  367. }
  368. /**
  369. * Tests that directly setting an instance in a config, will not return a different
  370. * instance later on
  371. */
  372. public function testConfigWithObject(): void
  373. {
  374. $connection = new FakeConnection();
  375. ConnectionManager::setConfig('test_variant', $connection);
  376. $this->assertSame($connection, ConnectionManager::get('test_variant'));
  377. }
  378. /**
  379. * Tests configuring an instance with a callable
  380. */
  381. public function testConfigWithCallable(): void
  382. {
  383. $connection = new FakeConnection();
  384. $callable = function ($alias) use ($connection) {
  385. $this->assertSame('test_variant', $alias);
  386. return $connection;
  387. };
  388. ConnectionManager::setConfig('test_variant', $callable);
  389. $this->assertSame($connection, ConnectionManager::get('test_variant'));
  390. }
  391. /**
  392. * Tests that setting a config will also correctly set the name for the connection
  393. */
  394. public function testSetConfigName(): void
  395. {
  396. //Set with explicit name
  397. ConnectionManager::setConfig('test_variant', [
  398. 'className' => FakeConnection::class,
  399. 'database' => ':memory:',
  400. ]);
  401. $result = ConnectionManager::get('test_variant');
  402. $this->assertSame('test_variant', $result->configName());
  403. ConnectionManager::drop('test_variant');
  404. ConnectionManager::setConfig([
  405. 'test_variant' => [
  406. 'className' => FakeConnection::class,
  407. 'database' => ':memory:',
  408. ],
  409. ]);
  410. $result = ConnectionManager::get('test_variant');
  411. $this->assertSame('test_variant', $result->configName());
  412. }
  413. }