MigrationsTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace Test\DB;
  8. use Doctrine\DBAL\Schema\Column;
  9. use Doctrine\DBAL\Schema\ForeignKeyConstraint;
  10. use Doctrine\DBAL\Schema\Index;
  11. use Doctrine\DBAL\Schema\Schema;
  12. use Doctrine\DBAL\Schema\SchemaException;
  13. use Doctrine\DBAL\Schema\Sequence;
  14. use Doctrine\DBAL\Schema\Table;
  15. use Doctrine\DBAL\Types\Type;
  16. use OC\DB\Connection;
  17. use OC\DB\MigrationService;
  18. use OC\DB\SchemaWrapper;
  19. use OC\Migration\MetadataManager;
  20. use OCP\App\IAppManager;
  21. use OCP\IDBConnection;
  22. use OCP\Migration\Attributes\AddColumn;
  23. use OCP\Migration\Attributes\AddIndex;
  24. use OCP\Migration\Attributes\ColumnType;
  25. use OCP\Migration\Attributes\CreateTable;
  26. use OCP\Migration\Attributes\DropColumn;
  27. use OCP\Migration\Attributes\DropIndex;
  28. use OCP\Migration\Attributes\DropTable;
  29. use OCP\Migration\Attributes\IndexType;
  30. use OCP\Migration\Attributes\ModifyColumn;
  31. use OCP\Migration\IMigrationStep;
  32. use OCP\Server;
  33. use PHPUnit\Framework\MockObject\MockObject;
  34. /**
  35. * Class MigrationsTest
  36. *
  37. * @package Test\DB
  38. */
  39. class MigrationsTest extends \Test\TestCase {
  40. private MigrationService|MockObject $migrationService;
  41. private MockObject|IDBConnection $db;
  42. private IAppManager $appManager;
  43. protected function setUp(): void {
  44. parent::setUp();
  45. $this->db = $this->createMock(Connection::class);
  46. $this->db->expects($this->any())->method('getPrefix')->willReturn('test_oc_');
  47. $this->migrationService = new MigrationService('testing', $this->db);
  48. $this->appManager = Server::get(IAppManager::class);
  49. }
  50. public function testGetters(): void {
  51. $this->assertEquals('testing', $this->migrationService->getApp());
  52. $this->assertEquals(\OC::$SERVERROOT . '/apps/testing/lib/Migration', $this->migrationService->getMigrationsDirectory());
  53. $this->assertEquals('OCA\Testing\Migration', $this->migrationService->getMigrationsNamespace());
  54. $this->assertEquals('test_oc_migrations', $this->migrationService->getMigrationsTableName());
  55. }
  56. public function testCore(): void {
  57. $this->migrationService = new MigrationService('core', $this->db);
  58. $this->assertEquals('core', $this->migrationService->getApp());
  59. $this->assertEquals(\OC::$SERVERROOT . '/core/Migrations', $this->migrationService->getMigrationsDirectory());
  60. $this->assertEquals('OC\Core\Migrations', $this->migrationService->getMigrationsNamespace());
  61. $this->assertEquals('test_oc_migrations', $this->migrationService->getMigrationsTableName());
  62. }
  63. public function testExecuteUnknownStep(): void {
  64. $this->expectException(\InvalidArgumentException::class);
  65. $this->expectExceptionMessage('Version 20170130180000 is unknown.');
  66. $this->migrationService->executeStep('20170130180000');
  67. }
  68. public function testUnknownApp(): void {
  69. $this->expectException(\Exception::class);
  70. $this->expectExceptionMessage('App not found');
  71. $migrationService = new MigrationService('unknown-bloody-app', $this->db);
  72. }
  73. public function testExecuteStepWithUnknownClass(): void {
  74. $this->expectException(\Exception::class);
  75. $this->expectExceptionMessage('Migration step \'X\' is unknown');
  76. $this->migrationService = $this->getMockBuilder(MigrationService::class)
  77. ->setMethods(['findMigrations'])
  78. ->setConstructorArgs(['testing', $this->db])
  79. ->getMock();
  80. $this->migrationService->expects($this->any())->method('findMigrations')->willReturn(
  81. ['20170130180000' => 'X', '20170130180001' => 'Y', '20170130180002' => 'Z', '20170130180003' => 'A']
  82. );
  83. $this->migrationService->executeStep('20170130180000');
  84. }
  85. public function testExecuteStepWithSchemaChange(): void {
  86. $schema = $this->createMock(Schema::class);
  87. $this->db->expects($this->any())
  88. ->method('createSchema')
  89. ->willReturn($schema);
  90. $this->db->expects($this->once())
  91. ->method('migrateToSchema');
  92. $wrappedSchema = $this->createMock(Schema::class);
  93. $wrappedSchema->expects($this->exactly(2))
  94. ->method('getTables')
  95. ->willReturn([]);
  96. $wrappedSchema->expects($this->exactly(2))
  97. ->method('getSequences')
  98. ->willReturn([]);
  99. $schemaResult = $this->createMock(SchemaWrapper::class);
  100. $schemaResult->expects($this->once())
  101. ->method('getWrappedSchema')
  102. ->willReturn($wrappedSchema);
  103. $step = $this->createMock(IMigrationStep::class);
  104. $step->expects($this->once())
  105. ->method('preSchemaChange');
  106. $step->expects($this->once())
  107. ->method('changeSchema')
  108. ->willReturn($schemaResult);
  109. $step->expects($this->once())
  110. ->method('postSchemaChange');
  111. $this->migrationService = $this->getMockBuilder(MigrationService::class)
  112. ->setMethods(['createInstance'])
  113. ->setConstructorArgs(['testing', $this->db])
  114. ->getMock();
  115. $this->migrationService->expects($this->any())
  116. ->method('createInstance')
  117. ->with('20170130180000')
  118. ->willReturn($step);
  119. $this->migrationService->executeStep('20170130180000');
  120. }
  121. public function testExecuteStepWithoutSchemaChange(): void {
  122. $schema = $this->createMock(Schema::class);
  123. $this->db->expects($this->any())
  124. ->method('createSchema')
  125. ->willReturn($schema);
  126. $this->db->expects($this->never())
  127. ->method('migrateToSchema');
  128. $step = $this->createMock(IMigrationStep::class);
  129. $step->expects($this->once())
  130. ->method('preSchemaChange');
  131. $step->expects($this->once())
  132. ->method('changeSchema')
  133. ->willReturn(null);
  134. $step->expects($this->once())
  135. ->method('postSchemaChange');
  136. $this->migrationService = $this->getMockBuilder(MigrationService::class)
  137. ->setMethods(['createInstance'])
  138. ->setConstructorArgs(['testing', $this->db])
  139. ->getMock();
  140. $this->migrationService->expects($this->any())
  141. ->method('createInstance')
  142. ->with('20170130180000')
  143. ->willReturn($step);
  144. $this->migrationService->executeStep('20170130180000');
  145. }
  146. public function dataGetMigration() {
  147. return [
  148. ['current', '20170130180001'],
  149. ['prev', '20170130180000'],
  150. ['next', '20170130180002'],
  151. ['latest', '20170130180003'],
  152. ];
  153. }
  154. /**
  155. * @dataProvider dataGetMigration
  156. * @param string $alias
  157. * @param string $expected
  158. */
  159. public function testGetMigration($alias, $expected): void {
  160. $this->migrationService = $this->getMockBuilder(MigrationService::class)
  161. ->setMethods(['getMigratedVersions', 'findMigrations'])
  162. ->setConstructorArgs(['testing', $this->db])
  163. ->getMock();
  164. $this->migrationService->expects($this->any())->method('getMigratedVersions')->willReturn(
  165. ['20170130180000', '20170130180001']
  166. );
  167. $this->migrationService->expects($this->any())->method('findMigrations')->willReturn(
  168. ['20170130180000' => 'X', '20170130180001' => 'Y', '20170130180002' => 'Z', '20170130180003' => 'A']
  169. );
  170. $this->assertEquals(
  171. ['20170130180000', '20170130180001', '20170130180002', '20170130180003'],
  172. $this->migrationService->getAvailableVersions());
  173. $migration = $this->migrationService->getMigration($alias);
  174. $this->assertEquals($expected, $migration);
  175. }
  176. public function testMigrate(): void {
  177. $this->migrationService = $this->getMockBuilder(MigrationService::class)
  178. ->setMethods(['getMigratedVersions', 'findMigrations', 'executeStep'])
  179. ->setConstructorArgs(['testing', $this->db])
  180. ->getMock();
  181. $this->migrationService->expects($this->any())->method('getMigratedVersions')->willReturn(
  182. ['20170130180000', '20170130180001']
  183. );
  184. $this->migrationService->expects($this->any())->method('findMigrations')->willReturn(
  185. ['20170130180000' => 'X', '20170130180001' => 'Y', '20170130180002' => 'Z', '20170130180003' => 'A']
  186. );
  187. $this->assertEquals(
  188. ['20170130180000', '20170130180001', '20170130180002', '20170130180003'],
  189. $this->migrationService->getAvailableVersions());
  190. $this->migrationService->expects($this->exactly(2))->method('executeStep')
  191. ->withConsecutive(['20170130180002'], ['20170130180003']);
  192. $this->migrationService->migrate();
  193. }
  194. public function testEnsureOracleConstraintsValid(): void {
  195. $column = $this->createMock(Column::class);
  196. $column->expects($this->once())
  197. ->method('getName')
  198. ->willReturn(\str_repeat('a', 30));
  199. $index = $this->createMock(Index::class);
  200. $index->expects($this->once())
  201. ->method('getName')
  202. ->willReturn(\str_repeat('a', 30));
  203. $foreignKey = $this->createMock(ForeignKeyConstraint::class);
  204. $foreignKey->expects($this->once())
  205. ->method('getName')
  206. ->willReturn(\str_repeat('a', 30));
  207. $table = $this->createMock(Table::class);
  208. $table->expects($this->atLeastOnce())
  209. ->method('getName')
  210. ->willReturn(\str_repeat('a', 30));
  211. $sequence = $this->createMock(Sequence::class);
  212. $sequence->expects($this->atLeastOnce())
  213. ->method('getName')
  214. ->willReturn(\str_repeat('a', 30));
  215. $primaryKey = $this->createMock(Index::class);
  216. $primaryKey->expects($this->once())
  217. ->method('getName')
  218. ->willReturn(\str_repeat('a', 30));
  219. $table->expects($this->once())
  220. ->method('getColumns')
  221. ->willReturn([$column]);
  222. $table->expects($this->once())
  223. ->method('getIndexes')
  224. ->willReturn([$index]);
  225. $table->expects($this->once())
  226. ->method('getForeignKeys')
  227. ->willReturn([$foreignKey]);
  228. $table->expects($this->once())
  229. ->method('getPrimaryKey')
  230. ->willReturn($primaryKey);
  231. $schema = $this->createMock(Schema::class);
  232. $schema->expects($this->once())
  233. ->method('getTables')
  234. ->willReturn([$table]);
  235. $schema->expects($this->once())
  236. ->method('getSequences')
  237. ->willReturn([$sequence]);
  238. $sourceSchema = $this->createMock(Schema::class);
  239. $sourceSchema->expects($this->any())
  240. ->method('getTable')
  241. ->willThrowException(new SchemaException());
  242. $sourceSchema->expects($this->any())
  243. ->method('hasSequence')
  244. ->willReturn(false);
  245. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  246. }
  247. public function testEnsureOracleConstraintsValidWithPrimaryKey(): void {
  248. $index = $this->createMock(Index::class);
  249. $index->expects($this->any())
  250. ->method('getName')
  251. ->willReturn(\str_repeat('a', 30));
  252. $table = $this->createMock(Table::class);
  253. $table->expects($this->any())
  254. ->method('getName')
  255. ->willReturn(\str_repeat('a', 26));
  256. $table->expects($this->once())
  257. ->method('getColumns')
  258. ->willReturn([]);
  259. $table->expects($this->once())
  260. ->method('getIndexes')
  261. ->willReturn([]);
  262. $table->expects($this->once())
  263. ->method('getForeignKeys')
  264. ->willReturn([]);
  265. $table->expects($this->once())
  266. ->method('getPrimaryKey')
  267. ->willReturn($index);
  268. $schema = $this->createMock(Schema::class);
  269. $schema->expects($this->once())
  270. ->method('getTables')
  271. ->willReturn([$table]);
  272. $schema->expects($this->once())
  273. ->method('getSequences')
  274. ->willReturn([]);
  275. $sourceSchema = $this->createMock(Schema::class);
  276. $sourceSchema->expects($this->any())
  277. ->method('getTable')
  278. ->willThrowException(new SchemaException());
  279. $sourceSchema->expects($this->any())
  280. ->method('hasSequence')
  281. ->willReturn(false);
  282. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  283. }
  284. public function testEnsureOracleConstraintsValidWithPrimaryKeyDefault(): void {
  285. $defaultName = 'PRIMARY';
  286. if ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_POSTGRES) {
  287. $defaultName = \str_repeat('a', 26) . '_' . \str_repeat('b', 30) . '_seq';
  288. } elseif ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) {
  289. $defaultName = \str_repeat('a', 26) . '_seq';
  290. }
  291. $index = $this->createMock(Index::class);
  292. $index->expects($this->any())
  293. ->method('getName')
  294. ->willReturn($defaultName);
  295. $index->expects($this->any())
  296. ->method('getColumns')
  297. ->willReturn([\str_repeat('b', 30)]);
  298. $table = $this->createMock(Table::class);
  299. $table->expects($this->any())
  300. ->method('getName')
  301. ->willReturn(\str_repeat('a', 25));
  302. $table->expects($this->once())
  303. ->method('getColumns')
  304. ->willReturn([]);
  305. $table->expects($this->once())
  306. ->method('getIndexes')
  307. ->willReturn([]);
  308. $table->expects($this->once())
  309. ->method('getForeignKeys')
  310. ->willReturn([]);
  311. $table->expects($this->once())
  312. ->method('getPrimaryKey')
  313. ->willReturn($index);
  314. $schema = $this->createMock(Schema::class);
  315. $schema->expects($this->once())
  316. ->method('getTables')
  317. ->willReturn([$table]);
  318. $schema->expects($this->once())
  319. ->method('getSequences')
  320. ->willReturn([]);
  321. $sourceSchema = $this->createMock(Schema::class);
  322. $sourceSchema->expects($this->any())
  323. ->method('getTable')
  324. ->willThrowException(new SchemaException());
  325. $sourceSchema->expects($this->any())
  326. ->method('hasSequence')
  327. ->willReturn(false);
  328. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  329. }
  330. public function testEnsureOracleConstraintsTooLongTableName(): void {
  331. $this->expectException(\InvalidArgumentException::class);
  332. $table = $this->createMock(Table::class);
  333. $table->expects($this->any())
  334. ->method('getName')
  335. ->willReturn(\str_repeat('a', 31));
  336. $schema = $this->createMock(Schema::class);
  337. $schema->expects($this->once())
  338. ->method('getTables')
  339. ->willReturn([$table]);
  340. $sourceSchema = $this->createMock(Schema::class);
  341. $sourceSchema->expects($this->any())
  342. ->method('getTable')
  343. ->willThrowException(new SchemaException());
  344. $sourceSchema->expects($this->any())
  345. ->method('hasSequence')
  346. ->willReturn(false);
  347. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  348. }
  349. public function testEnsureOracleConstraintsTooLongPrimaryWithDefault(): void {
  350. $this->expectException(\InvalidArgumentException::class);
  351. $defaultName = 'PRIMARY';
  352. if ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_POSTGRES) {
  353. $defaultName = \str_repeat('a', 27) . '_' . \str_repeat('b', 30) . '_seq';
  354. } elseif ($this->db->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) {
  355. $defaultName = \str_repeat('a', 27) . '_seq';
  356. }
  357. $index = $this->createMock(Index::class);
  358. $index->expects($this->any())
  359. ->method('getName')
  360. ->willReturn($defaultName);
  361. $index->expects($this->any())
  362. ->method('getColumns')
  363. ->willReturn([\str_repeat('b', 30)]);
  364. $table = $this->createMock(Table::class);
  365. $table->expects($this->any())
  366. ->method('getName')
  367. ->willReturn(\str_repeat('a', 27));
  368. $table->expects($this->once())
  369. ->method('getColumns')
  370. ->willReturn([]);
  371. $table->expects($this->once())
  372. ->method('getIndexes')
  373. ->willReturn([]);
  374. $table->expects($this->once())
  375. ->method('getForeignKeys')
  376. ->willReturn([]);
  377. $table->expects($this->once())
  378. ->method('getPrimaryKey')
  379. ->willReturn($index);
  380. $schema = $this->createMock(Schema::class);
  381. $schema->expects($this->once())
  382. ->method('getTables')
  383. ->willReturn([$table]);
  384. $sourceSchema = $this->createMock(Schema::class);
  385. $sourceSchema->expects($this->any())
  386. ->method('getTable')
  387. ->willThrowException(new SchemaException());
  388. $sourceSchema->expects($this->any())
  389. ->method('hasSequence')
  390. ->willReturn(false);
  391. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  392. }
  393. public function testEnsureOracleConstraintsTooLongPrimaryWithName(): void {
  394. $this->expectException(\InvalidArgumentException::class);
  395. $index = $this->createMock(Index::class);
  396. $index->expects($this->any())
  397. ->method('getName')
  398. ->willReturn(\str_repeat('a', 31));
  399. $table = $this->createMock(Table::class);
  400. $table->expects($this->any())
  401. ->method('getName')
  402. ->willReturn(\str_repeat('a', 26));
  403. $table->expects($this->once())
  404. ->method('getColumns')
  405. ->willReturn([]);
  406. $table->expects($this->once())
  407. ->method('getIndexes')
  408. ->willReturn([]);
  409. $table->expects($this->once())
  410. ->method('getForeignKeys')
  411. ->willReturn([]);
  412. $table->expects($this->once())
  413. ->method('getPrimaryKey')
  414. ->willReturn($index);
  415. $schema = $this->createMock(Schema::class);
  416. $schema->expects($this->once())
  417. ->method('getTables')
  418. ->willReturn([$table]);
  419. $sourceSchema = $this->createMock(Schema::class);
  420. $sourceSchema->expects($this->any())
  421. ->method('getTable')
  422. ->willThrowException(new SchemaException());
  423. $sourceSchema->expects($this->any())
  424. ->method('hasSequence')
  425. ->willReturn(false);
  426. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  427. }
  428. public function testEnsureOracleConstraintsTooLongColumnName(): void {
  429. $this->expectException(\InvalidArgumentException::class);
  430. $column = $this->createMock(Column::class);
  431. $column->expects($this->any())
  432. ->method('getName')
  433. ->willReturn(\str_repeat('a', 31));
  434. $table = $this->createMock(Table::class);
  435. $table->expects($this->any())
  436. ->method('getName')
  437. ->willReturn(\str_repeat('a', 30));
  438. $table->expects($this->once())
  439. ->method('getColumns')
  440. ->willReturn([$column]);
  441. $schema = $this->createMock(Schema::class);
  442. $schema->expects($this->once())
  443. ->method('getTables')
  444. ->willReturn([$table]);
  445. $sourceSchema = $this->createMock(Schema::class);
  446. $sourceSchema->expects($this->any())
  447. ->method('getTable')
  448. ->willThrowException(new SchemaException());
  449. $sourceSchema->expects($this->any())
  450. ->method('hasSequence')
  451. ->willReturn(false);
  452. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  453. }
  454. public function testEnsureOracleConstraintsTooLongIndexName(): void {
  455. $this->expectException(\InvalidArgumentException::class);
  456. $index = $this->createMock(Index::class);
  457. $index->expects($this->any())
  458. ->method('getName')
  459. ->willReturn(\str_repeat('a', 31));
  460. $table = $this->createMock(Table::class);
  461. $table->expects($this->any())
  462. ->method('getName')
  463. ->willReturn(\str_repeat('a', 30));
  464. $table->expects($this->once())
  465. ->method('getColumns')
  466. ->willReturn([]);
  467. $table->expects($this->once())
  468. ->method('getIndexes')
  469. ->willReturn([$index]);
  470. $schema = $this->createMock(Schema::class);
  471. $schema->expects($this->once())
  472. ->method('getTables')
  473. ->willReturn([$table]);
  474. $sourceSchema = $this->createMock(Schema::class);
  475. $sourceSchema->expects($this->any())
  476. ->method('getTable')
  477. ->willThrowException(new SchemaException());
  478. $sourceSchema->expects($this->any())
  479. ->method('hasSequence')
  480. ->willReturn(false);
  481. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  482. }
  483. public function testEnsureOracleConstraintsTooLongForeignKeyName(): void {
  484. $this->expectException(\InvalidArgumentException::class);
  485. $foreignKey = $this->createMock(ForeignKeyConstraint::class);
  486. $foreignKey->expects($this->any())
  487. ->method('getName')
  488. ->willReturn(\str_repeat('a', 31));
  489. $table = $this->createMock(Table::class);
  490. $table->expects($this->any())
  491. ->method('getName')
  492. ->willReturn(\str_repeat('a', 30));
  493. $table->expects($this->once())
  494. ->method('getColumns')
  495. ->willReturn([]);
  496. $table->expects($this->once())
  497. ->method('getIndexes')
  498. ->willReturn([]);
  499. $table->expects($this->once())
  500. ->method('getForeignKeys')
  501. ->willReturn([$foreignKey]);
  502. $schema = $this->createMock(Schema::class);
  503. $schema->expects($this->once())
  504. ->method('getTables')
  505. ->willReturn([$table]);
  506. $sourceSchema = $this->createMock(Schema::class);
  507. $sourceSchema->expects($this->any())
  508. ->method('getTable')
  509. ->willThrowException(new SchemaException());
  510. $sourceSchema->expects($this->any())
  511. ->method('hasSequence')
  512. ->willReturn(false);
  513. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  514. }
  515. public function testEnsureOracleConstraintsNoPrimaryKey(): void {
  516. $this->markTestSkipped('Test disabled for now due to multiple reasons, see https://github.com/nextcloud/server/pull/31580#issuecomment-1069182234 for details.');
  517. $this->expectException(\InvalidArgumentException::class);
  518. $table = $this->createMock(Table::class);
  519. $table->expects($this->atLeastOnce())
  520. ->method('getName')
  521. ->willReturn(\str_repeat('a', 30));
  522. $table->expects($this->once())
  523. ->method('getColumns')
  524. ->willReturn([]);
  525. $table->expects($this->once())
  526. ->method('getIndexes')
  527. ->willReturn([]);
  528. $table->expects($this->once())
  529. ->method('getForeignKeys')
  530. ->willReturn([]);
  531. $table->expects($this->once())
  532. ->method('getPrimaryKey')
  533. ->willReturn(null);
  534. $schema = $this->createMock(Schema::class);
  535. $schema->expects($this->once())
  536. ->method('getTables')
  537. ->willReturn([$table]);
  538. $schema->expects($this->once())
  539. ->method('getSequences')
  540. ->willReturn([]);
  541. $sourceSchema = $this->createMock(Schema::class);
  542. $sourceSchema->expects($this->any())
  543. ->method('getTable')
  544. ->willThrowException(new SchemaException());
  545. $sourceSchema->expects($this->any())
  546. ->method('hasSequence')
  547. ->willReturn(false);
  548. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  549. }
  550. public function testEnsureOracleConstraintsTooLongSequenceName(): void {
  551. $this->expectException(\InvalidArgumentException::class);
  552. $sequence = $this->createMock(Sequence::class);
  553. $sequence->expects($this->any())
  554. ->method('getName')
  555. ->willReturn(\str_repeat('a', 31));
  556. $schema = $this->createMock(Schema::class);
  557. $schema->expects($this->once())
  558. ->method('getTables')
  559. ->willReturn([]);
  560. $schema->expects($this->once())
  561. ->method('getSequences')
  562. ->willReturn([$sequence]);
  563. $sourceSchema = $this->createMock(Schema::class);
  564. $sourceSchema->expects($this->any())
  565. ->method('getTable')
  566. ->willThrowException(new SchemaException());
  567. $sourceSchema->expects($this->any())
  568. ->method('hasSequence')
  569. ->willReturn(false);
  570. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  571. }
  572. public function testEnsureOracleConstraintsBooleanNotNull(): void {
  573. $this->expectException(\InvalidArgumentException::class);
  574. $column = $this->createMock(Column::class);
  575. $column->expects($this->any())
  576. ->method('getName')
  577. ->willReturn('aaaa');
  578. $column->expects($this->any())
  579. ->method('getType')
  580. ->willReturn(Type::getType('boolean'));
  581. $column->expects($this->any())
  582. ->method('getNotnull')
  583. ->willReturn(true);
  584. $table = $this->createMock(Table::class);
  585. $table->expects($this->any())
  586. ->method('getName')
  587. ->willReturn(\str_repeat('a', 30));
  588. $table->expects($this->once())
  589. ->method('getColumns')
  590. ->willReturn([$column]);
  591. $schema = $this->createMock(Schema::class);
  592. $schema->expects($this->once())
  593. ->method('getTables')
  594. ->willReturn([$table]);
  595. $sourceSchema = $this->createMock(Schema::class);
  596. $sourceSchema->expects($this->any())
  597. ->method('getTable')
  598. ->willThrowException(new SchemaException());
  599. $sourceSchema->expects($this->any())
  600. ->method('hasSequence')
  601. ->willReturn(false);
  602. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  603. }
  604. public function testEnsureOracleConstraintsStringLength4000(): void {
  605. $this->expectException(\InvalidArgumentException::class);
  606. $column = $this->createMock(Column::class);
  607. $column->expects($this->any())
  608. ->method('getName')
  609. ->willReturn('aaaa');
  610. $column->expects($this->any())
  611. ->method('getType')
  612. ->willReturn(Type::getType('string'));
  613. $column->expects($this->any())
  614. ->method('getLength')
  615. ->willReturn(4001);
  616. $table = $this->createMock(Table::class);
  617. $table->expects($this->any())
  618. ->method('getName')
  619. ->willReturn(\str_repeat('a', 30));
  620. $table->expects($this->once())
  621. ->method('getColumns')
  622. ->willReturn([$column]);
  623. $schema = $this->createMock(Schema::class);
  624. $schema->expects($this->once())
  625. ->method('getTables')
  626. ->willReturn([$table]);
  627. $sourceSchema = $this->createMock(Schema::class);
  628. $sourceSchema->expects($this->any())
  629. ->method('getTable')
  630. ->willThrowException(new SchemaException());
  631. $sourceSchema->expects($this->any())
  632. ->method('hasSequence')
  633. ->willReturn(false);
  634. self::invokePrivate($this->migrationService, 'ensureOracleConstraints', [$sourceSchema, $schema, 3]);
  635. }
  636. public function testExtractMigrationAttributes(): void {
  637. $metadataManager = Server::get(MetadataManager::class);
  638. $this->appManager->loadApp('testing');
  639. $this->assertEquals($this->getMigrationMetadata(), json_decode(json_encode($metadataManager->extractMigrationAttributes('testing')), true));
  640. $this->appManager->disableApp('testing');
  641. }
  642. public function testDeserializeMigrationMetadata(): void {
  643. $metadataManager = Server::get(MetadataManager::class);
  644. $this->assertEquals(
  645. [
  646. 'core' => [],
  647. 'apps' => [
  648. 'testing' => [
  649. '30000Date20240102030405' => [
  650. new DropTable('old_table'),
  651. new CreateTable('new_table',
  652. description: 'Table is used to store things, but also to get more things',
  653. notes: ['this is a notice', 'and another one, if really needed']
  654. ),
  655. new AddColumn('my_table'),
  656. new AddColumn('my_table', 'another_field'),
  657. new AddColumn('other_table', 'last_one', ColumnType::DATE),
  658. new AddIndex('my_table'),
  659. new AddIndex('my_table', IndexType::PRIMARY),
  660. new DropColumn('other_table'),
  661. new DropColumn('other_table', 'old_column',
  662. description: 'field is not used anymore and replaced by \'last_one\''
  663. ),
  664. new DropIndex('other_table'),
  665. new ModifyColumn('other_table'),
  666. new ModifyColumn('other_table', 'this_field'),
  667. new ModifyColumn('other_table', 'this_field', ColumnType::BIGINT)
  668. ]
  669. ]
  670. ]
  671. ],
  672. $metadataManager->getMigrationsAttributesFromReleaseMetadata(
  673. [
  674. 'core' => [],
  675. 'apps' => ['testing' => $this->getMigrationMetadata()]
  676. ]
  677. )
  678. );
  679. }
  680. private function getMigrationMetadata(): array {
  681. return [
  682. '30000Date20240102030405' => [
  683. [
  684. 'class' => 'OCP\\Migration\\Attributes\\DropTable',
  685. 'table' => 'old_table',
  686. 'description' => '',
  687. 'notes' => [],
  688. 'columns' => []
  689. ],
  690. [
  691. 'class' => 'OCP\\Migration\\Attributes\\CreateTable',
  692. 'table' => 'new_table',
  693. 'description' => 'Table is used to store things, but also to get more things',
  694. 'notes' =>
  695. [
  696. 'this is a notice',
  697. 'and another one, if really needed'
  698. ],
  699. 'columns' => []
  700. ],
  701. [
  702. 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
  703. 'table' => 'my_table',
  704. 'description' => '',
  705. 'notes' => [],
  706. 'name' => '',
  707. 'type' => ''
  708. ],
  709. [
  710. 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
  711. 'table' => 'my_table',
  712. 'description' => '',
  713. 'notes' => [],
  714. 'name' => 'another_field',
  715. 'type' => ''
  716. ],
  717. [
  718. 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
  719. 'table' => 'other_table',
  720. 'description' => '',
  721. 'notes' => [],
  722. 'name' => 'last_one',
  723. 'type' => 'date'
  724. ],
  725. [
  726. 'class' => 'OCP\\Migration\\Attributes\\AddIndex',
  727. 'table' => 'my_table',
  728. 'description' => '',
  729. 'notes' => [],
  730. 'type' => ''
  731. ],
  732. [
  733. 'class' => 'OCP\\Migration\\Attributes\\AddIndex',
  734. 'table' => 'my_table',
  735. 'description' => '',
  736. 'notes' => [],
  737. 'type' => 'primary'
  738. ],
  739. [
  740. 'class' => 'OCP\\Migration\\Attributes\\DropColumn',
  741. 'table' => 'other_table',
  742. 'description' => '',
  743. 'notes' => [],
  744. 'name' => '',
  745. 'type' => ''
  746. ],
  747. [
  748. 'class' => 'OCP\\Migration\\Attributes\\DropColumn',
  749. 'table' => 'other_table',
  750. 'description' => 'field is not used anymore and replaced by \'last_one\'',
  751. 'notes' => [],
  752. 'name' => 'old_column',
  753. 'type' => ''
  754. ],
  755. [
  756. 'class' => 'OCP\\Migration\\Attributes\\DropIndex',
  757. 'table' => 'other_table',
  758. 'description' => '',
  759. 'notes' => [],
  760. 'type' => ''
  761. ],
  762. [
  763. 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
  764. 'table' => 'other_table',
  765. 'description' => '',
  766. 'notes' => [],
  767. 'name' => '',
  768. 'type' => ''
  769. ],
  770. [
  771. 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
  772. 'table' => 'other_table',
  773. 'description' => '',
  774. 'notes' => [],
  775. 'name' => 'this_field',
  776. 'type' => ''
  777. ],
  778. [
  779. 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
  780. 'table' => 'other_table',
  781. 'description' => '',
  782. 'notes' => [],
  783. 'name' => 'this_field',
  784. 'type' => 'bigint'
  785. ],
  786. ]
  787. ];
  788. }
  789. }