unverified()->create(); $this->assertFalse($user->hasVerifiedEmail(), 'Precondition: the user must start unverified.'); $this->assertFalse( $user->canAccessPanel(app(Panel::class)), 'Precondition: an unverified user must be denied the panel, otherwise this migration guards nothing.' ); $this->runBackfillMigration(); $this->assertTrue($user->fresh()->hasVerifiedEmail()); } public function test_it_leaves_already_verified_users_untouched(): void { $verifiedAt = now()->subMonth()->startOfSecond(); $user = User::factory()->create(['email_verified_at' => $verifiedAt]); $this->runBackfillMigration(); $this->assertTrue( $verifiedAt->equalTo($user->fresh()->email_verified_at), 'The backfill must not move an existing verification timestamp.' ); } public function test_it_uses_created_at_rather_than_the_migration_run_time(): void { $createdAt = now()->subYear()->startOfSecond(); $user = User::factory()->unverified()->create(); DB::table('users')->where('id', $user->id)->update(['created_at' => $createdAt]); $this->runBackfillMigration(); $this->assertTrue( $createdAt->equalTo($user->fresh()->email_verified_at), 'Verification should be dated to when the account was created, not to when the migration ran.' ); } /** * The migration is an anonymous class, so it is required and invoked * directly: RefreshDatabase has already run every migration before the * test body executes, so re-running it through Artisan would be a no-op. */ private function runBackfillMigration(): void { $migration = require database_path('migrations/2026_08_10_120000_verify_existing_user_emails.php'); $migration->up(); } }