Skip to content

Commit

Permalink
feat: implement GetSession for ExperienceComposer
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr00d committed Jun 21, 2024
1 parent fc15060 commit ea3ec87
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "1248e7070b81464c9789f46ad10e7764",
"sessionId": "flR1ZSBPY3QgMjkgMTI6MTM6MjMgUERUIDIwMTN",
"applicationId": "93e36bb9-b72c-45b6-a9ea-5c37dbc49906",
"createdAt": 1437676551000,
"callbackUrl": "https://example.com/video/events",
"updatedAt": 1437676551000,
"name": "Composed stream for Live event #1",
"url": "https://example.com/",
"resolution": "720x1280",
"status": "failed",
"streamId": "e32445b743678c98230f238",
"reason": "Could not load URL"
}
32 changes: 32 additions & 0 deletions Vonage.Test/Video/ExperienceComposer/GetSession/E2ETest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Vonage.Test.Common.Extensions;
using Vonage.Video.ExperienceComposer.GetSession;
using WireMock.ResponseBuilders;
using Xunit;

namespace Vonage.Test.Video.ExperienceComposer.GetSession;

[Trait("Category", "E2E")]
public class E2ETest : E2EBase
{
public E2ETest() : base(typeof(E2ETest).Namespace)
{
}

[Fact]
public async Task GetSession()
{
this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create()
.WithPath("/v2/project/e3e78a75-221d-41ec-8846-25ae3db1943a/render/EXP-123")
.WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue)
.UsingGet())
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK)
.WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserialize200))));
await this.Helper.VonageClient.VideoClient.ExperienceComposerClient
.GetSessionAsync(GetSessionRequest.Parse(new Guid("e3e78a75-221d-41ec-8846-25ae3db1943a"), "EXP-123"))
.Should()
.BeSuccessAsync(SerializationTest.BuildExpectedSession());
}
}
49 changes: 49 additions & 0 deletions Vonage.Test/Video/ExperienceComposer/GetSession/RequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using Vonage.Test.Common.Extensions;
using Vonage.Video.ExperienceComposer.GetSession;
using Xunit;

namespace Vonage.Test.Video.ExperienceComposer.GetSession;

[Trait("Category", "Request")]
public class RequestTest
{
private const string ValidExperienceComposerId = "EXP-123";
private readonly Guid validApplicationId = Guid.NewGuid();

[Fact]
public void GetEndpointPath_ShouldReturnApiEndpoint() =>
GetSessionRequest.Parse(this.validApplicationId, ValidExperienceComposerId)
.Map(request => request.GetEndpointPath())
.Should()
.BeSuccess($"/v2/project/{this.validApplicationId}/render/EXP-123");

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void Parse_ShouldReturnFailure_GivenExperienceComposerIdIsEmpty(string invalidId) =>
GetSessionRequest.Parse(Guid.NewGuid(), invalidId)
.Should()
.BeParsingFailure("ExperienceComposerId cannot be null or whitespace.");

[Fact]
public void Parse_ShouldReturnFailure_GivenApplicationIdIsEmpty() =>
GetSessionRequest.Parse(Guid.Empty, ValidExperienceComposerId)
.Should()
.BeParsingFailure("ApplicationId cannot be empty.");

[Fact]
public void Parse_ShouldSetApplicationId() =>
GetSessionRequest.Parse(this.validApplicationId, ValidExperienceComposerId)
.Map(request => request.ApplicationId)
.Should()
.BeSuccess(this.validApplicationId);

[Fact]
public void Parse_ShouldSetExperienceComposerId() =>
GetSessionRequest.Parse(this.validApplicationId, ValidExperienceComposerId)
.Map(request => request.ExperienceComposerId)
.Should()
.BeSuccess(ValidExperienceComposerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Vonage.Serialization;
using Vonage.Server;
using Vonage.Test.Common;
using Vonage.Test.Common.Extensions;
using Vonage.Video.ExperienceComposer;
using Xunit;

namespace Vonage.Test.Video.ExperienceComposer.GetSession;

[Trait("Category", "Serialization")]
public class SerializationTest
{
private readonly SerializationTestHelper helper = new SerializationTestHelper(
typeof(SerializationTest).Namespace,
JsonSerializerBuilder.BuildWithCamelCase());

[Fact]
public void ShouldDeserialize200() => this.helper.Serializer
.DeserializeObject<Session>(this.helper.GetResponseJson())
.Should()
.BeSuccess(BuildExpectedSession());

internal static Session BuildExpectedSession() =>
new Session("1248e7070b81464c9789f46ad10e7764",
"flR1ZSBPY3QgMjkgMTI6MTM6MjMgUERUIDIwMTN",
new Guid("93e36bb9-b72c-45b6-a9ea-5c37dbc49906"),
1437676551000,
new Uri("https://example.com/video/events"),
1437676551000,
"Composed stream for Live event #1",
new Uri("https://example.com/"),
RenderResolution.HighDefinitionPortrait,
SessionStatus.Failed,
"e32445b743678c98230f238",
"Could not load URL");
}
3 changes: 3 additions & 0 deletions Vonage.Test/Vonage.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,9 @@
<None Update="NumberVerification\Verify\Data\ShouldSerializeWithPeriod-request.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Video\ExperienceComposer\GetSession\Data\ShouldDeserialize200-response.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="node ../.scripts/init.js"/>
Expand Down
29 changes: 29 additions & 0 deletions Vonage/Video/ExperienceComposer/ExperienceComposerClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Vonage.Common.Client;
using Vonage.Common.Monads;
using Vonage.Serialization;
using Vonage.Video.ExperienceComposer.GetSession;

namespace Vonage.Video.ExperienceComposer;

/// <summary>
/// Represents a client exposing Experience Composer features.
/// </summary>
public class ExperienceComposerClient
{
private readonly VonageHttpClient vonageClient;

internal ExperienceComposerClient(VonageHttpClientConfiguration configuration) => this.vonageClient =
new VonageHttpClient(configuration, JsonSerializerBuilder.BuildWithCamelCase());

/// <summary>
/// Retrieves details on an Experience Composer session.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>
/// A success state with the archive if the operation succeeded. A failure state with the error message if it
/// failed.
/// </returns>
public Task<Result<Session>> GetSessionAsync(Result<GetSessionRequest> request) =>
this.vonageClient.SendWithResponseAsync<GetSessionRequest, Session>(request);
}
50 changes: 50 additions & 0 deletions Vonage/Video/ExperienceComposer/GetSession/GetSessionRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Net.Http;
using Vonage.Common.Client;
using Vonage.Common.Client.Builders;
using Vonage.Common.Monads;
using Vonage.Common.Validation;

namespace Vonage.Video.ExperienceComposer.GetSession;

/// <summary>
/// Represents a request to retrieve a session.
/// </summary>
public readonly struct GetSessionRequest : IVonageRequest, IHasApplicationId
{
/// <inheritdoc />
public Guid ApplicationId { get; internal init; }

/// <summary>
/// ID of the Experience Composer instance
/// </summary>
public string ExperienceComposerId { get; internal init; }

/// <inheritdoc />
public HttpRequestMessage BuildRequestMessage() =>
VonageRequestBuilder
.Initialize(HttpMethod.Get, this.GetEndpointPath())
.Build();

/// <inheritdoc />
public string GetEndpointPath() => $"/v2/project/{this.ApplicationId}/render/{this.ExperienceComposerId}";

/// <summary>
/// Parses the input into a GetEventRequest.
/// </summary>
/// <param name="application">The application Id.</param>
/// <param name="experienceComposerId">The experience composer Id.</param>
/// <returns>A success state with the request if the parsing succeeded. A failure state with an error if it failed.</returns>
public static Result<GetSessionRequest> Parse(Guid application, string experienceComposerId) =>
Result<GetSessionRequest>
.FromSuccess(new GetSessionRequest
{ApplicationId = application, ExperienceComposerId = experienceComposerId})
.Map(InputEvaluation<GetSessionRequest>.Evaluate)
.Bind(evaluation => evaluation.WithRules(VerifyExperienceComposerId, VerifyApplicationId));

private static Result<GetSessionRequest> VerifyExperienceComposerId(GetSessionRequest request) =>
InputValidation.VerifyNotEmpty(request, request.ExperienceComposerId, nameof(ExperienceComposerId));

private static Result<GetSessionRequest> VerifyApplicationId(GetSessionRequest request) =>
InputValidation.VerifyNotEmpty(request, request.ApplicationId, nameof(ApplicationId));
}
78 changes: 78 additions & 0 deletions Vonage/Video/ExperienceComposer/Session.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Vonage.Common.Serialization;
using Vonage.Server;

namespace Vonage.Video.ExperienceComposer;

/// <summary>
/// Represents an Experience Composer session.
/// </summary>
/// <param name="Id">The unique ID for the Experience Composer.</param>
/// <param name="SessionId">The session ID of the Vonage Video session you are working with</param>
/// <param name="ApplicationId">A Vonage Application ID</param>
/// <param name="CreatedAt">
/// The time the Experience Composer started, expressed in milliseconds since the Unix epoch
/// (January 1, 1970, 00:00:00 UTC).
/// </param>
/// <param name="CallbackUrl">The callback URL for Experience Composer events (if one was set).</param>
/// <param name="UpdatedAt">
/// This is the UNIX timestamp when the Experience Composer status was last updated. For this start
/// method, this timestamp matches the createdAt timestamp.
/// </param>
/// <param name="Name">The name of the composed output stream which is published to the session.</param>
/// <param name="Url">
/// A publicly reachable URL controlled by the customer and capable of generating the content to be
/// rendered without user intervention.
/// </param>
/// <param name="Resolution">
/// The resolution of the archive, either "640x480" (SD landscape, the default), "1280x720" (HD
/// landscape), "1920x1080" (FHD landscape), "480x640" (SD portrait), "720x1280" (HD portrait), or "1080x1920" (FHD
/// portrait). You may want to use a portrait aspect ratio for archives that include video streams from mobile devices
/// (which often use the portrait aspect ratio). This property only applies to composed archives. If you set this
/// property and set the outputMode property to "individual", the call to the REST method results in an error.
/// </param>
/// <param name="Status">The session status.</param>
/// <param name="StreamId">The ID of the composed stream being published.</param>
/// <param name="Reason">
/// The reason field is only available when the status is either "stopped" or "failed". If the status
/// is stopped, the reason field will contain either "Max Duration Exceeded" or "Stop Requested." If the status is
/// failed, the reason will contain a more specific error message.
/// </param>
public record Session(
string Id,
string SessionId,
Guid ApplicationId,
long CreatedAt,
Uri CallbackUrl,
long UpdatedAt,
string Name,
Uri Url,
[property: JsonConverter(typeof(EnumDescriptionJsonConverter<RenderResolution>))]
RenderResolution Resolution,
[property: JsonConverter(typeof(EnumDescriptionJsonConverter<SessionStatus>))]
SessionStatus Status,
string StreamId,
string Reason);

/// <summary>
/// </summary>
public enum SessionStatus
{
/// <summary>
/// </summary>
[Description("starting")] Starting,

/// <summary>
/// </summary>
[Description("started")] Started,

/// <summary>
/// </summary>
[Description("stopped")] Stopped,

/// <summary>
/// </summary>
[Description("failed")] Failed,
}
8 changes: 7 additions & 1 deletion Vonage/Video/IVideoClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Vonage.Video.Archives;
using Vonage.Video.Broadcast;
using Vonage.Video.ExperienceComposer;
using Vonage.Video.Moderation;
using Vonage.Video.Sessions;
using Vonage.Video.Signaling;
Expand Down Expand Up @@ -38,7 +39,12 @@ public interface IVideoClient
SignalingClient SignalingClient { get; }

/// <summary>
/// Clients for managing SIP calls in a video session.
/// Client for managing SIP calls in a video session.
/// </summary>
SipClient SipClient { get; }

/// <summary>
/// Client for managing experience composer.
/// </summary>
ExperienceComposerClient ExperienceComposerClient { get; }
}
6 changes: 5 additions & 1 deletion Vonage/Video/VideoClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Vonage.Common.Client;
using Vonage.Video.Archives;
using Vonage.Video.Broadcast;
using Vonage.Video.ExperienceComposer;
using Vonage.Video.Moderation;
using Vonage.Video.Sessions;
using Vonage.Video.Signaling;
Expand All @@ -13,6 +14,8 @@ public class VideoClient : IVideoClient
{
private readonly VonageHttpClientConfiguration configuration;

internal VideoClient(VonageHttpClientConfiguration configuration) => this.configuration = configuration;

/// <inheritdoc />
public ArchiveClient ArchiveClient => new(this.configuration);

Expand All @@ -31,5 +34,6 @@ public class VideoClient : IVideoClient
/// <inheritdoc />
public SipClient SipClient => new(this.configuration);

internal VideoClient(VonageHttpClientConfiguration configuration) => this.configuration = configuration;
/// <inheritdoc />
public ExperienceComposerClient ExperienceComposerClient => new ExperienceComposerClient(this.configuration);
}

0 comments on commit ea3ec87

Please sign in to comment.