PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / tests / Module / Checkout / ShippingRateFactoryTest.php

ShippingRateFactoryTest.php in Packeta trunk, at tests/Module/Checkout/ShippingRateFactoryTest.php

373 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare( strict_types=1 );
4
5 namespace Tests\Module\Checkout;
6
7 use Packetery\Core\Entity;
8 use Packetery\Module\Carrier;
9 use Packetery\Module\Carrier\CarDeliveryConfig;
10 use Packetery\Module\Carrier\CarrierOptionsFactory;
11 use Packetery\Module\Carrier\OptionPrefixer;
12 use Packetery\Module\Checkout\CartService;
13 use Packetery\Module\Checkout\CheckoutService;
14 use Packetery\Module\Checkout\CurrencySwitcherService;
15 use Packetery\Module\Checkout\RateCalculator;
16 use Packetery\Module\Checkout\ShippingRateFactory;
17 use Packetery\Module\DiagnosticsLogger\DiagnosticsLogger;
18 use Packetery\Module\Framework\WcAdapter;
19 use Packetery\Module\Options\OptionsProvider;
20 use Packetery\Module\Shipping\BaseShippingMethod;
21 use Packetery\Module\ShippingMethod;
22 use PHPUnit\Framework\MockObject\MockObject;
23 use PHPUnit\Framework\TestCase;
24 use ReflectionMethod;
25 use Tests\Core\DummyFactory;
26 use Tests\Module\MockFactory;
27
28 class ShippingRateFactoryTest extends TestCase {
29 private const DUMMY_RATE_ID = 'dummyRateId';
30
31 private CheckoutService&MockObject $checkoutServiceMock;
32 private Carrier\EntityRepository&MockObject $carrierEntityRepositoryMock;
33 private CartService&MockObject $cartServiceMock;
34 private CarrierOptionsFactory&MockObject $carrierOptionsFactoryMock;
35 private OptionsProvider&MockObject $optionsProviderMock;
36 private DiagnosticsLogger&MockObject $diagnosticsLogger;
37
38 public function createShippingRateFactory(): ShippingRateFactory {
39 $wpAdapterMock = MockFactory::createWpAdapter( $this );
40 $wpAdapterMock->method( '__' )
41 ->willReturnCallback(
42 function ( string $text ): string {
43 return $text;
44 }
45 );
46
47 $wcAdapterMock = $this->createMock( WcAdapter::class );
48 $this->checkoutServiceMock = $this->createMock( CheckoutService::class );
49 $this->carrierEntityRepositoryMock = $this->createMock( Carrier\EntityRepository::class );
50 $this->cartServiceMock = $this->createMock( CartService::class );
51 $this->carrierOptionsFactoryMock = $this->createMock( CarrierOptionsFactory::class );
52 $this->optionsProviderMock = $this->createMock( OptionsProvider::class );
53 $this->diagnosticsLogger = $this->createMock( DiagnosticsLogger::class );
54
55 $rateCalculator = new RateCalculator(
56 $wpAdapterMock,
57 $wcAdapterMock,
58 $this->createMock( CurrencySwitcherService::class ),
59 $this->diagnosticsLogger
60 );
61
62 return new ShippingRateFactory(
63 $wpAdapterMock,
64 $wcAdapterMock,
65 $this->checkoutServiceMock,
66 $this->carrierEntityRepositoryMock,
67 $this->cartServiceMock,
68 $this->carrierOptionsFactoryMock,
69 $this->createMock( CarDeliveryConfig::class ),
70 $rateCalculator,
71 $this->optionsProviderMock,
72 $this->diagnosticsLogger
73 );
74 }
75
76 public static function createShippingRateAndApplyTaxesProvider(): array {
77 return [
78 'free shipping label appended when cost zero and visibility on' => [
79 'cost' => 0.0,
80 'isFreeShippingShown' => true,
81 'expectedLabel' => 'Carrier Name: Free',
82 'arePricesTaxInclusive' => false,
83 ],
84 'free shipping label not appended when cost zero and visibility off' => [
85 'cost' => 0.0,
86 'isFreeShippingShown' => false,
87 'expectedLabel' => 'Carrier Name',
88 'arePricesTaxInclusive' => false,
89 ],
90 'positive cost keeps label unchanged' => [
91 'cost' => 123.45,
92 'isFreeShippingShown' => true,
93 'expectedLabel' => 'Carrier Name',
94 'arePricesTaxInclusive' => false,
95 ],
96 'apply taxes when prices are tax inclusive' => [
97 'cost' => 123.45,
98 'isFreeShippingShown' => true,
99 'expectedLabel' => 'Carrier Name',
100 'arePricesTaxInclusive' => true,
101 ],
102 ];
103 }
104
105 /**
106 * @dataProvider createShippingRateAndApplyTaxesProvider
107 */
108 public function testCreateShippingRateAndApplyTaxes(
109 float $cost,
110 bool $isFreeShippingShown,
111 string $expectedLabel,
112 bool $arePricesTaxInclusive,
113 ): void {
114 $shippingRateFactory = $this->createShippingRateFactory();
115
116 $this->optionsProviderMock->method( 'isFreeShippingShown' )
117 ->willReturn( $isFreeShippingShown );
118 $this->optionsProviderMock->method( 'arePricesTaxInclusive' )
119 ->willReturn( $arePricesTaxInclusive );
120
121 $reflection = new ReflectionMethod( ShippingRateFactory::class, 'createShippingRateAndApplyTaxes' );
122 $reflection->setAccessible( true );
123 $resultRate = $reflection->invoke( $shippingRateFactory, 'Carrier Name', $cost, self::DUMMY_RATE_ID );
124
125 self::assertIsArray( $resultRate );
126 self::assertSame( $expectedLabel, $resultRate['label'] );
127 self::assertSame( self::DUMMY_RATE_ID, $resultRate['id'] );
128 self::assertSame( $cost, $resultRate['cost'] );
129 self::assertSame( 'per_order', $resultRate['calc_tax'] );
130 }
131
132 public static function createShippingRatesProvider(): array {
133 $carrierCzPp = DummyFactory::createCarrierCzechPp();
134 $carrierCzHd = DummyFactory::createCarrierCzechHdRequiresSize();
135 $carrierDe = DummyFactory::createCarrierGermanPp();
136
137 $carrierOptionsCzPp = new Carrier\Options(
138 OptionPrefixer::getOptionId( $carrierCzPp->getId() ),
139 [
140 'id' => $carrierCzPp->getId(),
141 'active' => true,
142 'name' => 'Carrier CZ I',
143 'weight_limits' => [
144 [
145 'weight' => 5.0,
146 'price' => 30.0,
147 ],
148 ],
149 'free_shipping_limit' => null,
150 ],
151 );
152 $carrierOptionsCzHd = new Carrier\Options(
153 OptionPrefixer::getOptionId( $carrierCzHd->getId() ),
154 [
155 'id' => $carrierCzHd->getId(),
156 'active' => true,
157 'name' => 'Carrier CZ II',
158 'weight_limits' => null,
159 'product_value_limits' => [
160 [
161 'value' => 0.0,
162 'price' => 30.0,
163 ],
164 [
165 'value' => 2000.0,
166 'price' => 25.0,
167 ],
168 ],
169 'free_shipping_limit' => null,
170 'pricing_type' => 'byProductValue',
171 ],
172 );
173 $carrierOptionsDe = new Carrier\Options(
174 OptionPrefixer::getOptionId( $carrierDe->getId() ),
175 [
176 'id' => $carrierDe->getId(),
177 'active' => true,
178 'name' => 'Carrier DE',
179 'weight_limits' => [
180 [
181 'weight' => 5.0,
182 'price' => 30.0,
183 ],
184 ],
185 'free_shipping_limit' => 90.0,
186 ],
187 );
188
189 return [
190 'no customer country -> empty result' => [
191 'allowedCarrierNames' => null,
192 'methodId' => ShippingMethod::PACKETERY_METHOD_ID,
193 'instanceId' => 1,
194 'customerCountry' => null,
195 'availableCarriers' => [],
196 'cartTotal' => 100.0,
197 'cartWeight' => 1.0,
198 'totalValue' => 100.0,
199 'isAgeVerificationRequired' => false,
200 'disallowedRateIds' => [],
201 'oversized' => false,
202 'restrictedByCategory' => false,
203 'arePricesTaxInclusive' => false,
204 'rateCost' => 10.0,
205 'expectedRateCount' => 0,
206 ],
207 'packetery method with 2 carriers in same country' => [
208 'allowedCarrierNames' => null,
209 'methodId' => ShippingMethod::PACKETERY_METHOD_ID,
210 'instanceId' => 1,
211 'customerCountry' => 'cz',
212 'availableCarriers' => [ [ $carrierCzPp, $carrierOptionsCzPp ], [ $carrierCzHd, $carrierOptionsCzHd ] ],
213 'cartTotal' => 100.0,
214 'cartWeight' => 1.0,
215 'totalValue' => 100.0,
216 'isAgeVerificationRequired' => false,
217 'disallowedRateIds' => [],
218 'oversized' => false,
219 'restrictedByCategory' => false,
220 'arePricesTaxInclusive' => false,
221 'rateCost' => 10.0,
222 'expectedRateCount' => 2,
223 ],
224 'specific carrier method with matching country' => [
225 'allowedCarrierNames' => null,
226 'methodId' => BaseShippingMethod::PACKETA_METHOD_PREFIX . $carrierDe->getId(),
227 'instanceId' => 7,
228 'customerCountry' => 'de',
229 'availableCarriers' => [ [ $carrierDe, $carrierOptionsDe ], [ $carrierCzPp, $carrierOptionsCzPp ] ],
230 'cartTotal' => 100.0,
231 'cartWeight' => 1.0,
232 'totalValue' => 100.0,
233 'isAgeVerificationRequired' => false,
234 'disallowedRateIds' => [],
235 'oversized' => false,
236 'restrictedByCategory' => false,
237 'arePricesTaxInclusive' => false,
238 'rateCost' => 10.0,
239 'expectedRateCount' => 1,
240 ],
241 'specific carrier method with age verification not available' => [
242 'allowedCarrierNames' => null,
243 'methodId' => BaseShippingMethod::PACKETA_METHOD_PREFIX . $carrierDe->getId(),
244 'instanceId' => 7,
245 'customerCountry' => 'sk',
246 'availableCarriers' => [ [ $carrierDe, $carrierOptionsDe ] ],
247 'cartTotal' => 100.0,
248 'cartWeight' => 1.0,
249 'totalValue' => 100.0,
250 'isAgeVerificationRequired' => true,
251 'disallowedRateIds' => [],
252 'oversized' => false,
253 'restrictedByCategory' => false,
254 'arePricesTaxInclusive' => false,
255 'rateCost' => 10.0,
256 'expectedRateCount' => 0,
257 ],
258 'specific carrier method with mismatched country' => [
259 'allowedCarrierNames' => null,
260 'methodId' => BaseShippingMethod::PACKETA_METHOD_PREFIX . $carrierDe->getId(),
261 'instanceId' => 7,
262 'customerCountry' => 'cz',
263 'availableCarriers' => [ [ $carrierDe, $carrierOptionsDe ] ],
264 'cartTotal' => 100.0,
265 'cartWeight' => 1.0,
266 'totalValue' => 100.0,
267 'isAgeVerificationRequired' => false,
268 'disallowedRateIds' => [],
269 'oversized' => false,
270 'restrictedByCategory' => false,
271 'arePricesTaxInclusive' => false,
272 'rateCost' => 10.0,
273 'expectedRateCount' => 0,
274 ],
275 'allowedCarrierNames filter allows only one' => [
276 'allowedCarrierNames' => [ $carrierCzPp->getId() => 'Custom CZ' ],
277 'methodId' => ShippingMethod::PACKETERY_METHOD_ID,
278 'instanceId' => 1,
279 'customerCountry' => 'cz',
280 'availableCarriers' => [ [ $carrierCzPp, $carrierOptionsCzPp ], [ $carrierDe, $carrierOptionsDe ] ],
281 'cartTotal' => 100.0,
282 'cartWeight' => 1.0,
283 'totalValue' => 100.0,
284 'isAgeVerificationRequired' => false,
285 'disallowedRateIds' => [],
286 'oversized' => false,
287 'restrictedByCategory' => false,
288 'arePricesTaxInclusive' => false,
289 'rateCost' => 10.0,
290 'expectedRateCount' => 1,
291 ],
292 ];
293 }
294
295 /**
296 * @dataProvider createShippingRatesProvider
297 */
298 public function testCreateShippingRates(
299 ?array $allowedCarrierNames,
300 string $methodId,
301 int $instanceId,
302 ?string $customerCountry,
303 array $availableCarriers,
304 float $cartTotal,
305 float $cartWeight,
306 float $totalValue,
307 bool $isAgeVerificationRequired,
308 array $disallowedRateIds,
309 bool $oversized,
310 bool $restrictedByCategory,
311 bool $arePricesTaxInclusive,
312 float $rateCost,
313 int $expectedRateCount,
314 ): void {
315 $shippingRateFactory = $this->createShippingRateFactory();
316
317 $this->checkoutServiceMock->method( 'getCustomerCountry' )->willReturn( $customerCountry );
318
319 $carrierEntities = [];
320 $optionsMap = [];
321 foreach ( $availableCarriers as $pair ) {
322 if ( is_array( $pair ) && count( $pair ) === 2 ) {
323 /** @var array<Entity\Carrier, Carrier\Options> $pair */
324 [ $carrier, $options ] = $pair;
325
326 $carrierEntities[] = $carrier;
327 $optionsMap[ $options->getOptionId() ] = $options;
328 }
329 }
330
331 if ( $methodId === ShippingMethod::PACKETERY_METHOD_ID ) {
332 $this->carrierEntityRepositoryMock
333 ->method( 'getByCountryIncludingNonFeed' )
334 ->willReturn( $carrierEntities );
335 } else {
336 $targetCarrier = $carrierEntities[0] ?? null;
337 $this->carrierEntityRepositoryMock
338 ->method( 'getAnyById' )
339 ->willReturn( $targetCarrier );
340 }
341
342 $this->carrierOptionsFactoryMock
343 ->method( 'createByOptionId' )
344 ->willReturnCallback(
345 function ( string $optionId ) use ( $optionsMap ) {
346 return $optionsMap[ $optionId ] ?? new Carrier\Options( $optionId, [ 'active' => true ] );
347 }
348 );
349
350 $this->cartServiceMock->method( 'getCartContentsTotalIncludingTax' )->willReturn( $cartTotal );
351 $this->cartServiceMock->method( 'getCartWeightKg' )->willReturn( $cartWeight );
352 $this->cartServiceMock->method( 'getTotalCartProductValue' )->willReturn( $totalValue );
353 $this->cartServiceMock->method( 'isAgeVerificationRequired' )->willReturn( $isAgeVerificationRequired );
354 $this->cartServiceMock->method( 'getDisallowedShippingRateIds' )->willReturn( $disallowedRateIds );
355 $this->cartServiceMock->method( 'cartContainsProductOversizedForCarrier' )->willReturn( $oversized );
356 $this->cartServiceMock->method( 'isShippingRateRestrictedByProductsCategory' )->willReturn( $restrictedByCategory );
357
358 $this->optionsProviderMock->method( 'arePricesTaxInclusive' )->willReturn( $arePricesTaxInclusive );
359
360 $shippingRates = $shippingRateFactory->createShippingRates( $allowedCarrierNames, $methodId, $instanceId );
361
362 self::assertCount( $expectedRateCount, $shippingRates );
363 if ( $expectedRateCount > 0 ) {
364 $firstRate = array_key_first( $shippingRates );
365 if ( $methodId === ShippingMethod::PACKETERY_METHOD_ID ) {
366 self::assertStringStartsWith( ShippingMethod::PACKETERY_METHOD_ID . ':', $firstRate );
367 } else {
368 self::assertStringStartsWith( $methodId . ':', $firstRate );
369 }
370 }
371 }
372 }
373