Skip to content

Commit 38044b7

Browse files
authored
[feat] Consolidate SmartRate functions, add new functions (#566)
- Add Smart Deliver By and Smart Deliver On API support (estimate delivery date or recommend ship date based on carrier + route rather than shipment) - Consolidate and standardize naming for SmartRate functions - Handle deprecated classes as needed, mitigations (non-breaking)
1 parent 7a6b3b7 commit 38044b7

35 files changed

+1404
-118
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Next Release
44

5+
- Add new `SmartRate` service for interacting with the SmartRate API
6+
- New `RecommendShipDateForShipment` function to recommend ship date for a shipment based on a desired delivery date.
7+
- New `EstimateDeliveryDateForRoute` function to estimate delivery date based on a list of carriers, to/from ZIP codes and a planned ship date (no existing shipment required).
8+
- New `RecommendShipDateForRoute` function to to recommend ship date based on a list of carriers, to/from ZIP codes and a planned ship date (no existing shipment required).
9+
- New model classes as needed for JSON response to new API functions
510
- Enforce one-or-other for `Shipment` and `Batch` parameters in `Pickup.Create` parameter set
611
- Add internal parameter dependency utility
712

EasyPost.Integration/Basics.cs

+8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public void UserCanLocallyConstructResponseObject()
3434
var endShipper = new EndShipper();
3535
var endShipperCollection = new EndShipperCollection();
3636
var error = new Error();
37+
var estimateDeliveryDateForZipPairResult = new EstimateDeliveryDateForZipPairResult();
3738
var @event = new Event();
3839
var eventCollection = new EventCollection();
3940
var fee = new Fee();
@@ -55,6 +56,8 @@ public void UserCanLocallyConstructResponseObject()
5556
var predefinedPackage = new PredefinedPackage();
5657
var rate = new Rate();
5758
var rateWithEstimatedDeliveryDate = new RateWithEstimatedDeliveryDate();
59+
var recommendShipDateForZipPairResult = new RecommendShipDateForZipPairResult();
60+
var recommendShipDateForShipmentResult = new RecommendShipDateForShipmentResult();
5861
var referralCustomer = new ReferralCustomer();
5962
var refund = new Refund();
6063
var report = new Report();
@@ -70,6 +73,9 @@ public void UserCanLocallyConstructResponseObject()
7073
var supportedFeature = new SupportedFeature();
7174
var taxIdentifier = new TaxIdentifier();
7275
var timeInTransit = new TimeInTransit();
76+
var timeInTransitDetails = new TimeInTransitDetails();
77+
var timeInTransitDetailsForDeliveryDateEstimate = new TimeInTransitDetailsForDeliveryDateEstimate();
78+
var timeInTransitDetailsByDeliveryDate = new TimeInTransitDetailsForShipDateRecommendation();
7379
var tracker = new Tracker();
7480
var trackerCollection = new TrackerCollection();
7581
var trackingDetail = new TrackingDetail();
@@ -139,6 +145,8 @@ public void UserCanConstructParameterSets()
139145
var shipmentInsureParameters = new EasyPost.Parameters.Shipment.Insure();
140146
var shipmentRegenerateRatesParameters = new EasyPost.Parameters.Shipment.RegenerateRates();
141147
var shipmentRetrieveEstimatedDeliveryDateParameters = new EasyPost.Parameters.Shipment.RetrieveEstimatedDeliveryDate();
148+
var smartRateEstimateDeliveryDateForZipPairParameters = new EasyPost.Parameters.SmartRate.EstimateDeliveryDateForZipPair();
149+
var smartRateRecommendShipDateForZipPairParameters = new EasyPost.Parameters.SmartRate.RecommendShipDateForZipPair();
142150
var taxIdentifierCreateParameters = new EasyPost.Parameters.TaxIdentifier.Create();
143151
var trackerCreateParameters = new EasyPost.Parameters.Tracker.Create();
144152
var trackerAllParameters = new EasyPost.Parameters.Tracker.All();

EasyPost.Integration/TestUtils.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ internal Client SetUpTest(string cassetteName, Func<string, HttpClient, Client>
151151
// set up cassette
152152
Cassette cassette = new(_testCassettesFolder, cassetteName, new CassetteOrder.Alphabetical());
153153

154-
// add cassette to vcr
154+
// add cassette to vcr
155155
_vcr.Insert(cassette);
156156

157157
string filePath = Path.Combine(_testCassettesFolder, cassetteName + ".json");

EasyPost.Tests/EasyPost.Tests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,7 @@
6969

7070
<ItemGroup>
7171
<Folder Include="cassettes" />
72+
<Folder Include="cassettes\netstandard\smartrate_service_with_parameters\" />
73+
<Folder Include="cassettes\net\smartrate_service_with_parameters\" />
7274
</ItemGroup>
7375
</Project>

EasyPost.Tests/Fixture.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ internal static Dictionary<string, object> BasicPickup
8989

9090
internal static string PickupService => GetFixtureStructure().ServiceNames.Usps.PickupService;
9191

92-
internal static string PlannedShipDate => "2024-04-08";
92+
internal static string PlannedShipDate => "2024-07-17";
93+
94+
internal static string DesiredDeliveryDate => "2024-07-17";
9395

9496
internal static Dictionary<string, object> ReferralCustomer => GetFixtureStructure().Users.Referral;
9597

EasyPost.Tests/ServicesTests/ShipmentServiceTest.cs

+27
Original file line numberDiff line numberDiff line change
@@ -531,10 +531,37 @@ public async Task TestRetrieveEstimatedDeliveryDates()
531531

532532
foreach (var rate in ratesWithEstimatedDeliveryDates)
533533
{
534+
// Deprecated property
534535
Assert.NotNull(rate.EasyPostTimeInTransitData);
535536
Assert.NotNull(rate.EasyPostTimeInTransitData.EasyPostEstimatedDeliveryDate);
536537
Assert.NotNull(rate.EasyPostTimeInTransitData.DaysInTransit);
537538
Assert.NotNull(rate.EasyPostTimeInTransitData.PlannedShipDate);
539+
540+
// Replacement property, same data
541+
Assert.NotNull(rate.TimeInTransitDetails);
542+
Assert.NotNull(rate.TimeInTransitDetails.EasyPostEstimatedDeliveryDate);
543+
Assert.NotNull(rate.TimeInTransitDetails.DaysInTransit);
544+
Assert.NotNull(rate.TimeInTransitDetails.PlannedShipDate);
545+
}
546+
}
547+
548+
[Fact]
549+
[CrudOperations.Read]
550+
[Testing.Function]
551+
public async Task TestRecommendShipDateForShipment()
552+
{
553+
UseVCR("recommend_ship_date");
554+
555+
Shipment shipment = await Client.Shipment.Create(Fixtures.BasicShipment);
556+
557+
List<RecommendShipDateForShipmentResult> ratesWithEstimatedDeliveryDates = await Client.Shipment.RecommendShipDate(shipment.Id, Fixtures.DesiredDeliveryDate);
558+
559+
foreach (var rate in ratesWithEstimatedDeliveryDates)
560+
{
561+
Assert.NotNull(rate.TimeInTransitDetails);
562+
Assert.NotNull(rate.TimeInTransitDetails.EasyPostRecommendedShipDate);
563+
Assert.NotNull(rate.TimeInTransitDetails.DaysInTransit);
564+
Assert.NotNull(rate.TimeInTransitDetails.DesiredDeliveryDate);
538565
}
539566
}
540567

EasyPost.Tests/ServicesTests/WithParameters/ShipmentServiceTest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Threading.Tasks;
34
using EasyPost.Models.API;
45
using EasyPost.Tests._Utilities;
@@ -304,7 +305,7 @@ public async Task TestRegenerateRates()
304305

305306
[Fact]
306307
[Testing.Function]
307-
public async Task TestRetrieveEstimatedDeliveryDates()
308+
public async Task TestEstimatedDeliveryDates()
308309
{
309310
UseVCR("estimated_delivery_dates");
310311

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using EasyPost.Exceptions.General;
5+
using EasyPost.Models.API;
6+
using EasyPost.Tests._Utilities;
7+
using EasyPost.Tests._Utilities.Attributes;
8+
using EasyPost.Utilities.Internal.Attributes;
9+
using Xunit;
10+
11+
namespace EasyPost.Tests.ServicesTests.WithParameters
12+
{
13+
public class SmartRateServiceTests : UnitTest
14+
{
15+
public SmartRateServiceTests() : base("smartrate_service_with_parameters")
16+
{
17+
}
18+
19+
#region Tests
20+
21+
#region Test CRUD Operations
22+
23+
[Fact]
24+
[CrudOperations.Read]
25+
[Testing.Function]
26+
public async Task TestEstimateDeliveryDate()
27+
{
28+
UseVCR("estimate_delivery_date");
29+
30+
Dictionary<string, object> address1Data = Fixtures.CaAddress1;
31+
Dictionary<string, object> address2Data = Fixtures.CaAddress2;
32+
Parameters.Address.Create address1Parameters = Fixtures.Parameters.Addresses.Create(address1Data);
33+
Parameters.Address.Create address2Parameters = Fixtures.Parameters.Addresses.Create(address2Data);
34+
35+
Parameters.SmartRate.EstimateDeliveryDateForZipPair estimateDeliveryDateForZipPairParameters = new()
36+
{
37+
FromZip = address1Parameters.Zip,
38+
ToZip = address2Parameters.Zip,
39+
PlannedShipDate = Fixtures.PlannedShipDate,
40+
Carriers = ["USPS", "FedEx", "UPS", "DHL"],
41+
};
42+
43+
EstimateDeliveryDateForZipPairResult results = await Client.SmartRate.EstimateDeliveryDate(estimateDeliveryDateForZipPairParameters);
44+
45+
Assert.Equal(results.FromZip, estimateDeliveryDateForZipPairParameters.FromZip);
46+
Assert.Equal(results.ToZip, estimateDeliveryDateForZipPairParameters.ToZip);
47+
Assert.Equal(results.PlannedShipDate, estimateDeliveryDateForZipPairParameters.PlannedShipDate);
48+
Assert.NotNull(results.Results);
49+
Assert.NotEmpty(results.Results);
50+
foreach (var estimate in results.Results)
51+
{
52+
Assert.NotNull(estimate.Carrier);
53+
Assert.NotNull(estimate.Service);
54+
Assert.NotNull(estimate.TimeInTransitDetails);
55+
Assert.NotNull(estimate.TimeInTransitDetails.DaysInTransit);
56+
Assert.NotNull(estimate.TimeInTransitDetails.DaysInTransit.Percentile75);
57+
}
58+
}
59+
60+
[Fact]
61+
[CrudOperations.Read]
62+
[Testing.Function]
63+
public async Task TestRecommendShipDate()
64+
{
65+
UseVCR("recommend_ship_date");
66+
67+
Dictionary<string, object> address1Data = Fixtures.CaAddress1;
68+
Dictionary<string, object> address2Data = Fixtures.CaAddress2;
69+
Parameters.Address.Create address1Parameters = Fixtures.Parameters.Addresses.Create(address1Data);
70+
Parameters.Address.Create address2Parameters = Fixtures.Parameters.Addresses.Create(address2Data);
71+
72+
Parameters.SmartRate.RecommendShipDateForZipPair recommendShipDateForZipPairParameters = new()
73+
{
74+
FromZip = address1Parameters.Zip,
75+
ToZip = address2Parameters.Zip,
76+
DesiredDeliveryDate = Fixtures.DesiredDeliveryDate,
77+
Carriers = ["USPS", "FedEx", "UPS", "DHL"],
78+
};
79+
80+
RecommendShipDateForZipPairResult results = await Client.SmartRate.RecommendShipDate(recommendShipDateForZipPairParameters);
81+
82+
Assert.Equal(results.FromZip, recommendShipDateForZipPairParameters.FromZip);
83+
Assert.Equal(results.ToZip, recommendShipDateForZipPairParameters.ToZip);
84+
Assert.Equal(results.DesiredDeliveryDate, recommendShipDateForZipPairParameters.DesiredDeliveryDate);
85+
Assert.NotNull(results.Results);
86+
Assert.NotEmpty(results.Results);
87+
foreach (var estimate in results.Results)
88+
{
89+
Assert.NotNull(estimate.Carrier);
90+
Assert.NotNull(estimate.Service);
91+
Assert.NotNull(estimate.EasyPostTimeInTransitData);
92+
Assert.NotNull(estimate.EasyPostTimeInTransitData.DaysInTransit);
93+
Assert.NotNull(estimate.EasyPostTimeInTransitData.DaysInTransit.Percentile75);
94+
}
95+
}
96+
97+
#endregion
98+
99+
[Fact]
100+
[Testing.Function]
101+
public async Task TestLowestSmartRateFiltering()
102+
{
103+
/***
104+
* Mock rates since these can change from the API and we want to test the local filtering logic, not the API call.
105+
* The API call is tested in <see cref="TestGetSmartRates"/>
106+
*/
107+
List<SmartRate> smartRates =
108+
[
109+
new SmartRate
110+
{
111+
Service = "Priority",
112+
Carrier = "USPS",
113+
Rate = 1.00, // this rate is cheaper but doesn't meet the filters
114+
TimeInTransit = new TimeInTransit
115+
{
116+
Percentile90 = 3,
117+
},
118+
},
119+
120+
new SmartRate
121+
{
122+
Service = "First",
123+
Carrier = "USPS",
124+
Rate = 6.07,
125+
TimeInTransit = new TimeInTransit
126+
{
127+
Percentile90 = 2,
128+
},
129+
}
130+
131+
];
132+
133+
// test lowest SmartRate with valid filters
134+
SmartRate lowestSmartRate = Utilities.Rates.GetLowestSmartRate(smartRates, 2, SmartRateAccuracy.Percentile90);
135+
Assert.Equal("First", lowestSmartRate.Service);
136+
Assert.Equal(6.07, lowestSmartRate.Rate);
137+
Assert.Equal("USPS", lowestSmartRate.Carrier);
138+
139+
// test lowest SmartRate with invalid filters (should error due to strict delivery_days)
140+
await Assert.ThrowsAsync<FilteringError>(() => Task.FromResult(Utilities.Rates.GetLowestSmartRate(smartRates, 0, SmartRateAccuracy.Percentile90)));
141+
}
142+
143+
#endregion
144+
}
145+
}

0 commit comments

Comments
 (0)