A
last analyzed

Complexity

Total Complexity 1

Size/Duplication

Total Lines 6
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 1
c 1
b 0
f 1
dl 0
loc 6
rs 10
1
<?php
2
3
namespace Zenstruck\ScheduleBundle\Tests\Command;
4
5
use PHPUnit\Framework\TestCase;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\TestCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Symfony\Component\Console\Application;
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Helper\FormatterHelper;
9
use Symfony\Component\Console\Helper\HelperSet;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Tester\CommandTester;
12
use Zenstruck\ScheduleBundle\Command\ScheduleListCommand;
13
use Zenstruck\ScheduleBundle\Schedule;
14
use Zenstruck\ScheduleBundle\Schedule\Extension\ExtensionHandlerRegistry;
15
use Zenstruck\ScheduleBundle\Schedule\ScheduleBuilder;
16
use Zenstruck\ScheduleBundle\Schedule\Task\CommandTask;
17
use Zenstruck\ScheduleBundle\Tests\Fixture\MockScheduleBuilder;
18
use Zenstruck\ScheduleBundle\Tests\Fixture\MockTask;
19
20
/**
21
 * @author Kevin Bond <[email protected]>
22
 */
23
final class ScheduleListCommandTest extends TestCase
24
{
25
    /**
26
     * @test
27
     */
28
    public function no_tasks_defined()
29
    {
30
        $runner = (new MockScheduleBuilder())->getRunner();
31
        $commandTester = new CommandTester(new ScheduleListCommand($runner, new ExtensionHandlerRegistry([])));
32
33
        $this->expectException(\RuntimeException::class);
34
        $this->expectExceptionMessage('No scheduled tasks configured.');
35
36
        $commandTester->execute([]);
37
    }
38
39
    /**
40
     * @test
41
     */
42
    public function lists_configured_tasks_and_issues()
43
    {
44
        $runner = (new MockScheduleBuilder())
45
            ->addBuilder(new class() implements ScheduleBuilder {
46
                public function buildSchedule(Schedule $schedule): void
47
                {
48
                    $schedule->emailOnFailure('[email protected]');
49
                    $schedule->addCommand('my:command')
50
                        ->mondays()
51
                        ->at('1:30')
52
                        ->emailOnFailure('[email protected]')
53
                        ->pingOnFailure('https://example.com/my-command-failed')
54
                    ;
55
                }
56
            })
57
            ->getRunner()
58
        ;
59
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
60
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
61
        $command->setApplication(new Application());
62
        $commandTester = new CommandTester($command);
63
64
        $commandTester->execute([]);
65
        $output = $this->normalizeOutput($commandTester);
66
67
        $this->assertSame(1, $commandTester->getStatusCode());
68
        $this->assertStringContainsString('[!] CommandTask my:command 2 30 1 * * 1 (Every Monday at 1:30am)', $output);
69
        $this->assertStringContainsString('[WARNING] 4 task issues:', $output);
70
        $this->assertStringContainsString('[ERROR] No task runner registered for "CommandTask: my:command".', $output);
71
        $this->assertStringContainsString('[ERROR] To use the email extension you must configure a mailer (config path: "zenstruck_schedule.mailer").', $output);
72
        $this->assertStringContainsString('[ERROR] Symfony HttpClient is required to use the ping extension. Install with "composer require symfony/http-client".', $output);
73
        $this->assertStringContainsString('[ERROR] Command "my:command" not registered.', $output);
74
        $this->assertStringContainsString('1 Schedule Extension:', $output);
75
        $this->assertStringContainsString('On Schedule Failure, email output to "[email protected]"', $output);
76
        $this->assertStringContainsString('[WARNING] 1 issue with schedule:', $output);
77
        $this->assertStringNotContainsString('[OK] No schedule or task issues.', $output);
78
    }
79
80
    /**
81
     * @test
82
     */
83
    public function renders_exception_stack_trace_if_verbose()
84
    {
85
        $runner = (new MockScheduleBuilder())
86
            ->addBuilder(new class() implements ScheduleBuilder {
87
                public function buildSchedule(Schedule $schedule): void
88
                {
89
                    $schedule->addCommand('my:command')
90
                        ->mondays()
91
                        ->at('1:30')
92
                    ;
93
                }
94
            })
95
            ->getRunner()
96
        ;
97
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
98
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
99
        $command->setApplication(new Application());
100
        $commandTester = new CommandTester($command);
101
102
        $commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
103
        $output = $this->normalizeOutput($commandTester);
104
105
        $this->assertSame(1, $commandTester->getStatusCode());
106
        $this->assertStringContainsString('[WARNING] 2 task issues:', $output);
107
        $this->assertStringContainsString('ScheduleRunner.php', $output);
108
        $this->assertStringContainsString('MissingDependency.php', $output);
109
        $this->assertStringContainsString('No task runner registered for "CommandTask: my:command"', $output);
110
        $this->assertStringContainsString('In CommandTask.php line', $output);
111
        $this->assertStringContainsString('[Symfony\Component\Console\Exception\CommandNotFoundException]', $output);
112
        $this->assertStringContainsString('Command "my:command" not registered.', $output);
113
        $this->assertStringContainsString('Exception trace:', $output);
114
        $this->assertStringNotContainsString('[OK] No schedule or task issues.', $output);
115
    }
116
117
    /**
118
     * @test
119
     */
120
    public function lists_configured_tasks_and_issues_in_detail()
121
    {
122
        $runner = (new MockScheduleBuilder())
123
            ->addBuilder(new class() implements ScheduleBuilder {
124
                public function buildSchedule(Schedule $schedule): void
125
                {
126
                    $schedule->emailOnFailure('[email protected]');
127
                    $schedule->addCommand('my:command')
128
                        ->arguments('arg1', '--option1')
129
                        ->mondays()
130
                        ->at('1:30')
131
                        ->emailOnFailure('[email protected]')
132
                        ->pingOnFailure('https://example.com/my-command-failed')
133
                    ;
134
                }
135
            })
136
            ->getRunner()
137
        ;
138
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
139
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
140
        $command->setApplication(new Application());
141
        $commandTester = new CommandTester($command);
142
143
        $commandTester->execute(['--detail' => null]);
144
        $output = $this->normalizeOutput($commandTester);
145
146
        $this->assertSame(1, $commandTester->getStatusCode());
147
        $this->assertStringContainsString('1 Scheduled Task Configured', $output);
148
        $this->assertStringContainsString('(1/1) CommandTask: my:command', $output);
149
        $this->assertStringContainsString('2d8aa4774f95e50c7408156fca071b017bf11030', $output, 'Shows task id');
150
        $this->assertStringContainsString(CommandTask::class, $output, 'Shows task id');
151
        $this->assertStringContainsString('30 1 * * 1 (Every Monday at 1:30am)', $output);
152
        $this->assertStringContainsString('Mon,', $output);
153
        $this->assertStringContainsString('Command Arguments', $output);
154
        $this->assertStringContainsString('arg1 --option1', $output);
155
        $this->assertStringContainsString('2 Task Extensions:', $output);
156
        $this->assertStringContainsString('On Task Failure, email output to "[email protected]"', $output);
157
        $this->assertStringContainsString('On Task Failure, ping "https://example.com/my-command-failed"', $output);
158
        $this->assertStringContainsString('[WARNING] 4 issues with this task:', $output);
159
        $this->assertStringContainsString('[ERROR] No task runner registered for "CommandTask: my:command".', $output);
160
        $this->assertStringContainsString('[ERROR] To use the email extension you must configure a mailer (config path: "zenstruck_schedule.mailer").', $output);
161
        $this->assertStringContainsString('[ERROR] Symfony HttpClient is required to use the ping extension. Install with "composer require symfony/http-client".', $output);
162
        $this->assertStringContainsString('[ERROR] Command "my:command" not registered.', $output);
163
        $this->assertStringContainsString('1 Schedule Extension:', $output);
164
        $this->assertStringContainsString('On Schedule Failure, email output to "[email protected]"', $output);
165
        $this->assertStringContainsString('[WARNING] 1 issue with schedule:', $output);
166
        $this->assertStringNotContainsString('[OK] No schedule or task issues.', $output);
167
    }
168
169
    /**
170
     * @test
171
     */
172
    public function command_task_with_invalid_argument_shows_as_error()
173
    {
174
        $runner = (new MockScheduleBuilder())
175
            ->addTask(new CommandTask('my:command -v --option1'))
176
            ->getRunner()
177
        ;
178
179
        $application = new Application();
180
        $application->add(new class() extends Command {
181
            protected static $defaultName = 'my:command';
182
183
            protected function configure()
184
            {
185
                $this->addArgument('arg1');
186
            }
187
        });
188
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
189
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
190
        $command->setApplication($application);
191
        $commandTester = new CommandTester($command);
192
193
        $commandTester->execute([]);
194
        $output = $this->normalizeOutput($commandTester);
195
196
        $this->assertSame(1, $commandTester->getStatusCode());
197
        $this->assertStringContainsString('1 Scheduled Task Configured', $output);
198
        $this->assertStringContainsString('CommandTask my:command', $output);
199
        $this->assertStringContainsString('[WARNING] 2 task issues:', $output);
200
        $this->assertStringContainsString('[ERROR] The "--option1" option does not exist.', $output);
201
        $this->assertStringNotContainsString('[OK] No schedule or task issues.', $output);
202
    }
203
204
    /**
205
     * @test
206
     */
207
    public function no_issues_returns_successful_exit_code()
208
    {
209
        $runner = (new MockScheduleBuilder())
210
            ->addTask(new MockTask('my task'))
211
            ->getRunner()
212
        ;
213
214
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
215
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
216
        $command->setApplication(new Application());
217
        $commandTester = new CommandTester($command);
218
219
        $commandTester->execute([]);
220
        $output = $this->normalizeOutput($commandTester);
221
222
        $this->assertSame(0, $commandTester->getStatusCode());
223
        $this->assertStringContainsString('1 Scheduled Task Configured', $output);
224
        $this->assertStringContainsString('MockTask', $output);
225
        $this->assertStringContainsString('my task', $output);
226
        $this->assertStringContainsString('[OK] No schedule or task issues.', $output);
227
    }
228
229
    /**
230
     * @test
231
     */
232
    public function can_show_hashed_expressions()
233
    {
234
        $runner = (new MockScheduleBuilder())
235
            ->addTask((new MockTask('my task'))->cron('#daily'))
236
            ->getRunner()
237
        ;
238
239
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
240
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
241
        $command->setApplication(new Application());
242
        $commandTester = new CommandTester($command);
243
244
        $commandTester->execute([]);
245
        $output = $this->normalizeOutput($commandTester);
246
247
        $this->assertStringContainsString('56 20 * * * (Every day at 8:56pm)', $output);
248
        $this->assertStringNotContainsString('#daily', $output);
249
250
        $commandTester->execute(['--detail' => null]);
251
        $output = $this->normalizeOutput($commandTester);
252
253
        $this->assertStringContainsString('Calculated Frequency', $output);
254
        $this->assertStringContainsString('56 20 * * * (Every day at 8:56pm)', $output);
255
        $this->assertStringContainsString('Raw Frequency', $output);
256
        $this->assertStringContainsString('#daily', $output);
257
    }
258
259
    /**
260
     * @test
261
     */
262
    public function can_show_extended_expressions()
263
    {
264
        $runner = (new MockScheduleBuilder())
265
            ->addTask((new MockTask('my task'))->cron('@daily'))
266
            ->getRunner()
267
        ;
268
269
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
270
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
271
        $command->setApplication(new Application());
272
        $commandTester = new CommandTester($command);
273
274
        $commandTester->execute([]);
275
        $output = $this->normalizeOutput($commandTester);
276
277
        $this->assertStringContainsString('@daily', $output);
278
    }
279
280
    /**
281
     * @test
282
     */
283
    public function shows_full_task_id_in_detail()
284
    {
285
        $runner = (new MockScheduleBuilder())
286
            ->addTask($task = (new MockTask('my task'))->cron('@daily'))
287
            ->getRunner()
288
        ;
289
290
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
291
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
292
        $command->setApplication(new Application());
293
        $commandTester = new CommandTester($command);
294
295
        $commandTester->execute(['--detail' => null]);
296
        $output = $this->normalizeOutput($commandTester);
297
298
        $this->assertStringContainsString('ID', $output);
299
        $this->assertStringContainsString($task->getId(), $output);
300
    }
301
302
    /**
303
     * @test
304
     */
305
    public function shows_schedule_issue_for_duplicate_task_id()
306
    {
307
        $runner = (new MockScheduleBuilder())
308
            ->addTask(new MockTask('task1'))
309
            ->addTask(new MockTask('task2'))
310
            ->addTask(new MockTask('task2'))
311
            ->addTask(new MockTask('task3'))
312
            ->addTask(new MockTask('task3'))
313
            ->addTask(new MockTask('task3'))
314
            ->getRunner()
315
        ;
316
317
        $command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
318
        $command->setHelperSet(new HelperSet([new FormatterHelper()]));
319
        $command->setApplication(new Application());
320
        $commandTester = new CommandTester($command);
321
322
        $commandTester->execute([]);
323
        $output = $this->normalizeOutput($commandTester);
324
325
        $this->assertSame(1, $commandTester->getStatusCode());
326
        $this->assertStringContainsString('[WARNING] 2 issues with schedule:', $output);
327
        $this->assertStringContainsString('[ERROR] Task "MockTask: task2" (* * * * *) is duplicated 2 times. Make their descriptions unique to fix.', $output);
328
        $this->assertStringContainsString('[ERROR] Task "MockTask: task3" (* * * * *) is duplicated 3 times. Make their descriptions unique to fix.', $output);
329
    }
330
331
    private function normalizeOutput(CommandTester $tester): string
332
    {
333
        return \preg_replace('/\s+/', ' ', \str_replace("\n", '', $tester->getDisplay(true)));
334
    }
335
}
336