• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / Iznik / 23122

24 Jun 2026 07:09AM UTC coverage: 71.191%. Remained the same
23122

push

circleci

web-flow
Merge pull request #865 from Freegle/fix/abtest-coverage-determinism

fix(abtest): make epsilon-greedy selection coverage deterministic

11150 of 14816 branches covered (75.26%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

122201 of 172499 relevant lines covered (70.84%)

37.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.0
/iznik-batch/app/Console/Commands/Mail/SendModNotifsCommand.php
1
<?php
2

3
namespace App\Console\Commands\Mail;
4

5
use App\Console\Concerns\PreventsOverlapping;
6
use App\Mail\Admin\ModNotifMail;
7
use App\Services\ModNotifService;
8
use Illuminate\Console\Command;
9
use Illuminate\Support\Facades\Log;
10
use Illuminate\Support\Facades\Mail;
11

12
class SendModNotifsCommand extends Command
13
{
14
    use PreventsOverlapping;
15

16
    protected $signature = 'mail:mod-notifs
17
                            {--dry-run : Show what would be sent without sending}
18
                            {--force : Send even outside 08:00–21:00 window}';
19

20
    protected $description = 'Send moderation notification emails to moderators with pending work (V1: mod_notifs.php)';
21

22
    private const HOUR_START = 8;
23

24
    private const HOUR_END = 21;
25

26
    public function handle(ModNotifService $service): int
5✔
27
    {
28
        if (! $this->acquireLock()) {
5✔
29
            $this->info('Already running, exiting.');
×
30

31
            return Command::SUCCESS;
×
32
        }
33

34
        try {
35
            return $this->runLocked($service);
5✔
36
        } finally {
37
            $this->releaseLock();
5✔
38
        }
39
    }
40

41
    private function runLocked(ModNotifService $service): int
5✔
42
    {
43
        $dryRun = $this->option('dry-run');
5✔
44
        $force = $this->option('force');
5✔
45

46
        if (!$force) {
5✔
47
            $hour = (int) now()->setTimezone('Europe/London')->format('G');
1✔
48
            if ($hour < self::HOUR_START || $hour >= self::HOUR_END) {
1✔
UNCOV
49
                $this->info("Outside notification window ({$hour}:00, window is " . self::HOUR_START . ':00–' . self::HOUR_END . ':00). Use --force to override.');
×
50

UNCOV
51
                return Command::SUCCESS;
×
52
            }
53
        }
54

55
        if ($dryRun) {
5✔
56
            $this->info('DRY RUN — no emails will be sent.');
1✔
57
        }
58

59
        $notifications = $service->getNotificationsToSend();
5✔
60
        $this->info('Moderator notifications to send: ' . count($notifications));
5✔
61

62
        $sent = 0;
5✔
63

64
        foreach ($notifications as $notif) {
5✔
65
            Log::info('Sending mod notification', [
2✔
66
                'user_id' => $notif['user_id'],
2✔
67
                'email' => $notif['email'],
2✔
68
                'subject' => $notif['subject'],
2✔
69
            ]);
2✔
70

71
            if (!$dryRun) {
2✔
72
                $mail = new ModNotifMail(
2✔
73
                    $notif['name'],
2✔
74
                    $notif['email'],
2✔
75
                    $notif['user_id'],
2✔
76
                    $notif['subject'],
2✔
77
                    $notif['html_summary'],
2✔
78
                    $notif['text_summary']
2✔
79
                );
2✔
80

81
                // spool() returns the spool id on success and '' when the
82
                // address is a permanent failure (it records the bounce
83
                // internally and skips), so $delivered stays falsy in that
84
                // case — matching the old SafeMail::send() contract that
85
                // returned false and skipped recordSent. Anything spool()
86
                // re-throws (transient SMTP / MJML render error) is caught
87
                // here so one bad mod doesn't abort the whole notifications
88
                // run — the resilience SafeMail::send() used to provide.
89
                try {
90
                    $delivered = app(\App\Services\EmailSpoolerService::class)->spool($mail, $notif['email']);
2✔
91
                } catch (\Throwable $e) {
1✔
92
                    Log::warning('Skipping mod notification after spool failure; continuing loop', [
1✔
93
                        'user_id' => $notif['user_id'],
1✔
94
                        'email' => $notif['email'],
1✔
95
                        'error' => $e->getMessage(),
1✔
96
                    ]);
1✔
97
                    $delivered = '';
1✔
98
                }
99

100
                if ($delivered) {
2✔
101
                    $service->recordSent($notif['user_id'], $notif['text_summary']);
2✔
102
                }
103
            }
104

105
            $this->info("  [{$notif['user_id']}] {$notif['email']} — {$notif['subject']}");
2✔
106
            $sent++;
2✔
107
        }
108

109
        $prefix = $dryRun ? '[DRY RUN] ' : '';
5✔
110
        $this->info("{$prefix}Sent: {$sent}");
5✔
111
        Log::info('mod:notifs complete', ['sent' => $sent]);
5✔
112

113
        return Command::SUCCESS;
5✔
114
    }
115
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc