packages/ztd-query-mysql/tests/Unit/Schema/MySqlSchemaReflectorTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Connection\ConnectionInterface;
11use ZtdQuery\Connection\StatementInterface;
12use ZtdQuery\Platform\MySql\Schema\MySqlSchemaReflector;
13
14#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ReferenceReader::class)]
15#[CoversClass(MySqlSchemaReflector::class)]
16#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\MySqlSelectRelationParser::class)]
17#[UsesClass(\ZtdQuery\Platform\MySql\Schema\View\MySqlViewDefinitionParser::class)]
18#[UsesClass(\ZtdQuery\Platform\MySql\Sql\MySqlLexerProfile::class)]
19final class MySqlSchemaReflectorTest extends TestCase
20{
21    public function testReflectViewsReturnsEmptyWhenQueryFails(): void
22    {
23        $connection = self::createStub(ConnectionInterface::class);
24        $connection->method('query')->willReturn(false);
25
26        self::assertSame([], (new MySqlSchemaReflector($connection))->reflectViews());
27    }
28
29    public function testReflectViewsSkipsMalformedDefinitions(): void
30    {
31        $views = self::createStub(StatementInterface::class);
32        $views->method('fetchAll')->willReturn([
33            ['name' => null], ['name' => ''], ['name' => 'query_failed'],
34            ['name' => 'missing_row'], ['name' => 'non_string'], ['name' => 'invalid'],
35            ['name' => 'active`users'], ['name' => 'all_users'],
36        ]);
37        $missing = self::createStub(StatementInterface::class);
38        $missing->method('fetchAll')->willReturn([]);
39        $nonString = self::createStub(StatementInterface::class);
40        $nonString->method('fetchAll')->willReturn([['Create View' => null]]);
41        $invalid = self::createStub(StatementInterface::class);
42        $invalid->method('fetchAll')->willReturn([['Create View' => 'CREATE VIEW invalid']]);
43        $active = self::createStub(StatementInterface::class);
44        $active->method('fetchAll')->willReturn([['Create View' => 'CREATE VIEW `active``users` AS SELECT * FROM app.users']]);
45        $all = self::createStub(StatementInterface::class);
46        $all->method('fetchAll')->willReturn([['Create View' => 'CREATE VIEW all_users AS SELECT * FROM app.users']]);
47        $responses = [
48            "SHOW FULL TABLES WHERE Table_type = 'VIEW'" => $views,
49            'SHOW CREATE VIEW `query_failed`' => false,
50            'SHOW CREATE VIEW `missing_row`' => $missing,
51            'SHOW CREATE VIEW `non_string`' => $nonString,
52            'SHOW CREATE VIEW `invalid`' => $invalid,
53            'SHOW CREATE VIEW `active``users`' => $active,
54            'SHOW CREATE VIEW `all_users`' => $all,
55        ];
56        $queries = [];
57        $connection = self::createMock(ConnectionInterface::class);
58        $connection->expects(self::exactly(7))->method('query')->willReturnCallback(
59            static function (string $sql) use ($responses, &$queries): StatementInterface|false {
60                $queries[] = $sql;
61                return $responses[$sql];
62            },
63        );
64
65        $definitions = (new MySqlSchemaReflector($connection))->reflectViews();
66
67        self::assertSame(['active`users', 'all_users'], array_keys($definitions));
68        self::assertSame(['users'], $definitions['active`users']->dependencies);
69        self::assertSame([
70            "SHOW FULL TABLES WHERE Table_type = 'VIEW'",
71            'SHOW CREATE VIEW `query_failed`', 'SHOW CREATE VIEW `missing_row`',
72            'SHOW CREATE VIEW `non_string`', 'SHOW CREATE VIEW `invalid`',
73            'SHOW CREATE VIEW `active``users`', 'SHOW CREATE VIEW `all_users`',
74        ], $queries);
75    }
76
77    public function testGetCreateStatementReturnsNullWhenQueryFails(): void
78    {
79        $connection = self::createStub(ConnectionInterface::class);
80        $connection->method('query')->willReturn(false);
81
82        $reflector = new MySqlSchemaReflector($connection);
83        self::assertNull($reflector->getCreateStatement('users'));
84    }
85
86    public function testGetCreateStatementReturnsNullWhenNoRows(): void
87    {
88        $statement = self::createStub(StatementInterface::class);
89        $statement->method('fetchAll')->willReturn([]);
90
91        $connection = self::createStub(ConnectionInterface::class);
92        $connection->method('query')->willReturn($statement);
93
94        $reflector = new MySqlSchemaReflector($connection);
95        self::assertNull($reflector->getCreateStatement('users'));
96    }
97
98    public function testGetCreateStatementReturnsSql(): void
99    {
100        $createSql = 'CREATE TABLE users (id INT PRIMARY KEY)';
101        $statement = self::createStub(StatementInterface::class);
102        $statement->method('fetchAll')->willReturn([['Create Table' => $createSql]]);
103
104        $connection = self::createStub(ConnectionInterface::class);
105        $connection->method('query')->willReturn($statement);
106
107        $reflector = new MySqlSchemaReflector($connection);
108        self::assertSame($createSql, $reflector->getCreateStatement('users'));
109    }
110
111    public function testReflectAllReturnsEmptyWhenQueryFails(): void
112    {
113        $connection = self::createStub(ConnectionInterface::class);
114        $connection->method('query')->willReturn(false);
115
116        $reflector = new MySqlSchemaReflector($connection);
117        self::assertSame([], $reflector->reflectAll());
118    }
119}
120