Skip to content

Commit 8986d15

Browse files
committed
test: improve coverage
1 parent 431c782 commit 8986d15

5 files changed

Lines changed: 361 additions & 6 deletions

File tree

src/Migration/AbstractMigrator.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ public function __construct(
3030
$this->path = $path;
3131
$this->tableName = $tableName ?? $this->getDefaultTableName();
3232

33+
// @codeCoverageIgnoreStart
3334
if($this->driver !== Settings::DRIVER_SQLITE) {
3435
$settings = $settings->withoutSchema();
3536
}
37+
// @codeCoverageIgnoreEnd
3638

3739
$this->dbClient = new Database($settings);
3840
}
@@ -131,12 +133,14 @@ protected function tableHasColumn(string $columnName):bool {
131133
}
132134
return false;
133135

134-
default:
135-
$result = $this->dbClient->executeSql(
136-
"show columns from `{$this->tableName}` like ?",
137-
[$columnName]
138-
);
139-
return !empty($result->fetch());
136+
default:
137+
// @codeCoverageIgnoreStart
138+
$result = $this->dbClient->executeSql(
139+
"show columns from `{$this->tableName}` like ?",
140+
[$columnName]
141+
);
142+
return !empty($result->fetch());
143+
// @codeCoverageIgnoreEnd
140144
}
141145
}
142146

test/phpunit/Cli/ExecuteCommandTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,33 @@ public function testExecuteWithDevReportsIntegrityErrorWhenAppliedDevFileChanges
500500
}
501501
}
502502

503+
public function testExecuteWithDevReportsStatementExecutionError():void {
504+
$project = $this->createProjectDir();
505+
$sqlitePath = str_replace("\\", "/", $project . DIRECTORY_SEPARATOR . "cli-test.db");
506+
$this->writeConfigIni($project, $sqlitePath);
507+
$this->createMigrations($project, 1);
508+
$devFiles = $this->createDevMigrations($project, 1);
509+
file_put_contents($devFiles[0], "this is not valid sql");
510+
511+
$cwdBackup = getcwd();
512+
chdir($project);
513+
try {
514+
$cmd = new ExecuteCommand();
515+
$streams = $this->makeStreamFiles();
516+
$cmd->setStream($streams["stream"]);
517+
518+
$args = new ArgumentValueList();
519+
$args->set("dev");
520+
$cmd->run($args);
521+
522+
list("out" => $out) = $this->readFromFiles($streams["out"], $streams["err"]);
523+
self::assertStringContainsString("error executing dev migration file", $out);
524+
}
525+
finally {
526+
chdir($cwdBackup);
527+
}
528+
}
529+
503530
public function testExecuteReportsIntegrityErrorWhenPartialMigrationFileChanges():void {
504531
$project = $this->createProjectDir();
505532
$sqlitePath = str_replace("\\", "/", $project . DIRECTORY_SEPARATOR . "cli-test.db");

test/phpunit/Migration/AbstractMigratorTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ public function executeSqlFilePublic(string $file):string {
4747
public function outputPublic(string $message, string $streamName = self::STREAM_OUT):void {
4848
$this->output($message, $streamName);
4949
}
50+
51+
public function ensureColumnExistsPublic(string $columnName, string $definition):void {
52+
$this->ensureColumnExists($columnName, $definition);
53+
}
5054
};
5155
}
5256

@@ -82,6 +86,42 @@ public function testNowExpressionUsesDriverSpecificValue():void {
8286
self::assertSame("now()", $mysqlProbe->nowExpressionPublic());
8387
}
8488

89+
public function testCheckFileListOrderThrowsWhenFirstMigrationIsNotOne():void {
90+
$dir = $this->createWorkspace();
91+
$settings = $this->createSettings($dir, Settings::DRIVER_SQLITE, $dir . "/probe.sqlite");
92+
$probe = $this->createProbe($settings, $dir);
93+
94+
$this->expectException(\Gt\Database\Migration\MigrationSequenceOrderException::class);
95+
$probe->checkFileListOrder([$dir . "/002-start.sql"]);
96+
}
97+
98+
public function testCheckFileListOrderThrowsWhenOutOfOrder():void {
99+
$dir = $this->createWorkspace();
100+
$settings = $this->createSettings($dir, Settings::DRIVER_SQLITE, $dir . "/probe.sqlite");
101+
$probe = $this->createProbe($settings, $dir);
102+
103+
$this->expectException(\Gt\Database\Migration\MigrationSequenceOrderException::class);
104+
$probe->checkFileListOrder([
105+
$dir . "/001-first.sql",
106+
$dir . "/002-second.sql",
107+
$dir . "/001-third.sql",
108+
]);
109+
}
110+
111+
public function testEnsureColumnExistsAddsMissingColumn():void {
112+
$dir = $this->createWorkspace();
113+
$settings = $this->createSettings($dir, Settings::DRIVER_SQLITE, $dir . "/probe.sqlite");
114+
$probe = $this->createProbe($settings, $dir, "_probe");
115+
$db = new \Gt\Database\Database($settings);
116+
$db->executeSql("create table `_probe` (`id` int primary key)");
117+
118+
$probe->ensureColumnExistsPublic("lastStatement", "int null");
119+
120+
$result = $db->executeSql("PRAGMA table_info(`_probe`)");
121+
$columnNames = array_map(fn($row) => $row->getString("name"), $result->fetchAll());
122+
self::assertContains("lastStatement", $columnNames);
123+
}
124+
85125
public function testExecuteSqlFileRunsEachStatementAndReturnsHash():void {
86126
$dir = Helper::getTmpDir() . DIRECTORY_SEPARATOR . uniqid("probe-");
87127
mkdir($dir, 0775, true);

test/phpunit/Migration/DevMigratorTest.php

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@
1111
use PHPUnit\Framework\TestCase;
1212

1313
class DevMigratorTest extends TestCase {
14+
private function createProbe(Settings $settings, string $path):DevMigrator {
15+
return new class($settings, $path) extends DevMigrator {
16+
public function hasMigrationBeenAppliedPublic(string $fileName):bool {
17+
return $this->hasMigrationBeenApplied($fileName);
18+
}
19+
20+
public function getStoredHashPublic(string $fileName):?string {
21+
return $this->getStoredHash($fileName);
22+
}
23+
24+
public function recordMigrationSuccessPublic(string $fileName, string $hash):void {
25+
$this->recordMigrationSuccess($fileName, $hash);
26+
}
27+
28+
public function getStoredProgressPublic(string $fileName):?array {
29+
return $this->getStoredProgress($fileName);
30+
}
31+
};
32+
}
33+
1434
private function createProjectDir():string {
1535
$root = Helper::getTmpDir();
1636
$project = implode(DIRECTORY_SEPARATOR, [$root, uniqid("dev-mig-")]);
@@ -80,6 +100,68 @@ public function testDevMigrationIntegrityFailsWhenAppliedFileChanges():void {
80100
$devMigrator->checkIntegrity($files);
81101
}
82102

103+
public function testProbeMethodsExposeStoredProgressForAppliedFile():void {
104+
$project = $this->createProjectDir();
105+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
106+
$settings = $this->createSettings($project, $databasePath);
107+
$devPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration" . DIRECTORY_SEPARATOR . "dev";
108+
$files = $this->createMigrationFiles($devPath, "feature", 1);
109+
110+
$devMigrator = $this->createProbe($settings, $devPath);
111+
$devMigrator->createMigrationTable();
112+
$devMigrator->performMigration($files);
113+
114+
self::assertTrue($devMigrator->hasMigrationBeenAppliedPublic(basename($files[0])));
115+
self::assertSame(md5_file($files[0]), $devMigrator->getStoredHashPublic(basename($files[0])));
116+
}
117+
118+
public function testLegacyDevMigrationRowReturnsNullLastStatement():void {
119+
$project = $this->createProjectDir();
120+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
121+
$settings = $this->createSettings($project, $databasePath);
122+
$devPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration" . DIRECTORY_SEPARATOR . "dev";
123+
mkdir($devPath, 0775, true);
124+
$file = $devPath . DIRECTORY_SEPARATOR . "001-feature.sql";
125+
file_put_contents($file, "create table `test` (`id` int primary key)");
126+
127+
$db = new Database($settings);
128+
$db->executeSql(implode("\n", [
129+
"create table `_migration_dev` (",
130+
"`fileName` varchar(255) primary key,",
131+
"`queryHash` varchar(32) not null,",
132+
"`migratedAt` datetime not null",
133+
")",
134+
]));
135+
$db->executeSql(implode("\n", [
136+
"insert into `_migration_dev` (`fileName`, `queryHash`, `migratedAt`)",
137+
"values (?, ?, datetime('now'))",
138+
]), [basename($file), md5_file($file)]);
139+
140+
$devMigrator = $this->createProbe($settings, $devPath);
141+
$progress = $devMigrator->getStoredProgressPublic(basename($file));
142+
143+
self::assertSame(md5_file($file), $progress["hash"]);
144+
self::assertNull($progress["lastStatement"]);
145+
self::assertSame(0, $devMigrator->performMigration([$file]));
146+
}
147+
148+
public function testRecordMigrationSuccessStoresCompleteDevMigration():void {
149+
$project = $this->createProjectDir();
150+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
151+
$settings = $this->createSettings($project, $databasePath);
152+
$devPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration" . DIRECTORY_SEPARATOR . "dev";
153+
mkdir($devPath, 0775, true);
154+
$fileName = "001-feature.sql";
155+
156+
$devMigrator = $this->createProbe($settings, $devPath);
157+
$devMigrator->createMigrationTable();
158+
$devMigrator->recordMigrationSuccessPublic($fileName, "abc123");
159+
160+
$progress = $devMigrator->getStoredProgressPublic($fileName);
161+
self::assertSame("abc123", $progress["hash"]);
162+
self::assertNull($progress["lastStatement"]);
163+
}
164+
83165
public function testPerformDevMigrationRecordsCompletedStatementsAndResumes():void {
84166
$project = $this->createProjectDir();
85167
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
@@ -155,6 +237,37 @@ public function testDevMigrationIntegrityFailsWhenPartialFileChanges():void {
155237
$devMigrator->checkIntegrity([$file]);
156238
}
157239

240+
public function testPerformDevMigrationThrowsWhenPartialFileChanges():void {
241+
$project = $this->createProjectDir();
242+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
243+
$settings = $this->createSettings($project, $databasePath);
244+
$devPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration" . DIRECTORY_SEPARATOR . "dev";
245+
mkdir($devPath, 0775, true);
246+
$file = $devPath . DIRECTORY_SEPARATOR . "001-feature.sql";
247+
file_put_contents($file, implode(";\n", [
248+
"create table `test` (`id` int primary key)",
249+
"insert into `helper` (`id`) values (1)",
250+
]) . ";");
251+
252+
$devMigrator = new DevMigrator($settings, $devPath);
253+
$devMigrator->createMigrationTable();
254+
255+
try {
256+
$devMigrator->performMigration([$file]);
257+
self::fail("The dev migration should fail until the helper table exists.");
258+
}
259+
catch(\Gt\Database\DatabaseException) {
260+
}
261+
262+
file_put_contents($file, implode(";\n", [
263+
"create table `test` (`id` int primary key)",
264+
"insert into `helper` (`id`) values (2)",
265+
]) . ";");
266+
267+
$this->expectException(MigrationIntegrityException::class);
268+
$devMigrator->performMigration([$file]);
269+
}
270+
158271
public function testMergeIntoMainMigrationDirectoryPromotesAppliedDevMigrations():void {
159272
$project = $this->createProjectDir();
160273
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
@@ -192,6 +305,76 @@ public function testMergeIntoMainMigrationDirectoryPromotesAppliedDevMigrations(
192305
self::assertSame(3, $migrator->getMigrationCount());
193306
}
194307

308+
public function testMergeIntoMainMigrationDirectoryCreatesTargetPathWhenMissing():void {
309+
$project = $this->createProjectDir();
310+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
311+
$settings = $this->createSettings($project, $databasePath);
312+
$mainPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration";
313+
$devPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_dev-only";
314+
mkdir($devPath, 0775, true);
315+
$devFile = $devPath . DIRECTORY_SEPARATOR . "001-feature-1.sql";
316+
file_put_contents($devFile, "create table `test` (`id` int primary key)");
317+
318+
$migrator = new Migrator($settings, $mainPath);
319+
$migrator->createMigrationTable();
320+
321+
$devMigrator = new DevMigrator($settings, $devPath);
322+
$devMigrator->createMigrationTable();
323+
$devMigrator->performMigration([$devFile]);
324+
325+
self::assertSame(1, $devMigrator->mergeIntoMainMigrationDirectory($migrator, $mainPath));
326+
self::assertFileExists($mainPath . DIRECTORY_SEPARATOR . "0001-feature-1.sql");
327+
}
328+
329+
public function testMergeIntoMainMigrationDirectoryThrowsWhenAppliedRecordIsMissing():void {
330+
$project = $this->createProjectDir();
331+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
332+
$settings = $this->createSettings($project, $databasePath);
333+
$mainPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration";
334+
$devPath = $mainPath . DIRECTORY_SEPARATOR . "dev";
335+
mkdir($devPath, 0775, true);
336+
$devFile = $devPath . DIRECTORY_SEPARATOR . "001-feature-1.sql";
337+
file_put_contents($devFile, "create table `test` (`id` int primary key)");
338+
339+
$migrator = new Migrator($settings, $mainPath);
340+
$migrator->createMigrationTable();
341+
342+
$devMigrator = new DevMigrator($settings, $devPath);
343+
$devMigrator->createMigrationTable();
344+
345+
$this->expectException(MigrationIntegrityException::class);
346+
$devMigrator->mergeIntoMainMigrationDirectory($migrator, $mainPath);
347+
}
348+
349+
public function testMergeIntoMainMigrationDirectoryThrowsWhenTargetFileExists():void {
350+
$project = $this->createProjectDir();
351+
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";
352+
$settings = $this->createSettings($project, $databasePath);
353+
$mainPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_migration";
354+
$devPath = $mainPath . DIRECTORY_SEPARATOR . "dev";
355+
$targetPath = $project . DIRECTORY_SEPARATOR . "query" . DIRECTORY_SEPARATOR . "_merge-target";
356+
357+
mkdir($mainPath, 0775, true);
358+
mkdir($devPath, 0775, true);
359+
file_put_contents($mainPath . DIRECTORY_SEPARATOR . "0001-existing.sql", "create table `test` (`id` int primary key)");
360+
mkdir($targetPath, 0775, true);
361+
file_put_contents($targetPath . DIRECTORY_SEPARATOR . "0004-feature-1.sql", "select 1");
362+
$devFile = $devPath . DIRECTORY_SEPARATOR . "001-feature-1.sql";
363+
file_put_contents($devFile, "alter table `test` add `feature_1` text");
364+
365+
$migrator = new Migrator($settings, $mainPath);
366+
$migrator->createMigrationTable();
367+
$migrator->performMigration([$mainPath . DIRECTORY_SEPARATOR . "0001-existing.sql"]);
368+
$migrator->resetMigrationSequence(3);
369+
370+
$devMigrator = new DevMigrator($settings, $devPath);
371+
$devMigrator->createMigrationTable();
372+
$devMigrator->performMigration([$devFile]);
373+
374+
$this->expectException(MigrationSequenceOrderException::class);
375+
$devMigrator->mergeIntoMainMigrationDirectory($migrator, $targetPath);
376+
}
377+
195378
public function testDevMigratorThrowsOnGapsInSequence():void {
196379
$project = $this->createProjectDir();
197380
$databasePath = $project . DIRECTORY_SEPARATOR . "dev.sqlite";

0 commit comments

Comments
 (0)