Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,415 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Database\Schema\Comparison;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\Schema\Comparison\SchemaComparator;
use App\Framework\Database\Schema\Comparison\SchemaDifference;
use Mockery;
beforeEach(function () {
$this->sourceConnection = Mockery::mock(ConnectionInterface::class);
$this->targetConnection = Mockery::mock(ConnectionInterface::class);
$this->comparator = new SchemaComparator($this->sourceConnection, $this->targetConnection);
});
afterEach(function () {
Mockery::close();
});
test('compares schemas with different tables', function () {
// Mock source tables
$sourceTables = Mockery::mock(ResultInterface::class);
$sourceTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
['table_name' => 'posts'],
['table_name' => 'comments'],
]);
// Mock target tables
$targetTables = Mockery::mock(ResultInterface::class);
$targetTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
['table_name' => 'posts'],
['table_name' => 'categories'], // Different table
]);
// Mock table structure queries
$this->sourceConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->targetConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($sourceTables);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($targetTables);
// Mock column queries for each table
$this->mockTableStructure('users', true, true);
$this->mockTableStructure('posts', true, true);
$this->mockTableStructure('comments', true, false);
$this->mockTableStructure('categories', false, true);
$difference = $this->comparator->compare();
expect($difference)->toBeInstanceOf(SchemaDifference::class);
expect($difference->hasDifferences())->toBeTrue();
expect($difference->missingTables)->toHaveCount(1);
expect($difference->extraTables)->toHaveCount(1);
expect(array_keys($difference->missingTables))->toBe(['comments']);
expect(array_keys($difference->extraTables))->toBe(['categories']);
});
test('compares schemas with identical tables', function () {
// Mock source tables
$sourceTables = Mockery::mock(ResultInterface::class);
$sourceTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
['table_name' => 'posts'],
]);
// Mock target tables
$targetTables = Mockery::mock(ResultInterface::class);
$targetTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
['table_name' => 'posts'],
]);
// Mock table structure queries
$this->sourceConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->targetConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($sourceTables);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($targetTables);
// Mock identical column structure for each table
$this->mockIdenticalTableStructure('users');
$this->mockIdenticalTableStructure('posts');
$difference = $this->comparator->compare();
expect($difference)->toBeInstanceOf(SchemaDifference::class);
expect($difference->hasDifferences())->toBeFalse();
expect($difference->missingTables)->toBeEmpty();
expect($difference->extraTables)->toBeEmpty();
expect($difference->tableDifferences)->toBeEmpty();
});
test('compares schemas with different column definitions', function () {
// Mock source tables
$sourceTables = Mockery::mock(ResultInterface::class);
$sourceTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
]);
// Mock target tables
$targetTables = Mockery::mock(ResultInterface::class);
$targetTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
]);
// Mock table structure queries
$this->sourceConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->targetConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($sourceTables);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($targetTables);
// Mock source columns
$sourceColumns = Mockery::mock(ResultInterface::class);
$sourceColumns->shouldReceive('fetchAll')->andReturn([
[
'column_name' => 'id',
'data_type' => 'integer',
'is_nullable' => 'NO',
'column_default' => 'nextval(\'users_id_seq\'::regclass)',
],
[
'column_name' => 'name',
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
[
'column_name' => 'email',
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
]);
// Mock target columns with differences
$targetColumns = Mockery::mock(ResultInterface::class);
$targetColumns->shouldReceive('fetchAll')->andReturn([
[
'column_name' => 'id',
'data_type' => 'integer',
'is_nullable' => 'NO',
'column_default' => 'nextval(\'users_id_seq\'::regclass)',
],
[
'column_name' => 'name',
'data_type' => 'character varying',
'is_nullable' => 'YES', // Changed to nullable
'column_default' => null,
],
[
'column_name' => 'username', // Different column
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
]);
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*users/'), [])
->andReturn($sourceColumns);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*users/'), [])
->andReturn($targetColumns);
// Mock empty indexes and foreign keys
$this->mockEmptyIndexes('users');
$this->mockEmptyForeignKeys('users');
$difference = $this->comparator->compare();
expect($difference)->toBeInstanceOf(SchemaDifference::class);
expect($difference->hasDifferences())->toBeTrue();
expect($difference->tableDifferences)->toHaveCount(1);
expect($difference->tableDifferences['users']->missingColumns)->toHaveKey('email');
expect($difference->tableDifferences['users']->extraColumns)->toHaveKey('username');
expect($difference->tableDifferences['users']->modifiedColumns)->toHaveKey('name');
});
test('compares schemas with different indexes', function () {
// Mock source tables
$sourceTables = Mockery::mock(ResultInterface::class);
$sourceTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
]);
// Mock target tables
$targetTables = Mockery::mock(ResultInterface::class);
$targetTables->shouldReceive('fetchAll')->andReturn([
['table_name' => 'users'],
]);
// Mock table structure queries
$this->sourceConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->targetConnection->shouldReceive('getPdo->getAttribute')
->with(\PDO::ATTR_DRIVER_NAME)
->andReturn('pgsql');
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($sourceTables);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.tables/'), [])
->andReturn($targetTables);
// Mock identical columns
$this->mockIdenticalColumns('users');
// Mock source indexes
$sourceIndexes = Mockery::mock(ResultInterface::class);
$sourceIndexes->shouldReceive('fetchAll')->andReturn([
[
'indexname' => 'users_pkey',
'indexdef' => 'CREATE UNIQUE INDEX users_pkey ON users USING btree (id)',
],
[
'indexname' => 'users_email_idx',
'indexdef' => 'CREATE UNIQUE INDEX users_email_idx ON users USING btree (email)',
],
]);
// Mock target indexes with differences
$targetIndexes = Mockery::mock(ResultInterface::class);
$targetIndexes->shouldReceive('fetchAll')->andReturn([
[
'indexname' => 'users_pkey',
'indexdef' => 'CREATE UNIQUE INDEX users_pkey ON users USING btree (id)',
],
[
'indexname' => 'users_name_idx', // Different index
'indexdef' => 'CREATE INDEX users_name_idx ON users USING btree (name)',
],
]);
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/pg_indexes.*users/'), [])
->andReturn($sourceIndexes);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/pg_indexes.*users/'), [])
->andReturn($targetIndexes);
// Mock empty foreign keys
$this->mockEmptyForeignKeys('users');
$difference = $this->comparator->compare();
expect($difference)->toBeInstanceOf(SchemaDifference::class);
expect($difference->hasDifferences())->toBeTrue();
expect($difference->tableDifferences)->toHaveCount(1);
expect($difference->tableDifferences['users']->missingIndexes)->toHaveKey('users_email_idx');
expect($difference->tableDifferences['users']->extraIndexes)->toHaveKey('users_name_idx');
});
// Helper functions for tests
beforeEach(function () {
// Define helper methods in the test context
$this->mockTableStructure = function (string $tableName, bool $inSource, bool $inTarget): void {
if ($inSource) {
$sourceColumns = Mockery::mock(ResultInterface::class);
$sourceColumns->shouldReceive('fetchAll')->andReturn([
[
'column_name' => 'id',
'data_type' => 'integer',
'is_nullable' => 'NO',
'column_default' => 'nextval(\'' . $tableName . '_id_seq\'::regclass)',
],
[
'column_name' => 'name',
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
]);
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*' . $tableName . '/'), [])
->andReturn($sourceColumns);
$this->mockEmptyIndexes($tableName, true, false);
$this->mockEmptyForeignKeys($tableName, true, false);
}
if ($inTarget) {
$targetColumns = Mockery::mock(ResultInterface::class);
$targetColumns->shouldReceive('fetchAll')->andReturn([
[
'column_name' => 'id',
'data_type' => 'integer',
'is_nullable' => 'NO',
'column_default' => 'nextval(\'' . $tableName . '_id_seq\'::regclass)',
],
[
'column_name' => 'name',
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
]);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*' . $tableName . '/'), [])
->andReturn($targetColumns);
$this->mockEmptyIndexes($tableName, false, true);
$this->mockEmptyForeignKeys($tableName, false, true);
}
};
$this->mockIdenticalTableStructure = function (string $tableName): void {
$this->mockIdenticalColumns($tableName);
$this->mockEmptyIndexes($tableName);
$this->mockEmptyForeignKeys($tableName);
};
$this->mockIdenticalColumns = function (string $tableName): void {
$columns = Mockery::mock(ResultInterface::class);
$columns->shouldReceive('fetchAll')->andReturn([
[
'column_name' => 'id',
'data_type' => 'integer',
'is_nullable' => 'NO',
'column_default' => 'nextval(\'' . $tableName . '_id_seq\'::regclass)',
],
[
'column_name' => 'name',
'data_type' => 'character varying',
'is_nullable' => 'NO',
'column_default' => null,
],
]);
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*' . $tableName . '/'), [])
->andReturn($columns);
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.columns.*' . $tableName . '/'), [])
->andReturn($columns);
};
$this->mockEmptyIndexes = function (string $tableName, bool $source = true, bool $target = true): void {
$emptyIndexes = Mockery::mock(ResultInterface::class);
$emptyIndexes->shouldReceive('fetchAll')->andReturn([]);
if ($source) {
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/pg_indexes.*' . $tableName . '/'), [])
->andReturn($emptyIndexes);
}
if ($target) {
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/pg_indexes.*' . $tableName . '/'), [])
->andReturn($emptyIndexes);
}
};
$this->mockEmptyForeignKeys = function (string $tableName, bool $source = true, bool $target = true): void {
$emptyForeignKeys = Mockery::mock(ResultInterface::class);
$emptyForeignKeys->shouldReceive('fetchAll')->andReturn([]);
if ($source) {
$this->sourceConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.table_constraints.*' . $tableName . '/'), [])
->andReturn($emptyForeignKeys);
}
if ($target) {
$this->targetConnection->shouldReceive('query')
->with(Mockery::pattern('/information_schema.table_constraints.*' . $tableName . '/'), [])
->andReturn($emptyForeignKeys);
}
};
});

View File

@@ -0,0 +1,234 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Database\Schema\Comparison;
use App\Framework\Database\Schema\Comparison\SchemaDifference;
use App\Framework\Database\Schema\Comparison\TableDifference;
test('creates a schema difference with missing and extra tables', function () {
$missingTables = [
'users' => [
'columns' => [
'id' => ['type' => 'integer', 'nullable' => false],
'name' => ['type' => 'varchar', 'nullable' => false],
'email' => ['type' => 'varchar', 'nullable' => false],
],
],
'posts' => [
'columns' => [
'id' => ['type' => 'integer', 'nullable' => false],
'title' => ['type' => 'varchar', 'nullable' => false],
'content' => ['type' => 'text', 'nullable' => true],
],
],
];
$extraTables = [
'categories' => [
'columns' => [
'id' => ['type' => 'integer', 'nullable' => false],
'name' => ['type' => 'varchar', 'nullable' => false],
],
],
];
$difference = new SchemaDifference(
'source_schema',
'target_schema',
$missingTables,
$extraTables,
[]
);
expect($difference->sourceSchema)->toBe('source_schema');
expect($difference->targetSchema)->toBe('target_schema');
expect($difference->missingTables)->toBe($missingTables);
expect($difference->extraTables)->toBe($extraTables);
expect($difference->tableDifferences)->toBeEmpty();
expect($difference->hasDifferences())->toBeTrue();
});
test('creates a schema difference with table differences', function () {
$tableDifference = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
$difference = new SchemaDifference(
'source_schema',
'target_schema',
[], // missing tables
[], // extra tables
['users' => $tableDifference]
);
expect($difference->sourceSchema)->toBe('source_schema');
expect($difference->targetSchema)->toBe('target_schema');
expect($difference->missingTables)->toBeEmpty();
expect($difference->extraTables)->toBeEmpty();
expect($difference->tableDifferences)->toHaveCount(1);
expect($difference->tableDifferences['users'])->toBe($tableDifference);
expect($difference->hasDifferences())->toBeTrue();
});
test('creates a schema difference with no differences', function () {
$difference = new SchemaDifference(
'source_schema',
'target_schema',
[], // missing tables
[], // extra tables
[] // table differences
);
expect($difference->sourceSchema)->toBe('source_schema');
expect($difference->targetSchema)->toBe('target_schema');
expect($difference->missingTables)->toBeEmpty();
expect($difference->extraTables)->toBeEmpty();
expect($difference->tableDifferences)->toBeEmpty();
expect($difference->hasDifferences())->toBeFalse();
});
test('gets summary of differences', function () {
$tableDifference1 = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
$tableDifference2 = new TableDifference(
'posts',
[], // missing columns
[], // extra columns
[], // modified columns
['idx_posts_title' => ['type' => 'index', 'columns' => ['title']]], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
$difference = new SchemaDifference(
'source_schema',
'target_schema',
['comments' => []], // missing tables
['categories' => []], // extra tables
[
'users' => $tableDifference1,
'posts' => $tableDifference2,
]
);
$summary = $difference->getSummary();
expect($summary)->toBeArray();
expect($summary['missing_tables'])->toBe(1);
expect($summary['extra_tables'])->toBe(1);
expect($summary['modified_tables'])->toBe(2);
expect($summary['total_differences'])->toBe(4);
});
test('gets description of differences', function () {
$tableDifference = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
$difference = new SchemaDifference(
'source_schema',
'target_schema',
['comments' => []], // missing tables
['categories' => []], // extra tables
['users' => $tableDifference]
);
$description = $difference->getDescription();
expect($description)->toBeString();
expect($description)->toContain('Schema Differences');
expect($description)->toContain('Missing Tables');
expect($description)->toContain('comments');
expect($description)->toContain('Extra Tables');
expect($description)->toContain('categories');
expect($description)->toContain('Table Differences');
expect($description)->toContain('users');
});
test('generates migration code from differences', function () {
$tableDifference = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
$difference = new SchemaDifference(
'source_schema',
'target_schema',
['comments' => [
'columns' => [
'id' => ['type' => 'integer', 'nullable' => false],
'content' => ['type' => 'text', 'nullable' => false],
'post_id' => ['type' => 'integer', 'nullable' => false],
],
]], // missing tables
['categories' => []], // extra tables
['users' => $tableDifference]
);
$migrationCode = $difference->generateMigrationCode('UpdateSchema');
expect($migrationCode)->toBeString();
expect($migrationCode)->toContain('class UpdateSchema extends AbstractMigration');
expect($migrationCode)->toContain('public function up(ConnectionInterface $connection)');
expect($migrationCode)->toContain('public function down(ConnectionInterface $connection)');
expect($migrationCode)->toContain('$schema->create(\'comments\'');
expect($migrationCode)->toContain('$schema->table(\'users\'');
expect($migrationCode)->toContain('$schema->dropIfExists(\'categories\')');
});

View File

@@ -0,0 +1,264 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Database\Schema\Comparison;
use App\Framework\Database\Schema\Comparison\TableDifference;
test('creates a table difference with column differences', function () {
$tableDifference = new TableDifference(
'users',
[
'email' => ['type' => 'varchar', 'nullable' => false],
'created_at' => ['type' => 'timestamp', 'nullable' => false],
], // missing columns
[
'username' => ['type' => 'varchar', 'nullable' => false],
'last_login' => ['type' => 'timestamp', 'nullable' => true],
], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
expect($tableDifference->tableName)->toBe('users');
expect($tableDifference->missingColumns)->toHaveCount(2);
expect($tableDifference->extraColumns)->toHaveCount(2);
expect($tableDifference->modifiedColumns)->toHaveCount(1);
expect($tableDifference->missingIndexes)->toBeEmpty();
expect($tableDifference->extraIndexes)->toBeEmpty();
expect($tableDifference->modifiedIndexes)->toBeEmpty();
expect($tableDifference->missingForeignKeys)->toBeEmpty();
expect($tableDifference->extraForeignKeys)->toBeEmpty();
expect($tableDifference->modifiedForeignKeys)->toBeEmpty();
expect($tableDifference->hasDifferences())->toBeTrue();
});
test('creates a table difference with index differences', function () {
$tableDifference = new TableDifference(
'users',
[], // missing columns
[], // extra columns
[], // modified columns
[
'idx_users_email' => [
'type' => 'unique',
'columns' => ['email'],
],
'idx_users_name' => [
'type' => 'index',
'columns' => ['name'],
],
], // missing indexes
[
'idx_users_username' => [
'type' => 'unique',
'columns' => ['username'],
],
], // extra indexes
[
'idx_users_created_at' => [
'source' => [
'type' => 'index',
'columns' => ['created_at'],
],
'target' => [
'type' => 'index',
'columns' => ['created_at', 'updated_at'],
],
],
], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
expect($tableDifference->tableName)->toBe('users');
expect($tableDifference->missingColumns)->toBeEmpty();
expect($tableDifference->extraColumns)->toBeEmpty();
expect($tableDifference->modifiedColumns)->toBeEmpty();
expect($tableDifference->missingIndexes)->toHaveCount(2);
expect($tableDifference->extraIndexes)->toHaveCount(1);
expect($tableDifference->modifiedIndexes)->toHaveCount(1);
expect($tableDifference->missingForeignKeys)->toBeEmpty();
expect($tableDifference->extraForeignKeys)->toBeEmpty();
expect($tableDifference->modifiedForeignKeys)->toBeEmpty();
expect($tableDifference->hasDifferences())->toBeTrue();
});
test('creates a table difference with foreign key differences', function () {
$tableDifference = new TableDifference(
'posts',
[], // missing columns
[], // extra columns
[], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[
'fk_posts_user_id' => [
'columns' => ['user_id'],
'referenced_table' => 'users',
'referenced_columns' => ['id'],
'update_rule' => 'CASCADE',
'delete_rule' => 'CASCADE',
],
], // missing foreign keys
[
'fk_posts_category_id' => [
'columns' => ['category_id'],
'referenced_table' => 'categories',
'referenced_columns' => ['id'],
'update_rule' => 'CASCADE',
'delete_rule' => 'CASCADE',
],
], // extra foreign keys
[
'fk_posts_parent_id' => [
'source' => [
'columns' => ['parent_id'],
'referenced_table' => 'posts',
'referenced_columns' => ['id'],
'update_rule' => 'CASCADE',
'delete_rule' => 'CASCADE',
],
'target' => [
'columns' => ['parent_id'],
'referenced_table' => 'posts',
'referenced_columns' => ['id'],
'update_rule' => 'CASCADE',
'delete_rule' => 'SET NULL',
],
],
] // modified foreign keys
);
expect($tableDifference->tableName)->toBe('posts');
expect($tableDifference->missingColumns)->toBeEmpty();
expect($tableDifference->extraColumns)->toBeEmpty();
expect($tableDifference->modifiedColumns)->toBeEmpty();
expect($tableDifference->missingIndexes)->toBeEmpty();
expect($tableDifference->extraIndexes)->toBeEmpty();
expect($tableDifference->modifiedIndexes)->toBeEmpty();
expect($tableDifference->missingForeignKeys)->toHaveCount(1);
expect($tableDifference->extraForeignKeys)->toHaveCount(1);
expect($tableDifference->modifiedForeignKeys)->toHaveCount(1);
expect($tableDifference->hasDifferences())->toBeTrue();
});
test('creates a table difference with no differences', function () {
$tableDifference = new TableDifference(
'users',
[], // missing columns
[], // extra columns
[], // modified columns
[], // missing indexes
[], // extra indexes
[], // modified indexes
[], // missing foreign keys
[], // extra foreign keys
[] // modified foreign keys
);
expect($tableDifference->tableName)->toBe('users');
expect($tableDifference->missingColumns)->toBeEmpty();
expect($tableDifference->extraColumns)->toBeEmpty();
expect($tableDifference->modifiedColumns)->toBeEmpty();
expect($tableDifference->missingIndexes)->toBeEmpty();
expect($tableDifference->extraIndexes)->toBeEmpty();
expect($tableDifference->modifiedIndexes)->toBeEmpty();
expect($tableDifference->missingForeignKeys)->toBeEmpty();
expect($tableDifference->extraForeignKeys)->toBeEmpty();
expect($tableDifference->modifiedForeignKeys)->toBeEmpty();
expect($tableDifference->hasDifferences())->toBeFalse();
});
test('gets summary of differences', function () {
$tableDifference = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
['idx_users_email' => ['type' => 'unique', 'columns' => ['email']]], // missing indexes
['idx_users_username' => ['type' => 'unique', 'columns' => ['username']]], // extra indexes
[], // modified indexes
['fk_users_role_id' => []], // missing foreign keys
['fk_users_team_id' => []], // extra foreign keys
[] // modified foreign keys
);
$summary = $tableDifference->getSummary();
expect($summary)->toBeArray();
expect($summary['missing_columns'])->toBe(1);
expect($summary['extra_columns'])->toBe(1);
expect($summary['modified_columns'])->toBe(1);
expect($summary['missing_indexes'])->toBe(1);
expect($summary['extra_indexes'])->toBe(1);
expect($summary['modified_indexes'])->toBe(0);
expect($summary['missing_foreign_keys'])->toBe(1);
expect($summary['extra_foreign_keys'])->toBe(1);
expect($summary['modified_foreign_keys'])->toBe(0);
});
test('gets description of differences', function () {
$tableDifference = new TableDifference(
'users',
['email' => ['type' => 'varchar', 'nullable' => false]], // missing columns
['username' => ['type' => 'varchar', 'nullable' => false]], // extra columns
[
'name' => [
'source' => ['type' => 'varchar', 'nullable' => false],
'target' => ['type' => 'varchar', 'nullable' => true],
],
], // modified columns
['idx_users_email' => ['type' => 'unique', 'columns' => ['email']]], // missing indexes
['idx_users_username' => ['type' => 'unique', 'columns' => ['username']]], // extra indexes
[], // modified indexes
['fk_users_role_id' => [
'columns' => ['role_id'],
'referenced_table' => 'roles',
'referenced_columns' => ['id'],
]], // missing foreign keys
['fk_users_team_id' => [
'columns' => ['team_id'],
'referenced_table' => 'teams',
'referenced_columns' => ['id'],
]], // extra foreign keys
[] // modified foreign keys
);
$description = $tableDifference->getDescription();
expect($description)->toBeString();
expect($description)->toContain('Table: users');
expect($description)->toContain('Missing Columns');
expect($description)->toContain('email');
expect($description)->toContain('Extra Columns');
expect($description)->toContain('username');
expect($description)->toContain('Modified Columns');
expect($description)->toContain('name');
expect($description)->toContain('Missing Indexes');
expect($description)->toContain('idx_users_email');
expect($description)->toContain('Extra Indexes');
expect($description)->toContain('idx_users_username');
expect($description)->toContain('Missing Foreign Keys');
expect($description)->toContain('fk_users_role_id');
expect($description)->toContain('Extra Foreign Keys');
expect($description)->toContain('fk_users_team_id');
});