ConnectionManagerTest.php 20 KB

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