GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Story   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 1294
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 4

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 102
lcom 7
cbo 4
dl 0
loc 1294
rs 0.6314
c 4
b 1
f 0

79 Methods

Rating   Name   Duplication   Size   Complexity  
A getCategory() 0 4 1
A getGroup() 0 4 1
A getGroupAsString() 0 4 1
A getName() 0 4 1
A setScenario() 0 4 1
A getStoryFilename() 0 4 1
A addValidRole() 0 4 1
A andValidRole() 0 4 1
A hasRole() 0 4 1
A getRoleChanges() 0 4 1
A hasRoleChanges() 0 4 1
A setRoleChanges() 0 4 1
A getStoryTemplates() 0 4 1
A getRequiredStoryplayerVersion() 0 4 1
A getWhitelistedEnvironments() 0 4 1
A requiresTestEnvironmentWithRoles() 0 4 1
A getRequiredTestEnvironmentRoles() 0 4 1
A getPersistDevice() 0 4 1
A getTestCanRunCheck() 0 4 1
A hasTestCanRunCheck() 0 4 1
A addTestCanRunCheck() 0 4 1
A setTestEnvironmentSetup() 0 4 1
A addTestEnvironmentSetup() 0 4 1
A setTestEnvironmentTeardown() 0 4 1
A addTestEnvironmentTeardown() 0 4 1
A getTestSetup() 0 4 1
A hasTestSetup() 0 4 1
A addTestSetup() 0 4 1
A getTestTeardown() 0 4 1
A hasTestTeardown() 0 4 1
A addTestTeardown() 0 4 1
A getPerPhaseSetup() 0 4 1
A hasPerPhaseSetup() 0 4 1
A addPerPhaseSetup() 0 4 1
A getPerPhaseTeardown() 0 4 1
A hasPerPhaseTeardown() 0 4 1
A addPerPhaseTeardown() 0 4 1
A getDeviceSetup() 0 4 1
A hasDeviceSetup() 0 4 1
A addDeviceSetup() 0 4 1
A getDeviceTeardown() 0 4 1
A hasDeviceTeardown() 0 4 1
A addDeviceTeardown() 0 4 1
A getHints() 0 4 1
A hasHints() 0 4 1
A setHints() 0 4 1
A addHints() 0 4 1
A getPreTestPrediction() 0 4 1
A hasPreTestPrediction() 0 4 1
A addPreTestPrediction() 0 4 1
A getPreTestInspection() 0 4 1
A hasPreTestInspection() 0 4 1
A addPreTestInspection() 0 4 1
A addAction() 0 4 1
A addActions() 0 4 1
A getAction() 0 4 1
A hasActions() 0 4 1
A getPostTestInspection() 0 4 1
A hasPostTestInspection() 0 4 1
A addPostTestInspection() 0 4 1
A __toString() 0 4 1
A inGroup() 0 10 2
A called() 0 6 1
A setCategory() 0 5 1
A setGroup() 0 5 1
A setName() 0 5 1
B getNameForConsole() 0 23 4
A getScenario() 0 8 2
A setParams() 0 5 1
B getParams() 0 28 4
A determineStoryFilename() 0 5 1
F basedOn() 0 24 13
A requiresStoryplayerVersion() 0 5 1
A runsOn() 0 5 1
A andOn() 0 5 1
A setPersistDevice() 0 5 1
A getOneAction() 0 14 2
B setDefaultCallbacks() 0 45 2
A getResult() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Story often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Story, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Copyright (c) 2011-present Mediasift Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   Storyplayer/PlayerLib
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2011-present Mediasift Ltd www.datasift.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://datasift.github.io/storyplayer
42
 */
43
44
namespace DataSift\Storyplayer\PlayerLib;
45
46
use Exception;
47
48
/**
49
 * Object that represents a single story
50
 *
51
 * @category  Libraries
52
 * @package   Storyplayer/StoryLib
53
 * @author    Stuart Herbert <[email protected]>
54
 * @copyright 2011-present Mediasift Ltd www.datasift.com
55
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
56
 * @link      http://datasift.github.io/storyplayer
57
 */
58
class Story
59
{
60
    /**
61
     * the category that this story belongs to
62
     * @var string
63
     */
64
    protected $category;
65
66
    /**
67
     * the group that this story belongs to
68
     * @var array<string>
69
     */
70
    protected $group;
71
72
    /**
73
     * the name of this story
74
     * @var string
75
     */
76
    protected $name;
77
78
    /**
79
     * the function that provides hints about how this story changes
80
     * the state of the system or user
81
     *
82
     * @var callable
83
     */
84
    protected $hintsCallback;
85
86
    /**
87
     * the function that checks to see if the test should run at all,
88
     * or should be skipped
89
     *
90
     * @var array
91
     */
92
    protected $testCanRunCheckCallback = array();
93
94
    /**
95
     * the function that provides any story-specific setup work
96
     *
97
     * @var array
98
     */
99
    protected $testSetupCallback = array();
100
101
    /**
102
     * the function that provides any story-specific teardown action
103
     * @var array
104
     */
105
    protected $testTeardownCallback = array();
106
107
    /**
108
     * the function that provides any story-specific setup work that
109
     * happens before each phase of the test
110
     * @var array
111
     */
112
    protected $perPhaseSetupCallback = array();
113
114
    /**
115
     * the function that provides any story-specific teardown work that
116
     * happens at the end of each phase of the test
117
     * @var array
118
     */
119
    protected $perPhaseTeardownCallback = array();
120
121
    /**
122
     * the function that provides any story-specific setup work that
123
     * happens when we start a device
124
     * @var array
125
     */
126
    protected $deviceSetupCallback = array();
127
128
    /**
129
     * the function that provides any story-specific teardown work that
130
     * happens just before we stop a device
131
     * @var array
132
     */
133
    protected $deviceTeardownCallback = array();
134
135
    /**
136
     * the function that provides information about how this story has
137
     * changed the state of the system or user
138
     *
139
     * @var array
140
     */
141
    protected $roleChangesCallback = array();
142
143
    /**
144
     * the callback that dynamically determines in advance whether the
145
     * story actions should succeed or fail
146
     *
147
     * @var array
148
     */
149
    protected $preTestPredictionCallback = array();
150
151
    /**
152
     * the callback that dynamically determines afterwards whether or not
153
     * the story actions actually did succeed
154
     *
155
     * @var array
156
     */
157
    protected $reportTestResultsCallback = array();
158
159
    /**
160
     * the callback used to remember the state of the system *before*
161
     * the action occurs
162
     *
163
     * @var array
164
     */
165
    protected $preTestInspectionCallback = array();
166
167
    /**
168
     * the callback used to see if the action *did* change the state of
169
     * the system under test
170
     *
171
     * @var array
172
     */
173
    protected $postTestInspectionCallback = array();
174
175
    /**
176
     * the actions that execute the story on behalf of the user
177
     *
178
     * this is an array of callbacks.  Each callback is a single set of
179
     * actions to execute the story.  Each callback is an alternative way
180
     * to execute the story.  Each callback is meant to be equivalent; ie
181
     * they achieve the same thing, just in different ways.
182
     *
183
     * If any of the callbacks has different outcomes, then they belong
184
     * in separate user stories. NO EXCEPTIONS. This is a fundamental
185
     * assumption of Storyplayer; ignore it, and Storyplayer's no use to
186
     * you!
187
     *
188
     * @var array
189
     */
190
    protected $actionsCallbacks = array();
191
192
    /**
193
     * a list of the StoryTemplates that this story is based on. can be
194
     * empty
195
     *
196
     * @var array
197
     */
198
    protected $storyTemplates = array();
199
200
    /**
201
     * the parameters that get passed into virtual machines et al, and
202
     * which can be overridden on the command-line
203
     *
204
     * @var array
205
     */
206
    protected $params = array();
207
208
    /**
209
     * the environments that a story states it runs on
210
     *
211
     * by default, a story is allowed to run on all environments, *but*
212
     * if an environment's config sets 'mustBeWhitelisted' to TRUE, then
213
     * the story is only allowed to run on that environment if the story
214
     * declares itself safe to run
215
     *
216
     * we believe that this is the safest approach to handling those
217
     * stories that simply aren't safe to run absolutely everywhere
218
     */
219
    protected $whitelistedEnvironments = array();
220
221
    /**
222
     * the raw parser tree of this story, and of any templates that we
223
     * use
224
     *
225
     * @var array
226
     */
227
    protected $parserTrees = array();
228
229
    /**
230
     * the file that contains the story
231
     *
232
     * @var string
233
     */
234
    protected $storyFilename = '';
235
236
    /**
237
     * a list of the roles that the test environment must have defined,
238
     * otherwise we cannot run
239
     *
240
     * @var array
241
     */
242
    protected $requiredTestEnvRoles = array();
243
244
    /**
245
     * does the story want the test device kept open between phases?
246
     *
247
     * @var boolean
248
     */
249
    protected $persistDevice = false;
250
251
    /**
252
     * which version of Storyplayer is this test written for?
253
     *
254
     * you HAVE to set this in your story, otherwise we will skip your
255
     * story
256
     *
257
     * @var integer
258
     */
259
    protected $compatibleVersion = 1;
260
261
    /**
262
     * what happened to this story?
263
     * @var \DataSift\Storyplayer\PlayerLib\Story_Result
264
     */
265
    protected $storyResult = null;
266
267
    /**
268
     * what is this story trying to achieve?
269
     * @var null|array
270
     */
271
    protected $storyScenario = null;
272
273
    // ====================================================================
274
    //
275
    // Metadata about the story itself
276
    //
277
    // --------------------------------------------------------------------
278
279
    /**
280
     * which group of tests does this story belong to?
281
     *
282
     * @param  array|string $groupName
283
     *         the group to use
284
     * @return Story
285
     *         $this for fluent interface
286
     */
287
    public function inGroup($groupName)
288
    {
289
        if (!is_array($groupName)) {
290
            $parts = explode(" > ", $groupName);
291
            $groupName = $parts;
292
        }
293
        $this->setGroup($groupName);
294
295
        return $this;
296
    }
297
298
    /**
299
     * @return Story
300
     */
301
    public function called($userStoryText)
302
    {
303
        $this->setName($userStoryText);
304
305
        return $this;
306
    }
307
308
    /**
309
     * Get the category that this story belongs to
310
     *
311
     * Systems under test can grow to encompass hundreds, if not thousands
312
     * of user stories.  To make this manageable at scale, we break down
313
     * each user story like this:
314
     *
315
     * Name    : Starts as a free user with 10 USD in credit
316
     * Category: Billing User Stories
317
     * Group   : User States
318
     *
319
     * The 'name' is the summary text of the user story itself, which
320
     * should be no longer than a single sentence, please.
321
     *
322
     * The 'category' is the general group that the user story belongs to.
323
     * These are the top-level groups, such as 'Registration', 'Billing'
324
     * and so forth.
325
     *
326
     * The 'group' is the specific group _inside_ the category that the
327
     * user story belongs to.  The groups are specific to the category.
328
     *
329
     * @return string the category that this story belongs to
330
     */
331
    public function getCategory()
332
    {
333
        return $this->category;
334
    }
335
336
    /**
337
     * Set the category that this story belongs to
338
     *
339
     * Systems under test can grow to encompass hundreds, if not thousands
340
     * of user stories.  To make this manageable at scale, we break down
341
     * each user story like this:
342
     *
343
     * Name    : Starts as a free user with 10 USD in credit
344
     * Category: Billing User Stories
345
     * Group   : User States
346
     *
347
     * The 'name' is the summary text of the user story itself, which
348
     * should be no longer than a single sentence, please.
349
     *
350
     * The 'category' is the general group that the user story belongs to.
351
     * These are the top-level groups, such as 'Registration', 'Billing'
352
     * and so forth.
353
     *
354
     * The 'group' is the specific group _inside_ the category that the
355
     * user story belongs to.  The groups are specific to the category.
356
     *
357
     * @param  string $newCategory the category that this story belongs to
358
     * @return Story  $this
359
     */
360
    public function setCategory($newCategory)
361
    {
362
        $this->category = $newCategory;
363
        return $this;
364
    }
365
366
    /**
367
     * Get the group that this story belongs to
368
     *
369
     * Systems under test can grow to encompass hundreds, if not thousands
370
     * of user stories.  To make this manageable at scale, we break down
371
     * each user story like this:
372
     *
373
     * Name    : Starts as a free user with 10 USD in credit
374
     * Category: Billing User Stories
375
     * Group   : User States
376
     *
377
     * The 'name' is the summary text of the user story itself, which
378
     * should be no longer than a single sentence, please.
379
     *
380
     * The 'category' is the general group that the user story belongs to.
381
     * These are the top-level groups, such as 'Registration', 'Billing'
382
     * and so forth.
383
     *
384
     * The 'group' is the specific group _inside_ the category that the
385
     * user story belongs to.  The groups are specific to the category.
386
     *
387
     * @return array<string> the group that this story belongs to
388
     */
389
    public function getGroup()
390
    {
391
        return $this->group;
392
    }
393
394
    /**
395
     * return the story's group as a printable string
396
     *
397
     * @return string
398
     */
399
    public function getGroupAsString()
400
    {
401
        return implode(" > ", $this->group);
402
    }
403
404
    /**
405
     * Set the group that this story belongs to
406
     *
407
     * Systems under test can grow to encompass hundreds, if not thousands
408
     * of user stories.  To make this manageable at scale, we break down
409
     * each user story like this:
410
     *
411
     * Name    : Starts as a free user with 10 USD in credit
412
     * Category: Billing User Stories
413
     * Group   : User States
414
     *
415
     * The 'name' is the summary text of the user story itself, which
416
     * should be no longer than a single sentence, please.
417
     *
418
     * The 'category' is the general group that the user story belongs to.
419
     * These are the top-level groups, such as 'Registration', 'Billing'
420
     * and so forth.
421
     *
422
     * The 'group' is the specific group _inside_ the category that the
423
     * user story belongs to.  The groups are specific to the category.
424
     *
425
     * @param  array<string> $newGroup
426
     * @return Story  $this
427
     */
428
    public function setGroup($newGroup)
429
    {
430
        $this->group = $newGroup;
431
        return $this;
432
    }
433
434
    /**
435
     * Get the name of this story
436
     *
437
     * Systems under test can grow to encompass hundreds, if not thousands
438
     * of user stories.  To make this manageable at scale, we break down
439
     * each user story like this:
440
     *
441
     * Name    : Starts as a free user with 10 USD in credit
442
     * Category: Billing User Stories
443
     * Group   : User States
444
     *
445
     * The 'name' is the summary text of the user story itself, which
446
     * should be no longer than a single sentence, please.
447
     *
448
     * The 'category' is the general group that the user story belongs to.
449
     * These are the top-level groups, such as 'Registration', 'Billing'
450
     * and so forth.
451
     *
452
     * The 'group' is the specific group _inside_ the category that the
453
     * user story belongs to.  The groups are specific to the category.
454
     *
455
     * @return string the name of this story
456
     */
457
    public function getName()
458
    {
459
        return $this->name;
460
    }
461
462
    /**
463
     * Set the name of this story
464
     *
465
     * Systems under test can grow to encompass hundreds, if not thousands
466
     * of user stories.  To make this manageable at scale, we break down
467
     * each user story like this:
468
     *
469
     * Name    : Starts as a free user with 10 USD in credit
470
     * Category: Billing User Stories
471
     * Group   : User States
472
     *
473
     * The 'name' is the summary text of the user story itself, which
474
     * should be no longer than a single sentence, please.
475
     *
476
     * The 'category' is the general group that the user story belongs to.
477
     * These are the top-level groups, such as 'Registration', 'Billing'
478
     * and so forth.
479
     *
480
     * The 'group' is the specific group _inside_ the category that the
481
     * user story belongs to.  The groups are specific to the category.
482
     *
483
     * @param  string $newName the name of this story
484
     * @return Story  $this
485
     */
486
    public function setName($newName)
487
    {
488
        $this->name = $newName;
489
        return $this;
490
    }
491
492
    /**
493
     * get the name of this story, as is suitable for displaying in
494
     * the console
495
     *
496
     * @return string
497
     */
498
    public function getNameForConsole()
499
    {
500
        // this is for stories that made the effort to tell us
501
        // about themselves
502
        if (isset($this->category)) {
503
            return $this->getCategory() . ' > ' . $this->getGroupAsString() . ' > ' . $this->getName();
504
        }
505
506
        // this is for stories that have not gone to the trouble
507
        $cwd = getcwd() . DIRECTORY_SEPARATOR;
508
        $filename = $this->getStoryFilename();
509
510
        $filename = str_replace($cwd, '', $filename);
511
        $parts = explode(DIRECTORY_SEPARATOR, $filename);
512
        while (!empty($parts)) {
513
            $part = array_shift($parts);
514
            if (strtolower($part) === 'stories') {
515
                return implode(DIRECTORY_SEPARATOR, $parts);
516
            }
517
        }
518
519
        return $filename;
520
    }
521
522
    /**
523
     * what scenario is this story implementing?
524
     *
525
     * @return array|null
526
     */
527
    public function getScenario()
528
    {
529
        if ($this->storyScenario === null) {
530
            return [ "this story has not supplied a scenario; call \$story->setScenario() in your test."];
531
        }
532
533
        return $this->storyScenario;
534
    }
535
536
    /**
537
     * tell us what scenario this story is implementing
538
     *
539
     * @param array $scenario
540
     */
541
    public function setScenario($scenario)
542
    {
543
        $this->storyScenario = $scenario;
544
    }
545
546
    /**
547
     * Set the parameters for this story
548
     *
549
     * Parameters are a way of passing settings between Stories and
550
     * StoryTemplates.
551
     *
552
     * Order of precedence:
553
     *
554
     * 1) Story
555
     * 2) StoryTemplates (in 'basedOn()' order)
556
     *    (templates cannot override each other)
557
     *
558
     * @param array $defaults
559
     *        a list of the parameters for this story
560
     */
561
    public function setParams($defaults)
562
    {
563
        $this->params = $defaults;
564
        return $this;
565
    }
566
567
    /**
568
     * @return array
569
     */
570
    public function getParams()
571
    {
572
        // our return value
573
        $return = array();
574
575
        // populate it with the parameters from the templates
576
        foreach ($this->storyTemplates as $template) {
577
            // get any params from the template
578
            $params = $template->getParams();
579
580
            // any params already set have precedence
581
            foreach ($params as $key => $value) {
582
                // do we have a clash?
583
                if (!isset($return[$key])) {
584
                    $return[$key] = $value;
585
                }
586
            }
587
        }
588
589
        // now, merge in our own params
590
        //
591
        // our params always take precedence over the params set
592
        // in the template(s)
593
        $return = array_merge($this->params, $return);
594
595
        // all done
596
        return $return;
597
    }
598
599
    /**
600
     * @return void
601
     */
602
    public function determineStoryFilename()
603
    {
604
        $trace = debug_backtrace();
605
        $this->storyFilename = $trace[1]['file'];
606
    }
607
608
    /**
609
     * @return string
610
     */
611
    public function getStoryFilename()
612
    {
613
        return $this->storyFilename;
614
    }
615
616
    // ====================================================================
617
    //
618
    // Metadata for which states the story is valid for
619
    //
620
    // --------------------------------------------------------------------
621
622
    public function addValidRole($role)
0 ignored issues
show
Unused Code introduced by
The parameter $role is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
623
    {
624
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
625
    }
626
627
    /**
628
     * Synonym for addValidRole()
629
     *
630
     * @see Story::addValidRole
631
     */
632
    public function andValidRole($role)
0 ignored issues
show
Unused Code introduced by
The parameter $role is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
633
    {
634
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
635
    }
636
637
    public function hasRole($roleName)
0 ignored issues
show
Unused Code introduced by
The parameter $roleName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
638
    {
639
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
640
    }
641
642
    // ====================================================================
643
    //
644
    // Support for changing roles after a test has succeeded
645
    //
646
    // --------------------------------------------------------------------
647
648
    /**
649
     * get the role changes callback
650
     */
651
    public function getRoleChanges()
652
    {
653
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
654
    }
655
656
    /**
657
     * has the role changes callback been set?
658
     */
659
    public function hasRoleChanges()
660
    {
661
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
662
    }
663
664
    public function setRoleChanges($newCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $newCallback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
665
    {
666
        throw new E4xx_DeprecatedFeature('user roles in stories have been removed from Storyplayer v2.');
667
    }
668
669
    // ====================================================================
670
    //
671
    // Information about story templates
672
    //
673
    // --------------------------------------------------------------------
674
675
    /**
676
     * set up any templated methods from a predefined class
677
     *
678
     * @return Story
679
     */
680
    public function basedOn(StoryTemplate $tmpl)
681
    {
682
        // tell the template which story it is being used with
683
        $tmpl->setStory($this);
684
685
        $tmpl->hasTestCanRunCheck()         && $this->addTestCanRunCheck($tmpl->getTestCanRunCheck());
686
        $tmpl->hasTestSetup()               && $this->addTestSetup($tmpl->getTestSetup());
687
        $tmpl->hasTestTeardown()            && $this->addTestTeardown($tmpl->getTestTeardown());
688
        $tmpl->hasPerPhaseSetup()           && $this->addPerPhaseSetup($tmpl->getPerPhaseSetup());
689
        $tmpl->hasPerPhaseTeardown()        && $this->addPerPhaseTeardown($tmpl->getPerPhaseTeardown());
690
        $tmpl->hasDeviceSetup()             && $this->addDeviceSetup($tmpl->getDeviceSetup());
691
        $tmpl->hasDeviceTeardown()          && $this->addDeviceTeardown($tmpl->getDeviceTeardown());
692
        $tmpl->hasHints()                   && $this->addHints($tmpl->getHints());
693
        $tmpl->hasPreTestPrediction()       && $this->addPreTestPrediction($tmpl->getPreTestPrediction());
694
        $tmpl->hasPreTestInspection()       && $this->addPreTestInspection($tmpl->getPreTestInspection());
695
        $tmpl->hasAction()                  && $this->addAction($tmpl->getAction());
696
        $tmpl->hasPostTestInspection()      && $this->addPostTestInspection($tmpl->getPostTestInspection());
697
698
        // remember this template for future use
699
        $this->storyTemplates[] = $tmpl;
700
701
        // Return $this for a fluent interface
702
        return $this;
703
    }
704
705
    /**
706
     * get a list of the templates that this story is based on, in order
707
     * or precedence
708
     *
709
     * can be empty
710
     *
711
     * @return array<StoryTemplate>
712
     */
713
    public function getStoryTemplates()
714
    {
715
        return $this->storyTemplates;
716
    }
717
718
    // ==================================================================
719
    //
720
    // Information about dependencies
721
    //
722
    // ------------------------------------------------------------------
723
724
    /**
725
     * @return int
726
     */
727
    public function getRequiredStoryplayerVersion()
728
    {
729
        return $this->compatibleVersion;
730
    }
731
732
    /**
733
     * @return Story
734
     */
735
    public function requiresStoryplayerVersion($version)
736
    {
737
        $this->compatibleVersion = $version;
738
        return $this;
739
    }
740
741
    // ====================================================================
742
    //
743
    // Information about environments
744
    //
745
    // --------------------------------------------------------------------
746
747
    /**
748
     * @return Story
749
     */
750
    public function runsOn($envName)
751
    {
752
        $this->whitelistedEnvironments[$envName] = true;
753
        return $this;
754
    }
755
756
    /**
757
     * @return Story
758
     */
759
    public function andOn($envName)
760
    {
761
        $this->whitelistedEnvironments[$envName] = true;
762
        return $this;
763
    }
764
765
    /**
766
     * @return array
767
     */
768
    public function getWhitelistedEnvironments()
769
    {
770
        return $this->whitelistedEnvironments;
771
    }
772
773
    /**
774
     * @return void
775
     */
776
    public function requiresTestEnvironmentWithRoles($roles)
777
    {
778
        $this->requiredTestEnvRoles = $roles;
779
    }
780
781
    /**
782
     * @return array
783
     */
784
    public function getRequiredTestEnvironmentRoles()
785
    {
786
        return $this->requiredTestEnvRoles;
787
    }
788
789
    // ==================================================================
790
    //
791
    // Device support
792
    //
793
    // ------------------------------------------------------------------
794
795
    /**
796
     * does this story want to keep the web browser open between phases?
797
     *
798
     * @return boolean
799
     */
800
    public function getPersistDevice()
801
    {
802
        return $this->persistDevice;
803
    }
804
805
    /**
806
     * tell Storyplayer to keep the web browser open between test phases
807
     *
808
     * by default, we close the browser after every phase, to make sure
809
     * that the next phase always starts with a browser in a known state
810
     */
811
    public function setPersistDevice()
812
    {
813
        $this->persistDevice = true;
814
        return $this;
815
    }
816
817
    // ====================================================================
818
    //
819
    // Information about how check if the test should run at all
820
    //
821
    // --------------------------------------------------------------------
822
823
    /**
824
     * get the callback which allows the story to be skipped
825
     *
826
     * @return array
827
     */
828
    public function getTestCanRunCheck()
829
    {
830
        return $this->testCanRunCheckCallback;
831
    }
832
833
    /**
834
     * do we have a 'check story can run' callback?
835
     *
836
     * @return boolean true if the callback exists
837
     */
838
    public function hasTestCanRunCheck()
839
    {
840
        return count($this->testCanRunCheckCallback) > 0;
841
    }
842
843
    /**
844
     * @return void
845
     */
846
    public function addTestCanRunCheck($newCallback)
847
    {
848
        $this->testCanRunCheckCallback[] = $newCallback;
849
    }
850
851
    // ====================================================================
852
    //
853
    // Information about how to setup and teardown the test environment
854
    //
855
    // This is a feature from Storyplayer v1 that we've dropped in v2
856
    //
857
    // --------------------------------------------------------------------
858
859
    public function setTestEnvironmentSetup($newCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $newCallback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
860
    {
861
        throw new E4xx_DeprecatedFeature('TestEnvironmentSetup phase was removed from Storyplayer v2.');
862
    }
863
864
    public function addTestEnvironmentSetup($newCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $newCallback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
865
    {
866
        throw new E4xx_DeprecatedFeature('TestEnvironmentSetup phase was removed from Storyplayer v2.');
867
    }
868
869
    public function setTestEnvironmentTeardown($newCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $newCallback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
870
    {
871
        throw new E4xx_DeprecatedFeature('TestEnvironmentSetup phase was removed from Storyplayer v2.');
872
    }
873
874
    public function addTestEnvironmentTeardown($newCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $newCallback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
875
    {
876
        throw new E4xx_DeprecatedFeature('TestEnvironmentSetup phase was removed from Storyplayer v2.');
877
    }
878
879
    // ====================================================================
880
    //
881
    // Information about how to setup and teardown the test
882
    //
883
    // --------------------------------------------------------------------
884
885
    /**
886
     * get the callback for per-story setup work
887
     *
888
     * @return array
889
     */
890
    public function getTestSetup()
891
    {
892
        return $this->testSetupCallback;
893
    }
894
895
    /**
896
     * do we have a pre-story setup callback?
897
     *
898
     * @return boolean true if there is a pre-story setup callback
899
     */
900
    public function hasTestSetup()
901
    {
902
        return count($this->testSetupCallback) > 0;
903
    }
904
905
    public function addTestSetup($newCallback)
906
    {
907
        $this->testSetupCallback[] = $newCallback;
908
    }
909
910
    /**
911
     * get the callback for post-story teardown work
912
     *
913
     * @return array
914
     */
915
    public function getTestTeardown()
916
    {
917
        return $this->testTeardownCallback;
918
    }
919
920
    /**
921
     * do we have a post-story teardown callback?
922
     *
923
     * @return boolean true if there is a post-story teardown callback
924
     */
925
    public function hasTestTeardown()
926
    {
927
        return count($this->testTeardownCallback) > 0;
928
    }
929
930
    /**
931
     * @return void
932
     */
933
    public function addTestTeardown($newCallback)
934
    {
935
        $this->testTeardownCallback[] = $newCallback;
936
    }
937
938
    // ====================================================================
939
    //
940
    // Actions to happen before and after every phase of the test
941
    //
942
    // --------------------------------------------------------------------
943
944
    /**
945
     * get the callback for per-phase setup work
946
     *
947
     * @return array
948
     */
949
    public function getPerPhaseSetup()
950
    {
951
        return $this->perPhaseSetupCallback;
952
    }
953
954
    /**
955
     * do we have a per-phase setup callback?
956
     *
957
     * @return boolean true if there is a per-phase setup callback
958
     */
959
    public function hasPerPhaseSetup()
960
    {
961
        return count($this->perPhaseSetupCallback) > 0;
962
    }
963
964
    /**
965
     * @return void
966
     */
967
    public function addPerPhaseSetup($newCallback)
968
    {
969
        $this->perPhaseSetupCallback[] = $newCallback;
970
    }
971
972
    /**
973
     * get the callback for per-phase teardown work
974
     *
975
     * @return array
976
     */
977
    public function getPerPhaseTeardown()
978
    {
979
        return $this->perPhaseTeardownCallback;
980
    }
981
982
    /**
983
     * do we have a per-phase teardown callback?
984
     *
985
     * @return boolean true if there is a per-phase teardown callback
986
     */
987
    public function hasPerPhaseTeardown()
988
    {
989
        return count($this->perPhaseTeardownCallback) > 0;
990
    }
991
992
    /**
993
     * @return void
994
     */
995
    public function addPerPhaseTeardown($newCallback)
996
    {
997
        $this->perPhaseTeardownCallback[] = $newCallback;
998
    }
999
1000
    // ====================================================================
1001
    //
1002
    // Actions to happen when starting and stopping test devices
1003
    //
1004
    // --------------------------------------------------------------------
1005
1006
    /**
1007
     * get the callback for device setup work
1008
     *
1009
     * @return array
1010
     */
1011
    public function getDeviceSetup()
1012
    {
1013
        return $this->deviceSetupCallback;
1014
    }
1015
1016
    /**
1017
     * do we have a device setup callback?
1018
     *
1019
     * @return boolean true if there is a device setup callback
1020
     */
1021
    public function hasDeviceSetup()
1022
    {
1023
        return count($this->deviceSetupCallback) > 0;
1024
    }
1025
1026
    /**
1027
     * @return void
1028
     */
1029
    public function addDeviceSetup($newCallback)
1030
    {
1031
        $this->deviceSetupCallback[] = $newCallback;
1032
    }
1033
1034
    /**
1035
     * get the callback for device teardown work
1036
     *
1037
     * @return array
1038
     */
1039
    public function getDeviceTeardown()
1040
    {
1041
        return $this->deviceTeardownCallback;
1042
    }
1043
1044
    /**
1045
     * do we have a device teardown callback?
1046
     *
1047
     * @return boolean true if there is a device teardown callback
1048
     */
1049
    public function hasDeviceTeardown()
1050
    {
1051
        return count($this->deviceTeardownCallback) > 0;
1052
    }
1053
1054
    /**
1055
     * @return void
1056
     */
1057
    public function addDeviceTeardown($newCallback)
1058
    {
1059
        $this->deviceTeardownCallback[] = $newCallback;
1060
    }
1061
1062
    // ====================================================================
1063
    //
1064
    // Information about how the story changes the system
1065
    //
1066
    // --------------------------------------------------------------------
1067
1068
    /**
1069
     * get the hints callback
1070
     *
1071
     * @return callable
1072
     */
1073
    public function getHints()
1074
    {
1075
        return $this->hintsCallback;
1076
    }
1077
1078
    /**
1079
     * have any hints been set?
1080
     *
1081
     * @return boolean true if the callback has been set
1082
     */
1083
    public function hasHints()
1084
    {
1085
        return count($this->hintsCallback) > 0;
1086
    }
1087
1088
    /**
1089
     * @return void
1090
     */
1091
    public function setHints($newCallback)
1092
    {
1093
        $this->hintsCallback = array($newCallback);
1094
    }
1095
1096
    /**
1097
     * @return void
1098
     */
1099
    public function addHints($newCallback)
1100
    {
1101
        $this->hintsCallback[] = $newCallback;
1102
    }
1103
1104
    // ====================================================================
1105
    //
1106
    // Before and after tests
1107
    //
1108
    // --------------------------------------------------------------------
1109
1110
    /**
1111
     * get the callback to use to perform the preflight checks
1112
     *
1113
     * @return array
1114
     */
1115
    public function getPreTestPrediction()
1116
    {
1117
        return $this->preTestPredictionCallback;
1118
    }
1119
1120
    /**
1121
     * do we have a callback
1122
     * @return boolean [description]
1123
     */
1124
    public function hasPreTestPrediction()
1125
    {
1126
        return count($this->preTestPredictionCallback) > 0;
1127
    }
1128
1129
    /**
1130
     * @return void
1131
     */
1132
    public function addPreTestPrediction($newCallback)
1133
    {
1134
        $this->preTestPredictionCallback[] = $newCallback;
1135
    }
1136
1137
    // ====================================================================
1138
    //
1139
    // Checkpoint the relevant system state before actions occur
1140
    //
1141
    // --------------------------------------------------------------------
1142
1143
    /**
1144
     * get the callback to use to perform the preflight checkpoint
1145
     *
1146
     * @return array
1147
     */
1148
    public function getPreTestInspection()
1149
    {
1150
        return $this->preTestInspectionCallback;
1151
    }
1152
1153
    /**
1154
     * do we have a callback
1155
     * @return boolean [description]
1156
     */
1157
    public function hasPreTestInspection()
1158
    {
1159
        return count($this->preTestInspectionCallback) > 0;
1160
    }
1161
1162
    /**
1163
     * @return void
1164
     */
1165
    public function addPreTestInspection($newCallback)
1166
    {
1167
        $this->preTestInspectionCallback[] = $newCallback;
1168
    }
1169
1170
    // ====================================================================
1171
    //
1172
    // Add in the actions that make the story come to life
1173
    //
1174
    // --------------------------------------------------------------------
1175
1176
    /**
1177
     * @return void
1178
     */
1179
    public function addAction($newCallback)
1180
    {
1181
        $this->actionsCallbacks[] = $newCallback;
1182
    }
1183
1184
    /**
1185
     * @return void
1186
     */
1187
    public function addActions($newCallback)
1188
    {
1189
        $this->actionsCallbacks[] = $newCallback;
1190
    }
1191
1192
    /**
1193
     * pick one action at random, and return it to the caller
1194
     *
1195
     * @return callable
1196
     */
1197
    public function getOneAction()
1198
    {
1199
        // do we have any callbacks to pick from?
1200
        if (count($this->actionsCallbacks) == 0)
1201
        {
1202
            throw new E5xx_NoStoryActions($this->getName());
1203
        }
1204
1205
        // pick one story
1206
        $i = rand(0, count($this->actionsCallbacks) - 1);
1207
1208
        // return it to the caller
1209
        return $this->actionsCallbacks[$i];
1210
    }
1211
1212
    /**
1213
     * return all of our actions
1214
     *
1215
     * @return array
1216
     */
1217
    public function getAction()
1218
    {
1219
        return $this->actionsCallbacks;
1220
    }
1221
1222
    /**
1223
     * does this story have any actions?
1224
     *
1225
     * @return boolean true if this story has any actions
1226
     */
1227
    public function hasActions()
1228
    {
1229
        return (count($this->actionsCallbacks) > 0);
1230
    }
1231
1232
    // ====================================================================
1233
    //
1234
    // Determine whether the test passed or failed
1235
    //
1236
    // --------------------------------------------------------------------
1237
1238
    /**
1239
     * get the callback to use to work out the test results
1240
     *
1241
     * @return array
1242
     */
1243
    public function getPostTestInspection()
1244
    {
1245
        return $this->postTestInspectionCallback;
1246
    }
1247
1248
    /**
1249
     * @return bool
1250
     */
1251
    public function hasPostTestInspection()
1252
    {
1253
        return count($this->postTestInspectionCallback) > 0;
1254
    }
1255
1256
    /**
1257
     * @return void
1258
     */
1259
    public function addPostTestInspection($newCallback)
1260
    {
1261
        $this->postTestInspectionCallback[] = $newCallback;
1262
    }
1263
1264
    // ====================================================================
1265
    //
1266
    // Our default behaviour when the story object is instantiated
1267
    //
1268
    // --------------------------------------------------------------------
1269
1270
    /**
1271
     * @return void
1272
     */
1273
    public function setDefaultCallbacks()
1274
    {
1275
        // 1: test setup
1276
        // if (!$this->hasTestSetup()) {
1277
        //     $this->addTestSetup(function(StoryTeller $st) {
1278
        //         $st->usingReporting()->reportNotRequired();
1279
        //     });
1280
        // }
1281
1282
        // 2: pre-test prediction
1283
        if (!$this->hasPreTestPrediction()) {
1284
            $this->addPreTestPrediction(function(StoryTeller $st) {
1285
                $st->usingReporting()->reportShouldAlwaysSucceed();
1286
            });
1287
        }
1288
1289
        // 3: pre-test inspection
1290
        // if (!$this->hasPreTestInspection()) {
1291
        //     $this->addPreTestInspection(function(StoryTeller $st) {
1292
        //         $st->usingReporting()->reportNotRequired();
1293
        //     });
1294
        // }
1295
1296
        // 4: test action
1297
        //
1298
        // we set no default for this, because we do not want the action
1299
        // to be chosen by StoryPlayer
1300
        //
1301
        // (StoryPlayer chooses one action at random from the set of
1302
        // supplied actions)
1303
1304
        // 5: post-test inspection
1305
        //
1306
        // we set no default for this, because each story must provide
1307
        // this
1308
1309
        // 6: test tear down
1310
        // if (!$this->hasTestTeardown()) {
1311
        //     $this->addTestTeardown(function(StoryTeller $st) {
1312
        //         $st->usingReporting()->reportNotRequired();
1313
        //     });
1314
        // }
1315
1316
        // all done
1317
    }
1318
1319
    // ====================================================================
1320
    //
1321
    // Serialisation and other format convertors
1322
    //
1323
    // --------------------------------------------------------------------
1324
1325
    /**
1326
     * return a string representation of the story, for things like logging
1327
     * @return string
1328
     */
1329
    public function __toString()
1330
    {
1331
        return $this->getNameForConsole();
1332
    }
1333
1334
    // ==================================================================
1335
    //
1336
    // Dealing with the story result
1337
    //
1338
    // ------------------------------------------------------------------
1339
1340
    /**
1341
     * @return Story_Result
1342
     */
1343
    public function getResult()
1344
    {
1345
        if (!isset($this->storyResult)) {
1346
            $this->storyResult = new Story_Result($this);
1347
        }
1348
1349
        return $this->storyResult;
1350
    }
1351
}
1352