diff --git a/app/Console/Commands/NotifyEncryptedDataRemovalCommand.php b/app/Console/Commands/NotifyEncryptedDataRemovalCommand.php index 4ebb8f82..a5abb92d 100644 --- a/app/Console/Commands/NotifyEncryptedDataRemovalCommand.php +++ b/app/Console/Commands/NotifyEncryptedDataRemovalCommand.php @@ -39,16 +39,22 @@ class NotifyEncryptedDataRemovalCommand extends Command */ public function handle(): int { - $users = $this->excludeBilledUsers($this->usersWithLegacyEncryption()->get()); + $encryptedUsers = $this->usersWithLegacyEncryption()->get(); + + // Always report the total scope of legacy encrypted data (soft-deleted users are + // excluded by the model's default scope). This is the signal for deciding whether + // the browser-side encryption code can finally be removed, independent of who we + // actually email below. + $this->info("{$encryptedUsers->count()} non-deleted user(s) still have encrypted data."); + + $users = $this->excludeBilledUsers($encryptedUsers); if ($users->isEmpty()) { - $this->info('No users with encrypted data found.'); + $this->info('No users to warn.'); return self::SUCCESS; } - $this->info("Found {$users->count()} user(s) with encrypted data."); - if ($this->option('dry-run')) { $this->renderTable($users); $this->info('[dry-run] No emails sent.'); diff --git a/tests/Feature/Console/NotifyEncryptedDataRemovalCommandTest.php b/tests/Feature/Console/NotifyEncryptedDataRemovalCommandTest.php index 80f34244..3008bca9 100644 --- a/tests/Feature/Console/NotifyEncryptedDataRemovalCommandTest.php +++ b/tests/Feature/Console/NotifyEncryptedDataRemovalCommandTest.php @@ -45,6 +45,39 @@ test('it never warns a subscribed user', function () { Queue::assertNotPushed(SendUpdateEmailJob::class); }); +test('it always reports the total count of non-deleted encrypted users, including subscribed ones', function () { + config(['subscriptions.enabled' => true]); + + $subscribed = User::factory()->create(); + Transaction::factory()->for($subscribed)->create(); + $subscribed->subscriptions()->create([ + 'type' => 'default', + 'stripe_id' => 'sub_test123', + 'stripe_status' => 'active', + 'stripe_price' => 'price_test123', + ]); + + $unsubscribed = User::factory()->create(); + Transaction::factory()->for($unsubscribed)->create(); + + $deleted = User::factory()->create(); + Transaction::factory()->for($deleted)->create(); + $deleted->delete(); + + $clean = User::factory()->create(); + Transaction::factory()->for($clean)->plaintext()->create(); + + // The count spans everyone still holding encrypted data (subscribed included); the + // soft-deleted and plaintext users are excluded. + artisan('encryption:notify-removal', ['--force' => true]) + ->expectsOutputToContain('2 non-deleted user(s) still have encrypted data.') + ->assertSuccessful(); + + // Sending is unchanged: the subscribed user is counted but never emailed. + Queue::assertPushed(SendUpdateEmailJob::class, 1); + Queue::assertPushed(SendUpdateEmailJob::class, fn (SendUpdateEmailJob $job) => $job->user->is($unsubscribed)); +}); + test('it queues on the emails queue', function () { $user = User::factory()->create(); Transaction::factory()->for($user)->create();