Skip to content

Commit

Permalink
DEVX-6546 | [Video] Signaling (#315)
Browse files Browse the repository at this point in the history
* Set up Video project, implement Maybe monad

* Implement Result monad

* Fix coverage (error not extracted from monad)

* Add more client frameworks on test project, remove global usings

* Implement VideoClient / SessionClient

* Implement IpAddress and CreateSessionRequest, add monadic bind to Result

* Implement monadic bind on Maybe, factory method for ResultFailure

* Implement MaybeAssertions

* Implement custom assertions for monads (Should Be|BeSome|BeNone|BeSuccess|BeFailure)

* Implement IfFailure/IfSuccess on Result, use on ResultAssertions

* Add missing xml doc on IfFailure/IfSuccess

* Implement MapAsync, BindAsync for Result

* Implement CreateSession

* Add temporary sample test

* Test refactoring

* Add missing Xml Documentation

* Remove sample test

* Update test for token generation

* Implement GetStreamRequest

* Create structure for GetStream

* Make Failure state of Result an IResultFailure. It will allow custom formatting for specific failures

* Implement HttpFailure with status codes, adapt CreateSession to use the new failure.

* Implement GetStream with error codes

* Remove custom url for WireMock

* Refactoring with extension methods

* Implement GetStreamAsync with successful state

* Implement GetStreamAsync failure when response cannot be serialized

* Remove conflicts from last merge (ResultFailure)

* Test refactoring

* Code cleanup & Xml Documentation

* Implement GetStreamsRequest

* Implement GetStreams

* Replace FluentAssertions extension .Be by .BeSome/.BeSuccess/.BeFailure to avoid confusion with base .Be method
The extension using clause wasn't discovered by the IDE.

* Rename FluentAssertion extensions

* Implement use-case approach with Screaming architecture. This will allow to comply with OCP

* Fix type change after merge

* Add factory method for failure, handle empty response differently

* Solve merge conflicts

* Remove unnecessary setter

* Simplify token generation

* Simplify http request creation

* Extract ErrorCode to higher namespace

* Remove TestRun project

* Implement ChangeStreamLayoutRequest with Parsing

* Use specific settings for camelCase serialization

* Implement ChangeStreamLayout use case

* Use 'Hollywood principle' for reducing the number of dependencies on clients & use cases (token generation using credentials)

* Remove GetStream.ErrorResponse

* Setting up structure for Signaling

* Implement parsing for SendSignalsRequest

* Empty use case for SendSignals

* Implement SendSignals use case

* Implement SendSignalUseCase

* Address duplication in Signaling

* Address duplication for Sessions and Signaling

* Add test for CreateSession GetEndpointPath

* Handle null & empty bodies on responses

* Add missing Xml documentation
  • Loading branch information
Tr00d authored Dec 21, 2022
1 parent 27090f7 commit 071eeca
Show file tree
Hide file tree
Showing 35 changed files with 1,123 additions and 352 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,13 @@ public ChangeStreamLayoutTest()
this.client = new SessionClient(this.server.CreateClient(), () => this.token);
}

private string GetPathFromRequest() =>
this.request.Match(value => value.GetEndpointPath(), failure => string.Empty);

[Property]
public Property ShouldReturnFailure_GivenApiResponseIsError() =>
Prop.ForAll(
FsCheckExtensions.GetInvalidStatusCodes(),
Arb.From<string>(),
(statusCode, message) => this.VerifyReturnsFailureGivenStatusCodeIsFailure(statusCode, message).Wait());

private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode code, string message)
{
var expectedBody = this.jsonSerializer.SerializeObject(new ErrorResponse(((int) code).ToString(), message));
this.server
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(code, expectedBody));
var result =
await this.request.BindAsync(requestValue => this.client.ChangeStreamLayoutAsync(requestValue));
result.Should().BeFailure(HttpFailure.From(code, message));
}

[Property]
public Property ShouldReturnFailure_GivenApiErrorCannotBeParsed() =>
Prop.ForAll(
Expand All @@ -65,27 +51,43 @@ public Property ShouldReturnFailure_GivenApiErrorCannotBeParsed() =>
(statusCode, jsonError) =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(statusCode, jsonError).Wait());

private async Task VerifyReturnsFailureGivenErrorCannotBeParsed(HttpStatusCode code, string jsonError)
[Fact]
public async Task ShouldReturnSuccess_GivenApiResponseIsSuccess()
{
var expectedFailureMessage = $"Unable to deserialize '{jsonError}' into '{nameof(ErrorResponse)}'.";
this.server
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(code,
jsonError));
.RespondWith(CreateChangeStreamLayoutResponse(HttpStatusCode.OK));
var result =
await this.request.BindAsync(requestValue => this.client.ChangeStreamLayoutAsync(requestValue));
result.Should().BeFailure(ResultFailure.FromErrorMessage(expectedFailureMessage));
result.Should().BeSuccess(Unit.Default);
}

[Fact]
public async Task ShouldReturnSuccess_GivenApiResponseIsSuccess()
private string GetPathFromRequest() =>
this.request.Match(value => value.GetEndpointPath(), failure => string.Empty);

private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode code, string message)
{
var expectedBody = message is null
? null
: this.jsonSerializer.SerializeObject(new ErrorResponse(((int) code).ToString(), message));
this.server
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(HttpStatusCode.OK));
.RespondWith(CreateChangeStreamLayoutResponse(code, expectedBody));
var result =
await this.request.BindAsync(requestValue => this.client.ChangeStreamLayoutAsync(requestValue));
result.Should().BeSuccess(Unit.Default);
result.Should().BeFailure(HttpFailure.From(code, message ?? string.Empty));
}

private async Task VerifyReturnsFailureGivenErrorCannotBeParsed(HttpStatusCode code, string jsonError)
{
var expectedFailureMessage = $"Unable to deserialize '{jsonError}' into '{nameof(ErrorResponse)}'.";
this.server
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(code,
jsonError));
var result =
await this.request.BindAsync(requestValue => this.client.ChangeStreamLayoutAsync(requestValue));
result.Should().BeFailure(ResultFailure.FromErrorMessage(expectedFailureMessage));
}

private IRequestBuilder CreateChangeStreamLayoutRequest()
Expand All @@ -99,7 +101,9 @@ private IRequestBuilder CreateChangeStreamLayoutRequest()
}

private static IResponseBuilder CreateChangeStreamLayoutResponse(HttpStatusCode code, string body) =>
CreateChangeStreamLayoutResponse(code).WithBody(body);
body is null
? CreateChangeStreamLayoutResponse(code)
: CreateChangeStreamLayoutResponse(code).WithBody(body);

private static IResponseBuilder CreateChangeStreamLayoutResponse(HttpStatusCode code) =>
Response.Create().WithStatusCode(code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,11 @@ public void Default_ShouldReturnRequest()
CreateSessionRequest.Default.MediaMode.Should().Be(MediaMode.Relayed);
CreateSessionRequest.Default.ArchiveMode.Should().Be(ArchiveMode.Manual);
}

[Fact]
public void GetEndpointPath_ShouldReturnApiEndpoint() =>
CreateSessionRequest.Default.GetEndpointPath()
.Should()
.Be("/session/create");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
using AutoFixture;
using FsCheck;
using FsCheck.Xunit;
using Newtonsoft.Json;
using Vonage.Video.Beta.Common;
using Vonage.Video.Beta.Common.Failures;
using Vonage.Video.Beta.Test.Extensions;
using Vonage.Video.Beta.Video.Sessions;
using Vonage.Video.Beta.Video.Sessions.CreateSession;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using Xunit;
Expand All @@ -19,6 +20,7 @@ public class CreateSessionTest : IDisposable
{
private readonly SessionClient client;
private readonly Fixture fixture;
private readonly JsonSerializer jsonSerializer;
private readonly CreateSessionRequest request = CreateSessionRequest.Default;
private readonly WireMockServer server;
private readonly CreateSessionResponse session;
Expand All @@ -27,6 +29,7 @@ public class CreateSessionTest : IDisposable
public CreateSessionTest()
{
this.server = WireMockServer.Start();
this.jsonSerializer = new JsonSerializer();
this.fixture = new Fixture();
this.token = this.fixture.Create<string>();
this.session = this.fixture.Create<CreateSessionResponse>();
Expand All @@ -43,51 +46,36 @@ public void Dispose()
[Fact]
public async Task ShouldReturnSuccess_GivenSessionIsCreated()
{
var expectedResponse = JsonConvert.SerializeObject(new[] {this.session});
var expectedResponse = this.jsonSerializer.SerializeObject(new[] {this.session});
this.server
.Given(WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token)
.WithPath(CreateSessionRequest.CreateSessionEndpoint)
.WithBody(this.request.GetUrlEncoded())
.UsingPost())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(expectedResponse));
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(HttpStatusCode.OK, expectedResponse));
var result = await this.client.CreateSessionAsync(this.request);
result.Should().BeSuccess(this.session);
}

[Fact]
public async Task ShouldReturnSuccess_GivenMultipleSessionsAreCreated()
{
var expectedResponse = JsonConvert.SerializeObject(new[]
var expectedResponse = this.jsonSerializer.SerializeObject(new[]
{
this.session, this.fixture.Create<CreateSessionResponse>(),
this.fixture.Create<CreateSessionResponse>(),
});
this.server
.Given(WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token)
.WithPath(CreateSessionRequest.CreateSessionEndpoint)
.WithBody(this.request.GetUrlEncoded())
.UsingPost())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(expectedResponse));
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(HttpStatusCode.OK, expectedResponse));
var result = await this.client.CreateSessionAsync(this.request);
result.Should().BeSuccess(this.session);
}

[Fact]
public async Task ShouldReturnFailure_GivenResponseContainsNoSession()
{
var expectedResponse = JsonConvert.SerializeObject(Array.Empty<CreateSessionResponse>());
var expectedResponse = this.jsonSerializer.SerializeObject(Array.Empty<CreateSessionResponse>());
this.server
.Given(WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token)
.WithPath(CreateSessionRequest.CreateSessionEndpoint)
.WithBody(this.request.GetUrlEncoded())
.UsingPost())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody(expectedResponse));
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(HttpStatusCode.OK, expectedResponse));
var result = await this.client.CreateSessionAsync(this.request);
result.Should().BeFailure(ResultFailure.FromErrorMessage(CreateSessionResponse.NoSessionCreated));
}
Expand All @@ -96,21 +84,33 @@ public async Task ShouldReturnFailure_GivenResponseContainsNoSession()
public Property ShouldReturnFailure_GivenStatusCodeIsFailure() =>
Prop.ForAll(
FsCheckExtensions.GetInvalidStatusCodes(),
statusCode => this.VerifyReturnsFailureGivenStatusCodeIsFailure(statusCode).Wait());
Arb.From<string>(),
(statusCode, message) => this.VerifyReturnsFailureGivenStatusCodeIsFailure(statusCode, message).Wait());

private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode statusCode)
private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode code, string message)
{
const string expectedResponse = "Some reason session wasn't created.";
var expectedBody = message is null
? null
: this.jsonSerializer.SerializeObject(new ErrorResponse(((int) code).ToString(), message));
this.server
.Given(WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token)
.WithPath(CreateSessionRequest.CreateSessionEndpoint)
.WithBody(this.request.GetUrlEncoded())
.UsingPost())
.RespondWith(Response.Create()
.WithStatusCode(statusCode)
.WithBody(expectedResponse));
.Given(this.CreateChangeStreamLayoutRequest())
.RespondWith(CreateChangeStreamLayoutResponse(code, expectedBody));
var result = await this.client.CreateSessionAsync(this.request);
result.Should().BeFailure(HttpFailure.From(statusCode, expectedResponse));
result.Should().BeFailure(HttpFailure.From(code, message ?? string.Empty));
}

private IRequestBuilder CreateChangeStreamLayoutRequest() =>
WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token)
.WithPath(CreateSessionRequest.CreateSessionEndpoint)
.WithBody(this.request.GetUrlEncoded())
.UsingPost();

private static IResponseBuilder CreateChangeStreamLayoutResponse(HttpStatusCode code, string body) =>
body is null
? CreateChangeStreamLayoutResponse(code)
: CreateChangeStreamLayoutResponse(code).WithBody(body);

private static IResponseBuilder CreateChangeStreamLayoutResponse(HttpStatusCode code) =>
Response.Create().WithStatusCode(code);
}
}
61 changes: 33 additions & 28 deletions Vonage.Video.Beta.Test/Video/Sessions/GetStream/GetStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,6 @@ public Property ShouldReturnFailure_GivenApiResponseIsError() =>
Arb.From<string>(),
(statusCode, message) => this.VerifyReturnsFailureGivenStatusCodeIsFailure(statusCode, message).Wait());

private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode code, string message)
{
var expectedBody = this.jsonSerializer.SerializeObject(new ErrorResponse(((int) code).ToString(), message));
this.server
.Given(this.CreateGetStreamRequest())
.RespondWith(CreateGetStreamResponse(code, expectedBody));
var result = await this.request.BindAsync(requestValue => this.client.GetStreamAsync(requestValue));
result.Should().BeFailure(HttpFailure.From(code, message));
}

private string GetPathFromRequest() =>
this.request.Match(value => value.GetEndpointPath(), failure => string.Empty);

[Property]
public Property ShouldReturnFailure_GivenApiErrorCannotBeParsed() =>
Prop.ForAll(
Expand All @@ -75,17 +62,6 @@ public Property ShouldReturnFailure_GivenApiErrorCannotBeParsed() =>
(statusCode, jsonError) =>
this.VerifyReturnsFailureGivenErrorCannotBeParsed(statusCode, jsonError).Wait());

private async Task VerifyReturnsFailureGivenErrorCannotBeParsed(HttpStatusCode code, string jsonError)
{
var expectedFailureMessage = $"Unable to deserialize '{jsonError}' into '{nameof(ErrorResponse)}'.";
this.server
.Given(this.CreateGetStreamRequest())
.RespondWith(CreateGetStreamResponse(code,
jsonError));
var result = await this.request.BindAsync(requestValue => this.client.GetStreamAsync(requestValue));
result.Should().BeFailure(ResultFailure.FromErrorMessage(expectedFailureMessage));
}

[Fact]
public async Task ShouldReturnSuccess_GivenApiResponseIsSuccess()
{
Expand All @@ -104,9 +80,6 @@ public async Task ShouldReturnSuccess_GivenApiResponseIsSuccess()
});
}

private IRequestBuilder CreateGetStreamRequest() =>
WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token).WithPath(this.path).UsingGet();

[Fact]
public async Task ShouldReturnFailure_GivenApiResponseCannotBeParsed()
{
Expand All @@ -119,7 +92,39 @@ public async Task ShouldReturnFailure_GivenApiResponseCannotBeParsed()
result.Should().BeFailure(ResultFailure.FromErrorMessage(expectedFailureMessage));
}

private async Task VerifyReturnsFailureGivenStatusCodeIsFailure(HttpStatusCode code, string message)
{
var expectedBody = message is null
? null
: this.jsonSerializer.SerializeObject(new ErrorResponse(((int) code).ToString(), message));
this.server
.Given(this.CreateGetStreamRequest())
.RespondWith(CreateGetStreamResponse(code, expectedBody));
var result = await this.request.BindAsync(requestValue => this.client.GetStreamAsync(requestValue));
result.Should().BeFailure(HttpFailure.From(code, message ?? string.Empty));
}

private string GetPathFromRequest() =>
this.request.Match(value => value.GetEndpointPath(), failure => string.Empty);

private async Task VerifyReturnsFailureGivenErrorCannotBeParsed(HttpStatusCode code, string jsonError)
{
var expectedFailureMessage = $"Unable to deserialize '{jsonError}' into '{nameof(ErrorResponse)}'.";
this.server
.Given(this.CreateGetStreamRequest())
.RespondWith(CreateGetStreamResponse(code,
jsonError));
var result = await this.request.BindAsync(requestValue => this.client.GetStreamAsync(requestValue));
result.Should().BeFailure(ResultFailure.FromErrorMessage(expectedFailureMessage));
}

private IRequestBuilder CreateGetStreamRequest() =>
WireMockExtensions.BuildRequestWithAuthenticationHeader(this.token).WithPath(this.path).UsingGet();

private static IResponseBuilder CreateGetStreamResponse(HttpStatusCode code, string body) =>
Response.Create().WithStatusCode(code).WithBody(body);
body is null ? CreateGetStreamResponse(code) : CreateGetStreamResponse(code).WithBody(body);

private static IResponseBuilder CreateGetStreamResponse(HttpStatusCode code) =>
Response.Create().WithStatusCode(code);
}
}
Loading

0 comments on commit 071eeca

Please sign in to comment.