Skip to content

Commit db02dfa

Browse files
committed
fix: prevent same migration for being created twice
1 parent 64e074a commit db02dfa

File tree

2 files changed

+160
-6
lines changed

2 files changed

+160
-6
lines changed

src/WebPushServiceProvider.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace NotificationChannels\WebPush;
44

5+
use Illuminate\Filesystem\Filesystem;
6+
use Illuminate\Support\Collection;
57
use Illuminate\Support\ServiceProvider;
68
use Illuminate\Support\Str;
79
use Minishlink\WebPush\WebPush;
@@ -87,12 +89,24 @@ protected function definePublishing()
8789
__DIR__.'/../config/webpush.php' => config_path('webpush.php'),
8890
], 'config');
8991

90-
if (! class_exists('CreatePushSubscriptionsTable')) {
91-
$timestamp = date('Y_m_d_His', time());
9292

93-
$this->publishes([
94-
__DIR__.'/../migrations/create_push_subscriptions_table.php.stub' => database_path(sprintf('migrations/%s_create_push_subscriptions_table.php', $timestamp)),
95-
], 'migrations');
96-
}
93+
$this->publishes([
94+
__DIR__.'/../migrations/create_push_subscriptions_table.php.stub' => $this->getMigrationFileName('create_push_subscriptions_table.php'),
95+
], 'migrations');
96+
}
97+
98+
/**
99+
* Returns existing migration file if found, else uses the current timestamp.
100+
*/
101+
protected function getMigrationFileName(string $migrationFileName): string
102+
{
103+
$timestamp = date('Y_m_d_His');
104+
105+
$filesystem = $this->app->make(Filesystem::class);
106+
107+
return Collection::make([$this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR])
108+
->flatMap(fn ($path) => $filesystem->glob($path.'*_'.$migrationFileName))
109+
->push($this->app->databasePath()."/migrations/{$timestamp}_{$migrationFileName}")
110+
->first();
97111
}
98112
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
namespace NotificationChannels\WebPush\Test;
4+
5+
use Illuminate\Support\Facades\Artisan;
6+
use Illuminate\Support\Facades\File;
7+
use PHPUnit\Framework\Attributes\Test;
8+
9+
class WebPushServiceProviderTest extends TestCase
10+
{
11+
#[Test]
12+
public function it_publishes_config(): void
13+
{
14+
// Ensure config directory exists in the application config path
15+
$configPath = config_path('webpush.php');
16+
17+
if (File::exists($configPath)) {
18+
File::delete($configPath);
19+
}
20+
21+
$exit = Artisan::call('vendor:publish', [
22+
'--provider' => \NotificationChannels\WebPush\WebPushServiceProvider::class,
23+
'--tag' => 'config',
24+
]);
25+
26+
$this->assertEquals(0, $exit);
27+
$this->assertFileExists($configPath, 'The webpush config file was not published to the config path.');
28+
29+
// Cleanup
30+
if (File::exists($configPath)) {
31+
File::delete($configPath);
32+
}
33+
}
34+
35+
#[Test]
36+
public function it_publishes_migration(): void
37+
{
38+
$migrationsPath = $this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR;
39+
40+
// Remove any existing matching migrations in the database/migrations folder for a clean test
41+
$existing = glob($migrationsPath.'*_' . 'create_push_subscriptions_table.php');
42+
foreach ($existing as $file) {
43+
@unlink($file);
44+
}
45+
46+
$exit = Artisan::call('vendor:publish', [
47+
'--provider' => \NotificationChannels\WebPush\WebPushServiceProvider::class,
48+
'--tag' => 'migrations',
49+
]);
50+
51+
$this->assertEquals(0, $exit);
52+
53+
$found = glob($migrationsPath.'*_' . 'create_push_subscriptions_table.php');
54+
55+
$this->assertNotEmpty($found, 'No migration was published to the database/migrations path');
56+
57+
// Ensure exactly one migration file exists and has the expected suffix
58+
$this->assertStringEndsWith('_create_push_subscriptions_table.php', basename($found[0]));
59+
60+
// Cleanup
61+
foreach ($found as $file) {
62+
@unlink($file);
63+
}
64+
}
65+
66+
#[Test]
67+
public function it_does_not_generate_duplicate_migration_if_one_already_exists(): void
68+
{
69+
$migrationsPath = $this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR;
70+
71+
// Ensure migrations directory exists
72+
if (! is_dir($migrationsPath)) {
73+
mkdir($migrationsPath, 0755, true);
74+
}
75+
76+
// Ensure no existing matching migrations are present before the test
77+
$existing = glob($migrationsPath . '*_create_push_subscriptions_table.php');
78+
foreach ($existing as $file) {
79+
@unlink($file);
80+
}
81+
82+
// Create a fake existing migration file in the app migrations directory that should be detected and reused
83+
$existingFilename = $migrationsPath.'2020_01_01_000000_create_push_subscriptions_table.php';
84+
file_put_contents($existingFilename, "<?php\n// existing migration\n");
85+
86+
// Recompute the provider's publishes mapping so it detects the migration file we just created.
87+
// The service provider computes the destination filename during boot, so we need to refresh it
88+
// after creating the fake file to ensure vendor:publish reuses the existing file.
89+
$provider = new \NotificationChannels\WebPush\WebPushServiceProvider($this->app);
90+
$ref = new \ReflectionClass($provider);
91+
$method = $ref->getMethod('definePublishing');
92+
$method->setAccessible(true);
93+
$method->invoke($provider);
94+
95+
// Run vendor:publish which should reuse the existing filename rather than create a new one
96+
$exit = Artisan::call('vendor:publish', [
97+
'--provider' => \NotificationChannels\WebPush\WebPushServiceProvider::class,
98+
'--tag' => 'migrations',
99+
]);
100+
101+
$this->assertEquals(0, $exit);
102+
103+
// After publishing, ensure no additional migration with a different timestamp suffix was created
104+
$found = glob($migrationsPath.'*_' . 'create_push_subscriptions_table.php');
105+
106+
// There should be exactly one matching migration (the existing one we created)
107+
$this->assertCount(1, $found, 'A duplicate migration file was created instead of reusing the existing one');
108+
109+
// Cleanup
110+
foreach ($found as $file) {
111+
@unlink($file);
112+
}
113+
}
114+
115+
#[Test]
116+
public function it_reuses_existing_migration_filename_when_present(): void
117+
{
118+
$migrationsPath = $this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR;
119+
120+
if (! is_dir($migrationsPath)) {
121+
mkdir($migrationsPath, 0755, true);
122+
}
123+
124+
$existingFilename = $migrationsPath.'2020_01_01_000000_create_push_subscriptions_table.php';
125+
file_put_contents($existingFilename, "<?php\n// existing migration\n");
126+
127+
// Instantiate provider and call protected method getMigrationFileName via reflection
128+
$provider = new \NotificationChannels\WebPush\WebPushServiceProvider($this->app);
129+
130+
$ref = new \ReflectionClass($provider);
131+
$method = $ref->getMethod('getMigrationFileName');
132+
$method->setAccessible(true);
133+
134+
$result = $method->invokeArgs($provider, ['create_push_subscriptions_table.php']);
135+
136+
$this->assertEquals($existingFilename, $result, 'getMigrationFileName did not return the existing migration filename');
137+
138+
@unlink($existingFilename);
139+
}
140+
}

0 commit comments

Comments
 (0)