diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/Azure.Communication.CallAutomation.Tests.csproj b/sdk/communication/Azure.Communication.CallAutomation/tests/Azure.Communication.CallAutomation.Tests.csproj
index 020fe9bebd333..2c21b40de26f2 100644
--- a/sdk/communication/Azure.Communication.CallAutomation/tests/Azure.Communication.CallAutomation.Tests.csproj
+++ b/sdk/communication/Azure.Communication.CallAutomation/tests/Azure.Communication.CallAutomation.Tests.csproj
@@ -7,7 +7,11 @@
+
+
+
+
@@ -18,6 +22,7 @@
+
diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClient/CallAutomationClientAutomatedLiveTests.cs b/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClient/CallAutomationClientAutomatedLiveTests.cs
new file mode 100644
index 0000000000000..93829e678c7c5
--- /dev/null
+++ b/sdk/communication/Azure.Communication.CallAutomation/tests/CallAutomationClient/CallAutomationClientAutomatedLiveTests.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Azure.Communication.CallAutomation;
+using Azure.Communication.CallAutomation.Tests.Infrastructure;
+using Azure.Communication.Identity;
+using Azure.Core;
+using Azure.Core.TestFramework;
+using Microsoft.AspNetCore.Http;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace Azure.Communication.CallAutomation
+{
+ internal class CallAutomationClientAutomatedLiveTests : CallAutomationClientAutomatedLiveTestsBase
+ {
+ public CallAutomationClientAutomatedLiveTests(bool isAsync) : base(isAsync)
+ {
+ }
+
+ [RecordedTest]
+ public async Task CreateCallToACSGetCallAndHangUpCallTest()
+ {
+ /* Test case: ACS to ACS call
+ * 1. create a CallAutomationClient.
+ * 2. create a call from source to one ACS target.
+ * 3. get updated call properties and check for the connected state.
+ * 4. hang up the call.
+ * 5. once call is hung up, verify disconnected event
+ */
+
+ CallAutomationClient client = CreateInstrumentedCallAutomationClientWithConnectionString();
+
+ try
+ {
+ // create caller and receiver
+ var user = await CreateIdentityUserAsync().ConfigureAwait(false);
+ var target = await CreateIdentityUserAsync().ConfigureAwait(false);
+
+ // setup service bus
+ var uniqueId = await ServiceBusWithNewCall(user, target);
+
+ // create call and assert response
+ CreateCallResult response = await client.CreateCallAsync(new CreateCallOptions(new CallSource(user), new CommunicationIdentifier[] { target }, new Uri(TestEnvironment.DispatcherCallback + $"?q={uniqueId}"))).ConfigureAwait(false);
+ string callConnectionId = response.CallConnectionProperties.CallConnectionId;
+ Assert.IsNotEmpty(response.CallConnectionProperties.CallConnectionId);
+
+ // wait for incomingcall context
+ string? incomingCallContext = await WaitForIncomingCallContext(uniqueId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(incomingCallContext);
+
+ // answer the call
+ AnswerCallResult answerResponse = await client.AnswerCallAsync(incomingCallContext, new Uri(TestEnvironment.DispatcherCallback));
+
+ // wait for callConnected
+ var connectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(connectedEvent);
+ Assert.IsTrue(connectedEvent is CallConnected);
+ Assert.IsTrue(((CallConnected)connectedEvent!).CallConnectionId == callConnectionId);
+
+ // test get properties
+ Response properties = await response.CallConnection.GetCallConnectionPropertiesAsync().ConfigureAwait(false);
+ Assert.AreEqual(CallConnectionState.Connected, properties.Value.CallConnectionState);
+
+ // try hangup
+ await response.CallConnection.HangUpAsync(true).ConfigureAwait(false);
+ var disconnectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(disconnectedEvent);
+ Assert.IsTrue(disconnectedEvent is CallDisconnected);
+ Assert.IsTrue(((CallDisconnected)disconnectedEvent!).CallConnectionId == callConnectionId);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Unexpected error: {ex}");
+ }
+ }
+ }
+}
diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/CallRecording/CallRecordingAutomatedLiveTests.cs b/sdk/communication/Azure.Communication.CallAutomation/tests/CallRecording/CallRecordingAutomatedLiveTests.cs
new file mode 100644
index 0000000000000..82c024a088a9f
--- /dev/null
+++ b/sdk/communication/Azure.Communication.CallAutomation/tests/CallRecording/CallRecordingAutomatedLiveTests.cs
@@ -0,0 +1,193 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Azure.Communication.CallAutomation;
+using Azure.Communication.CallAutomation.Tests.Infrastructure;
+using Azure.Communication.Identity;
+using Azure.Core;
+using Azure.Core.TestFramework;
+using Microsoft.AspNetCore.Http;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace Azure.Communication.CallAutomation
+{
+ internal class CallRecordingAutomatedLiveTests : CallAutomationClientAutomatedLiveTestsBase
+ {
+ public CallRecordingAutomatedLiveTests(bool isAsync) : base(isAsync)
+ {
+ }
+
+ [RecordedTest]
+ public async Task CreateACSCallAndUnmixedAudioTest()
+ {
+ /* Test case: ACS to ACS call
+ * 1. create a CallAutomationClient.
+ * 2. create a call from source to one ACS target.
+ * 3. get updated call properties and check for the connected state.
+ * 4. start recording the call without channel affinity
+ * 5. stop recording the call
+ * 6. hang up the call.
+ * 7. once call is hung up, verify disconnected event
+ */
+
+ CallAutomationClient client = CreateInstrumentedCallAutomationClientWithConnectionString();
+
+ try
+ {
+ // create caller and receiver
+ var user = await CreateIdentityUserAsync().ConfigureAwait(false);
+ var target = await CreateIdentityUserAsync().ConfigureAwait(false);
+
+ // setup service bus
+ var uniqueId = await ServiceBusWithNewCall(user, target);
+
+ // create call and assert response
+ CreateCallResult response = await client.CreateCallAsync(new CreateCallOptions(new CallSource(user), new CommunicationIdentifier[] { target }, new Uri(TestEnvironment.DispatcherCallback + $"?q={uniqueId}"))).ConfigureAwait(false);
+ string callConnectionId = response.CallConnectionProperties.CallConnectionId;
+ Assert.IsNotEmpty(response.CallConnectionProperties.CallConnectionId);
+
+ // wait for incomingcall context
+ string? incomingCallContext = await WaitForIncomingCallContext(uniqueId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(incomingCallContext);
+
+ // answer the call
+ var answerResponse = await client.AnswerCallAsync(incomingCallContext, new Uri(TestEnvironment.DispatcherCallback));
+ Assert.AreEqual(answerResponse.GetRawResponse().Status, StatusCodes.Status200OK);
+
+ // wait for callConnected
+ var connectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(connectedEvent);
+ Assert.IsTrue(connectedEvent is CallConnected);
+ Assert.IsTrue(((CallConnected)connectedEvent!).CallConnectionId == callConnectionId);
+
+ // test get properties
+ Response properties = await response.CallConnection.GetCallConnectionPropertiesAsync().ConfigureAwait(false);
+ Assert.AreEqual(CallConnectionState.Connected, properties.Value.CallConnectionState);
+
+ // try start recording unmixed audio - no channel affinity
+ var startRecordingResponse = await client.GetCallRecording().StartRecordingAsync(
+ new StartRecordingOptions(new ServerCallLocator(properties.Value.ServerCallId))
+ {
+ RecordingChannel = RecordingChannel.Unmixed,
+ RecordingContent = RecordingContent.Audio,
+ RecordingFormat = RecordingFormat.Wav,
+ RecordingStateCallbackEndpoint = new Uri(TestEnvironment.DispatcherCallback)
+ });
+ Assert.AreEqual(StatusCodes.Status200OK, startRecordingResponse.GetRawResponse().Status);
+ Assert.NotNull(startRecordingResponse.Value.RecordingId);
+
+ // try stop recording
+ var stopRecordingResponse = await client.GetCallRecording().StopRecordingAsync(startRecordingResponse.Value.RecordingId);
+ Assert.AreEqual(StatusCodes.Status204NoContent, stopRecordingResponse.Status);
+
+ // wait for CallRecordingStateChanged event TODO: Figure out why this event not being received
+ // var recordingStartedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ // Assert.IsNotNull(recordingStartedEvent);
+ // Assert.IsTrue(recordingStartedEvent is CallRecordingStateChanged);
+ // Assert.IsTrue(((CallRecordingStateChanged)recordingStartedEvent!).CallConnectionId == callConnectionId);
+
+ // try hangup
+ await response.CallConnection.HangUpAsync(true).ConfigureAwait(false);
+ var disconnectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(disconnectedEvent);
+ Assert.IsTrue(disconnectedEvent is CallDisconnected);
+ Assert.IsTrue(((CallDisconnected)disconnectedEvent!).CallConnectionId == callConnectionId);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Unexpected error: {ex}");
+ }
+ }
+
+ [RecordedTest]
+ public async Task CreateACSCallUnmixedAudioAffinityTest()
+ {
+ /* Test case: ACS to ACS call
+ * 1. create a CallAutomationClient.
+ * 2. create a call from source to one ACS target.
+ * 3. get updated call properties and check for the connected state.
+ * 4. start recording the call with channel affinity
+ * 5. stop recording the call
+ * 6. hang up the call.
+ * 7. once call is hung up, verify disconnected event
+ */
+
+ CallAutomationClient client = CreateInstrumentedCallAutomationClientWithConnectionString();
+
+ try
+ {
+ // create caller and receiver
+ var user = await CreateIdentityUserAsync().ConfigureAwait(false);
+ var target = await CreateIdentityUserAsync().ConfigureAwait(false);
+
+ // setup service bus
+ var uniqueId = await ServiceBusWithNewCall(user, target);
+
+ // create call and assert response
+ CreateCallResult response = await client.CreateCallAsync(new CreateCallOptions(new CallSource(user), new CommunicationIdentifier[] { target }, new Uri(TestEnvironment.DispatcherCallback + $"?q={uniqueId}"))).ConfigureAwait(false);
+ string callConnectionId = response.CallConnectionProperties.CallConnectionId;
+ Assert.IsNotEmpty(response.CallConnectionProperties.CallConnectionId);
+
+ // wait for incomingcall context
+ string? incomingCallContext = await WaitForIncomingCallContext(uniqueId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(incomingCallContext);
+
+ // answer the call
+ var answerResponse = await client.AnswerCallAsync(incomingCallContext, new Uri(TestEnvironment.DispatcherCallback));
+ Assert.AreEqual(answerResponse.GetRawResponse().Status, StatusCodes.Status200OK);
+
+ // wait for callConnected
+ var connectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(connectedEvent);
+ Assert.IsTrue(connectedEvent is CallConnected);
+ Assert.IsTrue(((CallConnected)connectedEvent!).CallConnectionId == callConnectionId);
+
+ // test get properties
+ Response properties = await response.CallConnection.GetCallConnectionPropertiesAsync().ConfigureAwait(false);
+ Assert.AreEqual(CallConnectionState.Connected, properties.Value.CallConnectionState);
+
+ // try start recording unmixed audio with channel affinity
+ var startRecordingResponse = await client.GetCallRecording().StartRecordingAsync(
+ new StartRecordingOptions(new ServerCallLocator(properties.Value.ServerCallId))
+ {
+ RecordingChannel = RecordingChannel.Unmixed,
+ RecordingContent = RecordingContent.Audio,
+ RecordingFormat = RecordingFormat.Wav,
+ RecordingStateCallbackEndpoint = new Uri(TestEnvironment.DispatcherCallback),
+ ChannelAffinity = new List
+ {
+ new ChannelAffinity { Channel = 0, Participant = user },
+ new ChannelAffinity { Channel = 1, Participant = target }
+ }
+ });
+ Assert.AreEqual(StatusCodes.Status200OK, startRecordingResponse.GetRawResponse().Status);
+ Assert.NotNull(startRecordingResponse.Value.RecordingId);
+
+ // try stop recording
+ var stopRecordingResponse = await client.GetCallRecording().StopRecordingAsync(startRecordingResponse.Value.RecordingId);
+ Assert.AreEqual(StatusCodes.Status204NoContent, stopRecordingResponse.Status);
+
+ // wait for CallRecordingStateChanged event TODO: Figure out why event not received
+ // var recordingStartedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ // Assert.IsNotNull(recordingStartedEvent);
+ // Assert.IsTrue(recordingStartedEvent is CallRecordingStateChanged);
+ // Assert.IsTrue(((CallRecordingStateChanged)recordingStartedEvent!).CallConnectionId == callConnectionId);
+
+ // try hangup
+ await response.CallConnection.HangUpAsync(true).ConfigureAwait(false);
+ var disconnectedEvent = await WaitForEvent(callConnectionId, TimeSpan.FromSeconds(20));
+ Assert.IsNotNull(disconnectedEvent);
+ Assert.IsTrue(disconnectedEvent is CallDisconnected);
+ Assert.IsTrue(((CallDisconnected)disconnectedEvent!).CallConnectionId == callConnectionId);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Unexpected error: {ex}");
+ }
+ }
+ }
+}
diff --git a/sdk/communication/Azure.Communication.CallAutomation/tests/EventCatcher/EventRecordPlayer.cs b/sdk/communication/Azure.Communication.CallAutomation/tests/EventCatcher/EventRecordPlayer.cs
new file mode 100644
index 0000000000000..f9b19d5572034
--- /dev/null
+++ b/sdk/communication/Azure.Communication.CallAutomation/tests/EventCatcher/EventRecordPlayer.cs
@@ -0,0 +1,219 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Azure.Communication.CallAutomation.Tests.EventCatcher
+{
+ internal class EventRecordPlayer : IDisposable, IAsyncDisposable
+ {
+ private static JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions()
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+ private string _sessionFilePath;
+ private PersistedEventRecording _recording;
+ private SemaphoreSlim? _semaphore;
+ private const int MaxDepth = 16;
+ public bool IsRecording { get; private set; }
+ public IEnumerable Entries => _recording.Entries;
+
+ public List KeysToSanitize { get; } = new List() { "rawId", "id" };
+
+ public EventRecordPlayer(string sessionFilePath)
+ {
+ _sessionFilePath = sessionFilePath;
+ _recording = new PersistedEventRecording();
+ }
+
+ public EventRecordPlayer SetupForRecording()
+ {
+ IsRecording = true;
+ InitializeFile(_sessionFilePath);
+ _semaphore = new SemaphoreSlim(1);
+ return this;
+ }
+
+ public EventRecordPlayer SetupForPlayback()
+ {
+ LoadEvents();
+ return this;
+ }
+
+ public void Record(RecordedServiceBusReceivedMessage receivedMessage)
+ {
+ // maintain order in recording
+ _semaphore?.Wait();
+ _recording.Entries.Add(receivedMessage);
+ _semaphore?.Release();
+ }
+
+ private void LoadEvents()
+ {
+ var recordingExists = File.Exists(_sessionFilePath);
+ if (recordingExists)
+ {
+ Task.Run(async () =>
+ {
+ using FileStream openStream =
+ new FileStream(_sessionFilePath, FileMode.Open, FileAccess.Read, FileShare.None);
+ PersistedEventRecording? recording = await JsonSerializer.DeserializeAsync(openStream, JsonSerializerOptions);
+ if (recording != null)
+ {
+ _recording = recording;
+ }
+ }).Wait();
+ }
+ }
+
+ private void InitializeFile(string sessionFilePath)
+ {
+ var recordingExists = File.Exists(sessionFilePath);
+ if (recordingExists)
+ {
+ File.Delete(sessionFilePath);
+ }
+
+ InitializeDirectory(sessionFilePath);
+ using var fileStream = File.Create(sessionFilePath);
+ fileStream.Dispose();
+ }
+
+ private void InitializeDirectory(string sessionFilePath)
+ {
+ var directoryPath = Path.GetDirectoryName(sessionFilePath);
+ var directoryExists = Directory.Exists(directoryPath);
+ if (!directoryExists)
+ {
+ if (directoryPath != null)
+ {
+ Directory.CreateDirectory(directoryPath);
+ }
+ }
+ }
+
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (IsRecording)
+ {
+ using FileStream recorderFileStream = new FileStream(_sessionFilePath, FileMode.Append, FileAccess.Write, FileShare.None);
+ var scrubbedRecording = new List();
+ _recording.Entries
+ .ForEach(entry =>
+ {
+ entry.Body = SanitizeRecordedEvent(entry.Body);
+ scrubbedRecording.Add(entry);
+ });
+ await JsonSerializer.SerializeAsync(recorderFileStream, new PersistedEventRecording(){ Entries = scrubbedRecording }, JsonSerializerOptions);
+ recorderFileStream.Dispose();
+ _semaphore?.Dispose();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (IsRecording)
+ {
+ using FileStream recorderFileStream = new FileStream(_sessionFilePath, FileMode.Append, FileAccess.Write, FileShare.None);
+ Task.Run(async () =>
+ {
+ var scrubbedRecording = new List();
+ _recording.Entries
+ .ForEach(entry =>
+ {
+ entry.Body = SanitizeRecordedEvent(entry.Body);
+ scrubbedRecording.Add(entry);
+ });
+ await JsonSerializer.SerializeAsync(recorderFileStream, new PersistedEventRecording() { Entries = scrubbedRecording }, JsonSerializerOptions);
+ }).Wait();
+ recorderFileStream.Dispose();
+ _semaphore?.Dispose();
+ }
+ }
+
+ private string SanitizeRecordedEvent(string message)
+ {
+ JsonDocument document = JsonDocument.Parse(message);
+ var rootElement = document.RootElement;
+ switch (rootElement.ValueKind)
+ {
+ case JsonValueKind.Object:
+ var objEnumerator = document.RootElement.EnumerateObject();
+ var result = new Dictionary();
+ while (objEnumerator.MoveNext())
+ {
+ result.Add(objEnumerator.Current.Name, Scrub(objEnumerator.Current.Value, 0, MaxDepth));
+ }
+
+ var resultAsString = JsonSerializer.Serialize(result);
+ return resultAsString;
+ case JsonValueKind.Array:
+ var arrayEnumerator = document.RootElement.EnumerateArray();
+ var arrResult = new List