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

tests/behat/bootstrap/FeatureContext.php (2 issues)

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 Closure;
15
use DateTimeImmutable;
16
use hiqdev\php\billing\action\Action;
17
use hiqdev\php\billing\charge\Charge;
18
use hiqdev\php\billing\charge\ChargeInterface;
19
use hiqdev\php\billing\charge\Generalizer;
20
use hiqdev\php\billing\customer\Customer;
21
use hiqdev\php\billing\formula\FormulaEngine;
22
use hiqdev\php\billing\order\Calculator;
23
use hiqdev\php\billing\price\SinglePrice;
24
use hiqdev\php\billing\target\Target;
25
use hiqdev\php\billing\type\Type;
26
use hiqdev\php\units\Quantity;
27
use Money\Currencies\ISOCurrencies;
28
use Money\Parser\DecimalMoneyParser;
29
use NumberFormatter;
30
use PHPUnit\Framework\Assert;
31
32
/**
33
 * Defines application features from the specific context.
34
 */
35
class FeatureContext implements Context
36
{
37
    protected $engine;
38
39
    /** @var Customer */
40
    protected $customer;
41
    /**
42
     * @var \hiqdev\php\billing\price\PriceInterface|\hiqdev\php\billing\charge\FormulaChargeModifierTrait
43
     *
44
     * TODO: FormulaChargeModifierTrait::setFormula() must be moved to interface
45
     */
46
    protected $price;
47
48
    /** @var string */
49
    protected $formula;
50
51
    /**
52
     * @var \hiqdev\php\billing\action\ActionInterface|\hiqdev\php\billing\action\AbstractAction
53
     */
54
    protected $action;
55
    /**
56
     * @var ChargeInterface[]
57
     */
58
    protected $charges;
59
60
    /** @var \Money\MoneyParser */
61
    protected $moneyParser;
62
63
    /** @var string */
64
    protected $expectedError;
65
66
    /**
67
     * Initializes context.
68
     */
69
    public function __construct()
70
    {
71
        $this->customer = new Customer(null, 'somebody');
72
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
73
        $this->generalizer = new Generalizer();
0 ignored issues
show
Bug Best Practice introduced by
The property generalizer does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
74
        $this->calculator = new Calculator($this->generalizer, null, null);
0 ignored issues
show
Bug Best Practice introduced by
The property calculator does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
75
    }
76
77
    /**
78
     * @Given /(\S+) (\S+) price is ([0-9.]+) (\w+) per (\w+)/
79
     */
80
    public function priceIs($target, $type, $sum, $currency, $unit)
81
    {
82
        $type = new Type(Type::ANY, $type);
83
        $target = new Target(Target::ANY, $target);
84
        $quantity = Quantity::create($unit, 0);
85
        $sum = $this->moneyParser->parse($sum, $currency);
86
        $this->price = new SinglePrice(null, $type, $target, null, $quantity, $sum);
87
    }
88
89
    /**
90
     * @Given /action is (\S+) (\w+) ([0-9.]+) (\S+)/
91
     */
92
    public function actionIs($target, $type, $amount, $unit)
93
    {
94
        $type = new Type(Type::ANY, $type);
95
        $target = new Target(Target::ANY, $target);
96
        $quantity = Quantity::create($unit, $amount);
97
        $time = new DateTimeImmutable();
98
        $this->action = new Action(null, $type, $target, $quantity, $this->customer, $time);
99
    }
100
101
    /**
102
     * @Given /formula is (.+)/
103
     * @param string $formula
104
     */
105
    public function formulaIs(string $formula): void
106
    {
107
        $this->formula = $formula;
108
    }
109
110
    /**
111
     * @Given /formula continues (.+)/
112
     * @param string $formula
113
     */
114
    public function formulaContinues(string $formula): void
115
    {
116
        $this->formula .= "\n" . $formula;
117
    }
118
119
    protected function getFormulaEngine()
120
    {
121
        if ($this->engine === null) {
122
            $this->engine = new FormulaEngine();
123
        }
124
125
        return $this->engine;
126
    }
127
128
    /**
129
     * @When /action date is ([0-9.-]+)/
130
     * @param string $date
131
     * @throws \Exception
132
     */
133
    public function actionDateIs(string $date): void
134
    {
135
        $this->action->setTime(new DateTimeImmutable($date));
136
    }
137
138
    /**
139
     * @Then /^error is$/m
140
     */
141
    public function multilineErrorIs(\Behat\Gherkin\Node\PyStringNode $value)
142
    {
143
        $this->expectedError = $value->getRaw();
144
    }
145
146
    /**
147
     * @Then /^error is (.+)$/
148
     *
149
     * @param string $error
150
     */
151
    public function errorIs($error): void
152
    {
153
        $this->expectedError = $error;
154
    }
155
156
    /**
157
     * @Then /^(\w+) charge is ?$/
158
     * @param string $numeral
159
     */
160
    public function emptyCharge(string $numeral): void
161
    {
162
        $this->chargeIs($numeral);
163
    }
164
165
    /**
166
     * @Then /^(\w+) charge is (\S+) +(-?[0-9.]+) ([A-Z]{3})$/
167
     */
168
    public function chargeWithSum($numeral, $type = null, $sum = null, $currency = null): void
169
    {
170
        $this->chargeIs($numeral, $type, $sum, $currency);
171
    }
172
173
    /**
174
     * @Then /^(\w+) charge is (\S+) +(-?[0-9.]+) ([A-Z]{3}) reason (.+)/
175
     */
176
    public function chargeWithReason($numeral, $type = null, $sum = null, $currency = null, $reason = null): void
177
    {
178
        $this->chargeIs($numeral, $type, $sum, $currency, $reason);
179
    }
180
181
    public function chargeIs($numeral, $type = null, $sum = null, $currency = null, $reason = null): void
182
    {
183
        $no = $this->ensureNo($numeral);
184
        if ($no === 0) {
185
            $this->calculatePrice();
186
        }
187
        $this->assertCharge($this->charges[$no] ?? null, $type, $sum, $currency, $reason);
188
    }
189
190
    /**
191
     * @When /^calculating charges$/
192
     */
193
    public function calculatePrice(): void
194
    {
195
        $this->expectError(function () {
196
            $this->price->setModifier($this->getFormulaEngine()->build($this->formula));
197
            $this->charges = $this->calculator->calculatePrice($this->price, $this->action);
198
        });
199
    }
200
201
    public function expectError(Closure $closure): void
202
    {
203
        try {
204
            call_user_func($closure);
205
        } catch (\Exception $e) {
206
            if ($this->isExpectedError($e)) {
207
                $this->expectedError = null;
208
            } else {
209
                throw $e;
210
            }
211
        }
212
        if ($this->expectedError) {
213
            throw new \Exception('failed receive expected exception');
214
        }
215
    }
216
217
    protected function isExpectedError(\Exception $e): bool
218
    {
219
        return $this->startsWith($e->getMessage(), $this->expectedError);
220
    }
221
222
    protected function startsWith(string $string, string $prefix = null): bool
223
    {
224
        return $prefix && strncmp($string, $prefix, strlen($prefix)) === 0;
225
    }
226
227
    /**
228
     * @param ChargeInterface|null $charge
229
     * @param string|null $type
230
     * @param string|null $sum
231
     * @param string|null $currency
232
     * @param string|null $reason
233
     */
234
    public function assertCharge($charge, $type, $sum, $currency, $reason): void
235
    {
236
        if (empty($type) && empty($sum) && empty($currency)) {
237
            Assert::assertNull($charge);
238
239
            return;
240
        }
241
        Assert::assertInstanceOf(Charge::class, $charge);
242
        Assert::assertSame($type, $this->normalizeType($charge->getType()->getName()));
243
        $money = $this->moneyParser->parse($sum, $currency);
244
        Assert::assertEquals($money, $charge->getSum());
245
        if ($reason !== null) {
246
            Assert::assertSame($reason, $charge->getComment());
247
        }
248
    }
249
250
    private function normalizeType($string): string
251
    {
252
        return $string === 'discount,discount' ? 'discount' : $string;
253
    }
254
255
    private function ensureNo(string $numeral): int
256
    {
257
        $formatter = new NumberFormatter('en_EN', NumberFormatter::SPELLOUT);
258
        $result = $formatter->parse($numeral);
259
        if ($result === false) {
260
            throw new \Exception("Wrong numeral '$numeral'");
261
        }
262
263
        return --$result;
264
    }
265
}
266