2222use Magento \Framework \Exception \LocalizedException ;
2323use Magento \Framework \HTTP \Client \Curl ;
2424use Magento \Framework \HTTP \Client \CurlFactory ;
25+ use Magento \Framework \HTTP \ClientInterface ;
2526use Magento \Framework \Pricing \PriceCurrencyInterface ;
2627use Magento \Framework \Serialize \Serializer \Json ;
2728use Magento \Framework \TestFramework \Unit \Helper \ObjectManager ;
4950use PHPUnit \Framework \TestCase ;
5051use Psr \Log \LoggerInterface ;
5152use Magento \Catalog \Api \Data \ProductInterface ;
53+ use Magento \Framework \App \CacheInterface ;
5254
5355/**
5456 * CarrierTest contains units test for Fedex carrier methods
@@ -102,11 +104,6 @@ class CarrierTest extends TestCase
102104 */
103105 private Json $ serializer ;
104106
105- /**
106- * @var LoggerInterface|MockObject
107- */
108- private LoggerInterface $ logger ;
109-
110107 /**
111108 * @var CurrencyFactory|MockObject
112109 */
@@ -132,6 +129,11 @@ class CarrierTest extends TestCase
132129 */
133130 private DecoderInterface $ decoderInterface ;
134131
132+ /**
133+ * @var CacheInterface|MockObject
134+ */
135+ private $ cacheMock ;
136+
135137 /**
136138 * @return void
137139 * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -189,7 +191,7 @@ protected function setUp(): void
189191 ->disableOriginalConstructor ()
190192 ->getMock ();
191193
192- $ this -> logger = $ this ->getMockForAbstractClass (LoggerInterface::class);
194+ $ logger = $ this ->getMockForAbstractClass (LoggerInterface::class);
193195
194196 $ this ->curlFactory = $ this ->getMockBuilder (CurlFactory::class)
195197 ->disableOriginalConstructor ()
@@ -206,13 +208,16 @@ protected function setUp(): void
206208 ->onlyMethods (['decode ' ])
207209 ->getMock ();
208210
211+ $ this ->cacheMock = $ this ->getMockBuilder (CacheInterface::class)
212+ ->onlyMethods (['load ' , 'save ' ])
213+ ->getMockForAbstractClass ();
209214 $ this ->carrier = $ this ->getMockBuilder (Carrier::class)
210215 ->addMethods (['rateRequest ' ])
211216 ->setConstructorArgs (
212217 [
213218 'scopeConfig ' => $ this ->scope ,
214219 'rateErrorFactory ' => $ this ->errorFactory ,
215- 'logger ' => $ this -> logger ,
220+ 'logger ' => $ logger ,
216221 'xmlSecurity ' => new Security (),
217222 'xmlElFactory ' => $ elementFactory ,
218223 'rateFactory ' => $ rateFactory ,
@@ -229,6 +234,7 @@ protected function setUp(): void
229234 'productCollectionFactory ' => $ this ->collectionFactory ,
230235 'curlFactory ' => $ this ->curlFactory ,
231236 'decoderInterface ' => $ this ->decoderInterface ,
237+ 'cache ' => $ this ->cacheMock ,
232238 'data ' => [],
233239 'serializer ' => $ this ->serializer ,
234240 ]
@@ -1064,6 +1070,173 @@ public function testGetCodeWithDropoffTypeNoCode(): void
10641070 $ this ->assertEquals (__ ('Regular Pickup ' ), $ result ['REGULAR_PICKUP ' ]);
10651071 }
10661072
1073+ /**
1074+ * Test get access token from cache
1075+ */
1076+ public function testCollectRatesWithCachedAccessToken (): void
1077+ {
1078+ $ apiKey = 'TestApiKey ' ;
1079+ $ secretKey = 'TestSecretKey ' ;
1080+ $ accessToken = 'CachedTestAccessToken ' ;
1081+ $ cacheKey = 'fedex_access_token_ ' . hash ('sha256 ' , $ apiKey . $ secretKey );
1082+ $ expiresAt = time () + 3600 ;
1083+ $ cachedData = json_encode ([
1084+ 'access_token ' => $ accessToken ,
1085+ 'expires_at ' => $ expiresAt
1086+ ]);
1087+ $ this ->scope ->expects ($ this ->any ())
1088+ ->method ('getValue ' )
1089+ ->willReturnCallback ([$ this , 'scopeConfigGetValue ' ]);
1090+ $ this ->scope ->expects ($ this ->exactly (2 ))
1091+ ->method ('isSetFlag ' )
1092+ ->willReturn (true );
1093+
1094+ $ this ->cacheMock ->expects ($ this ->once ())
1095+ ->method ('load ' )
1096+ ->with ($ cacheKey )
1097+ ->willReturn ($ cachedData );
1098+
1099+ $ rateResponseMock = [
1100+ 'output ' => [
1101+ 'rateReplyDetails ' => [
1102+ [
1103+ 'serviceType ' => 'FEDEX_GROUND ' ,
1104+ 'ratedShipmentDetails ' => [
1105+ [
1106+ 'totalNetCharge ' => '28.75 ' ,
1107+ 'currency ' => 'USD ' ,
1108+ 'ratedPackages ' => [
1109+ ['packageRateDetail ' => ['rateType ' => 'RATED_ACCOUNT_PACKAGE ' ]]
1110+ ]
1111+ ]
1112+ ]
1113+ ]
1114+ ]
1115+ ]
1116+ ];
1117+ $ this ->serializer ->expects ($ this ->once ())
1118+ ->method ('serialize ' )
1119+ ->willReturn (json_encode (['mocked_request ' => 'data ' ]));
1120+ $ this ->serializer ->expects ($ this ->once ())
1121+ ->method ('unserialize ' )
1122+ ->willReturn ($ rateResponseMock );
1123+ $ this ->curlFactory ->expects ($ this ->once ())
1124+ ->method ('create ' )
1125+ ->willReturn ($ this ->curlClient );
1126+ $ this ->curlClient ->expects ($ this ->once ())
1127+ ->method ('setHeaders ' )
1128+ ->willReturnSelf ();
1129+ $ this ->curlClient ->expects ($ this ->once ())
1130+ ->method ('post ' )
1131+ ->willReturnSelf ();
1132+ $ this ->curlClient ->expects ($ this ->once ())
1133+ ->method ('getBody ' )
1134+ ->willReturn (json_encode ($ rateResponseMock ));
1135+ $ request = $ this ->getMockBuilder (RateRequest::class)
1136+ ->addMethods (['getBaseCurrency ' , 'getPackageWeight ' ])
1137+ ->disableOriginalConstructor ()
1138+ ->getMock ();
1139+ $ request ->method ('getPackageWeight ' )
1140+ ->willReturn (10.0 );
1141+ $ result = $ this ->carrier ->collectRates ($ request );
1142+ $ this ->assertInstanceOf (RateResult::class, $ result );
1143+ $ rates = $ result ->getAllRates ();
1144+ $ this ->assertNotEmpty ($ rates );
1145+ }
1146+
1147+ /**
1148+ * Test getTracking when a new access token is requested and saved to cache
1149+ */
1150+ public function testGetTrackingWithNewAccessTokenSavedToCache (): void
1151+ {
1152+ $ apiKey = 'TestApiKey ' ;
1153+ $ secretKey = 'TestSecretKey ' ;
1154+ $ accessToken = 'NewTrackingTestAccessToken ' ;
1155+ $ cacheKey = 'fedex_access_token_ ' . hash ('sha256 ' , $ apiKey . $ secretKey );
1156+ $ expiresIn = 3600 ;
1157+ $ cacheType = 'fedex_api ' ;
1158+ $ tokenResponse = [
1159+ 'access_token ' => $ accessToken ,
1160+ 'expires_in ' => $ expiresIn
1161+ ];
1162+ $ trackingNumber = '123456789012 ' ;
1163+ $ this ->scope ->expects ($ this ->any ())
1164+ ->method ('getValue ' )
1165+ ->willReturnCallback ([$ this , 'scopeConfigGetValue ' ]);
1166+ $ this ->cacheMock ->expects ($ this ->once ())
1167+ ->method ('load ' )
1168+ ->with ($ cacheKey )
1169+ ->willReturn (false );
1170+ $ this ->cacheMock ->expects ($ this ->once ())
1171+ ->method ('save ' )
1172+ ->with (
1173+ $ this ->callback (function ($ data ) use ($ accessToken , $ expiresIn ) {
1174+ $ decoded = json_decode ($ data , true );
1175+ return $ decoded ['access_token ' ] === $ accessToken &&
1176+ $ decoded ['expires_at ' ] <= (time () + $ expiresIn ) &&
1177+ $ decoded ['expires_at ' ] > time ();
1178+ }),
1179+ $ cacheKey ,
1180+ [$ cacheType ],
1181+ $ expiresIn
1182+ )
1183+ ->willReturn (true );
1184+ $ curlTokenClient = $ this ->createMock (ClientInterface::class);
1185+ $ this ->curlFactory ->expects ($ this ->exactly (2 ))
1186+ ->method ('create ' )
1187+ ->willReturnOnConsecutiveCalls ($ curlTokenClient , $ this ->curlClient );
1188+ $ curlTokenClient ->expects ($ this ->once ())
1189+ ->method ('setHeaders ' )
1190+ ->willReturnSelf ();
1191+ $ curlTokenClient ->expects ($ this ->once ())
1192+ ->method ('post ' )
1193+ ->willReturnSelf ();
1194+ $ curlTokenClient ->expects ($ this ->once ())
1195+ ->method ('getBody ' )
1196+ ->willReturn (json_encode ($ tokenResponse ));
1197+ $ trackingResponse = $ this ->getTrackingResponse ();
1198+ $ trackingStatusMock = $ this ->getMockBuilder (Status::class)
1199+ ->addMethods (['setCarrier ' , 'setCarrierTitle ' , 'setTracking ' ])
1200+ ->onlyMethods (['addData ' ])
1201+ ->getMock ();
1202+ $ this ->statusFactory ->expects ($ this ->once ())
1203+ ->method ('create ' )
1204+ ->willReturn ($ trackingStatusMock );
1205+ $ trackingStatusMock ->expects ($ this ->once ())
1206+ ->method ('setCarrier ' )
1207+ ->with (Carrier::CODE )
1208+ ->willReturnSelf ();
1209+ $ trackingStatusMock ->expects ($ this ->once ())
1210+ ->method ('setCarrierTitle ' )
1211+ ->willReturnSelf ();
1212+ $ trackingStatusMock ->expects ($ this ->once ())
1213+ ->method ('setTracking ' )
1214+ ->with ($ trackingNumber )
1215+ ->willReturnSelf ();
1216+ $ trackingStatusMock ->expects ($ this ->once ())
1217+ ->method ('addData ' )
1218+ ->willReturnSelf ();
1219+ $ this ->serializer ->expects ($ this ->once ())
1220+ ->method ('serialize ' )
1221+ ->willReturn (json_encode ($ this ->getTrackRequest ($ trackingNumber )));
1222+ $ this ->serializer ->expects ($ this ->exactly (2 ))
1223+ ->method ('unserialize ' )
1224+ ->willReturnOnConsecutiveCalls ($ tokenResponse , $ trackingResponse );
1225+ $ this ->curlClient ->expects ($ this ->once ())
1226+ ->method ('setHeaders ' )
1227+ ->willReturnSelf ();
1228+ $ this ->curlClient ->expects ($ this ->once ())
1229+ ->method ('post ' )
1230+ ->willReturnSelf ();
1231+ $ this ->curlClient ->expects ($ this ->once ())
1232+ ->method ('getBody ' )
1233+ ->willReturn (json_encode ($ trackingResponse ));
1234+ $ trackings = [$ trackingNumber ];
1235+ $ result = $ this ->carrier ->getTracking ($ trackings );
1236+ $ this ->assertInstanceOf (Result::class, $ result );
1237+ $ trackingsResult = $ result ->getAllTrackings ();
1238+ $ this ->assertNotEmpty ($ trackingsResult );
1239+ }
10671240 /**
10681241 * Gets list of variations for testing ship date.
10691242 *
@@ -1314,4 +1487,29 @@ private function getShipmentRequestMock(): MockObject
13141487 ])
13151488 ->getMock ();
13161489 }
1490+
1491+ /**
1492+ * @return array
1493+ */
1494+ private function getTrackingResponse (): array
1495+ {
1496+ return [
1497+ 'output ' => [
1498+ 'completeTrackResults ' => [
1499+ [
1500+ 'trackingNumber ' => '123456789012 ' ,
1501+ 'trackResults ' => [
1502+ [
1503+ 'trackingNumberInfo ' => ['trackingNumber ' => '123456789012 ' ],
1504+ 'statusDetail ' => ['description ' => 'Delivered ' ],
1505+ 'dateAndTimes ' => [
1506+ ['type ' => 'ACTUAL_DELIVERY ' , 'dateTime ' => '2025-05-20T10:00:00Z ' ]
1507+ ]
1508+ ]
1509+ ]
1510+ ]
1511+ ]
1512+ ]
1513+ ];
1514+ }
13171515}
0 commit comments