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:
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user