diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeApp-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeApp-response.json new file mode 100644 index 000000000..9c70f91e3 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeApp-response.json @@ -0,0 +1,52 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "app", + "from": { + "type": "app" + }, + "to": { + "type": "app", + "user": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeInitiatorAdmin-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeInitiatorAdmin-response.json new file mode 100644 index 000000000..d6321b825 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeInitiatorAdmin-response.json @@ -0,0 +1,50 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "invited": { + "is_system": true + } + }, + "channel": { + "type": "app", + "from": { + "type": "string" + }, + "to": { + "type": "app", + "user": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMMS-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMMS-response.json new file mode 100644 index 000000000..37189a302 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMMS-response.json @@ -0,0 +1,51 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "mms", + "from": { + "type": "mms" + }, + "to": { + "number": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMessenger-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMessenger-response.json new file mode 100644 index 000000000..79b93ba19 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeMessenger-response.json @@ -0,0 +1,51 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "messenger", + "from": { + "type": "messenger" + }, + "to": { + "id": "app" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializePhone-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializePhone-response.json new file mode 100644 index 000000000..7dc9bffe7 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializePhone-response.json @@ -0,0 +1,52 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "phone", + "from": { + "type": "phone" + }, + "to": { + "type": "phone", + "number": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeSMS-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeSMS-response.json new file mode 100644 index 000000000..8e0e4202f --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeSMS-response.json @@ -0,0 +1,52 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "sms", + "from": { + "type": "sms" + }, + "to": { + "type": "sms", + "number": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeViber-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeViber-response.json new file mode 100644 index 000000000..42763e702 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeViber-response.json @@ -0,0 +1,51 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "viber", + "from": { + "type": "viber" + }, + "to": { + "id": "app" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeWhatsApp-response.json b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeWhatsApp-response.json new file mode 100644 index 000000000..2c1614bf9 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/Data/ShouldDeserializeWhatsApp-response.json @@ -0,0 +1,51 @@ +{ + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "conversation_id": "CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "state": "JOINED", + "timestamp": { + "invited": "2020-01-01T14:00:00.00Z", + "joined": "2020-01-01T14:00:00.00Z", + "left": "2020-01-01T14:00:00.00Z" + }, + "initiator": { + "joined": { + "is_system": true, + "user_id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "member_id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + }, + "channel": { + "type": "whatsapp", + "from": { + "type": "whatsapp" + }, + "to": { + "number": "string" + } + }, + "media": { + "audio_settings": { + "enabled": true, + "earmuffed": true, + "muted": true + }, + "audio": true + }, + "knocking_id": "string", + "invited_by": "MEM-63f61863-4a51-4f6b-86e1-46edebio0378", + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/E2ETest.cs b/Vonage.Test/Conversations/GetMember/E2ETest.cs new file mode 100644 index 000000000..794f26ed7 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/E2ETest.cs @@ -0,0 +1,69 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Vonage.Conversations; +using Vonage.Conversations.GetMember; +using Vonage.Test.Common.Extensions; +using WireMock.ResponseBuilders; +using Xunit; + +namespace Vonage.Test.Conversations.GetMember; + +[Trait("Category", "E2E")] +public class E2ETest : E2EBase +{ + public E2ETest() : base(typeof(E2ETest).Namespace) + { + } + + [Fact] + public async Task GetMember_App() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeApp), SerializationTest.VerifyAppResponse); + + [Fact] + public async Task GetMember_Messenger() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeMessenger), + SerializationTest.VerifyMessengerResponse); + + [Fact] + public async Task GetMember_MMS() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeMMS), SerializationTest.VerifyMMSResponse); + + [Fact] + public async Task GetMember_Phone() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializePhone), + SerializationTest.VerifyPhoneResponse); + + [Fact] + public async Task GetMember_SMS() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeSMS), SerializationTest.VerifySMSResponse); + + [Fact] + public async Task GetMember_Viber() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeViber), + SerializationTest.VerifyViberResponse); + + [Fact] + public async Task GetMember_WhatsApp() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeWhatsApp), + SerializationTest.VerifyWhatsAppResponse); + + [Fact] + public async Task GetMember_InitiatorAdmin() => + await this.VerifyGetMember(nameof(SerializationTest.ShouldDeserializeInitiatorAdmin), + SerializationTest.VerifyInitiatorAdminResponse); + + private async Task VerifyGetMember(string response, Action verification) + { + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/v1/conversations/CON-123/members/MEM-123") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .UsingGet()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(response))); + await this.Helper.VonageClient.ConversationsClient + .GetMemberAsync(GetMemberRequest.Parse("CON-123", "MEM-123")) + .Should() + .BeSuccessAsync(verification); + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/RequestTest.cs b/Vonage.Test/Conversations/GetMember/RequestTest.cs new file mode 100644 index 000000000..9782e613e --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/RequestTest.cs @@ -0,0 +1,48 @@ +using Vonage.Conversations.GetMember; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.Conversations.GetMember; + +[Trait("Category", "Request")] +public class RequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + GetMemberRequest.Parse("CON-123", "MEM-123") + .Map(request => request.GetEndpointPath()) + .Should() + .BeSuccess("/v1/conversations/CON-123/members/MEM-123"); + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Parse_ShouldReturnFailure_GivenConversationIdIsEmpty(string invalidId) => + GetMemberRequest.Parse(invalidId, "MEM-123") + .Should() + .BeParsingFailure("ConversationId cannot be null or whitespace."); + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Parse_ShouldReturnFailure_GivenMemberIdIsEmpty(string invalidId) => + GetMemberRequest.Parse("CON-123", invalidId) + .Should() + .BeParsingFailure("MemberId cannot be null or whitespace."); + + [Fact] + public void Parse_ShouldSetConversationId() => + GetMemberRequest.Parse("CON-123", "MEM-123") + .Map(request => request.ConversationId) + .Should() + .BeSuccess("CON-123"); + + [Fact] + public void Parse_ShouldSetMemberId() => + GetMemberRequest.Parse("CON-123", "MEM-123") + .Map(request => request.MemberId) + .Should() + .BeSuccess("MEM-123"); +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMember/SerializationTest.cs b/Vonage.Test/Conversations/GetMember/SerializationTest.cs new file mode 100644 index 000000000..b80f564e4 --- /dev/null +++ b/Vonage.Test/Conversations/GetMember/SerializationTest.cs @@ -0,0 +1,218 @@ +using System; +using FluentAssertions; +using Vonage.Common; +using Vonage.Conversations; +using Vonage.Serialization; +using Vonage.Test.Common; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.Conversations.GetMember; + +[Trait("Category", "Serialization")] +public class SerializationTest +{ + private readonly SerializationTestHelper helper = new SerializationTestHelper( + typeof(SerializationTest).Namespace, + JsonSerializerBuilder.BuildWithSnakeCase()); + + [Fact] + public void ShouldDeserializeApp() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyAppResponse); + + [Fact] + public void ShouldDeserializeInitiatorAdmin() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyInitiatorAdminResponse); + + [Fact] + public void ShouldDeserializeMessenger() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyMessengerResponse); + + [Fact] + public void ShouldDeserializeViber() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyViberResponse); + + [Fact] + public void ShouldDeserializeMMS() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyMMSResponse); + + [Fact] + public void ShouldDeserializeWhatsApp() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyWhatsAppResponse); + + [Fact] + public void ShouldDeserializePhone() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyPhoneResponse); + + [Fact] + public void ShouldDeserializeSMS() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifySMSResponse); + + internal static void VerifyAppResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "app", + new MemberChannelFrom("app"), + new MemberChannelTo("app", "string", null, null) + )); + } + + internal static void VerifyMessengerResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "messenger", + new MemberChannelFrom("messenger"), + new MemberChannelTo(null, null, null, "app") + )); + } + + internal static void VerifyViberResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "viber", + new MemberChannelFrom("viber"), + new MemberChannelTo(null, null, null, "app") + )); + } + + internal static void VerifyMMSResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "mms", + new MemberChannelFrom("mms"), + new MemberChannelTo(null, null, "string", null) + )); + } + + internal static void VerifyWhatsAppResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "whatsapp", + new MemberChannelFrom("whatsapp"), + new MemberChannelTo(null, null, "string", null) + )); + } + + internal static void VerifyPhoneResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "phone", + new MemberChannelFrom("phone"), + new MemberChannelTo("phone", null, "string", null) + )); + } + + internal static void VerifySMSResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + new MemberInitiatorJoined(true, "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391"), + null + )); + response.Channel.Should().Be(new MemberChannel( + "sms", + new MemberChannelFrom("sms"), + new MemberChannelTo("sms", null, "string", null) + )); + } + + internal static void VerifyInitiatorAdminResponse(Member response) + { + VerifyResponse(response); + response.Initiator.Should().Be(new MemberInitiator( + null, + new MemberInitiatorInvited(true) + )); + response.Channel.Should().Be(new MemberChannel( + "app", + new MemberChannelFrom("string"), + new MemberChannelTo("app", "string", null, null) + )); + } + + private static void VerifyResponse(Member response) + { + response.Id.Should().Be("MEM-63f61863-4a51-4f6b-86e1-46edebio0391"); + response.ConversationId.Should().Be("CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a"); + response.State.Should().Be("JOINED"); + response.KnockingId.Should().Be("string"); + response.InvitedBy.Should().Be("MEM-63f61863-4a51-4f6b-86e1-46edebio0378"); + response.Timestamp.Should().Be(new MemberTimestamp( + DateTimeOffset.Parse("2020-01-01T14:00:00.00Z"), + DateTimeOffset.Parse("2020-01-01T14:00:00.00Z"), + DateTimeOffset.Parse("2020-01-01T14:00:00.00Z") + )); + response.Media.Should().Be(new MemberMedia( + new MemberMediaSettings(true, true, true), + true + )); + response.Links.Should() + .Be(new HalLink(new Uri( + "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391"))); + response.Embedded.Should().Be(new MemberEmbedded( + new MemberEmbeddedUser( + "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "my_user_name", + "My User Name", + new HalLinks + { + Self = new HalLink( + new Uri("https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec")), + } + ) + )); + } +} \ No newline at end of file diff --git a/Vonage.Test/Vonage.Test.csproj b/Vonage.Test/Vonage.Test.csproj index f7db4d6ff..ef40fa0b3 100644 --- a/Vonage.Test/Vonage.Test.csproj +++ b/Vonage.Test/Vonage.Test.csproj @@ -1134,6 +1134,30 @@ Always + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/Vonage/Conversations/ConversationsClient.cs b/Vonage/Conversations/ConversationsClient.cs index a99f4e221..b897322eb 100644 --- a/Vonage/Conversations/ConversationsClient.cs +++ b/Vonage/Conversations/ConversationsClient.cs @@ -5,93 +5,50 @@ using Vonage.Conversations.DeleteConversation; using Vonage.Conversations.GetConversation; using Vonage.Conversations.GetConversations; +using Vonage.Conversations.GetMember; using Vonage.Conversations.GetUserConversations; using Vonage.Conversations.UpdateConversation; using Vonage.Serialization; namespace Vonage.Conversations; -/// -/// Exposes Conversations features. -/// -public interface IConversationsClient -{ - /// - /// Creates a conversation. - /// - /// The request. - /// Success or Failure. - Task> CreateConversationAsync(Result request); - - /// - /// Deletes a conversation. - /// - /// The request. - /// Success or Failure. - Task> DeleteConversationAsync(Result request); - - /// - /// Retrieves a conversation. - /// - /// The request. - /// Success or Failure. - Task> GetConversationAsync(Result request); - - /// - /// Retrieves conversations. - /// - /// The request. - /// Success or Failure. - Task> GetConversationsAsync(Result request); - - /// - /// Retrieves conversations for a user. - /// - /// The request. - /// Success or Failure. - Task> GetUserConversationsAsync(Result request); - - /// - /// Updates a conversation. - /// - /// The request. - /// Success or Failure. - Task> UpdateConversationAsync(Result request); -} - internal class ConversationsClient : IConversationsClient { private readonly VonageHttpClient vonageClient; - + /// /// Creates a new client. /// /// The client configuration. internal ConversationsClient(VonageHttpClientConfiguration configuration) => this.vonageClient = new VonageHttpClient(configuration, JsonSerializerBuilder.BuildWithSnakeCase()); - + /// public Task> CreateConversationAsync(Result request) => this.vonageClient.SendWithResponseAsync(request); - + /// public Task> DeleteConversationAsync(Result request) => this.vonageClient.SendAsync(request); - + /// public Task> GetConversationAsync(Result request) => this.vonageClient.SendWithResponseAsync(request); - + + /// + public Task> GetMemberAsync(Result request) => + this.vonageClient.SendWithResponseAsync(request); + /// public Task> GetConversationsAsync(Result request) => this.vonageClient.SendWithResponseAsync(request); - + /// public Task> GetUserConversationsAsync( Result request) => this.vonageClient.SendWithResponseAsync(request); - + /// public Task> UpdateConversationAsync(Result request) => this.vonageClient.SendWithResponseAsync(request); diff --git a/Vonage/Conversations/GetMember/GetMemberRequest.cs b/Vonage/Conversations/GetMember/GetMemberRequest.cs new file mode 100644 index 000000000..55fef6307 --- /dev/null +++ b/Vonage/Conversations/GetMember/GetMemberRequest.cs @@ -0,0 +1,46 @@ +using System.Net.Http; +using Vonage.Common.Client; +using Vonage.Common.Monads; +using Vonage.Common.Validation; + +namespace Vonage.Conversations.GetMember; + +/// +public readonly struct GetMemberRequest : IVonageRequest +{ + /// + /// The conversation Id. + /// + public string ConversationId { get; private init; } + + /// + /// The member Id. + /// + public string MemberId { get; private init; } + + /// + public HttpRequestMessage BuildRequestMessage() => VonageRequestBuilder + .Initialize(HttpMethod.Get, this.GetEndpointPath()) + .Build(); + + /// + public string GetEndpointPath() => $"/v1/conversations/{this.ConversationId}/members/{this.MemberId}"; + + /// + /// Parses the input into a GetMemberRequest. + /// + /// The conversation Id. + /// The member Id. + /// A success state with the request if the parsing succeeded. A failure state with an error if it failed. + public static Result Parse(string conversationId, string memberId) => + Result + .FromSuccess(new GetMemberRequest {ConversationId = conversationId, MemberId = memberId}) + .Map(InputEvaluation.Evaluate) + .Bind(evaluation => evaluation.WithRules(VerifyConversationId, VerifyMemberId)); + + private static Result VerifyConversationId(GetMemberRequest request) => + InputValidation.VerifyNotEmpty(request, request.ConversationId, nameof(ConversationId)); + + private static Result VerifyMemberId(GetMemberRequest request) => + InputValidation.VerifyNotEmpty(request, request.MemberId, nameof(MemberId)); +} \ No newline at end of file diff --git a/Vonage/Conversations/IConversationsClient.cs b/Vonage/Conversations/IConversationsClient.cs new file mode 100644 index 000000000..f79e48455 --- /dev/null +++ b/Vonage/Conversations/IConversationsClient.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using Vonage.Common.Monads; +using Vonage.Conversations.CreateConversation; +using Vonage.Conversations.DeleteConversation; +using Vonage.Conversations.GetConversation; +using Vonage.Conversations.GetConversations; +using Vonage.Conversations.GetMember; +using Vonage.Conversations.GetUserConversations; +using Vonage.Conversations.UpdateConversation; + +namespace Vonage.Conversations; + +/// +/// Exposes Conversations features. +/// +public interface IConversationsClient +{ + /// + /// Creates a conversation. + /// + /// The request. + /// Success or Failure. + Task> CreateConversationAsync(Result request); + + /// + /// Deletes a conversation. + /// + /// The request. + /// Success or Failure. + Task> DeleteConversationAsync(Result request); + + /// + /// Retrieves a conversation. + /// + /// The request. + /// Success or Failure. + Task> GetConversationAsync(Result request); + + /// + /// Retrieves a member. + /// + /// The request. + /// Success or Failure. + Task> GetMemberAsync(Result request); + + /// + /// Retrieves conversations. + /// + /// The request. + /// Success or Failure. + Task> GetConversationsAsync(Result request); + + /// + /// Retrieves conversations for a user. + /// + /// The request. + /// Success or Failure. + Task> GetUserConversationsAsync(Result request); + + /// + /// Updates a conversation. + /// + /// The request. + /// Success or Failure. + Task> UpdateConversationAsync(Result request); +} \ No newline at end of file diff --git a/Vonage/Conversations/Member.cs b/Vonage/Conversations/Member.cs new file mode 100644 index 000000000..6a8917051 --- /dev/null +++ b/Vonage/Conversations/Member.cs @@ -0,0 +1,127 @@ +using System; +using System.Text.Json.Serialization; +using Vonage.Common; + +namespace Vonage.Conversations; + +/// +/// Represents a member. +/// +/// The member Id. +/// The conversation Id. +/// The state that the member is in. Possible values are INVITED, JOINED, LEFT, or UNKNOWN. +/// +/// +/// +/// +/// The member timestamps. +/// +/// +/// +public record Member( + string Id, + string ConversationId, + string State, + string KnockingId, + string InvitedBy, + MemberChannel Channel, + [property: JsonPropertyName("_embedded")] + MemberEmbedded Embedded, + MemberTimestamp Timestamp, + MemberInitiator Initiator, + MemberMedia Media, + [property: JsonPropertyName("_links")] HalLink Links +); + +/// +/// Represents a channel. +/// +/// The channel type. +/// Which channel types this member accepts messages from (if not set, all are accepted) +/// Settings which control who this member can send messages to. +public record MemberChannel(string Type, MemberChannelFrom From, MemberChannelTo To); + +/// +/// Represents the source channel. +/// +/// +/// Comma separated list, can include channel types app, phone, sms, mms, whatsapp , viber, or +/// messenger. +/// +public record MemberChannelFrom(string Type); + +/// +/// Represents the destination channel. +/// +/// The channel type. +/// The user ID of the member that this member can send messages to. +/// The phone number of the member that this member can send messages to. +/// The Id. +public record MemberChannelTo(string Type, string User, string Number, string Id); + +/// +/// Represents the member timestamps. +/// +/// The time that the member was invited to a conversation +/// The time that the member joined the conversation +/// The time that the member left the conversation +public record MemberTimestamp(DateTimeOffset Invited, DateTimeOffset Joined, DateTimeOffset Left); + +/// +/// Represents which user initiated the conversation. +/// +/// When invited by Admin JWT. +/// When invited by User. +public record MemberInitiator(MemberInitiatorJoined Joined, MemberInitiatorInvited Invited); + +/// +/// Represents a member invited by User. +/// +/// false if the user was invited by a user. +/// The user Id. +/// The member Id. +public record MemberInitiatorJoined(bool IsSystem, string UserId, string MemberId); + +/// +/// Represents a member invited by Admin JWT. +/// +/// true if the user was invited by an admin JWT. +public record MemberInitiatorInvited(bool IsSystem); + +/// +/// Represents details about the current media setting states. +/// +/// Audio settings +/// Indicates whether an audio connection is possible. +public record MemberMedia(MemberMediaSettings AudioSettings, bool Audio); + +/// +/// Represents audio settings. +/// +/// Is audio currently enabled. +/// Is audio currently earmuffed. +/// Is audio currently muted. +public record MemberMediaSettings( + bool Enabled, + [property: JsonPropertyName("earmuffed")] + bool EarMuffed, + bool Muted); + +/// +/// Represents the embedded data. +/// +/// The user. +public record MemberEmbedded(MemberEmbeddedUser User); + +/// +/// Represents a user. +/// +/// The user Id. +/// The unique user name. +/// The display name. +/// Link to the user. +public record MemberEmbeddedUser( + string Id, + string Name, + string DisplayName, + [property: JsonPropertyName("_links")] HalLinks Links); \ No newline at end of file