Completed
Push — master ( fc4ead...c5d222 )
by Andrii
05:51
created

FeatureContext::assertCharge()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 20
nc 11
nop 6
dl 0
loc 30
rs 8.0555
c 0
b 0
f 0
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 Behat\Behat\Tester\Exception\PendingException;
15
use Closure;
16
use DateTimeImmutable;
17
use hiqdev\php\billing\action\Action;
18
use hiqdev\php\billing\charge\Charge;
19
use hiqdev\php\billing\charge\ChargeInterface;
20
use hiqdev\php\billing\charge\Generalizer;
21
use hiqdev\php\billing\customer\Customer;
22
use hiqdev\php\billing\formula\FormulaEngine;
23
use hiqdev\php\billing\order\Calculator;
24
use hiqdev\php\billing\price\SinglePrice;
25
use hiqdev\php\billing\target\Target;
26
use hiqdev\php\billing\type\Type;
27
use hiqdev\php\units\Quantity;
28
use Money\Currencies\ISOCurrencies;
29
use Money\Parser\DecimalMoneyParser;
30
use NumberFormatter;
31
use PHPUnit\Framework\Assert;
32
33
/**
34
 * Defines application features from the specific context.
35
 */
36
class FeatureContext implements Context
37
{
38
    protected $engine;
39
40
    /** @var Customer */
41
    protected $customer;
42
    /**
43
     * @var \hiqdev\php\billing\price\PriceInterface|\hiqdev\php\billing\charge\FormulaChargeModifierTrait
0 ignored issues
show
Bug introduced by
The type hiqdev\php\billing\charg...mulaChargeModifierTrait was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
44
     *
45
     * TODO: FormulaChargeModifierTrait::setFormula() must be moved to interface
46
     */
47
    protected $price;
48
49
    /** @var string */
50
    protected $formula;
51
52
    /**
53
     * @var \hiqdev\php\billing\action\ActionInterface|\hiqdev\php\billing\action\AbstractAction
54
     */
55
    protected $action;
56
    /**
57
     * @var ChargeInterface[]
58
     */
59
    protected $charges;
60
61
    /** @var \Money\MoneyParser */
62
    protected $moneyParser;
63
64
    /** @var string */
65
    protected $expectedError;
66
    /** @var Generalizer */
67
    protected $generalizer;
68
    /** @var Calculator */
69
    protected $calculator;
70
71
    /**
72
     * Initializes context.
73
     */
74
    public function __construct()
75
    {
76
        $this->customer = new Customer(null, 'somebody');
77
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
78
        $this->generalizer = new Generalizer();
79
        $this->calculator = new Calculator($this->generalizer, null, null);
80
    }
81
82
    /**
83
     * @Given /(\S+) (\S+) price is ([0-9.]+) (\w+) per (\w+)/
84
     */
85
    public function priceIs($target, $type, $sum, $currency, $unit)
86
    {
87
        $type = new Type(Type::ANY, $type);
88
        $target = new Target(Target::ANY, $target);
89
        $quantity = Quantity::create($unit, 0);
90
        $sum = $this->moneyParser->parse($sum, $currency);
91
        $this->price = new SinglePrice(null, $type, $target, null, $quantity, $sum);
92
    }
93
94
    /**
95
     * @Given /action is (\S+) (\w+) ([0-9.]+) (\S+)/
96
     */
97
    public function actionIs($target, $type, $amount, $unit)
98
    {
99
        $type = new Type(Type::ANY, $type);
100
        $target = new Target(Target::ANY, $target);
101
        $quantity = Quantity::create($unit, $amount);
102
        $time = new DateTimeImmutable();
103
        $this->action = new Action(null, $type, $target, $quantity, $this->customer, $time);
104
    }
105
106
    /**
107
     * @Given /formula is (.+)/
108
     * @param string $formula
109
     */
110
    public function formulaIs(string $formula): void
111
    {
112
        $this->formula = $formula;
113
    }
114
115
    /**
116
     * @Given /formula continues (.+)/
117
     * @param string $formula
118
     */
119
    public function formulaContinues(string $formula): void
120
    {
121
        $this->formula .= "\n" . $formula;
122
    }
123
124
    protected function getFormulaEngine()
125
    {
126
        if ($this->engine === null) {
127
            $this->engine = new FormulaEngine();
128
        }
129
130
        return $this->engine;
131
    }
132
133
    /**
134
     * @When /action date is ([0-9.-]+)/
135
     * @param string $date
136
     * @throws \Exception
137
     */
138
    public function actionDateIs(string $date): void
139
    {
140
        $this->action->setTime(new DateTimeImmutable($date));
0 ignored issues
show
Bug introduced by
The method setTime() does not exist on hiqdev\php\billing\action\ActionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to hiqdev\php\billing\action\ActionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
        $this->action->/** @scrutinizer ignore-call */ 
141
                       setTime(new DateTimeImmutable($date));
Loading history...
141
    }
142
143
    /**
144
     * @Then /^error is$/m
145
     */
146
    public function multilineErrorIs(\Behat\Gherkin\Node\PyStringNode $value)
147
    {
148
        $this->expectedError = $value->getRaw();
149
    }
150
151
    /**
152
     * @Then /^error is (.+)$/
153
     *
154
     * @param string $error
155
     */
156
    public function errorIs($error): void
157
    {
158
        $this->expectedError = $error;
159
    }
160
161
    /**
162
     * @Then /^(\w+) charge is ?(?: with ?)?$/
163
     * @param string $numeral
164
     */
165
    public function emptyCharge(string $numeral): void
166
    {
167
        $this->chargeIs($numeral);
168
    }
169
170
    /**
171
     * @Then /^(\w+) charge is (\S+) +(-?[0-9.]+) ([A-Z]{3})(?: with (.+)?)?$/
172
     */
173
    public function chargeWithSum($numeral, $type = null, $sum = null, $currency = null, $events = null): void
174
    {
175
        $this->chargeIs($numeral, $type, $sum, $currency, null, $events);
176
    }
177
178
    /**
179
     * @Then /^(\w+) charge is (\S+) +(-?[0-9.]+) ([A-Z]{3}) reason ([\w]+)(?: with (.+)?)?$/
180
     */
181
    public function chargeWithReason($numeral, $type = null, $sum = null, $currency = null, $reason = null, $events = null): void
182
    {
183
        $this->chargeIs($numeral, $type, $sum, $currency, $reason, $events);
184
    }
185
186
    public function chargeIs($numeral, $type = null, $sum = null, $currency = null, $reason = null, $events = null): void
187
    {
188
        $no = $this->ensureNo($numeral);
189
        if ($no === 0) {
190
            $this->calculatePrice();
191
        }
192
        $this->assertCharge($this->charges[$no] ?? null, $type, $sum, $currency, $reason, $events);
193
    }
194
195
    /**
196
     * @When /^calculating charges$/
197
     */
198
    public function calculatePrice(): void
199
    {
200
        $this->expectError(function () {
201
            $this->price->setModifier($this->getFormulaEngine()->build($this->formula));
0 ignored issues
show
Bug introduced by
The method setModifier() does not exist on hiqdev\php\billing\price\PriceInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to hiqdev\php\billing\price\PriceInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

201
            $this->price->/** @scrutinizer ignore-call */ 
202
                          setModifier($this->getFormulaEngine()->build($this->formula));
Loading history...
202
            $this->charges = $this->calculator->calculatePrice($this->price, $this->action);
203
        });
204
    }
205
206
    public function expectError(Closure $closure): void
207
    {
208
        try {
209
            call_user_func($closure);
210
        } catch (\Exception $e) {
211
            if ($this->isExpectedError($e)) {
212
                $this->expectedError = null;
213
            } else {
214
                throw $e;
215
            }
216
        }
217
        if ($this->expectedError) {
218
            throw new \Exception('failed receive expected exception');
219
        }
220
    }
221
222
    protected function isExpectedError(\Exception $e): bool
223
    {
224
        return $this->startsWith($e->getMessage(), $this->expectedError);
225
    }
226
227
    protected function startsWith(string $string, string $prefix = null): bool
228
    {
229
        return $prefix && strncmp($string, $prefix, strlen($prefix)) === 0;
230
    }
231
232
    /**
233
     * @param ChargeInterface|Charge|null $charge
234
     * @param string|null $type
235
     * @param string|null $sum
236
     * @param string|null $currency
237
     * @param string|null $reason
238
     * @param string|null $events
239
     */
240
    public function assertCharge($charge, $type, $sum, $currency, $reason, $events): void
241
    {
242
        if (empty($type) && empty($sum) && empty($currency)) {
243
            Assert::assertNull($charge);
244
245
            return;
246
        }
247
        Assert::assertInstanceOf(Charge::class, $charge);
248
        Assert::assertSame($type, $this->normalizeType($charge->getType()->getName()));
0 ignored issues
show
Bug introduced by
The method getType() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

248
        Assert::assertSame($type, $this->normalizeType($charge->/** @scrutinizer ignore-call */ getType()->getName()));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
249
        $money = $this->moneyParser->parse($sum, $currency);
250
        Assert::assertEquals($money, $charge->getSum());
251
        if ($reason !== null) {
252
            Assert::assertSame($reason, $charge->getComment(),
253
                sprintf('Charge comment %s does not match expected %s', $charge->getComment(), $reason)
254
            );
255
        }
256
        if ($events !== null) {
257
            $storedEvents = $charge->releaseEvents();
0 ignored issues
show
Bug introduced by
The method releaseEvents() does not exist on hiqdev\php\billing\charge\ChargeInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to hiqdev\php\billing\charge\ChargeInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

257
            /** @scrutinizer ignore-call */ 
258
            $storedEvents = $charge->releaseEvents();
Loading history...
258
            foreach (array_map('trim', explode(',', $events)) as $eventClass) {
259
                foreach ($storedEvents as $storedEvent) {
260
                    $eventReflection = new \ReflectionObject($storedEvent);
261
                    if ($eventReflection->getShortName() === $eventClass) {
262
                        continue 2;
263
                    }
264
                }
265
266
                Assert::fail(sprintf('Event of class %s is not present is charge', $eventClass));
267
            }
268
        } else {
269
            Assert::assertEmpty($charge->releaseEvents(), 'Failed asserting that charge does not have events');
270
        }
271
    }
272
273
    private function normalizeType($string): string
274
    {
275
        return $string === 'discount,discount' ? 'discount' : $string;
276
    }
277
278
    private function ensureNo(string $numeral): int
279
    {
280
        $formatter = new NumberFormatter('en_EN', NumberFormatter::SPELLOUT);
281
        $result = $formatter->parse($numeral);
282
        if ($result === false) {
283
            throw new \Exception("Wrong numeral '$numeral'");
284
        }
285
286
        return --$result;
287
    }
288
}
289