From 4cd761979128f14e5b52b764d39a0bbc3530ba51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Fri, 17 Jul 2026 11:29:33 +0200 Subject: [PATCH] feat(encryption): report count of users still holding encrypted data (#687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What `encryption:notify-removal` now prints, on every run, the total number of **non-deleted** users who still hold legacy browser-encrypted data: ``` 4 non-deleted user(s) still have encrypted data. ``` The count is taken before the billing exclusion, so it reflects the full remaining scope of browser-encrypted data. Soft-deleted users are excluded (model default scope), as are users whose data is already plaintext. ## Why It's the signal for deciding when the browser-side encryption code can finally be removed: once this reaches zero, nothing in the app depends on client-side encrypted columns anymore. ## Not changed Email-sending logic is untouched. Subscribed users are still never warned — the recipient set continues to go through `excludeBilledUsers()` exactly as before. This PR only adds a reporting line. ## Testing - New test asserts the count spans everyone with encrypted data (subscribed included), while excluding soft-deleted and plaintext users, and that sending still skips the subscribed user. - Full `NotifyEncryptedDataRemovalCommandTest` suite green. --- .../NotifyEncryptedDataRemovalCommand.php | 14 +++++--- .../NotifyEncryptedDataRemovalCommandTest.php | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) 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();