packages/sql-fixture/tests/Integration/Sqlite/SqliteIntegrationTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration\Sqlite;
6
7use Faker\Factory;
8use PDO;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\Attributes\Test;
11use PHPUnit\Framework\Attributes\UsesClass;
12use PHPUnit\Framework\TestCase;
13use SqlFixture\DatabaseFixtureProvider;
14use SqlFixture\FixtureGenerator;
15use SqlFixture\FixtureProvider;
16use SqlFixture\Hydrator\ReflectionHydrator;
17use SqlFixture\Platform\PlatformFactory;
18use SqlFixture\Platform\Sqlite\SqliteSchemaFetcher;
19use SqlFixture\Platform\Sqlite\SqliteSchemaParser;
20use SqlFixture\Platform\Sqlite\SqliteTypeMapper;
21use SqlFixture\Schema\ColumnDefinition;
22use SqlFixture\Schema\TableSchema;
23
24#[CoversClass(DatabaseFixtureProvider::class)]
25#[UsesClass(FixtureProvider::class)]
26#[UsesClass(FixtureGenerator::class)]
27#[UsesClass(PlatformFactory::class)]
28#[UsesClass(SqliteSchemaFetcher::class)]
29#[UsesClass(SqliteSchemaParser::class)]
30#[UsesClass(SqliteTypeMapper::class)]
31#[UsesClass(ColumnDefinition::class)]
32#[UsesClass(TableSchema::class)]
33#[UsesClass(ReflectionHydrator::class)]
34#[CoversClass(\SqlFixture\Provider\DatabaseSchemaCache::class)]
35#[UsesClass(\SqlFixture\Fixture\FixtureSet::class)]
36#[UsesClass(\SqlFixture\Fixture\GenerationRun::class)]
37#[UsesClass(\SqlFixture\Fixture\OverrideRows::class)]
38#[UsesClass(\SqlFixture\Fixture\PlanGenerator::class)]
39#[UsesClass(\SqlFixture\Fixture\PlanSchemaException::class)]
40#[UsesClass(\SqlFixture\Fixture\PlanSchemaValidator::class)]
41#[UsesClass(\SqlFixture\Fixture\RowSpec::class)]
42#[UsesClass(\SqlFixture\Fixture\TableOverrides::class)]
43#[UsesClass(\SqlFixture\Hydrator\HydrationException::class)]
44#[UsesClass(\SqlFixture\Hydrator\HydratorInterface::class)]
45#[UsesClass(\SqlFixture\Plan\ColumnRef::class)]
46#[UsesClass(\SqlFixture\Plan\FixturePlan::class)]
47#[UsesClass(\SqlFixture\Plan\PlanParser::class)]
48#[UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
49#[UsesClass(\SqlFixture\Plan\PlanSyntaxException::class)]
50#[UsesClass(\SqlFixture\Plan\Relation::class)]
51#[UsesClass(\SqlFixture\Plan\RelationKind::class)]
52#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
53#[UsesClass(\SqlFixture\Platform\MySql\MySqlSchemaFetcher::class)]
54#[UsesClass(\SqlFixture\Platform\MySql\MySqlSchemaParser::class)]
55#[UsesClass(\SqlFixture\Platform\MySql\MySqlTypeMapper::class)]
56#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaFetcher::class)]
57#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaParser::class)]
58#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlTypeMapper::class)]
59#[UsesClass(\SqlFixture\Schema\SchemaFetcherInterface::class)]
60#[UsesClass(\SqlFixture\Schema\SchemaNotFoundException::class)]
61#[UsesClass(\SqlFixture\Schema\SchemaParseException::class)]
62#[UsesClass(\SqlFixture\Schema\SchemaParserInterface::class)]
63#[UsesClass(\SqlFixture\Schema\SchemaResolverInterface::class)]
64#[UsesClass(\SqlFixture\Schema\StaticSchemaResolver::class)]
65#[UsesClass(\SqlFixture\Schema\TableIdentifier::class)]
66#[UsesClass(\SqlFixture\TypeMapper\TypeMapperInterface::class)]
67#[UsesClass(\SqlFixture\Fixture\Generation\ConnectedTables::class)]
68#[UsesClass(\SqlFixture\Fixture\Generation\OverrideSpecs::class)]
69#[UsesClass(\SqlFixture\Fixture\Generation\RelationCounts::class)]
70#[UsesClass(\SqlFixture\Fixture\Generation\RelationProjection::class)]
71#[UsesClass(\SqlFixture\Fixture\Generation\RowMaterializer::class)]
72#[UsesClass(\SqlFixture\Fixture\RowGeneration::class)]
73#[UsesClass(\SqlFixture\Fixture\Validation\EndpointValidator::class)]
74#[UsesClass(\SqlFixture\Fixture\Validation\OverrideValidator::class)]
75#[UsesClass(\SqlFixture\Hydrator\Reflection\ConstructorHydration::class)]
76#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyHydration::class)]
77#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyNames::class)]
78#[UsesClass(\SqlFixture\Hydrator\Reflection\ValueConversion::class)]
79#[UsesClass(\SqlFixture\InvalidOverrideException::class)]
80#[UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
81#[UsesClass(\SqlFixture\Plan\Parsing\RelationCursor::class)]
82#[UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
83#[UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
84#[UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
85#[UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
86#[UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
87#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
88#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
89#[UsesClass(\SqlFixture\Platform\MySql\Schema\ColumnParser::class)]
90#[UsesClass(\SqlFixture\Platform\MySql\Schema\CreateTableQuery::class)]
91#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefaultExpression::class)]
92#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefinitionIntegrity::class)]
93#[UsesClass(\SqlFixture\Platform\MySql\Schema\IdentifierQuoter::class)]
94#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinition::class)]
95#[UsesClass(\SqlFixture\Platform\MySql\Schema\TypeParameters::class)]
96#[UsesClass(\SqlFixture\Platform\MySql\Value\ColumnGenerator::class)]
97#[UsesClass(\SqlFixture\Platform\MySql\Value\DecimalGenerator::class)]
98#[UsesClass(\SqlFixture\Platform\MySql\Value\GeometryGenerator::class)]
99#[UsesClass(\SqlFixture\Platform\MySql\Value\IntegerGenerator::class)]
100#[UsesClass(\SqlFixture\Platform\MySql\Value\NumericGenerator::class)]
101#[UsesClass(\SqlFixture\Platform\MySql\Value\SpatialGenerator::class)]
102#[UsesClass(\SqlFixture\Platform\MySql\Value\StringGenerator::class)]
103#[UsesClass(\SqlFixture\Platform\MySql\Value\TextGenerator::class)]
104#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogColumn::class)]
105#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogDdl::class)]
106#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogQuery::class)]
107#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogSchema::class)]
108#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\ColumnParser::class)]
109#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefaultExpression::class)]
110#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefinitionList::class)]
111#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TableSyntax::class)]
112#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TypeDeclaration::class)]
113#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\ColumnGenerator::class)]
114#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\DecimalGenerator::class)]
115#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\NumericGenerator::class)]
116#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StringGenerator::class)]
117#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StructuredGenerator::class)]
118#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\TemporalGenerator::class)]
119#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\ColumnParser::class)]
120#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\CreateTableQuery::class)]
121#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefaultExpression::class)]
122#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefinitionList::class)]
123#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaColumn::class)]
124#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaSchema::class)]
125#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TableSyntax::class)]
126#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TypeDeclaration::class)]
127#[UsesClass(\SqlFixture\Platform\Sqlite\Value\ColumnGenerator::class)]
128#[UsesClass(\SqlFixture\Platform\Sqlite\Value\TypeAffinity::class)]
129#[UsesClass(\SqlFixture\Provider\SqlSchemaProvider::class)]
130#[UsesClass(\SqlFixture\Schema\DefinitionSegments::class)]
131#[UsesClass(\SqlFixture\Schema\TypeShape::class)]
132#[UsesClass(\SqlFixture\TypeMapper\ParagraphGenerator::class)]
133#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinitionInput::class)]
134#[UsesClass(\SqlFixture\Hydrator\Exception\ClassNotFoundException::class)]
135#[UsesClass(\SqlFixture\Hydrator\Exception\MissingConstructorArgumentException::class)]
136#[UsesClass(\SqlFixture\Fixture\Exception\UnknownOverrideColumnException::class)]
137#[UsesClass(\SqlFixture\Fixture\Exception\NullOverrideException::class)]
138#[UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnOverrideException::class)]
139#[UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnReferenceException::class)]
140#[UsesClass(\SqlFixture\Fixture\Exception\MissingRelationValueException::class)]
141#[UsesClass(\SqlFixture\Fixture\Exception\UnknownPlanColumnException::class)]
142#[UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
143#[UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
144#[UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
145#[UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
146#[UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
147#[UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
148#[UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
149#[UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
150#[UsesClass(\SqlFixture\Plan\Exception\DuplicateColumnBindingException::class)]
151#[UsesClass(\SqlFixture\Plan\Exception\CyclicDependencyException::class)]
152#[UsesClass(\SqlFixture\Plan\Exception\UnboundedSelfReferenceException::class)]
153#[UsesClass(\SqlFixture\Schema\Exception\InvalidSqlException::class)]
154#[UsesClass(\SqlFixture\Schema\Exception\ExpectedCreateTableException::class)]
155#[UsesClass(\SqlFixture\Schema\Exception\MissingColumnDefinitionsException::class)]
156#[UsesClass(\SqlFixture\TypeMapper\IntegerRange::class)]
157#[UsesClass(\SqlFixture\TypeMapper\IntegerWidth::class)]
158#[UsesClass(\SqlFixture\TypeMapper\DecimalRange::class)]
159#[UsesClass(\SqlFixture\Hydrator\Reflection\ConversionTarget::class)]
160final class SqliteIntegrationTest extends TestCase
161{
162    #[Test]
163    public function fixtureProviderWithSqliteDialect(): void
164    {
165        $faker = Factory::create();
166        $faker->seed(12345);
167        $provider = new FixtureProvider(
168            $faker,
169            dialect: PlatformFactory::DRIVER_SQLITE
170        );
171
172        $fixture = $provider->fixture(
173            'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT)'
174        );
175        self::assertArrayHasKey('name', $fixture);
176        self::assertArrayHasKey('email', $fixture);
177        self::assertIsString($fixture['name']);
178    }
179
180    #[Test]
181    public function databaseFixtureProviderWithSqlite(): void
182    {
183        $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
184        $pdo->exec(<<<'SQL'
185            CREATE TABLE users (
186                id INTEGER PRIMARY KEY AUTOINCREMENT,
187                name TEXT NOT NULL,
188                email TEXT,
189                age INTEGER,
190                balance REAL,
191                created_at TEXT DEFAULT CURRENT_TIMESTAMP
192            )
193            SQL);
194
195        $faker = Factory::create();
196        $faker->seed(12345);
197        $provider = new DatabaseFixtureProvider($faker, $pdo);
198
199        self::assertSame(PlatformFactory::DRIVER_SQLITE, $provider->getDriver());
200
201        $fixture = $provider->fixture('users');
202        self::assertArrayNotHasKey('id', $fixture);
203        self::assertArrayHasKey('name', $fixture);
204        self::assertIsString($fixture['name']);
205    }
206
207    #[Test]
208    public function insertAndSelectFixture(): void
209    {
210        $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
211        $pdo->exec(<<<'SQL'
212            CREATE TABLE products (
213                id INTEGER PRIMARY KEY AUTOINCREMENT,
214                name TEXT NOT NULL,
215                price REAL NOT NULL,
216                quantity INTEGER DEFAULT 0
217            )
218            SQL);
219
220        $faker = Factory::create();
221        $faker->seed(12345);
222        $provider = new DatabaseFixtureProvider($faker, $pdo);
223
224        $fixture = $provider->fixture('products');
225
226        $stmt = $pdo->prepare(
227            'INSERT INTO products (name, price, quantity) VALUES (:name, :price, :quantity)'
228        );
229        $stmt->execute($fixture);
230
231        $id = (int) $pdo->lastInsertId();
232        $stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
233        $stmt->execute([$id]);
234        $row = $stmt->fetch(PDO::FETCH_ASSOC);
235
236        self::assertNotFalse($row);
237        self::assertIsArray($row);
238        self::assertSame($fixture['name'], $row['name']);
239        self::assertIsFloat($fixture['price']);
240        self::assertIsNumeric($row['price']);
241        self::assertEqualsWithDelta($fixture['price'], (float) $row['price'], 0.001);
242    }
243
244    #[Test]
245    public function fixtureWithOverrides(): void
246    {
247        $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
248        $pdo->exec(<<<'SQL'
249            CREATE TABLE users (
250                id INTEGER PRIMARY KEY,
251                name TEXT NOT NULL,
252                email TEXT NOT NULL
253            )
254            SQL);
255
256        $faker = Factory::create();
257        $provider = new DatabaseFixtureProvider($faker, $pdo);
258
259        $fixture = $provider->fixture('users', [
260            'name' => 'John Doe',
261            'email' => 'john@example.com',
262        ]);
263
264        self::assertSame('John Doe', $fixture['name']);
265        self::assertSame('john@example.com', $fixture['email']);
266    }
267
268    #[Test]
269    public function fixtureWithHydration(): void
270    {
271        $target = new class (0, '', '') {
272            public function __construct(
273                public int $id,
274                public string $name,
275                public string $email,
276            ) {
277            }
278        };
279        $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
280        $pdo->exec(<<<'SQL'
281            CREATE TABLE users (
282                id INTEGER PRIMARY KEY,
283                name TEXT NOT NULL,
284                email TEXT NOT NULL
285            )
286            SQL);
287
288        $faker = Factory::create();
289        $provider = new DatabaseFixtureProvider($faker, $pdo);
290
291        $user = $provider->fixture(
292            'users',
293            ['id' => 1],
294            $target::class
295        );
296
297        self::assertSame(1, $user->id);
298        self::assertNotEmpty($user->name);
299        self::assertNotEmpty($user->email);
300    }
301
302    #[Test]
303    public function fixtureProviderDialectOverride(): void
304    {
305        $faker = Factory::create();
306        $faker->seed(12345);
307
308        $provider = new FixtureProvider($faker);
309
310        $fixture = $provider->fixture(
311            'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)',
312            [],
313            null,
314            PlatformFactory::DRIVER_SQLITE
315        );
316        self::assertArrayHasKey('name', $fixture);
317    }
318
319    #[Test]
320    public function allSqliteTypesFixture(): void
321    {
322        $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
323        $pdo->exec(<<<'SQL'
324            CREATE TABLE all_types (
325                col_integer INTEGER NOT NULL,
326                col_int INT NOT NULL,
327                col_tinyint TINYINT NOT NULL,
328                col_smallint SMALLINT NOT NULL,
329                col_mediumint MEDIUMINT NOT NULL,
330                col_bigint BIGINT NOT NULL,
331                col_text TEXT NOT NULL,
332                col_varchar VARCHAR(100) NOT NULL,
333                col_char CHAR(10) NOT NULL,
334                col_real REAL NOT NULL,
335                col_float FLOAT NOT NULL,
336                col_double DOUBLE NOT NULL,
337                col_decimal DECIMAL(10, 2) NOT NULL,
338                col_blob BLOB NOT NULL,
339                col_boolean BOOLEAN NOT NULL,
340                col_date DATE NOT NULL,
341                col_time TIME NOT NULL,
342                col_datetime DATETIME NOT NULL
343            )
344            SQL);
345
346        $faker = Factory::create();
347        $faker->seed(12345);
348        $provider = new DatabaseFixtureProvider($faker, $pdo);
349
350        $fixture = $provider->fixture('all_types');
351
352        self::assertIsInt($fixture['col_integer']);
353        self::assertIsInt($fixture['col_int']);
354        self::assertIsInt($fixture['col_tinyint']);
355        self::assertIsInt($fixture['col_smallint']);
356        self::assertIsInt($fixture['col_mediumint']);
357        self::assertIsInt($fixture['col_bigint']);
358        self::assertIsString($fixture['col_text']);
359        self::assertIsString($fixture['col_varchar']);
360        self::assertIsString($fixture['col_char']);
361        self::assertIsFloat($fixture['col_real']);
362        self::assertIsFloat($fixture['col_float']);
363        self::assertIsFloat($fixture['col_double']);
364        self::assertIsFloat($fixture['col_decimal']);
365        self::assertIsString($fixture['col_blob']);
366        self::assertContains($fixture['col_boolean'], [0, 1]);
367        $colDate = $fixture['col_date'];
368        self::assertIsString($colDate);
369        self::assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}$/', $colDate);
370        $colTime = $fixture['col_time'];
371        self::assertIsString($colTime);
372        self::assertMatchesRegularExpression('/^\d{2}:\d{2}:\d{2}$/', $colTime);
373        $colDatetime = $fixture['col_datetime'];
374        self::assertIsString($colDatetime);
375        self::assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $colDatetime);
376    }
377}
378