From afc47c1ecf346f69592d549f7b9a6c36360ccbef Mon Sep 17 00:00:00 2001 From: atelegu Date: Fri, 22 May 2026 16:52:51 +0530 Subject: [PATCH 1/3] VAPI-3163 --- README.md | 25 ++++ docs/ReferCallStatusEnum.md | 8 ++ docs/ReferCompleteCallback.md | 24 ++++ .../Unit/Model/Bxml/TestRefer.cs | 46 +++++++ .../Unit/Model/ReferCompleteCallbackTests.cs | 58 ++++++++ .../Model/Bxml/ReferCompleteCallback.cs | 125 ++++++++++++++++++ .../Model/Bxml/Verbs/Refer.cs | 102 ++++++++++++++ .../Model/ReferCallStatusEnum.cs | 19 +++ 8 files changed, 407 insertions(+) create mode 100644 docs/ReferCallStatusEnum.md create mode 100644 docs/ReferCompleteCallback.md create mode 100644 src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs create mode 100644 src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs create mode 100644 src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs create mode 100644 src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs create mode 100644 src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs diff --git a/README.md b/README.md index 70cbdf8..4e27baa 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,31 @@ namespace Example } ``` +## BXML `` example + +```csharp +using Bandwidth.Standard.Model.Bxml; +using Bandwidth.Standard.Model.Bxml.Verbs; + +var refer = new Refer() + .WithSipUri("sip:alice@atlanta.example.com") + .WithReferCompleteUrl("https://example.com/handleRefer") + .WithReferCompleteMethod("POST") + .WithTag("handoff-123"); + +var bxml = new Response(refer).ToBXML(); + +When a REFER succeeds, the remote SIP endpoint redirects away from Bandwidth and the call is terminated. Do not expect post-success BXML execution on that call leg. + +## `referComplete` failure recovery example + +```csharp +// Example callback handler pseudocode +if (callback.ReferCallStatus == ReferCallStatusEnum.Failure) +{ + // Recover only on failure (e.g. fallback flow, retry, alternate route) +} + ## Documentation for API Endpoints diff --git a/docs/ReferCallStatusEnum.md b/docs/ReferCallStatusEnum.md new file mode 100644 index 0000000..6f190dc --- /dev/null +++ b/docs/ReferCallStatusEnum.md @@ -0,0 +1,8 @@ +# Bandwidth.Standard.Model.ReferCallStatusEnum + +## Enum + +* `Success` (value: `success`) +* `Failure` (value: `failure`) + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) \ No newline at end of file diff --git a/docs/ReferCompleteCallback.md b/docs/ReferCompleteCallback.md new file mode 100644 index 0000000..865e11c --- /dev/null +++ b/docs/ReferCompleteCallback.md @@ -0,0 +1,24 @@ +# Bandwidth.Standard.Model.ReferCompleteCallback +This event is sent to the referCompleteUrl of the A-leg's verb when the REFER attempt completes. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**EventType** | **string** | The event type. Always `referComplete`. | [optional] +**EventTime** | **DateTime** | The approximate UTC date and time when the event was generated by the Bandwidth server. | [optional] +**AccountId** | **string** | The user account associated with the call. | [optional] +**ApplicationId** | **string** | The id of the application associated with the call. | [optional] +**From** | **string** | The provided identifier of the caller. | [optional] +**To** | **string** | The phone number that received the call. | [optional] +**Direction** | **CallDirectionEnum** | Call direction (`inbound`). | [optional] +**CallId** | **string** | The call id associated with the event. | [optional] +**CallUrl** | **string** | The URL of the call associated with the event. | [optional] +**StartTime** | **DateTime** | Time the call was started. | [optional] +**AnswerTime** | **DateTime** | Time the call was answered. | [optional] +**ReferCallStatus** | **ReferCallStatusEnum** | Whether REFER succeeded or failed. | [optional] +**Tag** | **string** | Optional custom string included in callbacks. | [optional] +**ReferSipResponseCode** | **int?** | Optional SIP response code from the REFER transaction. | [optional] +**NotifySipResponseCode** | **int?** | Optional SIP response code from the NOTIFY transaction. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) \ No newline at end of file diff --git a/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs new file mode 100644 index 0000000..7380b5f --- /dev/null +++ b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Xml.Serialization; +using Bandwidth.Standard.Model.Bxml; +using Bandwidth.Standard.Model.Bxml.Verbs; +using Xunit; + +namespace Bandwidth.Standard.Test.Unit.Model.Bxml +{ + public class TestRefer + { + [Fact] + public void ReferRoundTripTest() + { + var expected = " sip:alice@atlanta.example.com "; + + var refer = new Refer() + .WithSipUri("sip:alice@atlanta.example.com") + .WithReferCompleteUrl("https://example.com/handleRefer") + .WithReferCompleteMethod("POST") + .WithTag("refer-tag"); + + var actual = new Response(refer).ToBXML(); + Assert.Equal(expected, actual.Replace("\n", "").Replace("\r", "")); + + const string referOnlyXml = "sip:alice@atlanta.example.com"; + var serializer = new XmlSerializer(typeof(Refer), ""); + Refer deserializedRefer; + using (var reader = new StringReader(referOnlyXml)) + { + deserializedRefer = (Refer)serializer.Deserialize(reader); + } + + Assert.Equal("sip:alice@atlanta.example.com", deserializedRefer.SipUriElement.Uri); + var roundTrip = new Response(deserializedRefer).ToBXML(); + Assert.Equal(expected, roundTrip.Replace("\n", "").Replace("\r", "")); + } + + [Fact] + public void ReferSipUriMustStartWithSipScheme() + { + var refer = new Refer(); + Assert.Throws(() => refer.WithSipUri("tel:+15551234567")); + } + } +} \ No newline at end of file diff --git a/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs b/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs new file mode 100644 index 0000000..bdfd4e2 --- /dev/null +++ b/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs @@ -0,0 +1,58 @@ +using Bandwidth.Standard.Model; +using Newtonsoft.Json; +using Xunit; + +namespace Bandwidth.Standard.Test.Unit.Model +{ + public class ReferCompleteCallbackTests + { + [Fact] + public void ReferCompleteSuccessTest() + { + var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"success\"}"); + + Assert.Equal(ReferCallStatusEnum.Success, callback.ReferCallStatus); + Assert.Null(callback.ReferSipResponseCode); + Assert.Null(callback.NotifySipResponseCode); + } + + [Fact] + public void ReferCompleteReferRejectedTest() + { + var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":405}"); + + Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); + Assert.Equal(405, callback.ReferSipResponseCode); + Assert.Null(callback.NotifySipResponseCode); + } + + [Fact] + public void ReferCompleteDestinationUnreachableTest() + { + var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":202,\"notifySipResponseCode\":486}"); + + Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); + Assert.Equal(202, callback.ReferSipResponseCode); + Assert.Equal(486, callback.NotifySipResponseCode); + } + + [Fact] + public void ReferCompleteNotifyTimeoutTest() + { + var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":202}"); + + Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); + Assert.Equal(202, callback.ReferSipResponseCode); + Assert.Null(callback.NotifySipResponseCode); + } + + private static ReferCompleteCallback Deserialize(string json) + { + var callback = JsonConvert.DeserializeObject(json); + Assert.NotNull(callback); + Assert.Equal("referComplete", callback.EventType); + Assert.Equal(CallDirectionEnum.Inbound, callback.Direction); + return callback; + } + } +} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs b/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs new file mode 100644 index 0000000..ea0b105 --- /dev/null +++ b/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using System.Text; + +namespace Bandwidth.Standard.Model +{ + /// This event is sent to the referCompleteUrl of the A-leg's <Refer> verb when the REFER attempt completes. + [DataContract(Name = "referCompleteCallback")] + public partial class ReferCompleteCallback : IValidatableObject + { + public ReferCompleteCallback( + string eventType = default(string), + DateTime eventTime = default(DateTime), + string accountId = default(string), + string applicationId = default(string), + string from = default(string), + string to = default(string), + CallDirectionEnum? direction = default(CallDirectionEnum?), + string callId = default(string), + string callUrl = default(string), + DateTime startTime = default(DateTime), + DateTime answerTime = default(DateTime), + ReferCallStatusEnum referCallStatus = default(ReferCallStatusEnum), + string tag = default(string), + int? referSipResponseCode = default(int?), + int? notifySipResponseCode = default(int?)) + { + EventType = eventType; + EventTime = eventTime; + AccountId = accountId; + ApplicationId = applicationId; + From = from; + To = to; + Direction = direction; + CallId = callId; + CallUrl = callUrl; + StartTime = startTime; + AnswerTime = answerTime; + ReferCallStatus = referCallStatus; + Tag = tag; + ReferSipResponseCode = referSipResponseCode; + NotifySipResponseCode = notifySipResponseCode; + } + + [DataMember(Name = "eventType", EmitDefaultValue = false)] + public string EventType { get; set; } + + [DataMember(Name = "eventTime", EmitDefaultValue = false)] + public DateTime EventTime { get; set; } + + [DataMember(Name = "accountId", EmitDefaultValue = false)] + public string AccountId { get; set; } + + [DataMember(Name = "applicationId", EmitDefaultValue = false)] + public string ApplicationId { get; set; } + + [DataMember(Name = "from", EmitDefaultValue = false)] + public string From { get; set; } + + [DataMember(Name = "to", EmitDefaultValue = false)] + public string To { get; set; } + + [DataMember(Name = "direction", EmitDefaultValue = false)] + public CallDirectionEnum? Direction { get; set; } + + [DataMember(Name = "callId", EmitDefaultValue = false)] + public string CallId { get; set; } + + [DataMember(Name = "callUrl", EmitDefaultValue = false)] + public string CallUrl { get; set; } + + [DataMember(Name = "startTime", EmitDefaultValue = false)] + public DateTime StartTime { get; set; } + + [DataMember(Name = "answerTime", EmitDefaultValue = false)] + public DateTime AnswerTime { get; set; } + + [DataMember(Name = "referCallStatus", EmitDefaultValue = false)] + public ReferCallStatusEnum ReferCallStatus { get; set; } + + [DataMember(Name = "tag", EmitDefaultValue = true)] + public string Tag { get; set; } + + [DataMember(Name = "referSipResponseCode", EmitDefaultValue = true)] + public int? ReferSipResponseCode { get; set; } + + [DataMember(Name = "notifySipResponseCode", EmitDefaultValue = true)] + public int? NotifySipResponseCode { get; set; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class ReferCompleteCallback {\n"); + sb.Append(" EventType: ").Append(EventType).Append("\n"); + sb.Append(" EventTime: ").Append(EventTime).Append("\n"); + sb.Append(" AccountId: ").Append(AccountId).Append("\n"); + sb.Append(" ApplicationId: ").Append(ApplicationId).Append("\n"); + sb.Append(" From: ").Append(From).Append("\n"); + sb.Append(" To: ").Append(To).Append("\n"); + sb.Append(" Direction: ").Append(Direction).Append("\n"); + sb.Append(" CallId: ").Append(CallId).Append("\n"); + sb.Append(" CallUrl: ").Append(CallUrl).Append("\n"); + sb.Append(" StartTime: ").Append(StartTime).Append("\n"); + sb.Append(" AnswerTime: ").Append(AnswerTime).Append("\n"); + sb.Append(" ReferCallStatus: ").Append(ReferCallStatus).Append("\n"); + sb.Append(" Tag: ").Append(Tag).Append("\n"); + sb.Append(" ReferSipResponseCode: ").Append(ReferSipResponseCode).Append("\n"); + sb.Append(" NotifySipResponseCode: ").Append(NotifySipResponseCode).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } +} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs new file mode 100644 index 0000000..fd93825 --- /dev/null +++ b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs @@ -0,0 +1,102 @@ +using Bandwidth.Standard.Model.Bxml; +using System; +using System.Xml.Serialization; + +namespace Bandwidth.Standard.Model.Bxml.Verbs +{ + /// The Refer verb is used to hand off a call to a SIP endpoint. + /// + public class Refer : IVerb + { + private string referCompleteMethod; + + /// URL to receive the refer complete callback. + [XmlAttribute("referCompleteUrl")] + public string ReferCompleteUrl { get; set; } + + /// HTTP method to send the refer complete callback. + [XmlAttribute("referCompleteMethod")] + public string ReferCompleteMethod + { + get { return referCompleteMethod; } + set + { + if (value != null && value != "GET" && value != "POST") + { + throw new ArgumentException("ReferCompleteMethod must be either 'GET' or 'POST'."); + } + referCompleteMethod = value; + } + } + + /// Optional custom string to include in callbacks. + [XmlAttribute("tag")] + public string Tag { get; set; } + + /// SIP URI destination for the REFER. + [XmlElement("SipUri")] + public SipUri SipUriElement { get; set; } + + /// Initializes a new instance of the Refer class. + public Refer() + { + ReferCompleteMethod = "POST"; + } + + /// Sets the SIP URI destination from a string. + public Refer WithSipUri(string sipUri) + { + SipUriElement = new SipUri { Uri = sipUri }; + return this; + } + + /// Sets the SIP URI destination from a SipUri object. + public Refer WithSipUri(SipUri sipUri) + { + SipUriElement = sipUri; + return this; + } + + /// Sets referCompleteUrl. + public Refer WithReferCompleteUrl(string referCompleteUrl) + { + ReferCompleteUrl = referCompleteUrl; + return this; + } + + /// Sets referCompleteMethod. + public Refer WithReferCompleteMethod(string referCompleteMethod) + { + ReferCompleteMethod = referCompleteMethod; + return this; + } + + /// Sets tag. + public Refer WithTag(string tag) + { + Tag = tag; + return this; + } + + /// BXML tag to represent a SIP URI for the refer verb. + public class SipUri : IVerb + { + private string _uri; + + /// SIP URI to refer the call to (must start with sip:). + [XmlText] + public string Uri + { + get { return _uri; } + set + { + if (value != null && !value.StartsWith("sip:", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("SipUri must start with 'sip:'."); + } + _uri = value; + } + } + } + } +} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs b/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs new file mode 100644 index 0000000..ad80039 --- /dev/null +++ b/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs @@ -0,0 +1,19 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Bandwidth.Standard.Model +{ + /// Result of a refer attempt. + [JsonConverter(typeof(StringEnumConverter))] + public enum ReferCallStatusEnum + { + /// Enum Success for value: success + [EnumMember(Value = "success")] + Success = 1, + + /// Enum Failure for value: failure + [EnumMember(Value = "failure")] + Failure = 2 + } +} \ No newline at end of file From 8c101d6adcc4ca80380cfbd6b035668fe2df6186 Mon Sep 17 00:00:00 2001 From: atelegu Date: Tue, 26 May 2026 17:22:13 +0530 Subject: [PATCH 2/3] VAPI-3163 --- docs/ReferCallStatusEnum.md | 8 -- docs/ReferCompleteCallback.md | 24 ---- .../Unit/Model/ReferCompleteCallbackTests.cs | 58 -------- .../Model/Bxml/ReferCompleteCallback.cs | 125 ------------------ .../Model/ReferCallStatusEnum.cs | 19 --- 5 files changed, 234 deletions(-) delete mode 100644 docs/ReferCallStatusEnum.md delete mode 100644 docs/ReferCompleteCallback.md delete mode 100644 src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs delete mode 100644 src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs delete mode 100644 src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs diff --git a/docs/ReferCallStatusEnum.md b/docs/ReferCallStatusEnum.md deleted file mode 100644 index 6f190dc..0000000 --- a/docs/ReferCallStatusEnum.md +++ /dev/null @@ -1,8 +0,0 @@ -# Bandwidth.Standard.Model.ReferCallStatusEnum - -## Enum - -* `Success` (value: `success`) -* `Failure` (value: `failure`) - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) \ No newline at end of file diff --git a/docs/ReferCompleteCallback.md b/docs/ReferCompleteCallback.md deleted file mode 100644 index 865e11c..0000000 --- a/docs/ReferCompleteCallback.md +++ /dev/null @@ -1,24 +0,0 @@ -# Bandwidth.Standard.Model.ReferCompleteCallback -This event is sent to the referCompleteUrl of the A-leg's verb when the REFER attempt completes. - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**EventType** | **string** | The event type. Always `referComplete`. | [optional] -**EventTime** | **DateTime** | The approximate UTC date and time when the event was generated by the Bandwidth server. | [optional] -**AccountId** | **string** | The user account associated with the call. | [optional] -**ApplicationId** | **string** | The id of the application associated with the call. | [optional] -**From** | **string** | The provided identifier of the caller. | [optional] -**To** | **string** | The phone number that received the call. | [optional] -**Direction** | **CallDirectionEnum** | Call direction (`inbound`). | [optional] -**CallId** | **string** | The call id associated with the event. | [optional] -**CallUrl** | **string** | The URL of the call associated with the event. | [optional] -**StartTime** | **DateTime** | Time the call was started. | [optional] -**AnswerTime** | **DateTime** | Time the call was answered. | [optional] -**ReferCallStatus** | **ReferCallStatusEnum** | Whether REFER succeeded or failed. | [optional] -**Tag** | **string** | Optional custom string included in callbacks. | [optional] -**ReferSipResponseCode** | **int?** | Optional SIP response code from the REFER transaction. | [optional] -**NotifySipResponseCode** | **int?** | Optional SIP response code from the NOTIFY transaction. | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) \ No newline at end of file diff --git a/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs b/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs deleted file mode 100644 index bdfd4e2..0000000 --- a/src/Bandwidth.Standard.Test/Unit/Model/ReferCompleteCallbackTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Bandwidth.Standard.Model; -using Newtonsoft.Json; -using Xunit; - -namespace Bandwidth.Standard.Test.Unit.Model -{ - public class ReferCompleteCallbackTests - { - [Fact] - public void ReferCompleteSuccessTest() - { - var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"success\"}"); - - Assert.Equal(ReferCallStatusEnum.Success, callback.ReferCallStatus); - Assert.Null(callback.ReferSipResponseCode); - Assert.Null(callback.NotifySipResponseCode); - } - - [Fact] - public void ReferCompleteReferRejectedTest() - { - var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":405}"); - - Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); - Assert.Equal(405, callback.ReferSipResponseCode); - Assert.Null(callback.NotifySipResponseCode); - } - - [Fact] - public void ReferCompleteDestinationUnreachableTest() - { - var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":202,\"notifySipResponseCode\":486}"); - - Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); - Assert.Equal(202, callback.ReferSipResponseCode); - Assert.Equal(486, callback.NotifySipResponseCode); - } - - [Fact] - public void ReferCompleteNotifyTimeoutTest() - { - var callback = Deserialize("{\"eventType\":\"referComplete\",\"eventTime\":\"2024-01-01T00:00:00.000Z\",\"accountId\":\"9900000\",\"applicationId\":\"04e88489-df02-4e34-a0ee-27a91849555f\",\"from\":\"+15555550100\",\"to\":\"+15555550101\",\"direction\":\"inbound\",\"callId\":\"c-123\",\"callUrl\":\"https://voice.bandwidth.com/api/v2/accounts/9900000/calls/c-123\",\"startTime\":\"2024-01-01T00:00:00.000Z\",\"answerTime\":\"2024-01-01T00:00:01.000Z\",\"referCallStatus\":\"failure\",\"referSipResponseCode\":202}"); - - Assert.Equal(ReferCallStatusEnum.Failure, callback.ReferCallStatus); - Assert.Equal(202, callback.ReferSipResponseCode); - Assert.Null(callback.NotifySipResponseCode); - } - - private static ReferCompleteCallback Deserialize(string json) - { - var callback = JsonConvert.DeserializeObject(json); - Assert.NotNull(callback); - Assert.Equal("referComplete", callback.EventType); - Assert.Equal(CallDirectionEnum.Inbound, callback.Direction); - return callback; - } - } -} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs b/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs deleted file mode 100644 index ea0b105..0000000 --- a/src/Bandwidth.Standard/Model/Bxml/ReferCompleteCallback.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using System.Text; - -namespace Bandwidth.Standard.Model -{ - /// This event is sent to the referCompleteUrl of the A-leg's <Refer> verb when the REFER attempt completes. - [DataContract(Name = "referCompleteCallback")] - public partial class ReferCompleteCallback : IValidatableObject - { - public ReferCompleteCallback( - string eventType = default(string), - DateTime eventTime = default(DateTime), - string accountId = default(string), - string applicationId = default(string), - string from = default(string), - string to = default(string), - CallDirectionEnum? direction = default(CallDirectionEnum?), - string callId = default(string), - string callUrl = default(string), - DateTime startTime = default(DateTime), - DateTime answerTime = default(DateTime), - ReferCallStatusEnum referCallStatus = default(ReferCallStatusEnum), - string tag = default(string), - int? referSipResponseCode = default(int?), - int? notifySipResponseCode = default(int?)) - { - EventType = eventType; - EventTime = eventTime; - AccountId = accountId; - ApplicationId = applicationId; - From = from; - To = to; - Direction = direction; - CallId = callId; - CallUrl = callUrl; - StartTime = startTime; - AnswerTime = answerTime; - ReferCallStatus = referCallStatus; - Tag = tag; - ReferSipResponseCode = referSipResponseCode; - NotifySipResponseCode = notifySipResponseCode; - } - - [DataMember(Name = "eventType", EmitDefaultValue = false)] - public string EventType { get; set; } - - [DataMember(Name = "eventTime", EmitDefaultValue = false)] - public DateTime EventTime { get; set; } - - [DataMember(Name = "accountId", EmitDefaultValue = false)] - public string AccountId { get; set; } - - [DataMember(Name = "applicationId", EmitDefaultValue = false)] - public string ApplicationId { get; set; } - - [DataMember(Name = "from", EmitDefaultValue = false)] - public string From { get; set; } - - [DataMember(Name = "to", EmitDefaultValue = false)] - public string To { get; set; } - - [DataMember(Name = "direction", EmitDefaultValue = false)] - public CallDirectionEnum? Direction { get; set; } - - [DataMember(Name = "callId", EmitDefaultValue = false)] - public string CallId { get; set; } - - [DataMember(Name = "callUrl", EmitDefaultValue = false)] - public string CallUrl { get; set; } - - [DataMember(Name = "startTime", EmitDefaultValue = false)] - public DateTime StartTime { get; set; } - - [DataMember(Name = "answerTime", EmitDefaultValue = false)] - public DateTime AnswerTime { get; set; } - - [DataMember(Name = "referCallStatus", EmitDefaultValue = false)] - public ReferCallStatusEnum ReferCallStatus { get; set; } - - [DataMember(Name = "tag", EmitDefaultValue = true)] - public string Tag { get; set; } - - [DataMember(Name = "referSipResponseCode", EmitDefaultValue = true)] - public int? ReferSipResponseCode { get; set; } - - [DataMember(Name = "notifySipResponseCode", EmitDefaultValue = true)] - public int? NotifySipResponseCode { get; set; } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append("class ReferCompleteCallback {\n"); - sb.Append(" EventType: ").Append(EventType).Append("\n"); - sb.Append(" EventTime: ").Append(EventTime).Append("\n"); - sb.Append(" AccountId: ").Append(AccountId).Append("\n"); - sb.Append(" ApplicationId: ").Append(ApplicationId).Append("\n"); - sb.Append(" From: ").Append(From).Append("\n"); - sb.Append(" To: ").Append(To).Append("\n"); - sb.Append(" Direction: ").Append(Direction).Append("\n"); - sb.Append(" CallId: ").Append(CallId).Append("\n"); - sb.Append(" CallUrl: ").Append(CallUrl).Append("\n"); - sb.Append(" StartTime: ").Append(StartTime).Append("\n"); - sb.Append(" AnswerTime: ").Append(AnswerTime).Append("\n"); - sb.Append(" ReferCallStatus: ").Append(ReferCallStatus).Append("\n"); - sb.Append(" Tag: ").Append(Tag).Append("\n"); - sb.Append(" ReferSipResponseCode: ").Append(ReferSipResponseCode).Append("\n"); - sb.Append(" NotifySipResponseCode: ").Append(NotifySipResponseCode).Append("\n"); - sb.Append("}\n"); - return sb.ToString(); - } - - public virtual string ToJson() - { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); - } - - IEnumerable IValidatableObject.Validate(ValidationContext validationContext) - { - yield break; - } - } -} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs b/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs deleted file mode 100644 index ad80039..0000000 --- a/src/Bandwidth.Standard/Model/ReferCallStatusEnum.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace Bandwidth.Standard.Model -{ - /// Result of a refer attempt. - [JsonConverter(typeof(StringEnumConverter))] - public enum ReferCallStatusEnum - { - /// Enum Success for value: success - [EnumMember(Value = "success")] - Success = 1, - - /// Enum Failure for value: failure - [EnumMember(Value = "failure")] - Failure = 2 - } -} \ No newline at end of file From ef31b2565b401c740ac60573ce978edccb703996 Mon Sep 17 00:00:00 2001 From: atelegu Date: Tue, 26 May 2026 17:42:21 +0530 Subject: [PATCH 3/3] VAPI-3163 --- .../Unit/Model/Bxml/TestRefer.cs | 24 ++++++++++++++++ .../Model/Bxml/Verbs/Refer.cs | 28 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs index 7380b5f..9064e42 100644 --- a/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs +++ b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs @@ -42,5 +42,29 @@ public void ReferSipUriMustStartWithSipScheme() var refer = new Refer(); Assert.Throws(() => refer.WithSipUri("tel:+15551234567")); } + + [Fact] + public void ReferInvalidMethodThrows() + { + var refer = new Refer(); + Assert.Throws(() => refer.WithReferCompleteMethod("DELETE")); + } + + [Fact] + public void ReferWithSipUriObjectOverload() + { + var sipUri = new Refer.SipUri { Uri = "sip:alice@atlanta.example.com" }; + var refer = new Refer().WithSipUri(sipUri); + Assert.Equal("sip:alice@atlanta.example.com", refer.SipUriElement.Uri); + } + + [Fact] + public void ReferMinimalOnlySipUri() + { + var refer = new Refer().WithSipUri("sip:bob@biloxi.example.com"); + var bxml = new Response(refer).ToBXML(); + Assert.Contains("sip:bob@biloxi.example.com", bxml); + Assert.Contains("referCompleteMethod=\"POST\"", bxml); + } } } \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs index fd93825..a712af8 100644 --- a/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs +++ b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs @@ -4,17 +4,23 @@ namespace Bandwidth.Standard.Model.Bxml.Verbs { + /// /// The Refer verb is used to hand off a call to a SIP endpoint. /// + /// public class Refer : IVerb { private string referCompleteMethod; + /// /// URL to receive the refer complete callback. + /// [XmlAttribute("referCompleteUrl")] public string ReferCompleteUrl { get; set; } - /// HTTP method to send the refer complete callback. + /// + /// HTTP method to send the refer complete callback. GET or POST. Default value is POST. + /// [XmlAttribute("referCompleteMethod")] public string ReferCompleteMethod { @@ -29,61 +35,81 @@ public string ReferCompleteMethod } } + /// /// Optional custom string to include in callbacks. + /// [XmlAttribute("tag")] public string Tag { get; set; } + /// /// SIP URI destination for the REFER. + /// [XmlElement("SipUri")] public SipUri SipUriElement { get; set; } + /// /// Initializes a new instance of the Refer class. + /// public Refer() { ReferCompleteMethod = "POST"; } + /// /// Sets the SIP URI destination from a string. + /// public Refer WithSipUri(string sipUri) { SipUriElement = new SipUri { Uri = sipUri }; return this; } + /// /// Sets the SIP URI destination from a SipUri object. + /// public Refer WithSipUri(SipUri sipUri) { SipUriElement = sipUri; return this; } + /// /// Sets referCompleteUrl. + /// public Refer WithReferCompleteUrl(string referCompleteUrl) { ReferCompleteUrl = referCompleteUrl; return this; } + /// /// Sets referCompleteMethod. + /// public Refer WithReferCompleteMethod(string referCompleteMethod) { ReferCompleteMethod = referCompleteMethod; return this; } + /// /// Sets tag. + /// public Refer WithTag(string tag) { Tag = tag; return this; } + /// /// BXML tag to represent a SIP URI for the refer verb. + /// public class SipUri : IVerb { private string _uri; + /// /// SIP URI to refer the call to (must start with sip:). + /// [XmlText] public string Uri {