Skip to content

Commit

Permalink
[chore] Add missing GenerateForm function (#469)
Browse files Browse the repository at this point in the history
- Add missing GenerateForm function
- Add unit tests for GenerateForm function
- Add assertion utility to confirm nothing in enumerable matches
  • Loading branch information
nwithan8 committed May 11, 2023
1 parent 469f348 commit 44a344e
Show file tree
Hide file tree
Showing 12 changed files with 829 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- EasyPost API failures can trigger a variety of specific exceptions, all inheriting from `ApiError`.
- Non-EasyPost API/HTTP failures will trigger an `ExternalApiError` exception.
- [Parameter sets](#v450-2023-03-22) are out of beta. Users can use access them via `EasyPost.Parameters` namespace.
- Reintroduce `GenerateForm` function for shipments that was accidentally removed in `v4`.


## v4.6.0 (2023-04-18)
Expand Down
28 changes: 27 additions & 1 deletion EasyPost.Tests/ServicesTests/ShipmentServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using EasyPost.Tests._Utilities.Attributes;
using EasyPost.Utilities.Internal.Attributes;
using Xunit;
using CustomAssertions = EasyPost.Tests._Utilities.Assertions.Assert;

namespace EasyPost.Tests.ServicesTests
{
Expand Down Expand Up @@ -547,7 +548,7 @@ public async Task TestForms()

Shipment shipment = await Client.Shipment.Create(Fixtures.FullShipment);

await Client.Shipment.Buy(shipment.Id, shipment.LowestRate().Id);
shipment = await Client.Shipment.Buy(shipment.Id, shipment.LowestRate().Id);

Assert.NotNull(shipment.Forms);

Expand All @@ -559,6 +560,31 @@ public async Task TestForms()
}
}

[Fact]
[Testing.Function]
public async Task TestGenerateForm()
{
UseVCR("generate_form");

const string formType = "label_qr_code";

Shipment shipment = await Client.Shipment.Create(Fixtures.FullShipment);

CustomAssertions.None(shipment.Forms, form => Assert.Equal(formType, form.FormType));

shipment = await Client.Shipment.Buy(shipment.Id, shipment.LowestRate().Id);

Dictionary<string, object> parameters = new()
{
{ "type", "label_qr_code" },
};

shipment = await Client.Shipment.GenerateForm(shipment.Id, parameters);

Assert.NotNull(shipment.Forms);
CustomAssertions.Any(shipment.Forms, form => Assert.Equal(formType, form.FormType));
}

[Fact]
[Testing.Function]
public async Task TestRetrieveEstimatedDeliveryDates()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public async Task TestGenerateLabel()
FileFormat = "ZPL",
};

await Client.Batch.GenerateLabel(batch.Id, generateLabelParameters);
batch = await Client.Batch.GenerateLabel(batch.Id, generateLabelParameters);

// We can't assert anything meaningful here because the label gets queued for generation and may not be immediately available
Assert.IsType<Batch>(batch);
Expand Down
35 changes: 35 additions & 0 deletions EasyPost.Tests/ServicesTests/WithParameters/ShipmentServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using EasyPost.Tests._Utilities.Attributes;
using EasyPost.Utilities.Internal.Attributes;
using Xunit;
using CustomAssertions = EasyPost.Tests._Utilities.Assertions.Assert;

namespace EasyPost.Tests.ServicesTests.WithParameters
{
Expand Down Expand Up @@ -313,6 +314,40 @@ public async Task TestGenerateLabel()
Assert.NotNull(shipment.PostageLabel.LabelZplUrl);
}

[Fact]
[Testing.Function]
public async Task TestGenerateForm()
{
UseVCR("generate_form");

const string formType = "label_qr_code";

Dictionary<string, object> shipmentData = Fixtures.FullShipment;
Parameters.Shipment.Create shipmentParameters = Fixtures.Parameters.Shipments.Create(shipmentData);
Shipment shipment = await Client.Shipment.Create(shipmentParameters);

CustomAssertions.None(shipment.Forms, form => Assert.Equal(formType, form.FormType));

Rate rate = shipment.LowestRate();

Parameters.Shipment.Buy buyParameters = new(rate)
{
CarbonOffset = true,
};

shipment = await Client.Shipment.Buy(shipment.Id, buyParameters);

Parameters.Shipment.GenerateForm generateFormParameters = new()
{
Type = formType,
};

shipment = await Client.Shipment.GenerateForm(shipment.Id, generateFormParameters);

Assert.NotNull(shipment.Forms);
CustomAssertions.Any(shipment.Forms, form => Assert.Equal(formType, form.FormType));
}

[Fact]
[CrudOperations.Update]
[Testing.Function]
Expand Down
53 changes: 53 additions & 0 deletions EasyPost.Tests/_Utilities/Assertions/CollectionAsserts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,58 @@ public static void Any<T>(IEnumerable<T> collection, Action<T, int> action)
if (!passed)
throw new AnyException();
}

/// <summary>
/// Verifies that no items in the collection pass when executed against
/// action.
/// </summary>
/// <typeparam name="T">The type of the object to be verified</typeparam>
/// <param name="collection">The collection</param>
/// <param name="action">The action to test each item against</param>
/// <exception cref="NoneException">Thrown when the collection contains any matching element</exception>
public static void None<T>(IEnumerable<T> collection, Action<T> action)
{
GuardArgumentNotNull(nameof(collection), collection);
GuardArgumentNotNull(nameof(action), action);

None(collection, (item, _) => action(item));
}

/// <summary>
/// Verifies that no items in the collection pass when executed against
/// action. The item index is provided to the action, in addition to the item.
/// </summary>
/// <typeparam name="T">The type of the object to be verified</typeparam>
/// <param name="collection">The collection</param>
/// <param name="action">The action to test each item against</param>
/// <exception cref="NoneException">Thrown when the collection contains any matching element</exception>
public static void None<T>(IEnumerable<T> collection, Action<T, int> action)
{
GuardArgumentNotNull(nameof(collection), collection);
GuardArgumentNotNull(nameof(action), action);

int idx = 0;

bool passed = false;

foreach (var item in collection)
{
try
{
action(item, idx);
passed = true;
break; // if we get here, it passed when it shouldn't have, so we can stop iterating
}
catch (Exception)
{
// we don't care about the exception, we just want to keep iterating
}

++idx;
}

if (passed)
throw new NoneException();
}
}
}
18 changes: 18 additions & 0 deletions EasyPost.Tests/_Utilities/Assertions/NoneException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Xunit.Sdk;

namespace EasyPost.Tests._Utilities.Assertions
{
/// <summary>
/// Exception thrown when a None assertion has one or more items fail an assertion.
/// </summary>
public class NoneException : XunitException
{
/// <summary>
/// Creates a new instance of the <see cref="NoneException"/> class.
/// </summary>
public NoneException()
: base("Assert.None() Failure")
{
}
}
}
155 changes: 155 additions & 0 deletions EasyPost.Tests/cassettes/net/shipment_service/generate_form.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions EasyPost/Parameters/Shipment/GenerateForm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using EasyPost.Exceptions.General;
using EasyPost.Utilities.Internal.Extensions;

namespace EasyPost.Parameters.Shipment
{
/// <summary>
/// <a href="https://www.easypost.com/docs/api#create-form">Parameters</a> for <see cref="EasyPost.Services.ShipmentService.GenerateForm(string, GenerateForm, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class GenerateForm : BaseParameters
{
#region Request Parameters

/// <summary>
/// The type for the new form.
/// </summary>
public string? Type { get; set; }

/// <summary>
/// The data for the new form.
/// </summary>
// There's many different types of forms, so we just collect a generic dictionary of data to pass along rather than building per-form parameter sets.
public Dictionary<string, object>? Data { get; set; }

/// <summary>
/// Override the default <see cref="BaseParameters.ToDictionary"/> method to handle the unique serialization requirements for this parameter set.
/// </summary>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/>.</returns>
/// <exception cref="MissingParameterError">Thrown when the form type was not provided.</exception>
internal override Dictionary<string, object> ToDictionary()
{
if (Type == null)
{
throw new MissingParameterError(nameof(Type));
}

Dictionary<string, object> data = Data ?? new Dictionary<string, object>();
data.Add("type", Type!);

return data.Wrap("form");
}

#endregion
}
}
30 changes: 30 additions & 0 deletions EasyPost/Services/ShipmentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,36 @@ public async Task<Shipment> GenerateLabel(string id, Parameters.Shipment.Generat
return await RequestAsync<Shipment>(Method.Get, $"shipments/{id}/label", cancellationToken, parameters.ToDictionary());
}

/// <summary>
/// Generate a <see cref="Form"/> for a <see cref="Shipment"/>.
/// <a href="https://www.easypost.com/docs/api#create-form">Related API documentation</a>.
/// </summary>
/// <param name="id">The ID of the <see cref="Shipment"/> to generate a form for.</param>
/// <param name="parameters">Parameters to generate the <see cref="Form"/> with.</param>
/// <param name="cancellationToken"><see cref="CancellationToken"/> to use for the HTTP request.</param>
/// <returns>The updated <see cref="Shipment"/>.</returns>
[CrudOperations.Update]
public async Task<Shipment> GenerateForm(string id, Dictionary<string, object> parameters, CancellationToken cancellationToken = default)
{
parameters = parameters.Wrap("form");

return await RequestAsync<Shipment>(Method.Post, $"shipments/{id}/forms", cancellationToken, parameters);
}

/// <summary>
/// Generate a <see cref="Form"/> for a <see cref="Shipment"/>.
/// <a href="https://www.easypost.com/docs/api#create-form">Related API documentation</a>.
/// </summary>
/// <param name="id">The ID of the <see cref="Shipment"/> to generate a form for.</param>
/// <param name="parameters"><see cref="Parameters.Shipment.GenerateForm"/> parameter set.</param>
/// <param name="cancellationToken"><see cref="CancellationToken"/> to use for the HTTP request.</param>
/// <returns>The updated <see cref="Shipment"/>.</returns>
[CrudOperations.Update]
public async Task<Shipment> GenerateForm(string id, Parameters.Shipment.GenerateForm parameters, CancellationToken cancellationToken = default)
{
return await RequestAsync<Shipment>(Method.Post, $"shipments/{id}/forms", cancellationToken, parameters.ToDictionary());
}

/// <summary>
/// Insure a <see cref="Shipment"/> for the given amount.
/// <a href="https://www.easypost.com/docs/api#insure-a-shipment">Related API documentation</a>.
Expand Down

0 comments on commit 44a344e

Please sign in to comment.