Completed
Push — master ( bf0cfa...c734e4 )
by Andrii
02:35
created

FeatureContext::isExpectedError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * PHP Billing Library
4
 *
5
 * @link      https://github.com/hiqdev/php-billing
6
 * @package   php-billing
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\tests\behat\bootstrap;
12
13
use Behat\Behat\Context\Context;
14
use DateTimeImmutable;
15
use hiqdev\php\billing\action\Action;
16
use hiqdev\php\billing\charge\Charge;
17
use hiqdev\php\billing\charge\ChargeInterface;
18
use hiqdev\php\billing\customer\Customer;
19
use hiqdev\php\billing\formula\FormulaEngine;
20
use hiqdev\php\billing\price\SinglePrice;
21
use hiqdev\php\billing\target\Target;
22
use hiqdev\php\billing\type\Type;
23
use hiqdev\php\units\Quantity;
24
use Money\Currencies\ISOCurrencies;
25
use Money\Parser\DecimalMoneyParser;
26
use NumberFormatter;
27
use PHPUnit\Framework\Assert;
28
29
/**
30
 * Defines application features from the specific context.
31
 */
32
class FeatureContext implements Context
33
{
34
    protected $engine;
35
36
    /** @var Customer */
37
    protected $customer;
38
    /**
39
     * @var \hiqdev\php\billing\price\PriceInterface|\hiqdev\php\billing\charge\FormulaChargeModifierTrait
40
     *
41
     * TODO: FormulaChargeModifierTrait::setFormula() must be moved to interface
42
     */
43
    protected $price;
44
    /**
45
     * @var \hiqdev\php\billing\action\ActionInterface|\hiqdev\php\billing\action\AbstractAction
46
     */
47
    protected $action;
48
    /**
49
     * @var ChargeInterface[]
50
     */
51
    protected $charges;
52
53
    /** @var \Money\MoneyParser */
54
    protected $moneyParser;
55
56
    /** @var string */
57
    protected $expectedError;
58
59
    /**
60
     * Initializes context.
61
     */
62
    public function __construct()
63
    {
64
        $this->customer = new Customer(null, 'somebody');
65
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
66
    }
67
68
    /**
69
     * @Given /(\S+) (\S+) price is ([0-9.]+) (\w+) per (\w+)/
70
     */
71 View Code Duplication
    public function priceIs($target, $type, $sum, $currency, $unit)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
72
    {
73
        $type = new Type(Type::ANY, $type);
74
        $target = new Target(Target::ANY, $target);
75
        $quantity = Quantity::create($unit, 0);
76
        $sum = $this->moneyParser->parse($sum, $currency);
77
        $this->price = new SinglePrice(null, $type, $target, null, $quantity, $sum);
78
    }
79
80
    /**
81
     * @Given /action is (\S+) (\w+) ([0-9.]+) (\S+)/
82
     */
83 View Code Duplication
    public function actionIs($target, $type, $amount, $unit)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
84
    {
85
        $type = new Type(Type::ANY, $type);
86
        $target = new Target(Target::ANY, $target);
87
        $quantity = Quantity::create($unit, $amount);
88
        $time = new DateTimeImmutable();
89
        $this->action = new Action(null, $type, $target, $quantity, $this->customer, $time);
90
    }
91
92
    /**
93
     * @Given /formula is (.+)/
94
     * @param string $formula
95
     */
96
    public function formulaIs(string $formula): void
97
    {
98
        $this->price->setFormula($this->getFormulaEngine()->build($formula));
0 ignored issues
show
Bug introduced by
The method setFormula does only exist in hiqdev\php\billing\charg...mulaChargeModifierTrait, but not in hiqdev\php\billing\price\PriceInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
99
    }
100
101
    protected function getFormulaEngine()
102
    {
103
        if ($this->engine === null) {
104
            $this->engine = new FormulaEngine();
105
        }
106
107
        return $this->engine;
108
    }
109
110
    /**
111
     * @When /action date is ([0-9.-]+)/
112
     * @param string $date
113
     * @throws \Exception
114
     */
115
    public function actionDateIs(string $date): void
116
    {
117
        $this->action->setTime(new DateTimeImmutable($date));
0 ignored issues
show
Bug introduced by
The method setTime does only exist in hiqdev\php\billing\action\AbstractAction, but not in hiqdev\php\billing\action\ActionInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
118
    }
119
120
    /**
121
     * @Then /^error is (.+)$/
122
     * @param string $error
123
     */
124
    public function errorIs(string $error): void
125
    {
126
        $this->expectedError = $error;
127
    }
128
129
    /**
130
     * @Then /^(\w+) charge is $/
131
     * @param string $numeral
132
     */
133
    public function emptyCharge(string $numeral): void
134
    {
135
        $this->chargeIs($numeral);
136
    }
137
138
    /**
139
     * @Then /^(\w+) charge is (\S+) +([0-9.]+) ([A-Z]{3})$/
140
     */
141
    public function chargeWithSum($numeral, $type = null, $sum = null, $currency = null): void
142
    {
143
        $this->chargeIs($numeral, $type, $sum, $currency);
144
    }
145
146
    /**
147
     * @Then /^(\w+) charge is (\S+) +([0-9.]+) ([A-Z]{3}) reason (.+)/
148
     */
149
    public function chargeWithReason($numeral, $type = null, $sum = null, $currency = null, $reason = null): void
150
    {
151
        $this->chargeIs($numeral, $type, $sum, $currency, $reason);
152
    }
153
154
    public function chargeIs($numeral, $type = null, $sum = null, $currency = null, $reason = null): void
155
    {
156
        $no = $this->ensureNo($numeral);
157
        if ($no === 0) {
158
            $this->calculateCharges();
159
        }
160
        $this->assertCharge($this->charges[$no] ?? null, $type, $sum, $currency, $reason);
161
    }
162
163
    /**
164
     * @When /^calculating charges$/
165
     */
166
    public function calculateCharges(): void
167
    {
168
        try {
169
            $this->charges = $this->price->calculateCharges($this->action);
0 ignored issues
show
Bug introduced by
The method calculateCharges does only exist in hiqdev\php\billing\price\PriceInterface, but not in hiqdev\php\billing\charg...mulaChargeModifierTrait.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
170
        } catch (\Exception $e) {
171
            if ($this->isExpectedError($e)) {
172
                $this->expectedError = null;
173
            } else {
174
                throw $e;
175
            }
176
        }
177
    }
178
179
    protected function isExpectedError(\Exception $e): bool
180
    {
181
        return $this->startsWith($e->getMessage(), $this->expectedError);
182
    }
183
184
    protected function startsWith(string $string, string $prefix): bool
185
    {
186
        return strncmp($string, $prefix, strlen($prefix)) === 0;
187
188
    }
189
190
    /**
191
     * @param ChargeInterface|null $charge
192
     * @param string|null $type
193
     * @param string|null $sum
194
     * @param string|null $currency
195
     * @param string|null $reason
196
     */
197
    public function assertCharge($charge, $type, $sum, $currency, $reason): void
198
    {
199
        if (empty($type) && empty($sum) && empty($currency)) {
200
            Assert::assertNull($charge);
201
202
            return;
203
        }
204
        Assert::assertInstanceOf(Charge::class, $charge);
205
        Assert::assertSame($type, $charge->getPrice()->getType()->getName());
0 ignored issues
show
Bug introduced by
It seems like $charge is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
206
        $money = $this->moneyParser->parse($sum, $currency);
207
        Assert::assertEquals($money, $charge->getSum()); // TODO: Should we add `getSum()` to ChargeInterface?
208
        if ($reason !== null) {
209
            Assert::assertSame($reason, $charge->getComment()); // TODO: Should we add `getComment()` to ChargeInterface?
210
        }
211
    }
212
213
    private function ensureNo(string $numeral): int
214
    {
215
        $formatter = new NumberFormatter('en_EN', NumberFormatter::SPELLOUT);
216
        $result = $formatter->parse($numeral);
217
        if ($result === false) {
218
            throw new \Exception("Wrong numeral '$numeral'");
219
        }
220
221
        return --$result;
222
    }
223
}
224