Issues (211)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

tests/QueuedJobsTest.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use SilverStripe\Dev\SapphireTest;
4
use SilverStripe\Core\Config\Config;
5
use SilverStripe\ORM\DataObject;
6
use Symbiote\QueuedJobs\DataObjects\QueuedJobDescriptor;
7
use Symbiote\QueuedJobs\Services\QueuedJob;
8
use Symbiote\QueuedJobs\Tests\QueuedJobsTest\TestQueuedJob;
9
use Symbiote\QueuedJobs\Tests\QueuedJobsTest\TestQJService;
10
11
/**
12
 *
13
 *
14
 * @author Marcus Nyeholt <[email protected]>
15
 */
16
class QueuedJobsTest extends SapphireTest
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
17
{
18
    /**
19
     * We need the DB for this test
20
     *
21
     * @var bool
22
     */
23
    protected $usesDatabase = true;
24
25
    /**
26
     * {@inheritDoc}
27
     */
28
    public function setUp()
29
    {
30
        parent::setUp();
31
32
        Config::nest();
33
        // Two restarts are allowed per job
34
        Config::modify()->set('Symbiote\\QueuedJobs\\Services\\QueuedJobService', 'stall_threshold', 2);
35
    }
36
37
    /**
38
     * {@inheritDoc}
39
     */
40
    public function tearDown()
41
    {
42
        Config::unnest();
43
        parent::tearDown();
44
    }
45
46
    /**
47
     * @return QueuedJobService
48
     */
49
    protected function getService()
50
    {
51
        return singleton(TestQJService::class);
52
    }
53
54
    public function testQueueJob()
55
    {
56
        $svc = $this->getService();
57
58
        // lets create a new job and add it tio the queue
59
        $job = new TestQueuedJob();
60
        $jobId = $svc->queueJob($job);
61
        $list = $svc->getJobList();
62
63
        $this->assertEquals(1, $list->count());
64
65
        $myJob = null;
66
        foreach ($list as $job) {
67
            if ($job->Implementation == TestQueuedJob::class) {
68
                $myJob = $job;
69
                break;
70
            }
71
        }
72
73
        $this->assertNotNull($myJob);
74
        $this->assertTrue($jobId > 0);
75
        $this->assertEquals(TestQueuedJob::class, $myJob->Implementation);
76
        $this->assertNotNull($myJob->SavedJobData);
77
    }
78
79
    public function testJobRunAs()
80
    {
81
        $svc = $this->getService();
82
        $list = $svc->getJobList();
83
        foreach ($list as $job) {
84
            $job->delete();
85
        }
86
87
        $this->logInWithPermission('DUMMY');
88
89
        // lets create a new job and add it tio the queue
90
        $job = new TestQueuedJob();
91
        $job->runningAs = "DUMMY";
92
        $jobId = $svc->queueJob($job);
93
        $list = $svc->getJobList();
94
95
        $myJob = $list->First();
96
97
        $this->assertEquals("[email protected]", $myJob->RunAs()->Email);
98
    }
99
100
    public function testQueueSignature()
101
    {
102
        $svc = $this->getService();
103
104
        // lets create a new job and add it tio the queue
105
        $job = new TestQueuedJob();
106
        $jobId = $svc->queueJob($job);
107
108
        $newJob = new TestQueuedJob();
109
        $newId = $svc->queueJob($newJob);
110
111
        $this->assertEquals($jobId, $newId);
112
113
        // now try another, but with different params
114
        $newJob = new TestQueuedJob();
115
        $newJob->randomParam = 'stuff';
116
        $newId = $svc->queueJob($newJob);
117
118
        $this->assertNotEquals($jobId, $newId);
119
    }
120
121
    public function testProcessJob()
122
    {
123
        $job = new TestQueuedJob();
124
        $job->setup();
125
        $job->process();
126
        // we should now have some  data
127
        $data = $job->getJobData();
128
        $this->assertNotNull($data->messages);
129
        $this->assertFalse($data->isComplete);
130
131
        $jd = $data->jobData;
132
        $this->assertTrue(isset($jd->times));
133
        $this->assertEquals(1, count($jd->times));
134
135
        // now take the 'saved' data and try restoring the job
136
    }
137
138
    public function testResumeJob()
139
    {
140
        $job = new TestQueuedJob();
141
        $job->setup();
142
        $job->process();
143
        // we should now have some  data
144
        $data = $job->getJobData();
145
146
        // so create a new job and restore it from this data
147
148
        $job = new TestQueuedJob();
149
        $job->setup();
150
151
        $job->setJobData($data->totalSteps, $data->currentStep, $data->isComplete, $data->jobData, $data->messages);
152
        $job->process();
153
154
        $data = $job->getJobData();
155
        $this->assertFalse($data->isComplete);
156
        $jd = $data->jobData;
157
        $this->assertTrue(isset($jd->times));
158
        $this->assertEquals(2, count($jd->times));
159
    }
160
161
    public function testInitialiseJob()
162
    {
163
        // okay, lets test it out on the actual service
164
        $svc = $this->getService();
165
        // lets create a new job and add it to the queue
166
        $job = new TestQueuedJob();
167
        $id = $svc->queueJob($job);
168
169
        $descriptor = DataObject::get_by_id('Symbiote\\QueuedJobs\\DataObjects\\QueuedJobDescriptor', $id);
170
171
        $job = $svc->testInit($descriptor);
172
        $this->assertInstanceOf(TestQueuedJob::class, $job, 'Job has been triggered');
173
174
        $descriptor = DataObject::get_by_id('Symbiote\\QueuedJobs\\DataObjects\\QueuedJobDescriptor', $id);
175
176
        $this->assertEquals(QueuedJob::STATUS_INIT, $descriptor->JobStatus);
177
    }
178
179
    public function testStartJob()
180
    {
181
        // okay, lets test it out on the actual service
182
        $svc = $this->getService();
183
        // lets create a new job and add it to the queue
184
185
        $this->logInWithPermission('DUMMYUSER');
186
187
        $job = new TestQueuedJob();
188
        $job->testingStartJob = true;
189
        $id = $svc->queueJob($job);
190
191
        $this->logInWithPermission('ADMIN');
192
193
        $result = $svc->runJob($id);
194
        $this->assertTrue($result);
195
196
        // we want to make sure that the current user is the runas user of the job
197
        $descriptor = DataObject::get_by_id('Symbiote\\QueuedJobs\\DataObjects\\QueuedJobDescriptor', $id);
198
        $this->assertEquals('Complete', $descriptor->JobStatus);
199
    }
200
201
    public function testImmediateQueuedJob()
202
    {
203
        // okay, lets test it out on the actual service
204
        $svc = $this->getService();
205
        // lets create a new job and add it to the queue
206
207
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
208
        $job->firstJob = true;
209
        $id = $svc->queueJob($job);
210
211
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
212
        $job->secondJob = true;
213
        $id = $svc->queueJob($job);
214
215
        $jobs = $svc->getJobList(QueuedJob::IMMEDIATE);
216
        $this->assertEquals(2, $jobs->count());
217
218
        // now fake a shutdown
219
        $svc->onShutdown();
220
221
        $jobs = $svc->getJobList(QueuedJob::IMMEDIATE);
222
        $this->assertInstanceOf('SilverStripe\\ORM\\DataList', $jobs);
223
        $this->assertEquals(0, $jobs->count());
224
    }
225
226
    public function testNextJob()
227
    {
228
        $svc = $this->getService();
229
        $list = $svc->getJobList();
230
231
        foreach ($list as $job) {
232
            $job->delete();
233
        }
234
235
        $list = $svc->getJobList();
236
        $this->assertEquals(0, $list->count());
237
238
        $job = new TestQueuedJob();
239
        $id1 = $svc->queueJob($job);
240
241
        $job = new TestQueuedJob();
242
        // to get around the signature checks
243
        $job->randomParam = 'me';
244
        $id2 = $svc->queueJob($job);
245
246
        $job = new TestQueuedJob();
247
        // to get around the signature checks
248
        $job->randomParam = 'mo';
249
        $id3 = $svc->queueJob($job);
250
251
        $this->assertEquals(2, $id3 - $id1);
252
253
        $list = $svc->getJobList();
254
        $this->assertEquals(3, $list->count());
255
256
        // okay, lets get the first one and initialise it, then make sure that a subsequent init attempt fails
257
        $job = $svc->getNextPendingJob();
258
259
        $this->assertEquals($id1, $job->ID);
260
        $svc->testInit($job);
261
262
        // now try and get another, it should be === false
263
        $next = $svc->getNextPendingJob();
264
265
        $this->assertFalse($next);
266
    }
267
268
    /**
269
     * Verify that broken jobs are correctly verified for health and restarted as necessary
270
     *
271
     * Order of checkJobHealth() and getNextPendingJob() is important
272
     *
273
     * Execution of this job is broken into several "loops", each of which represents one invocation
274
     * of ProcessJobQueueTask
275
     */
276
    public function testJobHealthCheck()
277
    {
278
        // Create a job and add it to the queue
279
        $svc = $this->getService();
280
        $logger = $svc->getLogger();
281
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
282
        $job->firstJob = true;
283
        $id = $svc->queueJob($job);
284
        $descriptor = QueuedJobDescriptor::get()->byID($id);
285
286
        // Verify initial state is new and LastProcessedCount is not marked yet
287
        $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
288
        $this->assertEquals(0, $descriptor->StepsProcessed);
289
        $this->assertEquals(-1, $descriptor->LastProcessedCount);
290
        $this->assertEquals(0, $descriptor->ResumeCounts);
291
292
        // Loop 1 - Pick up new job and attempt to run it
293
        // Job health should not attempt to cleanup unstarted jobs
294
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
295
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
296
297
        // Ensure that this is the next job ready to go
298
        $descriptor = QueuedJobDescriptor::get()->byID($id);
299
        $this->assertEquals($nextJob->ID, $descriptor->ID);
300
        $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
301
        $this->assertEquals(0, $descriptor->StepsProcessed);
302
        $this->assertEquals(-1, $descriptor->LastProcessedCount);
303
        $this->assertEquals(0, $descriptor->ResumeCounts);
304
305
        // Run 1 - Start the job (no work is done)
306
        $descriptor->JobStatus = QueuedJob::STATUS_INIT;
307
        $descriptor->write();
308
309
        // Assume that something bad happens at this point, the process dies during execution, and
310
        // the task is re-initiated somewhere down the track
311
312
        // Loop 2 - Detect broken job, and mark it for future checking.
313
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
314
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
315
316
        // Note that we don't immediately try to restart it until StepsProcessed = LastProcessedCount
317
        $descriptor = QueuedJobDescriptor::get()->byID($id);
318
        $this->assertFalse($nextJob); // Don't run it this round please!
319
        $this->assertEquals(QueuedJob::STATUS_INIT, $descriptor->JobStatus);
320
        $this->assertEquals(0, $descriptor->StepsProcessed);
321
        $this->assertEquals(0, $descriptor->LastProcessedCount);
322
        $this->assertEquals(0, $descriptor->ResumeCounts);
323
324
        // Loop 3 - We've previously marked this job as broken, so restart it this round
325
        // If no more work has been done on the job at this point, assume that we are able to
326
        // restart it
327
        $logger->clear();
328
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
329
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
330
331
        // This job is resumed and exeuction is attempted this round
332
        $descriptor = QueuedJobDescriptor::get()->byID($id);
333
        $this->assertEquals($nextJob->ID, $descriptor->ID);
334
        $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
335
        $this->assertEquals(0, $descriptor->StepsProcessed);
336
        $this->assertEquals(0, $descriptor->LastProcessedCount);
337
        $this->assertEquals(1, $descriptor->ResumeCounts);
338
        $this->assertContains('A job named A Test job appears to have stalled. It will be stopped and restarted, please login to make sure it has continued', $logger->getMessages());
339
340
        // Run 2 - First restart (work is done)
341
        $descriptor->JobStatus = QueuedJob::STATUS_RUN;
342
        $descriptor->StepsProcessed++; // Essentially delays the next restart by 1 loop
343
        $descriptor->write();
344
345
        // Once again, at this point, assume the job fails and crashes
346
347
        // Loop 4 - Assuming a job has LastProcessedCount < StepsProcessed we are in the same
348
        // situation as step 2.
349
        // Because the last time the loop ran, StepsProcessed was incremented,
350
        // this indicates that it's likely that another task could be working on this job, so
351
        // don't run this.
352
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
353
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
354
355
        $descriptor = QueuedJobDescriptor::get()->byID($id);
356
        $this->assertFalse($nextJob); // Don't run jobs we aren't sure should be restarted
357
        $this->assertEquals(QueuedJob::STATUS_RUN, $descriptor->JobStatus);
358
        $this->assertEquals(1, $descriptor->StepsProcessed);
359
        $this->assertEquals(1, $descriptor->LastProcessedCount);
360
        $this->assertEquals(1, $descriptor->ResumeCounts);
361
362
        // Loop 5 - Job is again found to not have been restarted since last iteration, so perform second
363
        // restart. The job should be attempted to run this loop
364
        $logger->clear();
365
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
366
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
367
368
        // This job is resumed and exeuction is attempted this round
369
        $descriptor = QueuedJobDescriptor::get()->byID($id);
370
        $this->assertEquals($nextJob->ID, $descriptor->ID);
371
        $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
372
        $this->assertEquals(1, $descriptor->StepsProcessed);
373
        $this->assertEquals(1, $descriptor->LastProcessedCount);
374
        $this->assertEquals(2, $descriptor->ResumeCounts);
375
        $this->assertContains('A job named A Test job appears to have stalled. It will be stopped and restarted, please login to make sure it has continued', $logger->getMessages());
376
377
        // Run 3 - Second and last restart (no work is done)
378
        $descriptor->JobStatus = QueuedJob::STATUS_RUN;
379
        $descriptor->write();
380
381
        // Loop 6 - As no progress has been made since loop 3, we can mark this as dead
382
        $logger->clear();
383
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
384
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
385
386
        // Since no StepsProcessed has been done, don't wait another loop to mark this as dead
387
        $descriptor = QueuedJobDescriptor::get()->byID($id);
388
        $this->assertEquals(QueuedJob::STATUS_PAUSED, $descriptor->JobStatus);
389
        $this->assertEmpty($nextJob);
390
        $this->assertContains('A job named A Test job appears to have stalled. It has been paused, please login to check it', $logger->getMessages());
391
    }
392
}
393