├── .github
└── workflows
│ ├── php.yml
│ └── phpstan.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpcs.xml
├── phpstan-baseline.neon
├── phpstan.neon
├── phpunit.xml.dist
├── run_tests.sh
├── src
├── ConfigReader.php
├── Migrator.php
├── SchemaCleaner.php
└── TestConnectionManager.php
└── tests
├── .env.Mysql
├── .env.Postgres
├── .env.Sqlite
├── Plugins
├── BarPlugin
│ └── config
│ │ └── Migrations
│ │ └── 20200208100000_bar_migration.php
└── FooPlugin
│ └── config
│ └── Migrations
│ └── 20200208100000_foo_migration.php
├── TestApp
├── config
│ └── Migrations
│ │ ├── 20200208100000_add_articles.php
│ │ ├── 20200209100000_populate_articles.php
│ │ └── 20200210100000_create_view.php
└── src
│ └── Model
│ └── Table
│ └── ArticlesTable.php
├── TestCase
├── ConfigReaderTest.php
├── MigratorTest.php
├── SchemaCleanerDumpTest.php
├── SchemaCleanerListTablesTest.php
├── SchemaCleanerTest.php
└── TestConnectionManagerTest.php
└── bootstrap.php
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: GitHub CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | pull_request:
8 | branches:
9 | - '*'
10 | schedule:
11 | - cron: '0 0 * * 0'
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | php-version: ['8.1', '8.2']
20 | db-type: [sqlite, mysql, pgsql]
21 | composer-type: [lowest, stable, dev]
22 | exclude:
23 | - php-version: '8.1'
24 | db-type: sqlite
25 | composer-type: lowest
26 |
27 | name: PHP ${{ matrix.php-version }} & ${{ matrix.db-type }} & ${{ matrix.composer-type }}
28 |
29 | services:
30 | postgres:
31 | image: postgres
32 | ports:
33 | - 5432:5432
34 | env:
35 | POSTGRES_DB: test_migrator
36 | POSTGRES_PASSWORD: root
37 | POSTGRES_USER: root
38 |
39 | steps:
40 | - uses: actions/checkout@v2
41 |
42 | - name: Setup PHP
43 | uses: shivammathur/setup-php@v2
44 | with:
45 | php-version: ${{ matrix.php-version }}
46 | extensions: mbstring, intl, apcu, pdo_${{ matrix.db-type }}
47 | ini-values: apc.enable_cli = 1
48 |
49 | - name: Update composer
50 | run: composer self-update
51 |
52 | - name: Validate composer.json
53 | run: composer validate
54 |
55 | - name: Install dependencies
56 | run: |
57 | if [[ ${{ matrix.composer-type }} == 'lowest' ]]; then
58 | composer update --prefer-dist --no-progress --no-suggest --prefer-stable --prefer-lowest
59 | elif [[ ${{ matrix.composer-type }} == 'stable' ]]; then
60 | composer update --prefer-dist --no-progress --no-suggest --prefer-stable
61 | else
62 | composer update --prefer-dist --no-progress --no-suggest
63 | fi
64 |
65 | - name: Run tests
66 | run: |
67 | if [ ${{ matrix.db-type }} == 'mysql' ]; then
68 | sudo service mysql start && mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE IF NOT EXISTS test_migrator;';
69 | fi
70 | composer ${{ matrix.db-type }}
71 |
--------------------------------------------------------------------------------
/.github/workflows/phpstan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | pull_request:
8 | branches:
9 | - '*'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | php-version: ['8.1']
19 |
20 | name: PHP ${{ matrix.php-version }}
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 |
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: ${{ matrix.php-version }}
29 | extensions: mbstring, intl, apcu
30 | ini-values: apc.enable_cli = 1
31 |
32 | - name: Update composer
33 | run: composer self-update
34 |
35 | - name: Validate composer.json
36 | run: composer validate
37 |
38 | - name: Install composer
39 | run: composer update --prefer-dist --no-progress --no-suggest --prefer-stable
40 |
41 | - name: Install PHPStan
42 | run: composer stan-setup
43 |
44 | - name: Run phpstan
45 | run: composer phpstan
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /composer.lock
3 | /vendor/
4 | /.phpunit.cache
5 | **/test_migrator
6 | tests/.env
7 | /tmp
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 vierge-noire
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cakephp-test-migrator
2 | A tool to run migrations prior to running tests
3 |
4 |
5 | ## The Migrator
6 |
7 | #### For CakePHP 3.x
8 | composer require --dev vierge-noire/cakephp-test-migrator "^1.0"
9 |
10 | #### For CakePHP 4.x
11 | composer require --dev vierge-noire/cakephp-test-migrator "^2.0"
12 |
13 | #### For CakePHP 4.3+
14 | The migrator was integrated into the migration plugin (see the [doc](https://book.cakephp.org/migrations/3/en/index.html#using-migrations-for-tests)). The present package will not be further maintained.
15 |
16 | ### Introduction
17 |
18 | CakePHP fixtures handle the test DB schema in a paralell manner to the default DB. On the one hand you will write migrations for your default DB. On the other hand you either hard coded describe the schema structure in your fixtures, or meme the default DB. The later is simpler, but it forces you to have two DBs. And in CI tools, you will have to run the migrations on your default DB, and the fixtures meme the default DB. So why not running migrations directly on the test DB?
19 |
20 | With the CakePHP Test Migrator, the schema of both default and test DB are handled exactly in the same way. You do not necessarily need a default DB. Tables are not dropped between test suites, which speeds up your tests. And migrations are part of the whole testing process: they get indirectly tested.
21 |
22 | ### Setting
23 |
24 | The package proposes a tool to run your [migrations](https://book.cakephp.org/migrations/3/en/index.html) once prior to the tests. In order to do so,
25 | you may place the following in your `tests/bootstrap.php`:
26 | ```php
27 | \CakephpTestMigrator\Migrator::migrate();
28 | ```
29 | This command will ensure that your migrations are well run and keeps the test DB(s) up to date. Since tables are truncated but never dropped by the present package's fixture manager, migrations will be run strictly when needed, namely only after a new migration was created by the developer.
30 |
31 | The `Migrator`approach presents the following advantages:
32 | * it improves the speed of the test suites by avoiding the creation and dropping of tables between each test case classes,
33 | * it eases the maintenance of your tests, since regular and test DBs are managed the same way,
34 | * it indirectly tests your migrations.
35 |
36 | You may pass `true` as the second argument for a verbose output on the console.
37 |
38 | ### Options
39 |
40 | | name | type | default | description |
41 | | ---- | ---- | ------- | ----------- |
42 | | verbose | *bool* | `false` | Print info about migrations done |
43 | | truncate | *bool* | `true` | Truncate all tables after migrations are done. You can call `truncate()` manually |
44 |
45 | You can pass a boolean as options, it will be used as `verbose`
46 |
47 | ```php
48 | \CakephpTestMigrator\Migrator::migrate([], true);
49 | // is the same as
50 | \CakephpTestMigrator\Migrator::migrate([], ['verbose' => true]);
51 | ```
52 |
53 | ### Multiple migrations settings
54 |
55 | You can pass the various migrations directly in the Migrator instantiation:
56 | ```php
57 | \CakephpTestMigrator\Migrator::migrate([
58 | ['connection' => 'test', 'source' => 'TestFolder'],
59 | ['plugin' => 'FooPlugin', 'connection' => 'FooConnection'],
60 | ['source' => 'BarFolder'],
61 | ...
62 | ], ['verbose' => true]);
63 | ```
64 |
65 | You can also pass the various migrations directly in your Datasource configuration, under the key `migrations`:
66 | ```php
67 | // In config/app.php
68 | 'test' => [
69 | 'className' => Connection::class,
70 | 'driver' => Mysql::class,
71 | 'persistent' => false,
72 | 'timezone' => 'UTC',
73 | 'flags' => [],
74 | 'cacheMetadata' => true,
75 | 'quoteIdentifiers' => false,
76 | 'log' => false,
77 | 'migrations' => [
78 | ['plugin' => 'FooPlugin'],
79 | ['source' => 'BarFolder'],
80 | ],
81 | ],
82 | ```
83 |
84 | You can set `migrations` simply to `true` if you which to use the default migration settings.
85 |
86 | ### Migrations status
87 |
88 | Information on a connection's migration status will be obtained as follows:
89 | ```php
90 | $migrator = Migrator::migrate();
91 | $connectionsWithModifiedStatus = $migrator->getConnectionsWithModifiedStatus();
92 | ```
93 |
94 | the method `getConnectionsWithModifiedStatus` returning a list of the connections with down
95 | migrations prior to running the migrations.
96 |
97 | ### Data truncation
98 |
99 | Use **truncate** option to truncate (or not) after migrations are done. This is useful if you need to do other tasks in the test suite bootstrap
100 |
101 | ```php
102 | $migrator = Migrator::migrate([], ['truncate' => false]);
103 | // do something with the migrated database (bake fixtures, etc)
104 | $migrator->truncate();
105 | ```
106 |
107 | ### What happens if I switch branches?
108 |
109 | If you ever switched to a branch with nonexistent up migrations, you've moved to a branch in a past state.
110 | The `Migrator` will automatically drop the tables where needed, and re-run the migrations. Switching branches therefore
111 | does not require any intervention on your side.
112 |
113 | ### What if I do not use migrations?
114 |
115 | The `Migrator::dump()` will help you import any schema from one or several sql file. Run for example:
116 |
117 | ```php
118 | Migrator::dump('test', 'path/to/file.sql')
119 | ```
120 | or with multiples files
121 | ```php
122 | Migrator::dump('test', [
123 | 'path1/to/file1.sql',
124 | 'path2/to/file2.sql',
125 | ])
126 | ```
127 | or for a verbose output
128 | ```php
129 | Migrator::dump('test', 'path/to/file.sql', true)
130 | ```
131 |
132 | The first argument is the name of the connection, the second the file(s) to dump, the third the verbosity (boolean).
133 |
134 | This method will however drop the schema prior to recreating it, which presents a significant loss of
135 | performance in comparison to the migration-based solution.
136 |
137 | ### Trouble shooting
138 |
139 | It might be required, right after you installed or updated the plugin, to drop and recreate your test database. If the problem persists, feel free to open an issue.
140 |
141 | ## Authors
142 | * Juan Pablo Ramirez
143 | * Nicolas Masson
144 |
145 |
146 | ## Support
147 | Contact us at vierge.noire.info@gmail.com for professional assistance.
148 |
149 | You like our work? [](https://ko-fi.com/L3L52P9JA)
150 |
151 |
152 | ## License
153 |
154 | The CakephpTestMigrator plugin is offered under an [MIT license](https://opensource.org/licenses/mit-license.php).
155 |
156 | Copyright 2020 Juan Pablo Ramirez and Nicolas Masson
157 |
158 | Licensed under The MIT License Redistributions of files must retain the above copyright notice.
159 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vierge-noire/cakephp-test-migrator",
3 | "description": "Migration helper for the CakePHP Test Suite Light",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Juan Pablo Ramirez",
8 | "email": "pabloelcolombiano@gmail.com"
9 | }
10 | ],
11 | "minimum-stability": "dev",
12 | "prefer-stable": true,
13 | "require": {
14 | "cakephp/cakephp": "5.x-dev"
15 | },
16 | "require-dev": {
17 | "cakephp/cakephp-codesniffer": "^5.1",
18 | "cakephp/migrations": "4.x-dev as 4.0.0",
19 | "josegonzalez/dotenv": "dev-master",
20 | "phpunit/phpunit": "^10.1"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "CakephpTestMigrator\\": "src"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "CakephpTestMigrator\\Test\\": "tests",
30 | "MigratorTestApp\\": "tests\\TestApp\\src"
31 | }
32 | },
33 | "scripts": {
34 | "cs-check": "phpcs --colors --parallel=16 -p src/ tests/",
35 | "cs-fix": "phpcbf --colors --parallel=16 -p src/ tests/",
36 | "mysql": "bash run_tests.sh Mysql",
37 | "pgsql": "bash run_tests.sh Postgres",
38 | "sqlite": "bash run_tests.sh Sqlite",
39 | "phpstan": "./vendor/bin/phpstan analyse --memory-limit=-1",
40 | "phpstan-baseline": "./vendor/bin/phpstan --generate-baseline",
41 | "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:1.10.18 && mv composer.backup composer.json"
42 | },
43 | "config": {
44 | "sort-packages": true,
45 | "allow-plugins": {
46 | "dealerdirect/phpcodesniffer-composer-installer": true
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | -
4 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\ConnectionInterface\\:\\:execute\\(\\)\\.$#"
5 | count: 1
6 | path: src/Migrator.php
7 |
8 | -
9 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\ConnectionInterface\\:\\:disableConstraints\\(\\)\\.$#"
10 | count: 1
11 | path: src/SchemaCleaner.php
12 |
13 | -
14 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\ConnectionInterface\\:\\:execute\\(\\)\\.$#"
15 | count: 2
16 | path: src/SchemaCleaner.php
17 |
18 | -
19 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\ConnectionInterface\\:\\:getSchemaCollection\\(\\)\\.$#"
20 | count: 2
21 | path: src/SchemaCleaner.php
22 |
23 | -
24 | message: "#^Call to an undefined method Cake\\\\Datasource\\\\ConnectionInterface\\:\\:transactional\\(\\)\\.$#"
25 | count: 1
26 | path: src/SchemaCleaner.php
27 |
28 | -
29 | message: "#^Call to an undefined method object\\:\\:schemaDialect\\(\\)\\.$#"
30 | count: 1
31 | path: src/SchemaCleaner.php
32 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | level: max
6 | checkMissingIterableValueType: false
7 | bootstrapFiles:
8 | - tests/bootstrap.php
9 | paths:
10 | - src/
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ./tests/TestCase/
19 |
20 |
21 |
22 |
23 |
24 |
25 | ./src/
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DRIVER=$1;
4 |
5 | echo "Starting PHPUNIT tests"
6 | export DB_DRIVER=$DRIVER
7 |
8 | ./vendor/bin/phpunit --testsuite Default --stop-on-fail
9 |
--------------------------------------------------------------------------------
/src/ConfigReader.php:
--------------------------------------------------------------------------------
1 | getActiveConnections() as $connectionName) {
34 | $connection = ConnectionManager::getConfig($connectionName);
35 | $config = [];
36 |
37 | if (is_array($connection)) {
38 | $migrations = $connection['migrations'] ?? false;
39 |
40 | if ($migrations) {
41 | if ($migrations === true) {
42 | $config = ['connection' => $connectionName ];
43 | $this->normalizeArray($config);
44 | } elseif (is_array($migrations)) {
45 | $config = $migrations;
46 | $this->normalizeArray($config);
47 | foreach ($config as $k => $v) {
48 | $config[$k]['connection'] = $v['connection'] ?? $connectionName;
49 | }
50 | }
51 | $this->config = array_merge($this->config, $config);
52 | }
53 | }
54 | }
55 |
56 | $this->processConfig();
57 |
58 | return $this;
59 | }
60 |
61 | /**
62 | * @param array|array $config An array of migration configs
63 | * @return $this
64 | */
65 | public function readConfig(array $config = [])
66 | {
67 | if (!empty($config)) {
68 | $this->normalizeArray($config);
69 | $this->config = $config;
70 | }
71 |
72 | $this->processConfig();
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * Make sure config is set properly
79 | *
80 | * @return void
81 | */
82 | public function processConfig(): void
83 | {
84 | foreach ($this->config as $k => $config) {
85 | $this->config[$k]['connection'] = $this->config[$k]['connection'] ?? 'test';
86 | }
87 | if (empty($this->config)) {
88 | $this->config = [['connection' => 'test']];
89 | }
90 | }
91 |
92 | /**
93 | * Initialize all connections used by the manager
94 | *
95 | * @return array
96 | */
97 | public function getActiveConnections(): array
98 | {
99 | $connections = ConnectionManager::configured();
100 | foreach ($connections as $i => $connectionName) {
101 | if ($this->skipConnection($connectionName)) {
102 | unset($connections[$i]);
103 | }
104 | }
105 |
106 | return $connections;
107 | }
108 |
109 | /**
110 | * @param string $connectionName Connection name
111 | * @return bool
112 | */
113 | public function skipConnection(string $connectionName): bool
114 | {
115 | // CakePHP 4 solves a DebugKit issue by creating an Sqlite connection
116 | // in tests/bootstrap.php. This connection should be ignored.
117 | if ($connectionName === 'test_debug_kit') {
118 | return true;
119 | }
120 |
121 | if ($connectionName === 'test' || strpos($connectionName, 'test_') === 0) {
122 | return false;
123 | }
124 |
125 | return true;
126 | }
127 |
128 | /**
129 | * Make array an array of arrays
130 | *
131 | * @param array $array
132 | * @return void
133 | */
134 | public function normalizeArray(array &$array): void
135 | {
136 | if (!empty($array) && !isset($array[0])) {
137 | $array = [$array];
138 | }
139 | }
140 |
141 | /**
142 | * @return array
143 | */
144 | public function getConfig(): array
145 | {
146 | return $this->config;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Migrator.php:
--------------------------------------------------------------------------------
1 |
35 | */
36 | protected array $connectionsWithModifiedStatus = [];
37 |
38 | /**
39 | * Migrator constructor.
40 | *
41 | * @param bool $verbose
42 | * @param null $configReader
43 | */
44 | final public function __construct(bool $verbose, ?ConfigReader $configReader = null)
45 | {
46 | $this->io = new ConsoleIo();
47 | $this->io->level($verbose ? ConsoleIo::NORMAL : ConsoleIo::QUIET);
48 | $this->configReader = $configReader ?? new ConfigReader();
49 |
50 | // Make sure that the connections are aliased, in case
51 | // the migrations invoke the table registry.
52 | TestConnectionManager::aliasConnections();
53 | }
54 |
55 | /**
56 | * General command to run before your tests run
57 | * E.g. in tests/bootstrap.php
58 | *
59 | * Options:
60 | * - verbose | bool | Set to true to display messages
61 | * - truncate | bool | Truncate tables after migrations are done.
62 | *
63 | * @param array $config
64 | * @param array{verbose?:bool,truncate?:bool}|bool $options Options for migrations
65 | * @return self
66 | */
67 | public static function migrate(array $config = [], array|bool $options = []): Migrator
68 | {
69 | if ($options === true || $options === false) {
70 | $options = ['verbose' => $options];
71 | }
72 | $options += [
73 | 'verbose' => false,
74 | 'truncate' => true,
75 | ];
76 | $migrator = new static($options['verbose']);
77 |
78 | $migrator->configReader->readMigrationsInDatasources();
79 | $migrator->configReader->readConfig($config);
80 | $migrator->handleMigrationsStatus();
81 |
82 | if ($options['truncate']) {
83 | $migrator->truncate();
84 | }
85 |
86 | return $migrator;
87 | }
88 |
89 | /**
90 | * Import the schema from a file, or an array of files.
91 | *
92 | * @param string $connectionName Connection
93 | * @param array|string $file File to dump
94 | * @param bool $verbose Set to true to display messages
95 | * @return void
96 | * @throws \Exception if the truncation failed
97 | * @throws \RuntimeException if the file could not be processed
98 | */
99 | public static function dump(string $connectionName, string|array $file, bool $verbose = false): void
100 | {
101 | $files = (array)$file;
102 |
103 | $migrator = new static($verbose);
104 | $schemaCleaner = new SchemaCleaner($migrator->io);
105 | $schemaCleaner->drop($connectionName);
106 |
107 | foreach ($files as $file) {
108 | if (!file_exists($file)) {
109 | throw new RuntimeException('The file ' . $file . ' could not found.');
110 | }
111 |
112 | $sql = file_get_contents($file);
113 | if ($sql === false) {
114 | throw new RuntimeException('The file ' . $file . ' could not read.');
115 | }
116 |
117 | ConnectionManager::get($connectionName)->execute($sql);
118 |
119 | $migrator->io->success(
120 | 'Dump of schema in file ' . $file . ' for connection ' . $connectionName . ' successful.'
121 | );
122 | }
123 |
124 | $schemaCleaner->truncate($connectionName);
125 | }
126 |
127 | /**
128 | * Run migrations for all configured migrations.
129 | *
130 | * @param array $config Migration configuration.
131 | * @return void
132 | */
133 | protected function runMigrations(array $config): void
134 | {
135 | $migrations = new Migrations();
136 | $result = $migrations->migrate($config);
137 |
138 | $msg = 'Migrations for ' . $this->stringifyConfig($config);
139 |
140 | if ($result === true) {
141 | $this->io->success($msg . ' successfully run.');
142 | } else {
143 | $this->io->error($msg . ' failed.');
144 | }
145 | }
146 |
147 | /**
148 | * Truncates connections on demand.
149 | *
150 | * @param array|null $connections Connections names to truncate. Defaults to modified connections
151 | * @return void
152 | */
153 | public function truncate(?array $connections = null): void
154 | {
155 | if ($connections === null) {
156 | $connections = $this->configReader->getActiveConnections();
157 | }
158 | $schemaCleaner = new SchemaCleaner($this->io);
159 | foreach ($connections as $connectionName) {
160 | $schemaCleaner->truncate($connectionName);
161 | }
162 | }
163 |
164 | /**
165 | * If a migration is missing or down, all tables of the considered connection are dropped.
166 | *
167 | * @return $this
168 | */
169 | protected function handleMigrationsStatus()
170 | {
171 | $schemaCleaner = new SchemaCleaner($this->io);
172 | foreach ($this->getConfigs() as &$config) {
173 | $connectionName = $config['connection'] = $config['connection'] ?? 'test';
174 | $this->io->info("Reading migrations status for {$this->stringifyConfig($config)}...");
175 | $migrations = new Migrations($config);
176 | if ($this->isStatusChanged($migrations)) {
177 | if (!in_array($connectionName, $this->connectionsWithModifiedStatus)) {
178 | $this->connectionsWithModifiedStatus[] = $connectionName;
179 | }
180 | }
181 | }
182 |
183 | if (empty($this->connectionsWithModifiedStatus)) {
184 | $this->io->success('No migration changes detected.');
185 |
186 | return $this;
187 | }
188 |
189 | foreach ($this->connectionsWithModifiedStatus as $connectionName) {
190 | $schemaCleaner->drop($connectionName);
191 | }
192 |
193 | foreach ($this->getConfigs() as $config) {
194 | $this->runMigrations($config);
195 | }
196 |
197 | return $this;
198 | }
199 |
200 | /**
201 | * Checks if any migrations are up but missing.
202 | *
203 | * @param \Migrations\Migrations $migrations
204 | * @return bool
205 | */
206 | protected function isStatusChanged(Migrations $migrations): bool
207 | {
208 | foreach ($migrations->status() as $migration) {
209 | if ($migration['status'] === 'up' && ($migration['missing'] ?? false)) {
210 | $this->io->info('Missing migration(s) detected.');
211 |
212 | return true;
213 | }
214 | if ($migration['status'] === 'down') {
215 | $this->io->info('New migration(s) found.');
216 |
217 | return true;
218 | }
219 | }
220 |
221 | return false;
222 | }
223 |
224 | /**
225 | * Stringify the migration parameters.
226 | *
227 | * @param array $config Config array
228 | * @return string
229 | */
230 | protected function stringifyConfig(array $config): string
231 | {
232 | $options = [];
233 | foreach (['connection', 'plugin', 'source', 'target'] as $option) {
234 | if (isset($config[$option])) {
235 | $options[] = $option . ' "' . $config[$option] . '"';
236 | }
237 | }
238 |
239 | return implode(', ', $options);
240 | }
241 |
242 | /**
243 | * @return array
244 | */
245 | public function getConfigs(): array
246 | {
247 | return $this->getConfigReader()->getConfig();
248 | }
249 |
250 | /**
251 | * @return \CakephpTestMigrator\ConfigReader
252 | */
253 | protected function getConfigReader(): ConfigReader
254 | {
255 | return $this->configReader;
256 | }
257 |
258 | /**
259 | * Returns an array of strings with all the connections
260 | * which migration status have changed and were migrated.
261 | *
262 | * @return array
263 | */
264 | public function getConnectionsWithModifiedStatus(): array
265 | {
266 | return $this->connectionsWithModifiedStatus;
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/src/SchemaCleaner.php:
--------------------------------------------------------------------------------
1 | io = $io;
40 | }
41 |
42 | /**
43 | * Drop all tables of the provided connection.
44 | *
45 | * @param string $connectionName Name of the connection.
46 | * @return void
47 | * @throws \Exception if the dropping failed.
48 | */
49 | public function drop(string $connectionName): void
50 | {
51 | $this->info("Dropping all tables for connection {$connectionName}.");
52 |
53 | $connection = ConnectionManager::get($connectionName);
54 | $stmts = [];
55 | foreach ($this->listTables($connection) as $table) {
56 | $table = $connection->getSchemaCollection()->describe($table);
57 | if ($table instanceof TableSchema) {
58 | $driver = $connection->getDriver();
59 | $stmts = array_merge($stmts, $driver->schemaDialect()->dropTableSql($table));
60 | }
61 | }
62 |
63 | $this->executeStatements(ConnectionManager::get($connectionName), $stmts);
64 | }
65 |
66 | /**
67 | * Truncate all tables of the provided connection.
68 | *
69 | * @param string $connectionName Name of the connection.
70 | * @return void
71 | * @throws \Exception if the truncation failed.
72 | */
73 | public function truncate(string $connectionName): void
74 | {
75 | $this->info("Truncating all tables for connection {$connectionName}.");
76 |
77 | /**
78 | * @var \Cake\Database\Connection $connection
79 | */
80 | $connection = ConnectionManager::get($connectionName);
81 | $stmts = [];
82 | $tables = $this->listTables($connection);
83 | $tables = $this->unsetMigrationTables($tables);
84 | foreach ($tables as $table) {
85 | $table = $connection->getSchemaCollection()->describe($table);
86 | if ($table instanceof TableSchema) {
87 | $driver = $connection->getDriver();
88 | $stmts = array_merge($stmts, $driver->schemaDialect()->truncateTableSql($table));
89 | }
90 | }
91 |
92 | $this->executeStatements($connection, $stmts);
93 | }
94 |
95 | /**
96 | * List sall tables, without views.
97 | *
98 | * @param \Cake\Datasource\ConnectionInterface $connection Connection.
99 | * @return array
100 | */
101 | public function listTables(ConnectionInterface $connection): array
102 | {
103 | $driver = $connection->getDriver();
104 | if ($driver instanceof Mysql) {
105 | $query = 'SHOW FULL TABLES WHERE Table_Type != "VIEW"';
106 | } elseif ($driver instanceof Postgres) {
107 | $query = "SELECT tablename AS TABLE FROM pg_tables WHERE schemaname = 'public'";
108 | } else {
109 | return $connection->getSchemaCollection()->listTables();
110 | }
111 |
112 | $result = $connection->execute($query)->fetchAll();
113 | if ($result === false) {
114 | throw new PDOException($query . ' failed');
115 | }
116 |
117 | return (array)Hash::extract($result, '{n}.0');
118 | }
119 |
120 | /**
121 | * @param \Cake\Datasource\ConnectionInterface $connection Connection.
122 | * @param array $commands Sql commands to run
123 | * @return void
124 | * @throws \Exception
125 | */
126 | protected function executeStatements(ConnectionInterface $connection, array $commands): void
127 | {
128 | $connection->disableConstraints(
129 | function (ConnectionInterface $connection) use ($commands): void {
130 | $connection->transactional(
131 | function (ConnectionInterface $connection) use ($commands): void {
132 | foreach ($commands as $sql) {
133 | $connection->execute($sql);
134 | }
135 | }
136 | );
137 | }
138 | );
139 | }
140 |
141 | /**
142 | * @param string $msg Message to display.
143 | * @return void
144 | */
145 | protected function info(string $msg): void
146 | {
147 | if ($this->io instanceof ConsoleIo) {
148 | $this->io->info($msg);
149 | }
150 | }
151 |
152 | /**
153 | * Unset the phinx migration tables from an array of tables.
154 | *
155 | * @param array $tables
156 | * @return array
157 | */
158 | public function unsetMigrationTables(array $tables): array
159 | {
160 | $endsWithPhinxlog = function (string $string) {
161 | $needle = 'phinxlog';
162 |
163 | return substr($string, -strlen($needle)) === $needle;
164 | };
165 |
166 | foreach ($tables as $i => $table) {
167 | if ($endsWithPhinxlog($table)) {
168 | unset($tables[$i]);
169 | }
170 | }
171 |
172 | return array_values($tables);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/TestConnectionManager.php:
--------------------------------------------------------------------------------
1 | _aliasConnections();
32 | self::$aliasConnectionIsLoaded = true;
33 | }
34 | }
35 |
36 | /**
37 | * Add aliases for all non test prefixed connections.
38 | *
39 | * This allows models to use the test connections without
40 | * a pile of configuration work.
41 | *
42 | * @return void
43 | */
44 | protected function _aliasConnections(): void
45 | {
46 | $connections = ConnectionManager::configured();
47 | ConnectionManager::alias('test', 'default');
48 | $map = [];
49 | foreach ($connections as $connection) {
50 | if ($connection === 'test' || $connection === 'default') {
51 | continue;
52 | }
53 | if (isset($map[$connection])) {
54 | continue;
55 | }
56 | if (strpos($connection, 'test_') === 0) {
57 | $map[$connection] = substr($connection, 5);
58 | } else {
59 | $map['test_' . $connection] = $connection;
60 | }
61 | }
62 | foreach ($map as $testConnection => $normal) {
63 | ConnectionManager::alias($testConnection, $normal);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/.env.Mysql:
--------------------------------------------------------------------------------
1 | DB_DRIVER=Mysql
2 | DB_USER=root
3 | DB_PWD=root
4 | DB_HOST=localhost
5 | DB_DATABASE=test_migrator
6 |
--------------------------------------------------------------------------------
/tests/.env.Postgres:
--------------------------------------------------------------------------------
1 | DB_DRIVER=Postgres
2 | DB_USER=root
3 | DB_PWD=root
4 | DB_HOST=localhost
5 | DB_DATABASE=test_migrator
6 |
--------------------------------------------------------------------------------
/tests/.env.Sqlite:
--------------------------------------------------------------------------------
1 | DB_DRIVER=Sqlite
2 | DB_USER=root
3 | DB_PWD=root
4 | DB_HOST=localhost
5 | DB_DATABASE=test_migrator
6 |
--------------------------------------------------------------------------------
/tests/Plugins/BarPlugin/config/Migrations/20200208100000_bar_migration.php:
--------------------------------------------------------------------------------
1 | table('articles')
22 | ->addPrimaryKey(['id'])
23 | ->addColumn(
24 | 'title',
25 | 'string',
26 | [
27 | 'limit' => 128,
28 | 'null' => false,
29 | ]
30 | )
31 | ->addTimestamps('created', 'modified')
32 | ->create();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/TestApp/config/Migrations/20200209100000_populate_articles.php:
--------------------------------------------------------------------------------
1 | table('articles')->insert(['title' => 'foo'])->save();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/TestApp/config/Migrations/20200210100000_create_view.php:
--------------------------------------------------------------------------------
1 | execute(
24 | 'CREATE VIEW ' . SchemaCleanerListTablesTest::VIEW_NAME . " AS SELECT '" . SchemaCleanerListTablesTest::VIEW_CONTENT . "'"
25 | );
26 | } catch (Throwable $e) {
27 | // Do nothing, the view might already exist.
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/TestApp/src/Model/Table/ArticlesTable.php:
--------------------------------------------------------------------------------
1 | ConfigReader = new ConfigReader();
29 | }
30 |
31 | public function tearDown(): void
32 | {
33 | unset($this->ConfigReader);
34 | }
35 |
36 | public function testSetConfigFromInjection(): void
37 | {
38 | $config = [
39 | ['connection' => 'Foo', 'plugin' => 'Bar',],
40 | ['plugin' => 'Bar',],
41 | ];
42 |
43 | $expect = [
44 | ['connection' => 'Foo', 'plugin' => 'Bar',],
45 | ['plugin' => 'Bar', 'connection' => 'test',],
46 | ];
47 |
48 | $this->ConfigReader->readConfig($config);
49 |
50 | $this->assertSame($expect, $this->ConfigReader->getConfig());
51 | }
52 |
53 | public function testSetConfigFromEmptyInjection(): void
54 | {
55 | $expect = [
56 | ['connection' => 'test'],
57 | ];
58 |
59 | $this->ConfigReader->readConfig();
60 |
61 | $this->assertSame($expect, $this->ConfigReader->getConfig());
62 | }
63 |
64 | public function testSetConfigWithConfigureAndInjection(): void
65 | {
66 | $config1 = [
67 | 'connection' => 'Foo1_testSetConfigWithConfigureAndInjection',
68 | 'plugin' => 'Bar1_testSetConfigWithConfigureAndInjection',
69 | ];
70 |
71 | $this->ConfigReader->readConfig($config1);
72 | $this->assertSame([$config1], $this->ConfigReader->getConfig());
73 | }
74 |
75 | public function testReadMigrationsInDatasource(): void
76 | {
77 | $this->ConfigReader->readMigrationsInDatasources();
78 | // Read empty config will not overwrite Datasource config
79 | $this->ConfigReader->readConfig();
80 | $act = $this->ConfigReader->getConfig();
81 | $expected = [
82 | ['source' => 'FooSource', 'connection' => 'test'],
83 | ['plugin' => 'FooPlugin', 'connection' => 'test'],
84 | ['plugin' => 'BarPlugin', 'connection' => 'test_2'],
85 | ['connection' => 'test_3'],
86 | ];
87 | $this->assertSame($expected, $act);
88 | }
89 |
90 | public function testReadMigrationsInDatasourceAndInjection(): void
91 | {
92 | $this->ConfigReader->readMigrationsInDatasources();
93 | // Read non-empty config will overwrite Datasource config
94 | $this->ConfigReader->readConfig(['source' => 'Foo']);
95 | $act = $this->ConfigReader->getConfig();
96 | $expected = [
97 | ['source' => 'Foo', 'connection' => 'test'],
98 | ];
99 | $this->assertSame($expected, $act);
100 | }
101 |
102 | public static function arrays(): array
103 | {
104 | return [
105 | [['a' => 'b'], [['a' => 'b']]],
106 | [[['a' => 'b']], [['a' => 'b']]],
107 | [[], []],
108 | ];
109 | }
110 |
111 | /**
112 | * @dataProvider arrays
113 | * @param array $input
114 | * @param array $expect
115 | */
116 | public function testNormalizeArray(array $input, array $expect): void
117 | {
118 | $this->ConfigReader->normalizeArray($input);
119 | $this->assertSame($expect, $input);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/tests/TestCase/MigratorTest.php:
--------------------------------------------------------------------------------
1 | selectQuery()
29 | ->select('migration_name')
30 | ->from($dbTable)
31 | ->execute()
32 | ->fetchAll();
33 |
34 | return (array)Hash::extract($result, '{n}.0');
35 | }
36 |
37 | public function testGetConfigFromDatasource(): void
38 | {
39 | $expect = [
40 | ['source' => 'FooSource', 'connection' => 'test'],
41 | ['plugin' => 'FooPlugin', 'connection' => 'test'],
42 | ['plugin' => 'BarPlugin', 'connection' => 'test_2'],
43 | ['connection' => 'test_3'],
44 | ];
45 | $migrator = Migrator::migrate();
46 | $config = $migrator->getConfigs();
47 | $this->assertSame($expect, $config);
48 | }
49 |
50 | public function testMigrate(): void
51 | {
52 | Migrator::migrate();
53 |
54 | $appMigrations = $this->fetchMigrationsInDB('phinxlog');
55 | $fooPluginMigrations = $this->fetchMigrationsInDB('foo_plugin_phinxlog');
56 | $barPluginMigrations = $this->fetchMigrationsInDB('bar_plugin_phinxlog');
57 |
58 | $this->assertSame(['AddArticles', 'PopulateArticles', 'CreateView'], $appMigrations);
59 | $this->assertSame(['FooMigration'], $fooPluginMigrations);
60 | $this->assertSame(['BarMigration'], $barPluginMigrations);
61 | }
62 |
63 | public function testGetConnectionsWithModifiedStatus(): void
64 | {
65 | $connections = [
66 | 'test',
67 | 'test_2',
68 | 'test_3',
69 | ];
70 |
71 | // Run the migrations once to make sure that all are up to date.
72 | Migrator::migrate();
73 |
74 | // Status did not changed.
75 | $migrator = Migrator::migrate();
76 | $this->assertSame([], $migrator->getConnectionsWithModifiedStatus());
77 |
78 | // Drop all connections' tables. Statuses are reset.
79 | $cleaner = new SchemaCleaner();
80 | foreach ($connections as $connection) {
81 | $cleaner->drop($connection);
82 | }
83 |
84 | // All connections were touched by the migrations.
85 | $migrator = Migrator::migrate();
86 | $connectionsWithModifiedStatus = $migrator->getConnectionsWithModifiedStatus();
87 | $this->assertSame($connections, $connectionsWithModifiedStatus);
88 | }
89 |
90 | public function testTableRegistryConnectionName(): void
91 | {
92 | $Articles = TableRegistry::getTableLocator()->get('Articles');
93 | ConnectionManager::getConfigOrFail('default');
94 | $this->assertSame('test', $Articles->getConnection()->configName());
95 | }
96 |
97 | public function testDropTablesForMissingMigrations(): void
98 | {
99 | $connection = ConnectionManager::get('test');
100 | $connection->insert('phinxlog', ['version' => 1, 'migration_name' => 'foo',]);
101 |
102 | $count = $connection->selectQuery()->select('version')->from('phinxlog')->execute()->rowCount();
103 | $this->assertSame(4, $count);
104 |
105 | Migrator::migrate();
106 | $count = $connection->selectQuery()->select('version')->from('phinxlog')->execute()->rowCount();
107 | $this->assertSame(3, $count);
108 | }
109 |
110 | public function testMigrateWithoutTruncate(): void
111 | {
112 | $connection = ConnectionManager::get('test');
113 | $cleaner = new SchemaCleaner();
114 | $cleaner->drop('test');
115 |
116 | $migrator = Migrator::migrate([], ['truncate' => false]);
117 |
118 | $count = $connection->selectQuery()->select('title')->from('articles')->execute()->rowCount();
119 | $this->assertSame(1, $count);
120 |
121 | $migrator->truncate();
122 |
123 | $count = $connection->selectQuery()->select('title')->from('articles')->execute()->rowCount();
124 | $this->assertSame(0, $count);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/tests/TestCase/SchemaCleanerDumpTest.php:
--------------------------------------------------------------------------------
1 | createSchemas();
31 | $this->assertSame(
32 | 1,
33 | $connection->selectQuery()->select('id')->from('test_table')->execute()->rowCount()
34 | );
35 | $this->assertSame(
36 | 1,
37 | $connection->selectQuery()->select('id')->from('test_table2')->execute()->rowCount()
38 | );
39 |
40 | $this->expectException(PDOException::class);
41 | $connection->delete('test_table');
42 | }
43 |
44 | public function testDropSchema()
45 | {
46 | $connection = ConnectionManager::get('test');
47 |
48 | $this->createSchemas();
49 |
50 | $tables = (new SchemaCleaner())->listTables($connection);
51 |
52 | // Assert that the schema is not empty
53 | $this->assertSame(['test_table', 'test_table2'], $tables, 'The schema should not be empty.');
54 |
55 | // Drop the schema
56 | (new SchemaCleaner())->drop('test');
57 |
58 | // Schema is empty
59 | $tables = (new SchemaCleaner())->listTables($connection);
60 | $this->assertSame([], $tables, 'The schema should be empty.');
61 | }
62 |
63 | public function testTruncateSchema()
64 | {
65 | $connection = ConnectionManager::get('test');
66 |
67 | $this->createSchemas();
68 |
69 | (new SchemaCleaner())->truncate('test');
70 |
71 | $this->assertSame(
72 | 0,
73 | $connection->selectQuery()->select('id')->from('test_table')->execute()->rowCount()
74 | );
75 | $this->assertSame(
76 | 0,
77 | $connection->selectQuery()->select('id')->from('test_table2')->execute()->rowCount()
78 | );
79 | }
80 |
81 | /**
82 | * Note that phinxlog tables are suffixed by _phinxlog.
83 | */
84 | public function testUnsetMigrationTables(): void
85 | {
86 | $input = ['foo', 'phinxlog', 'phinxlog_bar', 'some_table', 'some_plugin_phinxlog'];
87 | $output = (new SchemaCleaner())->unsetMigrationTables($input);
88 | $this->assertSame(['foo', 'phinxlog_bar', 'some_table',], $output);
89 | }
90 |
91 | private function createSchemas()
92 | {
93 | $schemaCleaner = new SchemaCleaner();
94 | $schemaCleaner->drop('test');
95 |
96 | $connection = ConnectionManager::get('test');
97 |
98 | $schema = new TableSchema('test_table');
99 | $schema
100 | ->addColumn('id', 'integer')
101 | ->addColumn('name', 'string')
102 | ->addConstraint(
103 | 'primary',
104 | [
105 | 'type' => TableSchema::CONSTRAINT_PRIMARY,
106 | 'columns' => ['id'],
107 | ]
108 | );
109 |
110 | $queries = $schema->createSql($connection);
111 | foreach ($queries as $sql) {
112 | $connection->execute($sql);
113 | }
114 |
115 | $schema = new TableSchema('test_table2');
116 | $schema
117 | ->addColumn('id', 'integer')
118 | ->addColumn('name', 'string')
119 | ->addColumn('fk_id', 'integer')
120 | ->addConstraint(
121 | 'primary',
122 | [
123 | 'type' => TableSchema::CONSTRAINT_PRIMARY,
124 | 'columns' => ['id'],
125 | ]
126 | )
127 | ->addConstraint(
128 | 'foreign_key',
129 | [
130 | 'columns' => ['fk_id'],
131 | 'type' => TableSchema::CONSTRAINT_FOREIGN,
132 | 'references' => ['test_table', 'id', ],
133 | ]
134 | );
135 |
136 | $queries = $schema->createSql($connection);
137 |
138 | foreach ($queries as $sql) {
139 | $connection->execute($sql);
140 | }
141 |
142 | $connection->insert('test_table', ['name' => 'foo']);
143 |
144 | $id = $connection->selectQuery()->select('id')->from('test_table')->limit(1)->execute()->fetch()[0];
145 | $connection->insert('test_table2', ['name' => 'foo', 'fk_id' => $id]);
146 |
147 | $connection->execute($connection->getDriver()->enableForeignKeySQL());
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/tests/TestCase/SchemaCleanerListTablesTest.php:
--------------------------------------------------------------------------------
1 | SchemaCleaner = new SchemaCleaner();
33 | }
34 |
35 | public function tearDown(): void
36 | {
37 | unset($this->SchemaCleaner);
38 | parent::tearDown();
39 | }
40 |
41 | /**
42 | * Check that the view has been created
43 | */
44 | public function testThatTheViewExists()
45 | {
46 | $connection = ConnectionManager::get('test');
47 | $result = $connection->execute('SELECT * FROM ' . self::VIEW_NAME)->fetch()[0];
48 | $this->assertSame(self::VIEW_CONTENT, $result);
49 | }
50 |
51 | /**
52 | * Check that the view is ignored
53 | */
54 | public function testThatTheViewIsIgnored()
55 | {
56 | $connection = ConnectionManager::get('test');
57 | $allTables = $this->SchemaCleaner->listTables($connection);
58 | $this->assertSame(false, in_array(self::VIEW_NAME, $allTables));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/TestCase/SchemaCleanerTest.php:
--------------------------------------------------------------------------------
1 | Articles = TableRegistry::getTableLocator()->get('Articles');
32 | }
33 |
34 | public function tearDown(): void
35 | {
36 | unset($this->Articles);
37 | }
38 |
39 | public function testDropSchema(): void
40 | {
41 | // Drop tables to ensure that the following migration runs
42 | (new SchemaCleaner())->drop('test');
43 |
44 | // Populate the schema
45 | $migrations = new Migrations();
46 | $migrations->migrate(['connection' => 'test']);
47 | $this->assertSame(1, $this->Articles->find()->count());
48 |
49 | // Drop the schema
50 | (new SchemaCleaner())->drop('test');
51 |
52 | $this->expectException(Exception::class);
53 | $this->Articles->find()->all();
54 | }
55 |
56 | public function testTruncateSchema(): void
57 | {
58 | // Drop tables to ensure that the following migration runs
59 | (new SchemaCleaner())->drop('test');
60 | // Populate the schema
61 | $migrations = new Migrations(['connection' => 'test']);
62 | $migrations->migrate();
63 | $this->assertSame(1, $this->Articles->find()->count());
64 |
65 | // Truncate the schema
66 | (new SchemaCleaner())->truncate('test');
67 | $this->assertSame(0, $this->Articles->find()->count());
68 |
69 | $migration = $migrations->status()[0];
70 | $this->assertSame('up', $migration['status']);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/TestCase/TestConnectionManagerTest.php:
--------------------------------------------------------------------------------
1 | config()['database'];
26 | $this->assertSame('test_migrator', $testDB);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | parse()
24 | ->putenv(true)
25 | ->toEnv(true)
26 | ->toServer(true);
27 | }
28 | };
29 |
30 | if (!getenv('DB_DRIVER')) {
31 | putenv('DB_DRIVER=Sqlite');
32 | }
33 | $driver = getenv('DB_DRIVER');
34 |
35 | if (file_exists(TESTS . ".env.$driver") && !file_exists(TESTS . '.env')) {
36 | copy(TESTS . ".env.$driver", TESTS . '.env');
37 | }
38 |
39 | /**
40 | * Read .env file(s).
41 | */
42 | $loadEnv(TESTS . '.env');
43 |
44 | // Re-read the driver
45 | $driver = getenv('DB_DRIVER');
46 | echo "Using driver $driver \n";
47 |
48 | Configure::write('debug', true);
49 | Configure::write(
50 | 'App',
51 | [
52 | 'namespace' => 'TestApp',
53 | 'paths' => [
54 | 'plugins' => [TESTS . 'Plugins' . DS],
55 | ],
56 | ]
57 | );
58 |
59 | $cacheConfig = [
60 | 'className' => 'File',
61 | 'path' => CACHE,
62 | 'url' => env('CACHE_DEFAULT_URL', null),
63 | 'duration' => '+2 minutes',
64 | ];
65 |
66 | Cache::setConfig('_cake_model_', $cacheConfig);
67 | Cache::setConfig('_cake_core_', $cacheConfig);
68 |
69 | $dbConnection = [
70 | 'className' => 'Cake\Database\Connection',
71 | 'driver' => 'Cake\Database\Driver\\' . $driver,
72 | 'persistent' => false,
73 | 'host' => getenv('DB_HOST'),
74 | 'username' => getenv('DB_USER'),
75 | 'password' => getenv('DB_PWD'),
76 | 'database' => getenv('DB_DATABASE'),
77 | 'encoding' => 'utf8',
78 | 'timezone' => 'UTC',
79 | 'cacheMetadata' => true,
80 | 'quoteIdentifiers' => true,
81 | 'log' => false,
82 | //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
83 | 'url' => env('DATABASE_TEST_URL', null),
84 | 'migrations' => [
85 | ['source' => 'FooSource'],
86 | ['plugin' => 'FooPlugin'],
87 | ],
88 | ];
89 |
90 | ConnectionManager::setConfig('test', $dbConnection);
91 |
92 | $dbDefaultConnection = $dbConnection;
93 | $dbDefaultConnection['database'] = 'migrator';
94 | ConnectionManager::setConfig('default', $dbConnection);
95 |
96 | $dbConnection['migrations'] = ['plugin' => 'BarPlugin'];
97 | ConnectionManager::setConfig('test_2', $dbConnection);
98 |
99 | $dbConnection['migrations'] = true;
100 | ConnectionManager::setConfig('test_3', $dbConnection);
101 |
--------------------------------------------------------------------------------