ConnectionManagerTest.php 16 KB

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