Skip to content

Commit

Permalink
feat: proactive connect - items (#399)
Browse files Browse the repository at this point in the history
* Implement CreateItemRequest

* Add ListItem deserialization

* Implement DeleteItemRequest

* Keep deserialization tests separated to maintain file structure

* Implement GetItemRequest

* Implement UpdateItemRequest

* Fix merge conflicts in csproj

* Add missing use case testing

* Linter broke test project again

* Remove redundant interface

* Rider removed all imports, well done!

* Implement ExtractItemsRequest

* Implement ImportItems

* Move CustomMessageHandler to Vonage.Common

* Fix version issue

* Implement GetItemsRequest

* Group embedded into PaginationResult

* Add complex item serialization test for CreateItem

* Add fetch order on GetLists

* Add fetch order on GetItems
  • Loading branch information
Tr00d authored Apr 28, 2023
1 parent 9dc1f4a commit 4cfae6d
Show file tree
Hide file tree
Showing 82 changed files with 3,330 additions and 1,049 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.Kernel;
using Vonage.Common.Client;
using Vonage.Common.Monads;
using Vonage.Common.Test;

namespace Vonage.Test.Unit.Meetings.UpdateThemeLogo
namespace Vonage.Common.Test.TestHelpers
{
/// <summary>
/// Custom message handler for CustomHttpMessageHandler.
Expand Down
17 changes: 17 additions & 0 deletions Vonage.Common.Test/UseCaseHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ public async Task VerifyReturnsFailureGivenTokenGenerationFails<TResponse>(
result.Should().BeFailure(new AuthenticationFailure());
}

/// <summary>
/// Verifies the operation returns the raw content given the response is success.
/// </summary>
/// <param name="expected">Expected values for the incoming request.</param>
/// <param name="operation">The call operation.</param>
public async Task VerifyReturnsRawContentGivenApiResponseIsSuccess(ExpectedRequest expected,
Func<VonageHttpClientConfiguration, Task<Result<string>>> operation)
{
var expectedResponse = this.Fixture.Create<string>();
var messageHandler = FakeHttpRequestHandler
.Build(HttpStatusCode.OK)
.WithExpectedRequest(expected)
.WithResponseContent(expectedResponse);
var result = await operation(this.CreateConfiguration(messageHandler));
result.Should().BeSuccess(expectedResponse);
}

/// <summary>
/// Verifies the operation returns the default unit value given the response is success.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions Vonage.Common/Client/VonageHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ public async Task<Result<Unit>> SendWithoutHeadersAsync<T>(Result<T> request) wh
await this.SendRequest(request, value => value.BuildRequestMessage(), this.ParseFailure<Unit>,
CreateSuccessResult);

/// <summary>
/// Sends a HttpRequest and returns the raw content.
/// </summary>
/// <param name="request">The request to send.</param>
/// <typeparam name="TRequest">Type of the request.</typeparam>
/// <returns>Success if the operation succeeds, Failure it if fails.</returns>
public async Task<Result<string>> SendWithRawResponseAsync<TRequest>(Result<TRequest> request)
where TRequest : IVonageRequest =>
await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<string>,
responseData => responseData.Content);

/// <summary>
/// Sends a HttpRequest and parses the response.
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions Vonage.Common/Validation/InputValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ public static Result<T> VerifyNotEmpty<T>(T request, string value, string name)
ResultFailure.FromErrorMessage($"{name} {StringCannotBeNullOrWhitespace}"))
: request;

/// <summary>
/// Verifies if not null or empty.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="value">The string.</param>
/// <param name="name">The display name.</param>
/// <typeparam name="T">The request type.</typeparam>
/// <typeparam name="TElement">The nested element type.</typeparam>
/// <returns>Success or Failure.</returns>
public static Result<T> VerifyNotEmpty<T, TElement>(T request, IEnumerable<TElement> value, string name) =>
!value.Any()
? Result<T>.FromFailure(
ResultFailure.FromErrorMessage($"{name} {GuidCannotBeNullOrWhitespace}"))
: request;

/// <summary>
/// Verifies if not null or empty.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Vonage.Test.Unit/Meetings/UpdateThemeLogo/UseCaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Vonage.Common.Monads;
using Vonage.Common.Test;
using Vonage.Common.Test.Extensions;
using Vonage.Common.Test.TestHelpers;
using Vonage.Meetings;
using Vonage.Meetings.Common;
using Vonage.Meetings.UpdateThemeLogo;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "29192c4a-4058-49da-86c2-3e349d1065b7",
"created_at": "2022-06-19T17:59:28.085Z",
"updated_at": "2022-06-19T17:59:28.085Z",
"data": {
"value1": "value",
"value2": 0,
"value3": true
},
"list_id": "4cb98f71-a879-49f7-b5cf-2314353eb52c"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"data": {
"value1": "value",
"value2": 0,
"value3": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"data": {
"fizz": {
"foo": "bar"
},
"baz": 2,
"Bat": "qux",
"more_items": [
1,
2,
"three",
true,
{
"foo": "bar"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoFixture;
using FluentAssertions;
using Vonage.Common.Failures;
using Vonage.Common.Test.Extensions;
using Vonage.ProactiveConnect.Items.CreateItem;
using Xunit;

namespace Vonage.Test.Unit.ProactiveConnect.Items.CreateItem
{
public class RequestBuilderTest
{
private readonly Guid listId;
private readonly KeyValuePair<string, object> element;

public RequestBuilderTest()
{
var fixture = new Fixture();
this.listId = fixture.Create<Guid>();
this.element = fixture.Create<KeyValuePair<string, object>>();
}

[Fact]
public void Build_ShouldReturnFailure_GivenDataIsEmpty() =>
CreateItemRequest
.Build()
.WithListId(this.listId)
.Create()
.Should()
.BeFailure(ResultFailure.FromErrorMessage("Data cannot be empty."));

[Fact]
public void Build_ShouldReturnFailure_GivenListIdIsEmpty() =>
CreateItemRequest
.Build()
.WithListId(Guid.Empty)
.WithCustomData(this.element)
.Create()
.Should()
.BeFailure(ResultFailure.FromErrorMessage("ListId cannot be empty."));

[Fact]
public void Build_ShouldSetData() =>
CreateItemRequest
.Build()
.WithListId(this.listId)
.WithCustomData(new KeyValuePair<string, object>("value1", "value"))
.WithCustomData(new KeyValuePair<string, object>("value2", 0))
.WithCustomData(new KeyValuePair<string, object>("value3", true))
.Create()
.Map(request => request.Data)
.Should()
.BeSuccess(data => data.ToList().Should().BeEquivalentTo(new[]
{
new KeyValuePair<string, object>("value1", "value"),
new KeyValuePair<string, object>("value2", 0),
new KeyValuePair<string, object>("value3", true),
}));

[Fact]
public void Build_ShouldSetListId() =>
CreateItemRequest
.Build()
.WithListId(this.listId)
.WithCustomData(this.element)
.Create()
.Map(request => request.ListId)
.Should()
.BeSuccess(this.listId);
}
}
27 changes: 27 additions & 0 deletions Vonage.Test.Unit/ProactiveConnect/Items/CreateItem/RequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using AutoFixture;
using Vonage.Common.Test.Extensions;
using Vonage.ProactiveConnect.Items.CreateItem;
using Xunit;

namespace Vonage.Test.Unit.ProactiveConnect.Items.CreateItem
{
public class RequestTest
{
private readonly KeyValuePair<string, object> element;

public RequestTest() => this.element = new Fixture().Create<KeyValuePair<string, object>>();

[Fact]
public void GetEndpointPath_ShouldReturnApiEndpoint() =>
CreateItemRequest
.Build()
.WithListId(new Guid("95a462d3-ed87-4aa5-9d91-098e08093b0b"))
.WithCustomData(this.element)
.Create()
.Map(request => request.GetEndpointPath())
.Should()
.BeSuccess("/v0.1/bulk/lists/95a462d3-ed87-4aa5-9d91-098e08093b0b/items");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Vonage.Common;
using Vonage.Common.Test;
using Vonage.Common.Test.Extensions;
using Vonage.ProactiveConnect.Items;
using Vonage.ProactiveConnect.Items.CreateItem;
using Xunit;

namespace Vonage.Test.Unit.ProactiveConnect.Items.CreateItem
{
public class SerializationTest
{
private readonly SerializationTestHelper helper;

public SerializationTest() =>
this.helper = new SerializationTestHelper(typeof(SerializationTest).Namespace,
JsonSerializer.BuildWithSnakeCase());

[Fact]
public void ShouldDeserialize200() =>
this.helper.Serializer
.DeserializeObject<ListItem>(this.helper.GetResponseJson())
.Should()
.BeSuccess(success =>
{
success.Id.Should().Be(new Guid("29192c4a-4058-49da-86c2-3e349d1065b7"));
success.CreatedAt.Should().Be(DateTimeOffset.Parse("2022-06-19T17:59:28.085Z"));
success.UpdatedAt.Should().Be(DateTimeOffset.Parse("2022-06-19T17:59:28.085Z"));
success.ListId.Should().Be(new Guid("4cb98f71-a879-49f7-b5cf-2314353eb52c"));
success.Data["value1"].ToString().Should().Be("value");
int.Parse(success.Data["value2"].ToString()).Should().Be(0);
bool.Parse(success.Data["value3"].ToString()).Should().BeTrue();
});

[Fact]
public void ShouldSerialize() =>
CreateItemRequest
.Build()
.WithListId(Guid.NewGuid())
.WithCustomData(new KeyValuePair<string, object>("value1", "value"))
.WithCustomData(new KeyValuePair<string, object>("value2", 0))
.WithCustomData(new KeyValuePair<string, object>("value3", true))
.Create()
.GetStringContent()
.Should()
.BeSuccess(this.helper.GetRequestJson());

[Fact]
public void ShouldSerializeComplexObject() =>
CreateItemRequest
.Build()
.WithListId(Guid.NewGuid())
.WithCustomData(new KeyValuePair<string, object>("fizz", new {Foo = "bar"}))
.WithCustomData(new KeyValuePair<string, object>("baz", 2))
.WithCustomData(new KeyValuePair<string, object>("Bat", "qux"))
.WithCustomData(new KeyValuePair<string, object>("more_items", new object[]
{
1,
2,
"three",
true,
new {Foo = "bar"},
}))
.Create()
.GetStringContent()
.Should()
.BeSuccess(this.helper.GetRequestJson());
}
}
71 changes: 71 additions & 0 deletions Vonage.Test.Unit/ProactiveConnect/Items/CreateItem/UseCaseTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.Kernel;
using FsCheck;
using FsCheck.Xunit;
using Vonage.Common.Client;
using Vonage.Common.Monads;
using Vonage.Common.Test;
using Vonage.ProactiveConnect;
using Vonage.ProactiveConnect.Items;
using Vonage.ProactiveConnect.Items.CreateItem;
using Xunit;

namespace Vonage.Test.Unit.ProactiveConnect.Items.CreateItem
{
public class UseCaseTest : BaseUseCase
{
private Func<VonageHttpClientConfiguration, Task<Result<ListItem>>> Operation =>
configuration => new ProactiveConnectClient(configuration).CreateItemAsync(this.request);

private readonly Result<CreateItemRequest> request;

public UseCaseTest() => this.request = BuildRequest(this.helper.Fixture);

[Property]
public Property ShouldReturnFailure_GivenApiErrorCannotBeParsed() =>
this.helper.VerifyReturnsFailureGivenErrorCannotBeParsed(this.BuildExpectedRequest(), this.Operation);

[Fact]
public async Task ShouldReturnFailure_GivenApiResponseCannotBeParsed() =>
await this.helper.VerifyReturnsFailureGivenApiResponseCannotBeParsed(this.BuildExpectedRequest(),
this.Operation);

[Property]
public Property ShouldReturnFailure_GivenApiResponseIsError() =>
this.helper.VerifyReturnsFailureGivenApiResponseIsError(this.BuildExpectedRequest(), this.Operation);

[Fact]
public async Task ShouldReturnFailure_GivenRequestIsFailure() =>
await this.helper
.VerifyReturnsFailureGivenRequestIsFailure<CreateItemRequest, ListItem>(
(configuration, failureRequest) =>
new ProactiveConnectClient(configuration).CreateItemAsync(failureRequest));

[Fact]
public async Task ShouldReturnFailure_GivenTokenGenerationFailed() =>
await this.helper.VerifyReturnsFailureGivenTokenGenerationFails(this.Operation);

[Fact]
public async Task ShouldReturnSuccess_GivenApiResponseIsSuccess() =>
await this.helper.VerifyReturnsExpectedValueGivenApiResponseIsSuccess(this.BuildExpectedRequest(),
this.Operation);

private ExpectedRequest BuildExpectedRequest() =>
new ExpectedRequest
{
Method = HttpMethod.Post,
RequestUri = new Uri(UseCaseHelper.GetPathFromRequest(this.request), UriKind.Relative),
Content = this.request
.Map(value => this.helper.Serializer.SerializeObject(value))
.IfFailure(string.Empty),
};

private static Result<CreateItemRequest> BuildRequest(ISpecimenBuilder fixture) =>
CreateItemRequest.Build().WithListId(fixture.Create<Guid>())
.WithCustomData(new KeyValuePair<string, object>("value", "value")).Create();
}
}
Loading

0 comments on commit 4cfae6d

Please sign in to comment.