diff --git a/sdk/communication/Azure.Communication.CallAutomation/CHANGELOG.md b/sdk/communication/Azure.Communication.CallAutomation/CHANGELOG.md index 33e37063628e..793f4aaf227b 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/CHANGELOG.md +++ b/sdk/communication/Azure.Communication.CallAutomation/CHANGELOG.md @@ -1,14 +1,10 @@ # Release History -## 1.6.0-beta.1 (Unreleased) +## 1.5.1 (2026-01-28) ### Features Added -### Breaking Changes - -### Bugs Fixed - -### Other Changes +- Support for MicrosoftTeamsAppIdentifier CommunicationIdentifier ## 1.5.0 (2025-08-25) diff --git a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net10.0.cs b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net10.0.cs index fe2d92bc439f..5b51c632471e 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net10.0.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net10.0.cs @@ -333,6 +333,7 @@ public CallIntelligenceOptions() { } public partial class CallInvite { public CallInvite(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public CallInvite(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public CallInvite(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public CallInvite(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity, Azure.Communication.PhoneNumberIdentifier callerIdNumber) { } public CallInvite(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } @@ -1627,6 +1628,7 @@ internal TransferCallToParticipantResult() { } public partial class TransferToParticipantOptions { public TransferToParticipantOptions(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity) { } public TransferToParticipantOptions(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } diff --git a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net8.0.cs b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net8.0.cs index fe2d92bc439f..5b51c632471e 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net8.0.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.net8.0.cs @@ -333,6 +333,7 @@ public CallIntelligenceOptions() { } public partial class CallInvite { public CallInvite(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public CallInvite(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public CallInvite(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public CallInvite(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity, Azure.Communication.PhoneNumberIdentifier callerIdNumber) { } public CallInvite(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } @@ -1627,6 +1628,7 @@ internal TransferCallToParticipantResult() { } public partial class TransferToParticipantOptions { public TransferToParticipantOptions(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity) { } public TransferToParticipantOptions(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } diff --git a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.netstandard2.0.cs b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.netstandard2.0.cs index 4a8e5fde32c6..b2a2ce53103a 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.netstandard2.0.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/api/Azure.Communication.CallAutomation.netstandard2.0.cs @@ -333,6 +333,7 @@ public CallIntelligenceOptions() { } public partial class CallInvite { public CallInvite(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public CallInvite(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public CallInvite(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public CallInvite(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity, Azure.Communication.PhoneNumberIdentifier callerIdNumber) { } public CallInvite(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } @@ -1626,6 +1627,7 @@ internal TransferCallToParticipantResult() { } public partial class TransferToParticipantOptions { public TransferToParticipantOptions(Azure.Communication.CommunicationUserIdentifier targetIdentity) { } + public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsAppIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.MicrosoftTeamsUserIdentifier targetIdentity) { } public TransferToParticipantOptions(Azure.Communication.PhoneNumberIdentifier targetPhoneNumberIdentity) { } public TransferToParticipantOptions(Azure.Communication.TeamsExtensionUserIdentifier targetIdentity) { } diff --git a/sdk/communication/Azure.Communication.CallAutomation/src/Azure.Communication.CallAutomation.csproj b/sdk/communication/Azure.Communication.CallAutomation/src/Azure.Communication.CallAutomation.csproj index a97d63cde740..45108e592622 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/src/Azure.Communication.CallAutomation.csproj +++ b/sdk/communication/Azure.Communication.CallAutomation/src/Azure.Communication.CallAutomation.csproj @@ -6,7 +6,7 @@ Microsoft Azure Communication Call Automation quickstart - https://learn.microsoft.com/azure/communication-services/quickstarts/voice-video-calling/callflows-for-customer-interactions?pivots=programming-language-csharp Azure Communication CallAutomation Service - 1.6.0-beta.1 + 1.5.1 1.5.0 Microsoft Azure Communication CallAutomation Service;Microsoft;Azure;Azure Communication Service;Azure Communication CallAutomation Service;Calling;Communication;$(PackageCommonTags) diff --git a/sdk/communication/Azure.Communication.CallAutomation/src/CallConnection.cs b/sdk/communication/Azure.Communication.CallAutomation/src/CallConnection.cs index 07ce3c3e36ed..ddbc0c72bc2a 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/src/CallConnection.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/src/CallConnection.cs @@ -170,6 +170,14 @@ public virtual async Task> TransferCal { options = new TransferToParticipantOptions(targetParticipant as MicrosoftTeamsUserIdentifier); } + else if (targetParticipant is MicrosoftTeamsAppIdentifier) + { + options = new TransferToParticipantOptions(targetParticipant as MicrosoftTeamsAppIdentifier); + } + else if (targetParticipant is TeamsExtensionUserIdentifier) + { + options = new TransferToParticipantOptions(targetParticipant as TeamsExtensionUserIdentifier); + } else { throw new ArgumentException("targetParticipant type is invalid.", nameof(targetParticipant)); @@ -233,6 +241,14 @@ public virtual Response TransferCallToParticipa { options = new TransferToParticipantOptions(targetParticipant as MicrosoftTeamsUserIdentifier); } + else if (targetParticipant is MicrosoftTeamsAppIdentifier) + { + options = new TransferToParticipantOptions(targetParticipant as MicrosoftTeamsAppIdentifier); + } + else if (targetParticipant is TeamsExtensionUserIdentifier) + { + options = new TransferToParticipantOptions(targetParticipant as TeamsExtensionUserIdentifier); + } else { throw new ArgumentException("targetParticipant type is invalid.", nameof(targetParticipant)); diff --git a/sdk/communication/Azure.Communication.CallAutomation/src/Models/CallInvite.cs b/sdk/communication/Azure.Communication.CallAutomation/src/Models/CallInvite.cs index 84d3a05930a6..9667f3bf3ada 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/src/Models/CallInvite.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/src/Models/CallInvite.cs @@ -15,8 +15,8 @@ public class CallInvite /// When the source of the call is a Teams App source, callerIdNumber is not supported and should be null. /// Sip Headers are supported for PSTN calls only. Voip Headers are not supported. /// - /// - /// + /// The phone number identifier of the target participant. + /// The caller ID phone number to be displayed to the target participant. public CallInvite(PhoneNumberIdentifier targetPhoneNumberIdentity, PhoneNumberIdentifier callerIdNumber) { Target = targetPhoneNumberIdentity; @@ -28,7 +28,7 @@ public CallInvite(PhoneNumberIdentifier targetPhoneNumberIdentity, PhoneNumberId /// Creates a new CallInvite object. /// Sip Headers are not supported. Voip Headers are supported for ACS Users. /// - /// + /// The Communication User identifier of the target participant. public CallInvite(CommunicationUserIdentifier targetIdentity) { Target = targetIdentity; @@ -39,7 +39,7 @@ public CallInvite(CommunicationUserIdentifier targetIdentity) /// Creates a new CallInvite object. /// Sip Headers are not supported. Voip Headers are supported for Microsoft teams Users. /// - /// + /// The Microsoft Teams User identifier of the target participant. public CallInvite(MicrosoftTeamsUserIdentifier targetIdentity) { Target = targetIdentity; @@ -50,13 +50,24 @@ public CallInvite(MicrosoftTeamsUserIdentifier targetIdentity) /// Creates a new CallInvite object. /// Sip Headers are not supported. Voip Headers are supported for Microsoft Teams Apps. /// - /// + /// The Teams Extension User identifier of the target participant. public CallInvite(TeamsExtensionUserIdentifier targetIdentity) { Target = targetIdentity; CustomCallingContext = new CustomCallingContext(sipHeaders: null, voipHeaders: new Dictionary()); } + /// + /// Creates a new CallInvite object. + /// Sip Headers are not supported. Voip Headers are supported for Microsoft Teams Apps. + /// + /// The Microsoft Teams App identifier of the target participant. + public CallInvite(MicrosoftTeamsAppIdentifier targetIdentity) + { + Target = targetIdentity; + CustomCallingContext = new CustomCallingContext(sipHeaders: null, voipHeaders: new Dictionary()); + } + /// /// The target callee. /// diff --git a/sdk/communication/Azure.Communication.CallAutomation/src/Models/TransferToParticipantOptions.cs b/sdk/communication/Azure.Communication.CallAutomation/src/Models/TransferToParticipantOptions.cs index 5c96c3ea543b..ee25e8aa67d8 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/src/Models/TransferToParticipantOptions.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/src/Models/TransferToParticipantOptions.cs @@ -51,6 +51,16 @@ public TransferToParticipantOptions(TeamsExtensionUserIdentifier targetIdentity) CustomCallingContext = new CustomCallingContext(sipHeaders: null, voipHeaders: new Dictionary()); } + /// + /// Creates a new TransferToParticipantOptions object. + /// + /// The target to transfer the call to. + public TransferToParticipantOptions(MicrosoftTeamsAppIdentifier targetIdentity) + { + Target = targetIdentity; + CustomCallingContext = new CustomCallingContext(sipHeaders: null, voipHeaders: new Dictionary()); + } + /// /// The target callee. /// diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClients/CallAutomationClientTests.cs b/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClients/CallAutomationClientTests.cs index 5b17477d4405..1a4598970311 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClients/CallAutomationClientTests.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClients/CallAutomationClientTests.cs @@ -326,8 +326,118 @@ public void CreateCallAsync_404NotFound(CallInvite target, Uri callbackUri) Assert.AreEqual(ex?.Status, 404); } - [TestCaseSource(nameof(TestData_CreateCall))] - public void CreateCall_404NotFound(CallInvite target, Uri callbackUri) + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public async Task CreateCallAsync_MicrosoftTeamsAppIdentifier_201Created(CallInvite target, Uri callbackUri) + { + CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(201, CreateOrAnswerCallOrGetCallConnectionPayload); + + var options = new CreateCallOptions(target, callbackUri); + var response = await callAutomationClient.CreateCallAsync(options).ConfigureAwait(false); + CreateCallResult result = (CreateCallResult)response; + Assert.NotNull(result); + Assert.AreEqual((int)HttpStatusCode.Created, response.GetRawResponse().Status); + verifyCallConnectionProperties(result.CallConnectionProperties); + Assert.Null(response.Value.CallConnectionProperties.MediaStreamingSubscription); + Assert.Null(response.Value.CallConnectionProperties.TranscriptionSubscription); + Assert.AreEqual(CallConnectionId, result.CallConnection.CallConnectionId); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(target.Target); + var teamsApp = target.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public void CreateCall_MicrosoftTeamsAppIdentifier_201Created(CallInvite target, Uri callbackUri) + { + CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(201, CreateOrAnswerCallOrGetCallConnectionPayload); + + var options = new CreateCallOptions(target, callbackUri); + var response = callAutomationClient.CreateCall(options); + CreateCallResult result = (CreateCallResult)response; + Assert.NotNull(result); + Assert.AreEqual((int)HttpStatusCode.Created, response.GetRawResponse().Status); + verifyCallConnectionProperties(result.CallConnectionProperties); + Assert.Null(response.Value.CallConnectionProperties.MediaStreamingSubscription); + Assert.Null(response.Value.CallConnectionProperties.TranscriptionSubscription); + Assert.AreEqual(CallConnectionId, result.CallConnection.CallConnectionId); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(target.Target); + var teamsApp = target.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public async Task CreateCallWithOptionsAsync_MicrosoftTeamsAppIdentifier_201Created(CallInvite target, Uri callbackUri) + { + CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(201, CreateOrAnswerCallOrGetCallConnectionWithMediaSubscriptionAndTranscriptionPayload); + CreateCallOptions options = new CreateCallOptions( + callInvite: target, + callbackUri: callbackUri) + { + MediaStreamingOptions = _mediaStreamingConfiguration, + TranscriptionOptions = _transcriptionConfiguration, + OperationContext = "teams-app-context" + }; + + var response = await callAutomationClient.CreateCallAsync(options).ConfigureAwait(false); + CreateCallResult result = (CreateCallResult)response; + Assert.NotNull(result); + Assert.AreEqual((int)HttpStatusCode.Created, response.GetRawResponse().Status); + verifyCallConnectionProperties(result.CallConnectionProperties); + Assert.AreEqual(CallConnectionId, result.CallConnection.CallConnectionId); + Assert.NotNull(response.Value.CallConnectionProperties.MediaStreamingSubscription); + Assert.NotNull(response.Value.CallConnectionProperties.TranscriptionSubscription); + + // Verify all optional properties can be set + Assert.AreEqual("teams-app-context", options.OperationContext); + Assert.IsNotNull(options.MediaStreamingOptions); + Assert.IsNotNull(options.TranscriptionOptions); + } + + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public void CreateCallWithOptions_MicrosoftTeamsAppIdentifier_201Created(CallInvite target, Uri callbackUri) + { + CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(201, CreateOrAnswerCallOrGetCallConnectionWithMediaSubscriptionAndTranscriptionPayload); + CreateCallOptions options = new CreateCallOptions( + callInvite: target, + callbackUri: callbackUri) + { + MediaStreamingOptions = _mediaStreamingConfiguration, + TranscriptionOptions = _transcriptionConfiguration, + OperationContext = "teams-app-context" + }; + + var response = callAutomationClient.CreateCall(options); + CreateCallResult result = (CreateCallResult)response; + Assert.NotNull(result); + Assert.AreEqual((int)HttpStatusCode.Created, response.GetRawResponse().Status); + verifyCallConnectionProperties(result.CallConnectionProperties); + Assert.AreEqual(CallConnectionId, result.CallConnection.CallConnectionId); + Assert.NotNull(response.Value.CallConnectionProperties.MediaStreamingSubscription); + Assert.NotNull(response.Value.CallConnectionProperties.TranscriptionSubscription); + + // Verify all optional properties can be set + Assert.AreEqual("teams-app-context", options.OperationContext); + Assert.IsNotNull(options.MediaStreamingOptions); + Assert.IsNotNull(options.TranscriptionOptions); + } + + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public void CreateCallAsync_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite target, Uri callbackUri) + { + CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(404); + + RequestFailedException? ex = Assert.ThrowsAsync(async () => await callAutomationClient.CreateCallAsync(new CreateCallOptions(target, callbackUri)).ConfigureAwait(false)); + Assert.NotNull(ex); + Assert.AreEqual(ex?.Status, 404); + } + + [TestCaseSource(nameof(TestData_CreateCall_MicrosoftTeamsApp))] + public void CreateCall_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite target, Uri callbackUri) { CallAutomationClient callAutomationClient = CreateMockCallAutomationClient(404); @@ -512,6 +622,39 @@ private static void ValidateCallConnectionProperties(CallConnectionProperties pr }; } + private static IEnumerable TestData_CreateCall_MicrosoftTeamsApp() + { + var callInvite = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId")); + callInvite.CustomCallingContext.AddVoip("key1", "value1"); + return new[] + { + new object?[] + { + callInvite, + new Uri("https://bot.contoso.com/callback") + }, + }; + } + + private static IEnumerable TestData_CreateCall_MicrosoftTeamsAppWithCloud() + { + var callInvitePublic = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Public)); + callInvitePublic.CustomCallingContext.AddVoip("key1", "value1"); + + var callInviteDod = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Dod)); + callInviteDod.CustomCallingContext.AddVoip("key2", "value2"); + + var callInviteGcch = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Gcch)); + callInviteGcch.CustomCallingContext.AddVoip("key3", "value3"); + + return new[] + { + new object?[] { callInvitePublic, new Uri("https://bot.contoso.com/callback") }, + new object?[] { callInviteDod, new Uri("https://bot.contoso.com/callback") }, + new object?[] { callInviteGcch, new Uri("https://bot.contoso.com/callback") }, + }; + } + private static IEnumerable TestData_CreateGroupCall() { return new[] diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/CallConnections/CallConnectionTests.cs b/sdk/communication/Azure.Communication.CallAutomation/tests/CallConnections/CallConnectionTests.cs index 0e99f6d85465..765529bb4aa0 100644 --- a/sdk/communication/Azure.Communication.CallAutomation/tests/CallConnections/CallConnectionTests.cs +++ b/sdk/communication/Azure.Communication.CallAutomation/tests/CallConnections/CallConnectionTests.cs @@ -246,6 +246,162 @@ public void AddParticipants_202Accepted(CommunicationIdentifier participantToAdd verifyAddParticipantsResult(response); } + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public async Task AddParticipantAsync_MicrosoftTeamsAppIdentifier_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var response = await callConnection.AddParticipantAsync(new AddParticipantOptions(callInvite)).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public void AddParticipant_MicrosoftTeamsAppIdentifier_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var response = callConnection.AddParticipant(new AddParticipantOptions(callInvite)); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsAppWithCloud))] + public async Task AddParticipantAsync_MicrosoftTeamsAppIdentifier_DifferentClouds_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var response = await callConnection.AddParticipantAsync(new AddParticipantOptions(callInvite)).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify the target maintains the correct cloud environment + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + Assert.IsTrue(teamsApp.Cloud == CommunicationCloudEnvironment.Public || + teamsApp.Cloud == CommunicationCloudEnvironment.Dod || + teamsApp.Cloud == CommunicationCloudEnvironment.Gcch); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsAppWithCloud))] + public void AddParticipant_MicrosoftTeamsAppIdentifier_DifferentClouds_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var response = callConnection.AddParticipant(new AddParticipantOptions(callInvite)); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify the target maintains the correct cloud environment + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + Assert.IsTrue(teamsApp.Cloud == CommunicationCloudEnvironment.Public || + teamsApp.Cloud == CommunicationCloudEnvironment.Dod || + teamsApp.Cloud == CommunicationCloudEnvironment.Gcch); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public async Task AddParticipantAsync_MicrosoftTeamsAppIdentifier_WithAllOptions_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var options = new AddParticipantOptions(callInvite) + { + OperationContext = "custom-context", + InvitationTimeoutInSeconds = 60, + OperationCallbackUri = new Uri("https://example.com/callback") + }; + + var response = await callConnection.AddParticipantAsync(options).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify all optional properties can be set + Assert.AreEqual("custom-context", options.OperationContext); + Assert.AreEqual(60, options.InvitationTimeoutInSeconds); + Assert.IsNotNull(options.OperationCallbackUri); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public void AddParticipant_MicrosoftTeamsAppIdentifier_WithAllOptions_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + + var options = new AddParticipantOptions(callInvite) + { + OperationContext = "custom-context", + InvitationTimeoutInSeconds = 60, + OperationCallbackUri = new Uri("https://example.com/callback") + }; + + var response = callConnection.AddParticipant(options); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyAddParticipantsResult(response); + + // Verify all optional properties can be set + Assert.AreEqual("custom-context", options.OperationContext); + Assert.AreEqual(60, options.InvitationTimeoutInSeconds); + Assert.IsNotNull(options.OperationCallbackUri); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public void AddParticipantAsync_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(404); + + RequestFailedException? ex = Assert.ThrowsAsync(async () => await callConnection.AddParticipantAsync(new AddParticipantOptions(callInvite)).ConfigureAwait(false)); + Assert.NotNull(ex); + Assert.AreEqual(ex?.Status, 404); + } + + [TestCaseSource(nameof(TestData_AddParticipant_MicrosoftTeamsApp))] + public void AddParticipant_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(404); + + RequestFailedException? ex = Assert.Throws(() => callConnection.AddParticipant(new AddParticipantOptions(callInvite))); + Assert.NotNull(ex); + Assert.AreEqual(ex?.Status, 404); + } + + [Test] + public void AddParticipantAsync_NullMicrosoftTeamsAppCallInvite_ThrowsArgumentNullException() + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + CallInvite? nullCallInvite = null; + + ArgumentNullException? ex = Assert.ThrowsAsync(async () => await callConnection.AddParticipantAsync(new AddParticipantOptions(nullCallInvite!)).ConfigureAwait(false)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("Value cannot be null")); + } + + [Test] + public void AddParticipant_NullMicrosoftTeamsAppCallInvite_ThrowsArgumentNullException() + { + var callConnection = CreateMockCallConnection(202, AddParticipantPayload); + CallInvite? nullCallInvite = null; + + ArgumentNullException? ex = Assert.Throws(() => callConnection.AddParticipant(new AddParticipantOptions(nullCallInvite!))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("Value cannot be null")); + } + [Test] public void AddParticipantsAsync_NullParticipantToAdd() { @@ -489,6 +645,196 @@ public async Task CancelAddParticipantAsync_202Accepted() Assert.AreEqual(invitationId, response.Value.InvitationId); } + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public async Task TransferCallToParticipantAsync_MicrosoftTeamsAppIdentifier_simpleMethod_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var response = await callConnection.TransferCallToParticipantAsync(callInvite.Target).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public void TransferCallToParticipant_MicrosoftTeamsAppIdentifier_simpleMethod_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var response = callConnection.TransferCallToParticipant(callInvite.Target); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the target is correctly typed as MicrosoftTeamsAppIdentifier + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public async Task TransferCallToParticipantAsync_MicrosoftTeamsAppIdentifier_WithOptions_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var options = new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier); + + var response = await callConnection.TransferCallToParticipantAsync(options).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the options were created correctly + Assert.IsInstanceOf(options.Target); + Assert.IsNotNull(options.CustomCallingContext); + Assert.IsEmpty(options.CustomCallingContext.SipHeaders); + Assert.IsNotNull(options.CustomCallingContext.VoipHeaders); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public void TransferCallToParticipant_MicrosoftTeamsAppIdentifier_WithOptions_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var options = new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier); + + var response = callConnection.TransferCallToParticipant(options); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the options were created correctly + Assert.IsInstanceOf(options.Target); + Assert.IsNotNull(options.CustomCallingContext); + Assert.IsEmpty(options.CustomCallingContext.SipHeaders); + Assert.IsNotNull(options.CustomCallingContext.VoipHeaders); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsAppWithCloud))] + public async Task TransferCallToParticipantAsync_MicrosoftTeamsAppIdentifier_DifferentClouds_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var response = await callConnection.TransferCallToParticipantAsync(callInvite.Target).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the target maintains the correct cloud environment + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + Assert.IsTrue(teamsApp.Cloud == CommunicationCloudEnvironment.Public || + teamsApp.Cloud == CommunicationCloudEnvironment.Dod || + teamsApp.Cloud == CommunicationCloudEnvironment.Gcch); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsAppWithCloud))] + public void TransferCallToParticipant_MicrosoftTeamsAppIdentifier_DifferentClouds_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + + var response = callConnection.TransferCallToParticipant(callInvite.Target); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify the target maintains the correct cloud environment + Assert.IsInstanceOf(callInvite.Target); + var teamsApp = callInvite.Target as MicrosoftTeamsAppIdentifier; + Assert.IsNotNull(teamsApp); + Assert.AreEqual("testAppId", teamsApp!.AppId); + Assert.IsTrue(teamsApp.Cloud == CommunicationCloudEnvironment.Public || + teamsApp.Cloud == CommunicationCloudEnvironment.Dod || + teamsApp.Cloud == CommunicationCloudEnvironment.Gcch); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public async Task TransferCallToParticipantAsync_MicrosoftTeamsAppIdentifier_WithTransferee_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + var options = new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier); + options.Transferee = new CommunicationUserIdentifier("transfereeId"); + options.OperationContext = "custom-context"; + options.OperationCallbackUri = new Uri("https://example.com/callback"); + options.SourceCallerIdNumber = new PhoneNumberIdentifier("+14255551234"); + + var response = await callConnection.TransferCallToParticipantAsync(options).ConfigureAwait(false); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify all optional properties can be set + Assert.IsNotNull(options.Transferee); + Assert.IsNotNull(options.OperationCallbackUri); + Assert.IsNotNull(options.SourceCallerIdNumber); + Assert.AreEqual("custom-context", options.OperationContext); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public void TransferCallToParticipant_MicrosoftTeamsAppIdentifier_WithTransferee_202Accepted(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + var options = new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier); + options.Transferee = new CommunicationUserIdentifier("transfereeId"); + options.OperationContext = "custom-context"; + options.OperationCallbackUri = new Uri("https://example.com/callback"); + options.SourceCallerIdNumber = new PhoneNumberIdentifier("+14255551234"); + + var response = callConnection.TransferCallToParticipant(options); + Assert.AreEqual((int)HttpStatusCode.Accepted, response.GetRawResponse().Status); + verifyOperationContext(response); + + // Verify all optional properties can be set + Assert.IsNotNull(options.Transferee); + Assert.IsNotNull(options.OperationCallbackUri); + Assert.IsNotNull(options.SourceCallerIdNumber); + Assert.AreEqual("custom-context", options.OperationContext); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public void TransferCallToParticipantAsync_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(404); + + RequestFailedException? ex = Assert.ThrowsAsync(async () => await callConnection.TransferCallToParticipantAsync(new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier)).ConfigureAwait(false)); + Assert.NotNull(ex); + Assert.AreEqual(ex?.Status, 404); + } + + [TestCaseSource(nameof(TestData_TransferCallToParticipant_MicrosoftTeamsApp))] + public void TransferCallToParticipant_MicrosoftTeamsAppIdentifier_404NotFound(CallInvite callInvite) + { + var callConnection = CreateMockCallConnection(404); + + RequestFailedException? ex = Assert.Throws(() => callConnection.TransferCallToParticipant(new TransferToParticipantOptions(callInvite.Target as MicrosoftTeamsAppIdentifier))); + Assert.NotNull(ex); + Assert.AreEqual(ex?.Status, 404); + } + + [Test] + public void TransferCallToParticipantAsync_NullMicrosoftTeamsAppIdentifier_ThrowsArgumentNullException() + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + MicrosoftTeamsAppIdentifier? nullTeamsAppIdentifier = null; + + ArgumentNullException? ex = Assert.ThrowsAsync(async () => await callConnection.TransferCallToParticipantAsync(nullTeamsAppIdentifier!).ConfigureAwait(false)); + Assert.IsNotNull(ex); + Assert.AreEqual("targetParticipant", ex!.ParamName); + } + + [Test] + public void TransferCallToParticipant_NullMicrosoftTeamsAppIdentifier_ThrowsArgumentNullException() + { + var callConnection = CreateMockCallConnection(202, OperationContextPayload); + MicrosoftTeamsAppIdentifier? nullTeamsAppIdentifier = null; + + ArgumentNullException? ex = Assert.Throws(() => callConnection.TransferCallToParticipant(nullTeamsAppIdentifier!)); + Assert.IsNotNull(ex); + Assert.AreEqual("targetParticipant", ex!.ParamName); + } + private CallConnection CreateMockCallConnection(int responseCode, string? responseContent = null, string callConnectionId = "9ec7da16-30be-4e74-a941-285cfc4bffc5") { return CreateMockCallAutomationClient(responseCode, responseContent).GetCallConnection(callConnectionId); @@ -532,6 +878,38 @@ private CallConnection CreateMockCallConnection(int responseCode, string? respon }; } + private static IEnumerable TestData_TransferCallToParticipant_MicrosoftTeamsApp() + { + var callInvite = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId")); + callInvite.CustomCallingContext.AddVoip("key1", "value1"); + return new[] + { + new object?[] + { + callInvite + }, + }; + } + + private static IEnumerable TestData_TransferCallToParticipant_MicrosoftTeamsAppWithCloud() + { + var callInvitePublic = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Public)); + callInvitePublic.CustomCallingContext.AddVoip("key1", "value1"); + + var callInviteDod = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Dod)); + callInviteDod.CustomCallingContext.AddVoip("key2", "value2"); + + var callInviteGcch = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Gcch)); + callInviteGcch.CustomCallingContext.AddVoip("key3", "value3"); + + return new[] + { + new object?[] { callInvitePublic }, + new object?[] { callInviteDod }, + new object?[] { callInviteGcch }, + }; + } + private static IEnumerable TestData_GetParticipant() { return new[] @@ -554,6 +932,38 @@ private CallConnection CreateMockCallConnection(int responseCode, string? respon }; } + private static IEnumerable TestData_AddParticipant_MicrosoftTeamsApp() + { + var callInvite = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId")); + callInvite.CustomCallingContext.AddVoip("key1", "value1"); + return new[] + { + new object?[] + { + callInvite + }, + }; + } + + private static IEnumerable TestData_AddParticipant_MicrosoftTeamsAppWithCloud() + { + var callInvitePublic = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Public)); + callInvitePublic.CustomCallingContext.AddVoip("key1", "value1"); + + var callInviteDod = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Dod)); + callInviteDod.CustomCallingContext.AddVoip("key2", "value2"); + + var callInviteGcch = new CallInvite(new MicrosoftTeamsAppIdentifier("testAppId", CommunicationCloudEnvironment.Gcch)); + callInviteGcch.CustomCallingContext.AddVoip("key3", "value3"); + + return new[] + { + new object?[] { callInvitePublic }, + new object?[] { callInviteDod }, + new object?[] { callInviteGcch }, + }; + } + private static IEnumerable TestData_MuteParticipant() { return new[]