Passed
Pull Request — master (#2)
by Kevin
01:48
created

task_frequency_must_be_valid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Zenstruck\ScheduleBundle\Tests\DependencyInjection;
4
5
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
6
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
7
use Zenstruck\ScheduleBundle\Command\ScheduleListCommand;
8
use Zenstruck\ScheduleBundle\Command\ScheduleRunCommand;
9
use Zenstruck\ScheduleBundle\DependencyInjection\ZenstruckScheduleExtension;
10
use Zenstruck\ScheduleBundle\EventListener\ConfigureScheduleSubscriber;
11
use Zenstruck\ScheduleBundle\EventListener\ConfigureTasksSubscriber;
12
use Zenstruck\ScheduleBundle\EventListener\LogScheduleSubscriber;
13
use Zenstruck\ScheduleBundle\EventListener\ScheduleBuilderSubscriber;
14
use Zenstruck\ScheduleBundle\EventListener\SelfSchedulingSubscriber;
15
use Zenstruck\ScheduleBundle\EventListener\TimezoneSubscriber;
16
use Zenstruck\ScheduleBundle\Schedule\Extension\EmailExtension;
17
use Zenstruck\ScheduleBundle\Schedule\Extension\EnvironmentExtension;
18
use Zenstruck\ScheduleBundle\Schedule\Extension\ExtensionHandlerRegistry;
19
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\EmailHandler;
20
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\EnvironmentHandler;
21
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\PingHandler;
22
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\SelfHandlingHandler;
23
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\SingleServerHandler;
24
use Zenstruck\ScheduleBundle\Schedule\Extension\Handler\WithoutOverlappingHandler;
25
use Zenstruck\ScheduleBundle\Schedule\Extension\PingExtension;
26
use Zenstruck\ScheduleBundle\Schedule\Extension\SingleServerExtension;
27
use Zenstruck\ScheduleBundle\Schedule\Extension\WithoutOverlappingExtension;
28
use Zenstruck\ScheduleBundle\Schedule\ScheduleRunner;
29
use Zenstruck\ScheduleBundle\Schedule\Task\Runner\CommandTaskRunner;
30
use Zenstruck\ScheduleBundle\Schedule\Task\Runner\SelfRunningTaskRunner;
31
32
/**
33
 * @author Kevin Bond <[email protected]>
34
 */
35
final class ZenstruckScheduleExtensionTest extends AbstractExtensionTestCase
36
{
37
    /**
38
     * @test
39
     */
40
    public function empty_config_loads_default_services()
41
    {
42
        $this->load([]);
43
44
        $this->assertContainerBuilderHasService(ScheduleListCommand::class);
45
        $this->assertContainerBuilderHasServiceDefinitionWithTag(ScheduleListCommand::class, 'console.command');
46
47
        $this->assertContainerBuilderHasService(ScheduleRunCommand::class);
48
        $this->assertContainerBuilderHasServiceDefinitionWithTag(ScheduleRunCommand::class, 'console.command');
49
50
        $this->assertContainerBuilderHasService(ScheduleRunner::class);
51
52
        $this->assertContainerBuilderHasService(ScheduleBuilderSubscriber::class);
53
        $this->assertContainerBuilderHasServiceDefinitionWithTag(ScheduleBuilderSubscriber::class, 'kernel.event_subscriber');
54
55
        $this->assertContainerBuilderHasService(ConfigureScheduleSubscriber::class);
56
        $this->assertContainerBuilderHasServiceDefinitionWithTag(ConfigureScheduleSubscriber::class, 'kernel.event_subscriber');
57
58
        $this->assertContainerBuilderHasService(SelfSchedulingSubscriber::class);
59
        $this->assertContainerBuilderHasServiceDefinitionWithTag(SelfSchedulingSubscriber::class, 'kernel.event_subscriber');
60
61
        $this->assertContainerBuilderHasService(CommandTaskRunner::class);
62
        $this->assertContainerBuilderHasServiceDefinitionWithTag(CommandTaskRunner::class, 'schedule.task_runner');
63
64
        $this->assertContainerBuilderHasService(SelfRunningTaskRunner::class);
65
        $this->assertContainerBuilderHasServiceDefinitionWithTag(SelfRunningTaskRunner::class, 'schedule.task_runner');
66
67
        $this->assertContainerBuilderHasService(LogScheduleSubscriber::class);
68
        $this->assertContainerBuilderHasServiceDefinitionWithTag(LogScheduleSubscriber::class, 'kernel.event_subscriber');
69
        $this->assertContainerBuilderHasServiceDefinitionWithTag(LogScheduleSubscriber::class, 'monolog.logger', ['channel' => 'schedule']);
70
71
        $this->assertContainerBuilderHasService(ExtensionHandlerRegistry::class);
72
73
        $this->assertContainerBuilderHasService(SelfHandlingHandler::class);
74
        $this->assertContainerBuilderHasServiceDefinitionWithTag(SelfHandlingHandler::class, 'schedule.extension_handler', ['priority' => -100]);
75
76
        $this->assertContainerBuilderHasService(EnvironmentHandler::class);
77
        $this->assertContainerBuilderHasServiceDefinitionWithTag(EnvironmentHandler::class, 'schedule.extension_handler');
78
79
        $this->assertContainerBuilderHasService(ConfigureTasksSubscriber::class);
80
        $this->assertContainerBuilderHasServiceDefinitionWithTag(ScheduleBuilderSubscriber::class, 'kernel.event_subscriber');
81
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(ConfigureTasksSubscriber::class, 0, []);
82
    }
83
84
    /**
85
     * @test
86
     */
87
    public function can_configure_default_timezone()
88
    {
89
        $this->load(['timezone' => 'UTC']);
90
91
        $this->assertContainerBuilderHasService(TimezoneSubscriber::class);
92
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(TimezoneSubscriber::class, 0, 'UTC');
93
        $this->assertContainerBuilderHasServiceDefinitionWithTag(TimezoneSubscriber::class, 'kernel.event_subscriber');
94
    }
95
96
    /**
97
     * @test
98
     */
99
    public function schedule_timezone_must_be_valid()
100
    {
101
        $this->expectException(InvalidConfigurationException::class);
102
        $this->expectExceptionMessage('Invalid configuration for path "zenstruck_schedule.timezone": Timezone "invalid" is not available');
103
104
        $this->load(['timezone' => 'invalid']);
105
    }
106
107
    /**
108
     * @test
109
     */
110
    public function can_configure_single_server_lock_factory()
111
    {
112
        $this->load(['single_server_handler' => 'my_factory']);
113
114
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(SingleServerHandler::class, 0, 'my_factory');
115
        $this->assertContainerBuilderHasServiceDefinitionWithTag(SingleServerHandler::class, 'schedule.extension_handler');
116
    }
117
118
    /**
119
     * @test
120
     */
121
    public function can_configure_without_overlapping_handler_lock_factory()
122
    {
123
        $this->load(['without_overlapping_handler' => 'my_factory']);
124
125
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(WithoutOverlappingHandler::class, 0, 'my_factory');
126
        $this->assertContainerBuilderHasServiceDefinitionWithTag(WithoutOverlappingHandler::class, 'schedule.extension_handler');
127
    }
128
129
    /**
130
     * @test
131
     */
132
    public function can_configure_ping_handler_http_client()
133
    {
134
        $this->load(['ping_handler' => 'my_client']);
135
136
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(PingHandler::class, 0, 'my_client');
137
        $this->assertContainerBuilderHasServiceDefinitionWithTag(PingHandler::class, 'schedule.extension_handler');
138
    }
139
140
    /**
141
     * @test
142
     */
143
    public function can_configure_email_handler()
144
    {
145
        $this->load(['email_handler' => [
146
            'service' => 'my_mailer',
147
            'default_from' => '[email protected]',
148
            'default_to' => '[email protected]',
149
            'subject_prefix' => '[Acme Inc]',
150
        ]]);
151
152
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 0, 'my_mailer');
153
        $this->assertContainerBuilderHasServiceDefinitionWithTag(EmailHandler::class, 'schedule.extension_handler');
154
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 1, '[email protected]');
155
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 2, '[email protected]');
156
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 3, '[Acme Inc]');
157
    }
158
159
    /**
160
     * @test
161
     */
162
    public function minimum_email_handler_configuration()
163
    {
164
        $this->load(['email_handler' => [
165
            'service' => 'my_mailer',
166
        ]]);
167
168
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 0, 'my_mailer');
169
        $this->assertContainerBuilderHasServiceDefinitionWithTag(EmailHandler::class, 'schedule.extension_handler');
170
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 1, null);
171
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 2, null);
172
        $this->assertContainerBuilderHasServiceDefinitionWithArgument(EmailHandler::class, 3, null);
173
    }
174
175
    /**
176
     * @test
177
     */
178
    public function can_add_schedule_environment_as_string()
179
    {
180
        $this->load(['schedule_extensions' => [
181
            'environments' => 'prod',
182
        ]]);
183
184
        $this->assertContainerBuilderHasService('zenstruck_schedule.extension.environments', EnvironmentExtension::class);
185
        $this->assertContainerBuilderHasServiceDefinitionWithArgument('zenstruck_schedule.extension.environments', 0, ['prod']);
186
        $this->assertContainerBuilderHasServiceDefinitionWithTag('zenstruck_schedule.extension.environments', 'schedule.configured_extension');
187
    }
188
189
    /**
190
     * @test
191
     */
192
    public function can_add_schedule_environment_as_array()
193
    {
194
        $this->load(['schedule_extensions' => [
195
            'environments' => ['prod', 'stage'],
196
        ]]);
197
198
        $this->assertContainerBuilderHasServiceDefinitionWithArgument('zenstruck_schedule.extension.environments', 0, ['prod', 'stage']);
199
    }
200
201
    /**
202
     * @test
203
     */
204
    public function can_enable_single_server_schedule_extension()
205
    {
206
        $this->load(['schedule_extensions' => [
207
            'on_single_server' => null,
208
        ]]);
209
210
        $this->assertContainerBuilderHasService('zenstruck_schedule.extension.on_single_server', SingleServerExtension::class);
211
        $this->assertContainerBuilderHasServiceDefinitionWithTag('zenstruck_schedule.extension.on_single_server', 'schedule.configured_extension');
212
    }
213
214
    /**
215
     * @test
216
     */
217
    public function can_enable_email_on_failure_schedule_extension()
218
    {
219
        $this->load(['schedule_extensions' => [
220
            'email_on_failure' => [
221
                'to' => '[email protected]',
222
                'subject' => 'my subject',
223
            ],
224
        ]]);
225
226
        $this->assertContainerBuilderHasService('zenstruck_schedule.extension.email_on_failure', EmailExtension::class);
227
        $this->assertContainerBuilderHasServiceDefinitionWithTag('zenstruck_schedule.extension.email_on_failure', 'schedule.configured_extension');
228
229
        $definition = $this->container->getDefinition('zenstruck_schedule.extension.email_on_failure');
230
231
        $this->assertSame([EmailExtension::class, 'scheduleFailure'], $definition->getFactory());
232
        $this->assertSame(['[email protected]', 'my subject'], $definition->getArguments());
233
    }
234
235
    /**
236
     * @test
237
     * @dataProvider pingScheduleExtensionProvider
238
     */
239
    public function can_enable_ping_schedule_extensions($key, $method)
240
    {
241
        $this->load(['schedule_extensions' => [
242
            $key => [
243
                'url' => 'example.com',
244
            ],
245
        ]]);
246
247
        $this->assertContainerBuilderHasService('zenstruck_schedule.extension.'.$key, PingExtension::class);
248
        $this->assertContainerBuilderHasServiceDefinitionWithTag('zenstruck_schedule.extension.'.$key, 'schedule.configured_extension');
249
250
        $definition = $this->container->getDefinition('zenstruck_schedule.extension.'.$key);
251
252
        $this->assertSame([PingExtension::class, $method], $definition->getFactory());
253
        $this->assertSame(['example.com', 'GET', []], $definition->getArguments());
254
    }
255
256
    public static function pingScheduleExtensionProvider()
257
    {
258
        return [
259
            ['ping_before', 'scheduleBefore'],
260
            ['ping_after', 'scheduleAfter'],
261
            ['ping_on_success', 'scheduleSuccess'],
262
            ['ping_on_failure', 'scheduleFailure'],
263
        ];
264
    }
265
266
    /**
267
     * @test
268
     */
269
    public function minimum_task_configuration()
270
    {
271
        $this->load([
272
            'tasks' => [
273
                [
274
                    'command' => 'my:command',
275
                    'frequency' => '0 * * * *',
276
                ],
277
            ],
278
        ]);
279
280
        $config = $this->container->getDefinition(ConfigureTasksSubscriber::class)->getArgument(0)[0];
281
282
        $this->assertSame(['my:command'], $config['command']);
283
        $this->assertSame('0 * * * *', $config['frequency']);
284
        $this->assertNull($config['description']);
285
        $this->assertNull($config['timezone']);
286
        $this->assertFalse($config['without_overlapping']['enabled']);
287
        $this->assertFalse($config['between']['enabled']);
288
        $this->assertFalse($config['unless_between']['enabled']);
289
        $this->assertFalse($config['ping_before']['enabled']);
290
        $this->assertFalse($config['ping_after']['enabled']);
291
        $this->assertFalse($config['ping_on_success']['enabled']);
292
        $this->assertFalse($config['ping_on_failure']['enabled']);
293
        $this->assertFalse($config['email_after']['enabled']);
294
        $this->assertFalse($config['email_on_failure']['enabled']);
295
    }
296
297
    /**
298
     * @test
299
     */
300
    public function task_frequency_must_be_valid()
301
    {
302
        $this->expectException(InvalidConfigurationException::class);
303
        $this->expectExceptionMessage('Invalid configuration for path "zenstruck_schedule.tasks.0.frequency": "invalid" is an invalid cron expression.');
304
305
        $this->load([
306
            'tasks' => [
307
                [
308
                    'command' => 'my:command',
309
                    'frequency' => 'invalid',
310
                ],
311
            ],
312
        ]);
313
    }
314
315
    /**
316
     * @test
317
     */
318
    public function full_task_configuration()
319
    {
320
        $this->load([
321
            'tasks' => [
322
                [
323
                    'command' => [
324
                        'my:command --option',
325
                        'another:command',
326
                    ],
327
                    'frequency' => '0 0 * * *',
328
                    'description' => 'my description',
329
                    'timezone' => 'UTC',
330
                    'without_overlapping' => null,
331
                    'between' => [
332
                        'start' => 9,
333
                        'end' => 17,
334
                    ],
335
                    'unless_between' => [
336
                        'start' => 12,
337
                        'end' => '13:30',
338
                    ],
339
                    'ping_before' => [
340
                        'url' => 'https://example.com/before',
341
                    ],
342
                    'ping_after' => [
343
                        'url' => 'https://example.com/after',
344
                    ],
345
                    'ping_on_success' => [
346
                        'url' => 'https://example.com/success',
347
                    ],
348
                    'ping_on_failure' => [
349
                        'url' => 'https://example.com/failure',
350
                        'method' => 'POST',
351
                    ],
352
                    'email_after' => null,
353
                    'email_on_failure' => [
354
                        'to' => '[email protected]',
355
                        'subject' => 'my subject',
356
                    ],
357
                ],
358
            ],
359
        ]);
360
361
        $config = $this->container->getDefinition(ConfigureTasksSubscriber::class)->getArgument(0)[0];
362
363
        $this->assertSame(['my:command --option', 'another:command'], $config['command']);
364
        $this->assertSame('0 0 * * *', $config['frequency']);
365
        $this->assertSame('my description', $config['description']);
366
        $this->assertSame('UTC', $config['timezone']);
367
        $this->assertTrue($config['without_overlapping']['enabled']);
368
        $this->assertSame(WithoutOverlappingExtension::DEFAULT_TTL, $config['without_overlapping']['ttl']);
369
        $this->assertTrue($config['between']['enabled']);
370
        $this->assertSame(9, $config['between']['start']);
371
        $this->assertSame(17, $config['between']['end']);
372
        $this->assertTrue($config['unless_between']['enabled']);
373
        $this->assertSame(12, $config['unless_between']['start']);
374
        $this->assertSame('13:30', $config['unless_between']['end']);
375
        $this->assertTrue($config['ping_before']['enabled']);
376
        $this->assertSame('https://example.com/before', $config['ping_before']['url']);
377
        $this->assertSame('GET', $config['ping_before']['method']);
378
        $this->assertTrue($config['ping_after']['enabled']);
379
        $this->assertSame('https://example.com/after', $config['ping_after']['url']);
380
        $this->assertSame('GET', $config['ping_after']['method']);
381
        $this->assertTrue($config['ping_on_success']['enabled']);
382
        $this->assertSame('https://example.com/success', $config['ping_on_success']['url']);
383
        $this->assertSame('GET', $config['ping_on_success']['method']);
384
        $this->assertTrue($config['ping_on_failure']['enabled']);
385
        $this->assertSame('https://example.com/failure', $config['ping_on_failure']['url']);
386
        $this->assertSame('POST', $config['ping_on_failure']['method']);
387
        $this->assertTrue($config['email_after']['enabled']);
388
        $this->assertNull($config['email_after']['to']);
389
        $this->assertNull($config['email_after']['subject']);
390
        $this->assertTrue($config['email_on_failure']['enabled']);
391
        $this->assertSame('[email protected]', $config['email_on_failure']['to']);
392
        $this->assertSame('my subject', $config['email_on_failure']['subject']);
393
    }
394
395
    /**
396
     * @test
397
     */
398
    public function email_and_ping_configuration_can_be_shortened()
399
    {
400
        $this->load([
401
            'tasks' => [
402
                [
403
                    'command' => 'my:command --option',
404
                    'frequency' => '0 0 * * *',
405
                    'ping_after' => 'https://example.com/after',
406
                    'email_after' => '[email protected]',
407
                ],
408
            ],
409
        ]);
410
411
        $config = $this->container->getDefinition(ConfigureTasksSubscriber::class)->getArgument(0)[0];
412
413
        $this->assertTrue($config['ping_after']['enabled']);
414
        $this->assertSame('https://example.com/after', $config['ping_after']['url']);
415
        $this->assertSame('GET', $config['ping_after']['method']);
416
        $this->assertSame([], $config['ping_after']['options']);
417
418
        $this->assertTrue($config['email_after']['enabled']);
419
        $this->assertSame('[email protected]', $config['email_after']['to']);
420
        $this->assertNull($config['email_after']['subject']);
421
    }
422
423
    /**
424
     * @test
425
     */
426
    public function between_and_unless_between_config_can_be_shortened()
427
    {
428
        $this->load([
429
            'tasks' => [
430
                [
431
                    'command' => 'my:command --option',
432
                    'frequency' => '0 0 * * *',
433
                    'between' => '9-17',
434
                    'unless_between' => '11:30-13:15',
435
                ],
436
            ],
437
        ]);
438
439
        $config = $this->container->getDefinition(ConfigureTasksSubscriber::class)->getArgument(0)[0];
440
441
        $this->assertTrue($config['between']['enabled']);
442
        $this->assertSame('9', $config['between']['start']);
443
        $this->assertSame('17', $config['between']['end']);
444
445
        $this->assertTrue($config['unless_between']['enabled']);
446
        $this->assertSame('11:30', $config['unless_between']['start']);
447
        $this->assertSame('13:15', $config['unless_between']['end']);
448
    }
449
450
    protected function getContainerExtensions(): array
451
    {
452
        return [new ZenstruckScheduleExtension()];
453
    }
454
}
455