diff --git a/examples/Console/AccessingPolymorhycType.cs b/examples/Console/AccessingPolymorhycType.cs
deleted file mode 100644
index e9132ada..00000000
--- a/examples/Console/AccessingPolymorhycType.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Sinch;
-using Sinch.Conversation.Messages.Message;
-
-namespace Examples
-{
- public class AccessingPolymorphicType
- {
- public static void Example()
- {
- var sinchClient = new SinchClient("KEY_ID", "KEY_SECRET", "PROJECT_ID");
- var message = sinchClient.Conversation.Messages.Get("1").Result;
- var messageType = message.AppMessage.Message switch
- {
- CardMessage cardMessage => "card",
- CarouselMessage carouselMessage => "carousel",
- ChoiceMessage choiceMessage => "choice",
- ListMessage listMessage => "list",
- LocationMessage locationMessage => "location",
- MediaMessage mediaMessage => "media",
- TemplateMessage templateMessage => "template",
- TextMessage textMessage => "text",
- _ => "none"
- };
- Console.WriteLine(messageType);
- }
- }
-}
diff --git a/examples/Console/AccessingPolymorphicType.cs b/examples/Console/AccessingPolymorphicType.cs
new file mode 100644
index 00000000..250d3999
--- /dev/null
+++ b/examples/Console/AccessingPolymorphicType.cs
@@ -0,0 +1,33 @@
+using Sinch;
+using Sinch.Verification.Report.Request;
+using Sinch.Verification.Report.Response;
+
+namespace Examples
+{
+ public class AccessingPolymorphicType
+ {
+ public static void Example()
+ {
+ var sinchClient = new SinchClient("KEY_ID", "KEY_SECRET", "PROJECT_ID");
+ var response = sinchClient.Verification("APP_KEY", "APP_SECRET").Verification
+ .ReportId("id", new SmsVerificationReportRequest()
+ {
+ Sms = new SmsVerify()
+ {
+ Code = "123",
+ Cli = "it's a cli"
+ }
+ }).Result;
+ var id = response switch
+ {
+ FlashCallVerificationReportResponse flashCallVerificationReportResponse =>
+ flashCallVerificationReportResponse.Id,
+ PhoneCallVerificationReportResponse phoneCallVerificationReportResponse =>
+ phoneCallVerificationReportResponse.Id,
+ SmsVerificationReportResponse smsVerificationReportResponse => smsVerificationReportResponse.Id,
+ _ => throw new ArgumentOutOfRangeException(nameof(response))
+ };
+ Console.WriteLine(id);
+ }
+ }
+}
diff --git a/src/Sinch/Conversation/Contacts/Contact.cs b/src/Sinch/Conversation/Contacts/Contact.cs
index 261dbe75..3a0f54e1 100644
--- a/src/Sinch/Conversation/Contacts/Contact.cs
+++ b/src/Sinch/Conversation/Contacts/Contact.cs
@@ -6,13 +6,8 @@
namespace Sinch.Conversation.Contacts
{
- public sealed class Contact
+ public sealed class Contact : PropertyMaskQuery
{
- ///
- /// Tracks the fields which where initialized.
- ///
- private readonly ISet _setFields = new HashSet();
-
private List _channelIdentities;
private List _channelPriority;
private string _displayName;
@@ -30,7 +25,7 @@ public List ChannelIdentities
get => _channelIdentities;
set
{
- _setFields.Add(nameof(ChannelIdentities));
+ SetFields.Add(nameof(ChannelIdentities));
_channelIdentities = value;
}
}
@@ -44,7 +39,7 @@ public List ChannelPriority
get => _channelPriority;
set
{
- _setFields.Add(nameof(ChannelPriority));
+ SetFields.Add(nameof(ChannelPriority));
_channelPriority = value;
}
}
@@ -58,7 +53,7 @@ public string DisplayName
get => _displayName;
set
{
- _setFields.Add(nameof(DisplayName));
+ SetFields.Add(nameof(DisplayName));
_displayName = value;
}
}
@@ -72,7 +67,7 @@ public string Email
get => _email;
set
{
- _setFields.Add(nameof(Email));
+ SetFields.Add(nameof(Email));
_email = value;
}
}
@@ -86,7 +81,7 @@ public string ExternalId
get => _externalId;
set
{
- _setFields.Add(nameof(ExternalId));
+ SetFields.Add(nameof(ExternalId));
_externalId = value;
}
}
@@ -100,7 +95,7 @@ public string Id
get => _id;
set
{
- _setFields.Add(nameof(Id));
+ SetFields.Add(nameof(Id));
_id = value;
}
}
@@ -114,7 +109,7 @@ public ConversationLanguage Language
get => _language;
set
{
- _setFields.Add(nameof(Language));
+ SetFields.Add(nameof(Language));
_language = value;
}
}
@@ -128,20 +123,12 @@ public string Metadata
get => _metadata;
set
{
- _setFields.Add(nameof(Metadata));
+ SetFields.Add(nameof(Metadata));
_metadata = value;
}
}
- ///
- /// Get the comma separated snake_case list of properties which were directly initialized in this object.
- /// If, for example, DisplayName and Metadata were set, will return display_name,metadata
- ///
- ///
- internal string GetPropertiesMask()
- {
- return string.Join(',', _setFields.Select(StringUtils.ToSnakeCase));
- }
+
///
/// Returns the string presentation of the object
diff --git a/src/Sinch/Conversation/ConversationChannel.cs b/src/Sinch/Conversation/ConversationChannel.cs
index de376761..5edb7f6a 100644
--- a/src/Sinch/Conversation/ConversationChannel.cs
+++ b/src/Sinch/Conversation/ConversationChannel.cs
@@ -73,5 +73,10 @@ public record ConversationChannel(string Value) : EnumRecord(Value)
/// WeChat channel.
///
public static readonly ConversationChannel WeChat = new("WECHAT");
+
+ ///
+ /// Channel has not been specified
+ ///
+ public static readonly ConversationChannel Unspecified = new("CHANNEL_UNSPECIFIED");
}
}
diff --git a/src/Sinch/Conversation/Conversations/Conversation.cs b/src/Sinch/Conversation/Conversations/Conversation.cs
new file mode 100644
index 00000000..bdf7eef6
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/Conversation.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Text;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using Sinch.Core;
+
+namespace Sinch.Conversation.Conversations
+{
+ ///
+ /// A collection of messages exchanged between a contact and an app. Conversations are normally created on the fly by
+ /// Conversation API once a message is sent and there is no active conversation already. There can be only one active
+ /// conversation at any given time between a particular contact and an app.
+ ///
+ public sealed class Conversation : PropertyMaskQuery
+ {
+ private bool? _active;
+ private ConversationChannel _activeChannel;
+ private string _appId;
+ private string _contactId;
+ private string _correlationId;
+ private string _metadata;
+ private JsonObject _metadataJson;
+
+ ///
+ /// Gets or Sets ActiveChannel
+ ///
+ public ConversationChannel ActiveChannel
+ {
+ get => _activeChannel;
+ set
+ {
+ SetFields.Add(nameof(ActiveChannel));
+ _activeChannel = value;
+ }
+ }
+
+
+ ///
+ /// Flag for whether this conversation is active.
+ ///
+ public bool? Active
+ {
+ get => _active;
+ set
+ {
+ SetFields.Add(nameof(Active));
+ _active = value;
+ }
+ }
+
+ ///
+ /// The ID of the participating app.
+ ///
+ public string AppId
+ {
+ get => _appId;
+ set
+ {
+ SetFields.Add(nameof(AppId));
+ _appId = value;
+ }
+ }
+
+ ///
+ /// The ID of the participating contact.
+ ///
+ public string ContactId
+ {
+ get => _contactId;
+ set
+ {
+ SetFields.Add(nameof(ContactId));
+ _contactId = value;
+ }
+ }
+
+ ///
+ /// The ID of the conversation.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// The timestamp of the latest message in the conversation.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public DateTime LastReceived { get; set; }
+
+ ///
+ /// Arbitrary data set by the Conversation API clients. Up to 1024 characters long.
+ ///
+ public string Metadata
+ {
+ get => _metadata;
+ set
+ {
+ SetFields.Add(nameof(Metadata));
+ _metadata = value;
+ }
+ }
+
+ ///
+ /// Arbitrary data set by the Conversation API clients and/or provided in the conversation_metadata field of a
+ /// SendMessageRequest. A valid JSON object.
+ ///
+ public JsonObject MetadataJson
+ {
+ get => _metadataJson;
+ set
+ {
+ SetFields.Add(nameof(MetadataJson));
+ _metadataJson = value;
+ }
+ }
+
+ public string CorrelationId
+ {
+ get => _correlationId;
+ set
+ {
+ SetFields.Add(nameof(CorrelationId));
+ _correlationId = value;
+ }
+ }
+
+
+ ///
+ /// Returns the string presentation of the object
+ ///
+ /// String presentation of the object
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append("class Conversation {\n");
+ sb.Append(" Active: ").Append(Active).Append("\n");
+ sb.Append(" ActiveChannel: ").Append(ActiveChannel).Append("\n");
+ sb.Append(" AppId: ").Append(AppId).Append("\n");
+ sb.Append(" ContactId: ").Append(ContactId).Append("\n");
+ sb.Append(" Id: ").Append(Id).Append("\n");
+ sb.Append(" LastReceived: ").Append(LastReceived).Append("\n");
+ sb.Append(" Metadata: ").Append(Metadata).Append("\n");
+ sb.Append(" MetadataJson: ").Append(MetadataJson).Append("\n");
+ sb.Append(" CorrelationId: ").Append(CorrelationId).Append("\n");
+ sb.Append("}\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/Conversations.cs b/src/Sinch/Conversation/Conversations/Conversations.cs
new file mode 100644
index 00000000..c20d5b95
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/Conversations.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web;
+using Sinch.Conversation.Conversations.Create;
+using Sinch.Conversation.Conversations.InjectMessage;
+using Sinch.Conversation.Conversations.List;
+using Sinch.Core;
+using Sinch.Logger;
+
+namespace Sinch.Conversation.Conversations
+{
+ ///
+ /// Endpoints for working with the conversation log.
+ ///
+ public interface ISinchConversationConversations
+ {
+ ///
+ /// Creates a new empty conversation. It is generally not needed to create a conversation explicitly since sending or
+ /// receiving a message automatically creates a new conversation if it does not already exist between the given app and
+ /// contact. Creating empty conversation is useful if the metadata of the conversation should be populated when the
+ /// first message in the conversation is a contact message or the first message in the conversation comes out-of-band
+ /// and needs to be injected with InjectMessage endpoint.
+ ///
+ ///
+ ///
+ ///
+ Task Create(CreateConversationRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// This operation lists all conversations that are associated with an app and/or a contact.
+ ///
+ ///
+ ///
+ ///
+ Task List(ListConversationsRequest request,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// This operation lists all conversations automatically that are associated with an app and/or a contact.
+ ///
+ ///
+ ///
+ ///
+ IAsyncEnumerable ListAuto(ListConversationsRequest request,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves a conversation by id. A conversation has two participating entities, an app and a contact.
+ ///
+ /// The unique ID of the conversation. This is generated by the system.
+ ///
+ ///
+ Task Get(string conversationId, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deletes a conversation together with all the messages sent as part of the conversation.
+ ///
+ /// The unique ID of the conversation. This is generated by the system.
+ ///
+ ///
+ Task Delete(string conversationId, CancellationToken cancellationToken = default);
+
+ ///
+ /// This operation stops the referenced conversation, if the conversation is still active. A new conversation will be
+ /// created if a new message is exchanged between the app or contact that was part of the stopped conversation.
+ ///
+ /// The unique ID of the conversation. This is generated by the system.
+ ///
+ ///
+ Task Stop(string conversationId, CancellationToken cancellationToken = default);
+
+ ///
+ /// This operation updates a conversation which can, for instance, be used to update the metadata associated with a
+ /// conversation.
+ ///
+ /// A conversation to update, Id should be set.
+ /// Update strategy for the conversation_metadata field.
+ ///
+ ///
+ Task Update(Conversation conversation,
+ MetadataUpdateStrategy metadataUpdateStrategy = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// This operation injects a conversation message in to a specific conversation.
+ ///
+ ///
+ ///
+ ///
+ Task InjectMessage(InjectMessageRequest injectMessageRequest, CancellationToken cancellationToken = default);
+ }
+
+ internal class ConversationsClient : ISinchConversationConversations
+ {
+ private readonly Uri _baseAddress;
+ private readonly IHttp _http;
+ private readonly ILoggerAdapter _logger;
+ private readonly string _projectId;
+
+ public ConversationsClient(string projectId, Uri baseAddress,
+ ILoggerAdapter logger, IHttp http)
+ {
+ _projectId = projectId;
+ _baseAddress = baseAddress;
+ _logger = logger;
+ _http = http;
+ }
+
+ ///
+ public Task Create(CreateConversationRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var uri = new Uri(_baseAddress, $"v1/projects/{_projectId}/conversations");
+ _logger?.LogDebug("Creating a conversation for {project}", _projectId);
+ return _http.Send(uri, HttpMethod.Post, request,
+ cancellationToken);
+ }
+
+ ///
+ public Task List(ListConversationsRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var uri = new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations?{Utils.ToSnakeCaseQueryString(request)}");
+ _logger?.LogDebug("Listing a conversations for {project}", _projectId);
+ return _http.Send(uri, HttpMethod.Get,
+ cancellationToken);
+ }
+
+ ///
+ public async IAsyncEnumerable ListAuto(ListConversationsRequest request,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ _logger?.LogDebug("Auto Listing conversations for {projectId}", _projectId);
+ do
+ {
+ var query = Utils.ToSnakeCaseQueryString(request);
+ var uri = new Uri(_baseAddress, $"/v1/projects/{_projectId}/conversations?{query}");
+ var response =
+ await _http.Send(uri, HttpMethod.Get, cancellationToken);
+ request.PageToken = response.NextPageToken;
+ foreach (var conversation in response.Conversations) yield return conversation;
+ } while (!string.IsNullOrEmpty(request.PageToken));
+ }
+
+ ///
+ public Task Get(string conversationId, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(conversationId))
+ throw new ArgumentNullException(nameof(conversationId), "Should have a value");
+
+ var uri = new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations/{conversationId}");
+ _logger?.LogDebug("Getting a {conversationId} of {project}", conversationId, _projectId);
+ return _http.Send(uri, HttpMethod.Get,
+ cancellationToken);
+ }
+
+ ///
+ public Task Delete(string conversationId, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(conversationId))
+ throw new ArgumentNullException(nameof(conversationId), "Should have a value");
+
+ var uri = new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations/{conversationId}");
+ _logger?.LogDebug("Deleting a {conversationId} of {project}", conversationId, _projectId);
+ return _http.Send(uri, HttpMethod.Delete,
+ cancellationToken);
+ }
+
+ ///
+ public Task Stop(string conversationId, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(conversationId))
+ throw new ArgumentNullException(nameof(conversationId), "Should have a value");
+
+ var uri = new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations/{conversationId}:stop");
+ _logger?.LogDebug("Stopping a {conversationId} of {project}", conversationId, _projectId);
+ return _http.Send(uri, HttpMethod.Post,
+ cancellationToken);
+ }
+
+ ///
+ public Task Update(Conversation conversation,
+ MetadataUpdateStrategy metadataUpdateStrategy = null,
+ CancellationToken cancellationToken = default)
+ {
+ if (conversation == null)
+ throw new ArgumentNullException(nameof(conversation), "Conversation shouldn't be null");
+
+ if (string.IsNullOrEmpty(conversation.Id))
+ throw new NullReferenceException($"{nameof(conversation.Id)} should have a value");
+
+ var builder = new UriBuilder(new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations/{conversation.Id}"));
+
+ var queryString = HttpUtility.ParseQueryString(string.Empty);
+ var propMask = conversation.GetPropertiesMask();
+ if (!string.IsNullOrEmpty(propMask)) queryString.Add("update_mask", propMask);
+
+ if (metadataUpdateStrategy is not null)
+ queryString.Add("metadata_update_strategy", metadataUpdateStrategy.Value);
+
+ builder.Query = queryString?.ToString()!; // it's okay to pass null.
+
+ _logger?.LogDebug("Updating a {conversationId} of {project}", conversation.Id, _projectId);
+ return _http.Send(builder.Uri, HttpMethod.Patch, conversation,
+ cancellationToken);
+ }
+
+ ///
+ public Task InjectMessage(InjectMessageRequest injectMessageRequest,
+ CancellationToken cancellationToken = default)
+ {
+ if (injectMessageRequest == null)
+ throw new ArgumentNullException(nameof(injectMessageRequest), "Shouldn't be null");
+
+ if (string.IsNullOrEmpty(injectMessageRequest.ConversationId))
+ throw new NullReferenceException(
+ $"{nameof(injectMessageRequest)}.{nameof(injectMessageRequest.ConversationId)} should have a value");
+
+ var uri = new Uri(_baseAddress,
+ $"v1/projects/{_projectId}/conversations/{injectMessageRequest.ConversationId}:inject-message");
+ _logger?.LogDebug("Injecting a message into {conversationId} of {project}",
+ injectMessageRequest.ConversationId, _projectId);
+ return _http.Send(uri, HttpMethod.Post, injectMessageRequest,
+ cancellationToken);
+ }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/Create/CreateConversationRequest.cs b/src/Sinch/Conversation/Conversations/Create/CreateConversationRequest.cs
new file mode 100644
index 00000000..f5f1dc99
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/Create/CreateConversationRequest.cs
@@ -0,0 +1,71 @@
+using System.Text;
+using System.Text.Json.Nodes;
+using Sinch.Conversation.Messages;
+
+namespace Sinch.Conversation.Conversations.Create
+{
+ public class CreateConversationRequest
+ {
+ ///
+ /// Gets or Sets ActiveChannel
+ ///
+ public ConversationChannel ActiveChannel { get; set; }
+
+ ///
+ /// Flag for whether this conversation is active.
+ ///
+ public bool Active { get; set; }
+
+
+ ///
+ /// The ID of the participating app.
+ ///
+#if NET7_0_OR_GREATER
+ public required string AppId { get; set; }
+#else
+ public string AppId { get; set; }
+#endif
+
+
+ ///
+ /// The ID of the participating contact.
+ ///
+#if NET7_0_OR_GREATER
+ public required string ContactId { get; set; }
+#else
+ public string ContactId { get; set; }
+#endif
+
+
+ ///
+ /// Arbitrary data set by the Conversation API clients. Up to 1024 characters long.
+ ///
+ public string Metadata { get; set; }
+
+
+ ///
+ /// Arbitrary data set by the Conversation API clients and/or provided in the `conversation_metadata` field
+ /// of a SendMessageRequest. A valid JSON object.
+ ///
+ public JsonObject MetadataJson { get; set; }
+
+
+ ///
+ /// Returns the string presentation of the object
+ ///
+ /// String presentation of the object
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append("class CreateConversationRequest {\n");
+ sb.Append(" Active: ").Append(Active).Append("\n");
+ sb.Append(" ActiveChannel: ").Append(ActiveChannel).Append("\n");
+ sb.Append(" AppId: ").Append(AppId).Append("\n");
+ sb.Append(" ContactId: ").Append(ContactId).Append("\n");
+ sb.Append(" Metadata: ").Append(Metadata).Append("\n");
+ sb.Append(" MetadataJson: ").Append(MetadataJson).Append("\n");
+ sb.Append("}\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/InjectMessage/InjectMessageRequest.cs b/src/Sinch/Conversation/Conversations/InjectMessage/InjectMessageRequest.cs
new file mode 100644
index 00000000..bddc27ba
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/InjectMessage/InjectMessageRequest.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Text;
+using System.Text.Json.Serialization;
+using Sinch.Conversation.Messages;
+using Sinch.Conversation.Messages.Message;
+
+namespace Sinch.Conversation.Conversations.InjectMessage
+{
+ ///
+ /// A message on a particular channel.
+ ///
+ public sealed class InjectMessageRequest
+ {
+ [JsonIgnore]
+ public string ConversationId { get; set; }
+
+ ///
+ /// Gets or Sets Direction
+ ///
+ public ConversationDirection Direction { get; set; }
+
+ ///
+ /// The processed time of the message in UTC timezone. Must be less than current_time and greater than (current_time -
+ /// 30 days)
+ ///
+#if NET7_0_OR_GREATER
+ public required DateTime AcceptTime { get; set; }
+#else
+ public DateTime AcceptTime { get; set; }
+#endif
+
+ ///
+ /// Gets or Sets AppMessage
+ ///
+ public AppMessage AppMessage { get; set; }
+
+
+ ///
+ /// Gets or Sets ChannelIdentity
+ ///
+ public ChannelIdentity ChannelIdentity { get; set; }
+
+
+ ///
+ /// The ID of the contact registered in the conversation provided.
+ ///
+ public string ContactId { get; set; }
+
+
+ ///
+ /// Gets or Sets ContactMessage
+ ///
+ public ContactMessage ContactMessage { get; set; }
+
+
+ ///
+ /// Optional. Metadata associated with the contact. Up to 1024 characters long.
+ ///
+ public string Metadata { get; set; }
+
+
+ ///
+ /// Returns the string presentation of the object
+ ///
+ /// String presentation of the object
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append("class ConversationMessageInjected {\n");
+ sb.Append(" AcceptTime: ").Append(AcceptTime).Append("\n");
+ sb.Append(" AppMessage: ").Append(AppMessage).Append("\n");
+ sb.Append(" ChannelIdentity: ").Append(ChannelIdentity).Append("\n");
+ sb.Append(" ContactId: ").Append(ContactId).Append("\n");
+ sb.Append(" ContactMessage: ").Append(ContactMessage).Append("\n");
+ sb.Append(" Direction: ").Append(Direction).Append("\n");
+ sb.Append(" Metadata: ").Append(Metadata).Append("\n");
+ sb.Append("}\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/List/ListConversationsRequest.cs b/src/Sinch/Conversation/Conversations/List/ListConversationsRequest.cs
new file mode 100644
index 00000000..a23ce757
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/List/ListConversationsRequest.cs
@@ -0,0 +1,41 @@
+using Sinch.Conversation.Messages;
+
+namespace Sinch.Conversation.Conversations.List
+{
+ public sealed class ListConversationsRequest
+ {
+ ///
+ /// Required. True if only active conversations should be listed.
+ ///
+#if NET7_0_OR_GREATER
+ public required bool OnlyActive { get; set; }
+#else
+ public bool OnlyActive { get; set; }
+#endif
+
+ ///
+ /// At least one of app_id or contact_id must be present.
+ ///
+ public string AppId { get; set; }
+
+ ///
+ /// At least one of app_id or contact_id must be present.
+ ///
+ public string ContactId { get; set; }
+
+ ///
+ /// The maximum number of conversations to fetch. Defaults to 10 and the maximum is 20.
+ ///
+ public int? PageSize { get; set; }
+
+ ///
+ /// Next page token previously returned if any.
+ ///
+ public string PageToken { get; set; }
+
+ ///
+ /// Only fetch conversations from the active_channel
+ ///
+ public ConversationChannel ActiveChannel { get; set; }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/List/ListConversationsResponse.cs b/src/Sinch/Conversation/Conversations/List/ListConversationsResponse.cs
new file mode 100644
index 00000000..eee9fc7f
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/List/ListConversationsResponse.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Sinch.Conversation.Conversations.List
+{
+ public sealed class ListConversationsResponse
+ {
+ ///
+ /// List of conversations matching the search query.
+ ///
+ public IEnumerable Conversations { get; set; }
+
+ ///
+ /// Token that should be included in the next request to fetch the next page.
+ ///
+ public string NextPageToken { get; set; }
+
+ ///
+ /// Total count of conversations
+ ///
+ public int TotalSize { get; set; }
+ }
+}
diff --git a/src/Sinch/Conversation/Conversations/MetadataUpdateStrategy.cs b/src/Sinch/Conversation/Conversations/MetadataUpdateStrategy.cs
new file mode 100644
index 00000000..e83bb1d2
--- /dev/null
+++ b/src/Sinch/Conversation/Conversations/MetadataUpdateStrategy.cs
@@ -0,0 +1,21 @@
+using Sinch.Core;
+
+namespace Sinch.Conversation.Conversations
+{
+ ///
+ /// Update strategy for the conversation_metadata field.
+ ///
+ ///
+ public record MetadataUpdateStrategy(string Value) : EnumRecord(Value)
+ {
+ ///
+ /// The default strategy. Replaces the whole conversation_metadata field with the new value provided.
+ ///
+ public static readonly MetadataUpdateStrategy Replace = new("REPLACE");
+
+ ///
+ /// Patches the conversation_metadata field with the patch provided according to RFC 7386.
+ ///
+ public static readonly MetadataUpdateStrategy MergePatch = new("MERGE_PATCH");
+ }
+}
diff --git a/src/Sinch/Conversation/Messages/Message/AppMessage.cs b/src/Sinch/Conversation/Messages/Message/AppMessage.cs
index bfc52754..5c437ba7 100644
--- a/src/Sinch/Conversation/Messages/Message/AppMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/AppMessage.cs
@@ -1,24 +1,101 @@
-using System.Text;
+using System;
+using System.Text;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
namespace Sinch.Conversation.Messages.Message
{
public class AppMessage
{
+ // Thank you System.Text.Json -_-
+ [JsonConstructor]
+ [Obsolete("Needed for System.Text.Json", true)]
+ public AppMessage()
+ {
+ }
+
+ public AppMessage(ChoiceMessage choiceMessage)
+ {
+ ChoiceMessage = choiceMessage;
+ }
+
+ public AppMessage(LocationMessage locationMessage)
+ {
+ LocationMessage = locationMessage;
+ }
+
+ public AppMessage(MediaMessage mediaMessage)
+ {
+ MediaMessage = mediaMessage;
+ }
+
+ public AppMessage(TemplateMessage templateMessage)
+ {
+ TemplateMessage = templateMessage;
+ }
+
+ public AppMessage(ListMessage listMessage)
+ {
+ ListMessage = listMessage;
+ }
+
+ public AppMessage(TextMessage textMessage)
+ {
+ TextMessage = textMessage;
+ }
+
+ public AppMessage(CardMessage cardMessage)
+ {
+ CardMessage = cardMessage;
+ }
+
+ public AppMessage(CarouselMessage carouselMessage)
+ {
+ CarouselMessage = carouselMessage;
+ }
+
///
/// Optional. Channel specific messages, overriding any transcoding.
/// The key in the map must point to a valid conversation channel as defined by the enum ConversationChannel.
///
- public object ExplicitChannelMessage { get; set; }
+ public JsonObject ExplicitChannelMessage { get; set; }
///
/// Gets or Sets AdditionalProperties
///
public AppMessageAdditionalProperties AdditionalProperties { get; set; }
-
- ///
- /// Message originating from an app
- ///
- public IMessage Message { get; set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public TextMessage TextMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public CardMessage CardMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public CarouselMessage CarouselMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public ChoiceMessage ChoiceMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public LocationMessage LocationMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public MediaMessage MediaMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public TemplateMessage TemplateMessage { get; private set; }
+
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public ListMessage ListMessage { get; private set; }
}
///
diff --git a/src/Sinch/Conversation/Messages/Message/CardMessage.cs b/src/Sinch/Conversation/Messages/Message/CardMessage.cs
index a8fd1abf..466071d6 100644
--- a/src/Sinch/Conversation/Messages/Message/CardMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/CardMessage.cs
@@ -7,7 +7,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// Message containing text, media and choices.
///
- public sealed class CardMessage : IMessage
+ public sealed class CardMessage
{
///
/// Gets or Sets Height
diff --git a/src/Sinch/Conversation/Messages/Message/CarouselMessage.cs b/src/Sinch/Conversation/Messages/Message/CarouselMessage.cs
index fcd16a04..ec0489a5 100644
--- a/src/Sinch/Conversation/Messages/Message/CarouselMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/CarouselMessage.cs
@@ -3,7 +3,7 @@
namespace Sinch.Conversation.Messages.Message
{
- public class CarouselMessage : IMessage
+ public class CarouselMessage
{
///
/// A list of up to 10 cards.
diff --git a/src/Sinch/Conversation/Messages/Message/ChoiceMessage.cs b/src/Sinch/Conversation/Messages/Message/ChoiceMessage.cs
index dddeb0de..5def173b 100644
--- a/src/Sinch/Conversation/Messages/Message/ChoiceMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/ChoiceMessage.cs
@@ -4,7 +4,7 @@
namespace Sinch.Conversation.Messages.Message
{
- public class ChoiceMessage : IMessage
+ public class ChoiceMessage
{
///
/// The number of choices is limited to 10.
diff --git a/src/Sinch/Conversation/Messages/Message/ContactMessage.cs b/src/Sinch/Conversation/Messages/Message/ContactMessage.cs
index b037298f..8d17748f 100644
--- a/src/Sinch/Conversation/Messages/Message/ContactMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/ContactMessage.cs
@@ -1,50 +1,108 @@
-using System.Text;
+using System;
+using System.Text;
+using System.Text.Json.Serialization;
namespace Sinch.Conversation.Messages.Message
{
-
public class ContactMessage
-{ ///
+ {
+ // Thank you System.Text.Json -_-
+ [JsonConstructor]
+ [Obsolete("Needed for System.Text.Json", true)]
+ public ContactMessage()
+ {
+ }
+
+ public ContactMessage(ChoiceResponseMessage choiceResponseMessage)
+ {
+ ChoiceResponseMessage = choiceResponseMessage;
+ }
+
+ public ContactMessage(FallbackMessage fallbackMessage)
+ {
+ FallbackMessage = fallbackMessage;
+ }
+
+ public ContactMessage(LocationMessage locationMessage)
+ {
+ LocationMessage = locationMessage;
+ }
+
+ public ContactMessage(MediaCarouselMessage mediaCardMessage)
+ {
+ MediaCardMessage = mediaCardMessage;
+ }
+
+ public ContactMessage(MediaMessage mediaMessage)
+ {
+ MediaMessage = mediaMessage;
+ }
+
+ public ContactMessage(ReplyTo replyTo)
+ {
+ ReplyTo = replyTo;
+ }
+
+ public ContactMessage(TextMessage textMessage)
+ {
+ TextMessage = textMessage;
+ }
+
+ ///
/// Gets or Sets ChoiceResponseMessage
///
- public ChoiceResponseMessage ChoiceResponseMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public ChoiceResponseMessage ChoiceResponseMessage { get; private set; }
+
///
/// Gets or Sets FallbackMessage
///
- public FallbackMessage FallbackMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public FallbackMessage FallbackMessage { get; private set; }
+
///
/// Gets or Sets LocationMessage
///
- public LocationMessage LocationMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public LocationMessage LocationMessage { get; private set; }
+
///
/// Gets or Sets MediaCardMessage
///
- public MediaCarouselMessage MediaCardMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public MediaCarouselMessage MediaCardMessage { get; private set; }
+
///
/// Gets or Sets MediaMessage
///
- public MediaMessage MediaMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public MediaMessage MediaMessage { get; private set; }
+
///
/// Gets or Sets ReplyTo
///
- public ReplyTo ReplyTo { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public ReplyTo ReplyTo { get; private set; }
+
///
/// Gets or Sets TextMessage
///
- public TextMessage TextMessage { get; set; }
-
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public TextMessage TextMessage { get; private set; }
+
///
/// Returns the string presentation of the object
@@ -64,6 +122,5 @@ public override string ToString()
sb.Append("}\n");
return sb.ToString();
}
+ }
}
-}
-
diff --git a/src/Sinch/Conversation/Messages/Message/Message.cs b/src/Sinch/Conversation/Messages/Message/ConversationMessage.cs
similarity index 93%
rename from src/Sinch/Conversation/Messages/Message/Message.cs
rename to src/Sinch/Conversation/Messages/Message/ConversationMessage.cs
index 77f04cf8..1e1fba12 100644
--- a/src/Sinch/Conversation/Messages/Message/Message.cs
+++ b/src/Sinch/Conversation/Messages/Message/ConversationMessage.cs
@@ -1,69 +1,71 @@
using System;
using System.Text;
+using System.Text.Json.Serialization;
namespace Sinch.Conversation.Messages.Message
{
public class ConversationMessage
{
- ///
- /// Gets or Sets Direction
+ ///
+ /// Gets or Sets Direction
///
public ConversationDirection Direction { get; set; }
///
/// The time Conversation API processed the message.
///
+ [JsonInclude]
public DateTime AcceptTime { get; private set; }
-
+
///
/// Gets or Sets AppMessage
///
public AppMessage AppMessage { get; set; }
-
+
///
/// Gets or Sets ChannelIdentity
///
public ChannelIdentity ChannelIdentity { get; set; }
-
+
///
/// The ID of the contact.
///
public string ContactId { get; set; }
-
+
///
/// Gets or Sets ContactMessage
///
public ContactMessage ContactMessage { get; set; }
-
+
///
/// The ID of the conversation.
///
public string ConversationId { get; set; }
-
+
///
/// The ID of the message.
///
public string Id { get; set; }
-
+
///
/// Optional. Metadata associated with the contact. Up to 1024 characters long.
///
public string Metadata { get; set; }
-
+
///
/// Flag for whether this message was injected.
///
- public bool Injected { get; private set; }
-
+ public bool Injected { get; }
+
///
/// Returns the string presentation of the object
diff --git a/src/Sinch/Conversation/Messages/Message/IMessage.cs b/src/Sinch/Conversation/Messages/Message/IMessage.cs
deleted file mode 100644
index 9308e75a..00000000
--- a/src/Sinch/Conversation/Messages/Message/IMessage.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Sinch.Core;
-
-namespace Sinch.Conversation.Messages.Message
-{
- ///
- /// Marker interface for conversation messages types.
- ///
- [JsonInterfaceConverter(typeof(InterfaceConverter))]
- public interface IMessage
- {
- }
-}
diff --git a/src/Sinch/Conversation/Messages/Message/ListMessage.cs b/src/Sinch/Conversation/Messages/Message/ListMessage.cs
index eeb80fac..e36bfa01 100644
--- a/src/Sinch/Conversation/Messages/Message/ListMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/ListMessage.cs
@@ -7,7 +7,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// A message containing a list of options to choose from
///
- public sealed class ListMessage : IMessage
+ public sealed class ListMessage
{
///
/// A title for the message that is displayed near the products or choices.
diff --git a/src/Sinch/Conversation/Messages/Message/LocationMessage.cs b/src/Sinch/Conversation/Messages/Message/LocationMessage.cs
index 8693a22b..30b9b2aa 100644
--- a/src/Sinch/Conversation/Messages/Message/LocationMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/LocationMessage.cs
@@ -5,7 +5,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// Message containing geographic location.
///
- public sealed class LocationMessage : IMessage
+ public sealed class LocationMessage
{
///
/// Gets or Sets Coordinates
diff --git a/src/Sinch/Conversation/Messages/Message/MediaMessage.cs b/src/Sinch/Conversation/Messages/Message/MediaMessage.cs
index 86907879..36eaa65b 100644
--- a/src/Sinch/Conversation/Messages/Message/MediaMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/MediaMessage.cs
@@ -6,7 +6,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// A message containing a media component, such as an image, document, or video.
///
- public sealed class MediaMessage : IMessage
+ public sealed class MediaMessage
{
///
/// An optional parameter. Will be used where it is natively supported.
diff --git a/src/Sinch/Conversation/Messages/Message/TemplateMessage.cs b/src/Sinch/Conversation/Messages/Message/TemplateMessage.cs
index 69002e41..a7d87ad7 100644
--- a/src/Sinch/Conversation/Messages/Message/TemplateMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/TemplateMessage.cs
@@ -7,7 +7,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// TemplateMessage
///
- public sealed class TemplateMessage : IMessage
+ public sealed class TemplateMessage
{
///
/// Optional. Channel specific template reference with parameters per channel.
diff --git a/src/Sinch/Conversation/Messages/Message/TextMessage.cs b/src/Sinch/Conversation/Messages/Message/TextMessage.cs
index 08b144dd..5d994dd5 100644
--- a/src/Sinch/Conversation/Messages/Message/TextMessage.cs
+++ b/src/Sinch/Conversation/Messages/Message/TextMessage.cs
@@ -5,7 +5,7 @@ namespace Sinch.Conversation.Messages.Message
///
/// A message containing only text.
///
- public sealed class TextMessage : IMessage
+ public sealed class TextMessage
{
///
/// A message containing only text.
diff --git a/src/Sinch/Conversation/Messages/Messages.cs b/src/Sinch/Conversation/Messages/Messages.cs
index 2be65e51..44678122 100644
--- a/src/Sinch/Conversation/Messages/Messages.cs
+++ b/src/Sinch/Conversation/Messages/Messages.cs
@@ -96,6 +96,8 @@ public Task Send(SendMessageRequest request, CancellationTo
_logger?.LogDebug("Sending a message...");
return _http.Send(uri, HttpMethod.Post, request, cancellationToken: cancellationToken);
}
+
+ //TODO: add simplified send text to app of recipient
///
public Task Get(string messageId, MessageSource messagesSource = default,
diff --git a/src/Sinch/Conversation/Conversation.cs b/src/Sinch/Conversation/SinchConversationClient.cs
similarity index 68%
rename from src/Sinch/Conversation/Conversation.cs
rename to src/Sinch/Conversation/SinchConversationClient.cs
index 0e6f25d6..68ac711d 100644
--- a/src/Sinch/Conversation/Conversation.cs
+++ b/src/Sinch/Conversation/SinchConversationClient.cs
@@ -1,6 +1,7 @@
using System;
using Sinch.Conversation.Apps;
using Sinch.Conversation.Contacts;
+using Sinch.Conversation.Conversations;
using Sinch.Conversation.Messages;
using Sinch.Core;
using Sinch.Logger;
@@ -9,8 +10,7 @@ namespace Sinch.Conversation
{
///
/// Send and receive messages globally over SMS, RCS, WhatsApp, Viber Business,
- /// Facebook messenger and other popular channels using the Sinch Conversation API.
- ///
+ /// Facebook messenger and other popular channels using the Sinch Conversation API.
/// The Conversation API endpoint uses built-in transcoding to give you the power of conversation across all
/// supported channels and, if required, full control over channel specific features.
///
@@ -24,17 +24,23 @@ public interface ISinchConversation
///
ISinchConversationContacts Contacts { get; }
+
+ ///
+ ISinchConversationConversations Conversations { get; }
}
///
- internal class Conversation : ISinchConversation
+ internal class SinchConversationClient : ISinchConversation
{
- internal Conversation(string projectId, Uri baseAddress, LoggerFactory loggerFactory, IHttp http)
+ internal SinchConversationClient(string projectId, Uri baseAddress, LoggerFactory loggerFactory, IHttp http)
{
Messages = new Messages.Messages(projectId, baseAddress, loggerFactory?.Create(),
http);
Apps = new Apps.Apps(projectId, baseAddress, loggerFactory?.Create(), http);
- Contacts = new Contacts.Contacts(projectId, baseAddress, loggerFactory?.Create(), http);
+ Contacts = new Contacts.Contacts(projectId, baseAddress,
+ loggerFactory?.Create(), http);
+ Conversations = new ConversationsClient(projectId, baseAddress,
+ loggerFactory?.Create(), http);
}
///
@@ -45,5 +51,8 @@ internal Conversation(string projectId, Uri baseAddress, LoggerFactory loggerFac
///
public ISinchConversationContacts Contacts { get; }
+
+ ///
+ public ISinchConversationConversations Conversations { get; }
}
}
diff --git a/src/Sinch/Core/PropertyMaskQuery.cs b/src/Sinch/Core/PropertyMaskQuery.cs
new file mode 100644
index 00000000..5191e348
--- /dev/null
+++ b/src/Sinch/Core/PropertyMaskQuery.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Sinch.Core
+{
+ public abstract class PropertyMaskQuery
+ {
+ protected readonly ISet SetFields = new HashSet();
+
+ ///
+ /// Get the comma separated snake_case list of properties which were directly initialized in this object.
+ /// If, for example, DisplayName and Metadata were set, will return display_name,metadata
+ ///
+ ///
+ internal string GetPropertiesMask()
+ {
+ return string.Join(',', SetFields.Select(StringUtils.ToSnakeCase));
+ }
+ }
+}
diff --git a/src/Sinch/SinchClient.cs b/src/Sinch/SinchClient.cs
index 237c7d2d..b6e2413a 100644
--- a/src/Sinch/SinchClient.cs
+++ b/src/Sinch/SinchClient.cs
@@ -182,7 +182,7 @@ public SinchClient(string keyId, string keySecret, string projectId,
Sms = new Sms(projectId, GetSmsBaseAddress(optionsObj.SmsHostingRegion, _apiUrlOverrides?.SmsUrl),
_loggerFactory,
httpSnakeCase);
- Conversation = new Conversation.Conversation(projectId,
+ Conversation = new Conversation.SinchConversationClient(projectId,
new Uri(_apiUrlOverrides?.ConversationUrl ??
string.Format(ConversationApiUrlTemplate, optionsObj.ConversationRegion.Value)),
_loggerFactory, httpSnakeCase);
diff --git a/tests/Sinch.Tests/Conversation/ConversationTestBase.cs b/tests/Sinch.Tests/Conversation/ConversationTestBase.cs
index 48d43dbe..d5718580 100644
--- a/tests/Sinch.Tests/Conversation/ConversationTestBase.cs
+++ b/tests/Sinch.Tests/Conversation/ConversationTestBase.cs
@@ -9,7 +9,7 @@ public class ConversationTestBase : TestBase
protected ConversationTestBase()
{
- Conversation = new Sinch.Conversation.Conversation(ProjectId, new Uri("https://us.conversation.api.sinch.com"),
+ Conversation = new Sinch.Conversation.SinchConversationClient(ProjectId, new Uri("https://us.conversation.api.sinch.com"),
default, HttpSnakeCase);
}
}
diff --git a/tests/Sinch.Tests/Conversation/ConversationsTests.cs b/tests/Sinch.Tests/Conversation/ConversationsTests.cs
new file mode 100644
index 00000000..edf8b865
--- /dev/null
+++ b/tests/Sinch.Tests/Conversation/ConversationsTests.cs
@@ -0,0 +1,40 @@
+using System.Text.Json.Nodes;
+using FluentAssertions;
+using Xunit;
+
+namespace Sinch.Tests.Conversation
+{
+ public class ConversationsTests
+ {
+ [Fact]
+ public void UpdateMaskConversation()
+ {
+ var conversation = new Sinch.Conversation.Conversations.Conversation
+ {
+ ActiveChannel = null,
+ Active = true,
+ AppId = "null",
+ ContactId = "id",
+ Id = "1",
+ Metadata = "n",
+ MetadataJson = new JsonObject(),
+ CorrelationId = string.Empty
+ };
+
+ conversation.GetPropertiesMask().Should().BeEquivalentTo(
+ "active_channel,active,app_id,contact_id,metadata,metadata_json,correlation_id");
+ }
+
+ [Fact]
+ public void UpdateMaskConversationOnlyOneField()
+ {
+ var conversation = new Sinch.Conversation.Conversations.Conversation
+ {
+ AppId = "AppId",
+ };
+
+ conversation.GetPropertiesMask().Should().BeEquivalentTo(
+ "app_id");
+ }
+ }
+}
diff --git a/tests/Sinch.Tests/Conversation/MessagesTests.cs b/tests/Sinch.Tests/Conversation/MessagesTests.cs
index 44b7571c..9b0aedcb 100644
--- a/tests/Sinch.Tests/Conversation/MessagesTests.cs
+++ b/tests/Sinch.Tests/Conversation/MessagesTests.cs
@@ -31,43 +31,42 @@ public async Task GetMessage()
var response = await Conversation.Messages.Get(messageId, MessageSource.ConversationSource);
response.Should().NotBeNull();
- response.AppMessage.Message.Should().BeOfType()
- .Which.Should().BeEquivalentTo(new ListMessage
+ response.AppMessage.ListMessage.Should().BeEquivalentTo(new ListMessage
+ {
+ Title = "title",
+ Sections = new List()
{
- Title = "title",
- Sections = new List()
+ new ListSection()
{
- new ListSection()
+ Title = "sec1",
+ Items = new List()
{
- Title = "sec1",
- Items = new List()
+ new ListItemChoice()
{
- new ListItemChoice()
+ Title = "title",
+ Description = "desc",
+ Media = new MediaMessage()
{
- Title = "title",
- Description = "desc",
- Media = new MediaMessage()
- {
- Url = new Uri("http://localhost")
- },
- PostbackData = "postback"
- }
+ Url = new Uri("http://localhost")
+ },
+ PostbackData = "postback"
}
- },
- new ListSection()
+ }
+ },
+ new ListSection()
+ {
+ Title = "sec2",
+ Items = new List()
{
- Title = "sec2",
- Items = new List()
+ new ListItemProduct()
{
- new ListItemProduct()
- {
- Id = "id",
- Marketplace = "amazon"
- }
+ Id = "id",
+ Marketplace = "amazon"
}
}
}
- });
+ }
+ });
response.Direction.Should().Be(ConversationDirection.UndefinedDirection);
response.ContactMessage.ReplyTo.MessageId.Should().Be("string");
response.ChannelIdentity.Should().BeEquivalentTo(new ChannelIdentity()
@@ -193,7 +192,7 @@ private static object Message()
accept_time = "2019-08-24T14:15:22Z",
app_message = new
{
- message = new
+ list_message = new
{
title = "title",
sections = new dynamic[]
diff --git a/tests/Sinch.Tests/Conversation/SendMessageTests.cs b/tests/Sinch.Tests/Conversation/SendMessageTests.cs
index a6d007b9..867ffc12 100644
--- a/tests/Sinch.Tests/Conversation/SendMessageTests.cs
+++ b/tests/Sinch.Tests/Conversation/SendMessageTests.cs
@@ -23,9 +23,8 @@ public class SendMessageTests : ConversationTestBase
private readonly SendMessageRequest _baseRequest = new SendMessageRequest
{
AppId = "123",
- Message = new AppMessage()
+ Message = new AppMessage(new TextMessage("I'm a texter"))
{
- Message = new TextMessage("I'm a texter"),
ExplicitChannelMessage = null,
AdditionalProperties = null
},
@@ -58,7 +57,7 @@ public SendMessageTests()
[Fact]
public async Task SendText()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.text_message = new
{
text = "I'm a texter"
};
@@ -78,7 +77,7 @@ public async Task SendText()
[Fact]
public async Task SendLocation()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.location_message = new
{
label = "label",
title = "title",
@@ -88,15 +87,12 @@ public async Task SendLocation()
longitude = 4.20f,
}
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new LocationMessage
{
- Message = new LocationMessage
- {
- Coordinates = new Coordinates(3.18f, 4.20f),
- Label = "label",
- Title = "title"
- }
- };
+ Coordinates = new Coordinates(3.18f, 4.20f),
+ Label = "label",
+ Title = "title"
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
@@ -113,7 +109,7 @@ public async Task SendLocation()
[Fact]
public async Task SendCarousel()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.carousel_message = new
{
cards = new[]
{
@@ -151,40 +147,37 @@ public async Task SendCarousel()
}
}
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new CarouselMessage()
{
- Message = new CarouselMessage()
+ Cards = new List()
{
- Cards = new List()
+ new()
{
- new()
+ Description = "card description",
+ Title = "Title Card",
+ Height = CardHeight.Tall,
+ MediaMessage = new MediaCarouselMessage()
{
- Description = "card description",
- Title = "Title Card",
- Height = CardHeight.Tall,
- MediaMessage = new MediaCarouselMessage()
- {
- Caption = "cap",
- Url = new Uri("https://localmob"),
- },
- Choices = new List
+ Caption = "cap",
+ Url = new Uri("https://localmob"),
+ },
+ Choices = new List
+ {
+ new Choice
{
- new Choice
- {
- CallMessage = new("123", "Jhon"),
- }
+ CallMessage = new("123", "Jhon"),
}
}
- },
- Choices = new List()
+ }
+ },
+ Choices = new List()
+ {
+ new Choice()
{
- new Choice()
- {
- TextMessage = new TextMessage("123")
- }
+ TextMessage = new TextMessage("123")
}
}
- };
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
_sendUrl)
@@ -199,7 +192,7 @@ public async Task SendCarousel()
[Fact]
public async Task SendChoice()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.choice_message = new
{
choices = new[]
{
@@ -217,21 +210,18 @@ public async Task SendChoice()
text = "123",
}
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new ChoiceMessage()
{
- Message = new ChoiceMessage()
+ Choices = new List()
{
- Choices = new List()
+ new Choice()
{
- new Choice()
- {
- TextMessage = new TextMessage("123"),
- PostbackData = "postback"
- }
- },
- TextMessage = new TextMessage("123")
- }
- };
+ TextMessage = new TextMessage("123"),
+ PostbackData = "postback"
+ }
+ },
+ TextMessage = new TextMessage("123")
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
_sendUrl)
@@ -246,19 +236,16 @@ public async Task SendChoice()
[Fact]
public async Task SendMedia()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.media_message = new
{
url = "http://yup/ls",
thumbnail_url = "https://img.c",
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new MediaMessage
{
- Message = new MediaMessage
- {
- Url = new Uri("http://yup/ls"),
- ThumbnailUrl = new Uri("https://img.c")
- }
- };
+ Url = new Uri("http://yup/ls"),
+ ThumbnailUrl = new Uri("https://img.c")
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
_sendUrl)
@@ -273,7 +260,7 @@ public async Task SendMedia()
[Fact]
public async Task SendTemplate()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.template_message = new
{
omni_template = new
{
@@ -299,37 +286,34 @@ public async Task SendTemplate()
}
}
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new TemplateMessage()
{
- Message = new TemplateMessage()
+ OmniTemplate = new TemplateReference
{
- OmniTemplate = new TemplateReference
+ LanguageCode = "es",
+ Parameters = new Dictionary()
{
- LanguageCode = "es",
- Parameters = new Dictionary()
- {
- { "key", "val" }
- },
- TemplateId = "tempid",
- Version = "1.0"
+ { "key", "val" }
},
- ChannelTemplate = new Dictionary()
+ TemplateId = "tempid",
+ Version = "1.0"
+ },
+ ChannelTemplate = new Dictionary()
+ {
{
+ "test", new TemplateReference
{
- "test", new TemplateReference
+ TemplateId = "abc",
+ Version = "305",
+ Parameters = new Dictionary()
{
- TemplateId = "abc",
- Version = "305",
- Parameters = new Dictionary()
- {
- { "tarnished", "order" }
- },
- LanguageCode = "de"
- }
+ { "tarnished", "order" }
+ },
+ LanguageCode = "de"
}
}
}
- };
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
_sendUrl)
@@ -344,7 +328,7 @@ public async Task SendTemplate()
[Fact]
public async Task SendList()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.list_message = new
{
title = "list_title",
description = "description",
@@ -383,48 +367,45 @@ public async Task SendList()
}
}
};
- _baseRequest.Message = new AppMessage()
+ _baseRequest.Message = new AppMessage(new ListMessage
{
- Message = new ListMessage
+ Title = "list_title",
+ Description = "description",
+ Sections = new List()
{
- Title = "list_title",
- Description = "description",
- Sections = new List()
+ new ListSection()
{
- new ListSection()
+ Title = "item1",
+ Items = new List()
{
- Title = "item1",
- Items = new List()
+ new ListItemChoice()
{
- new ListItemChoice()
- {
- Title = "listitemchoice",
- PostbackData = "postno",
- Description = "desc",
- Media = new MediaMessage()
- {
- Url = new Uri("https://nolocalhost"),
- ThumbnailUrl = new Uri("https://knowyourmeme.com/photos/377946")
- }
- },
- new ListItemProduct
+ Title = "listitemchoice",
+ PostbackData = "postno",
+ Description = "desc",
+ Media = new MediaMessage()
{
- Id = "prod_id",
- Marketplace = "amazon",
- Currency = "eur",
- Quantity = 20,
- ItemPrice = 12.1000004f,
+ Url = new Uri("https://nolocalhost"),
+ ThumbnailUrl = new Uri("https://knowyourmeme.com/photos/377946")
}
+ },
+ new ListItemProduct
+ {
+ Id = "prod_id",
+ Marketplace = "amazon",
+ Currency = "eur",
+ Quantity = 20,
+ ItemPrice = 12.1000004f,
}
}
- },
- MessageProperties = new ListMessageMessageProperties()
- {
- Menu = "omenu",
- CatalogId = "id1"
}
+ },
+ MessageProperties = new ListMessageMessageProperties()
+ {
+ Menu = "omenu",
+ CatalogId = "id1"
}
- };
+ });
HttpMessageHandlerMock
.When(HttpMethod.Post,
_sendUrl)
@@ -439,7 +420,7 @@ public async Task SendList()
[Fact]
public async Task SendAllParams()
{
- _baseMessageExpected.message.message = new
+ _baseMessageExpected.message.text_message = new
{
text = "I'm a texter"
};
diff --git a/tests/Sinch.Tests/e2e/Conversation/ConversationsTests.cs b/tests/Sinch.Tests/e2e/Conversation/ConversationsTests.cs
new file mode 100644
index 00000000..54c009fe
--- /dev/null
+++ b/tests/Sinch.Tests/e2e/Conversation/ConversationsTests.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Sinch.Conversation;
+using Sinch.Conversation.Conversations;
+using Sinch.Conversation.Conversations.Create;
+using Sinch.Conversation.Conversations.InjectMessage;
+using Sinch.Conversation.Conversations.List;
+using Sinch.Conversation.Messages;
+using Sinch.Conversation.Messages.Message;
+using Xunit;
+
+namespace Sinch.Tests.e2e.Conversation
+{
+ public class ConversationsTests : TestBase
+ {
+ private readonly Sinch.Conversation.Conversations.Conversation _conversation =
+ new()
+ {
+ Id = "01HMXSGKNG4HCAW3XXTVK6Q8WD",
+ AppId = "01HKWRC164GNT3K2GCPK5W2B1J",
+ ContactId = "01HKWT3XRVH6RP17S8KSBC4PYR",
+ ActiveChannel = ConversationChannel.Instagram,
+ Active = true,
+ Metadata = "meta",
+ CorrelationId = "cor_id",
+ MetadataJson = new JsonObject
+ {
+ ["hi"] = "hi2",
+ ["hey"] = new JsonArray("a", "b")
+ },
+ LastReceived = DateTime.Parse("1970-01-01T00:00:00Z", CultureInfo.InvariantCulture).ToUniversalTime()
+ };
+
+ [Fact]
+ public async Task Create()
+ {
+ var response = await SinchClientMockServer.Conversation.Conversations.Create(new CreateConversationRequest
+ {
+ AppId = "01HKWRC164GNT3K2GCPK5W2B1J",
+ ActiveChannel = ConversationChannel.Instagram,
+ Active = true,
+ Metadata = "meta",
+ ContactId = "01HKWT3XRVH6RP17S8KSBC4PYR",
+ MetadataJson = new JsonObject
+ {
+ ["hi"] = "hi2",
+ ["hey"] = new JsonArray("a", "b")
+ }
+ });
+
+ response.Should().BeEquivalentTo(_conversation, options =>
+ options.ExcludingNestedObjects().Excluding(x => x.MetadataJson));
+ ValidateMetadata(response);
+ }
+
+ [Fact]
+ public async Task Get()
+ {
+ var response = await SinchClientMockServer.Conversation.Conversations.Get(_conversation.Id);
+
+ response.Should().BeEquivalentTo(_conversation, options =>
+ options.ExcludingNestedObjects().Excluding(x => x.MetadataJson));
+ ValidateMetadata(response);
+ }
+
+ [Fact]
+ public async Task List()
+ {
+ var response = await SinchClientMockServer.Conversation.Conversations.List(new ListConversationsRequest
+ {
+ AppId = "01HKWRC164GNT3K2GCPK5W2B1J",
+ ActiveChannel = ConversationChannel.KakaoTalkChat,
+ PageSize = 10,
+ PageToken = "ABC",
+ ContactId = "01HKWT3XRVH6RP17S8KSBC4PYR",
+ OnlyActive = true
+ });
+ response.Conversations.Should().HaveCount(2);
+ response.TotalSize.Should().Be(2);
+ response.NextPageToken.Should().BeEquivalentTo("abc");
+ }
+
+ [Fact]
+ public async Task Delete()
+ {
+ var op = () => SinchClientMockServer.Conversation.Conversations.Delete(_conversation.Id);
+ await op.Should().NotThrowAsync();
+ }
+
+ [Fact]
+ public async Task Update()
+ {
+ var response = await
+ SinchClientMockServer.Conversation.Conversations.Update(_conversation,
+ MetadataUpdateStrategy.MergePatch);
+
+ response.Should().BeEquivalentTo(_conversation, options =>
+ options.ExcludingNestedObjects().Excluding(x => x.MetadataJson));
+ ValidateMetadata(response);
+ }
+
+ [Fact]
+ public async Task Inject()
+ {
+ var op = () => SinchClientMockServer.Conversation.Conversations.InjectMessage(new InjectMessageRequest
+ {
+ Direction = ConversationDirection.ToApp,
+ AcceptTime = DateTime.Parse("1970-01-01T00:00:00Z", CultureInfo.InvariantCulture).ToUniversalTime(),
+ AppMessage = new AppMessage(new TextMessage("hi")),
+ ChannelIdentity = new ChannelIdentity
+ {
+ Identity = "01HN31W37910AANG1JGE8Y6RFF",
+ Channel = ConversationChannel.Instagram
+ },
+ ContactId = _conversation.ContactId,
+ ConversationId = _conversation.Id,
+ Metadata = "meta",
+ ContactMessage = new ContactMessage(new TextMessage("oi"))
+ });
+ await op.Should().NotThrowAsync();
+ }
+
+ [Fact]
+ public async Task Stop()
+ {
+ var op = () => SinchClientMockServer.Conversation.Conversations.Stop(_conversation.Id);
+ await op.Should().NotThrowAsync();
+ }
+
+ private static void ValidateMetadata(Sinch.Conversation.Conversations.Conversation response)
+ {
+ response.MetadataJson["hi"]!.ToString().Should().BeEquivalentTo("hi2");
+ response.MetadataJson["hey"]!.AsArray().Select(x => x.ToString()).ToList().Should().BeEquivalentTo(
+ new List
+ {
+ "a", "b"
+ });
+ }
+ }
+}