Add separate test file for PR-Answer tests

This documents the subtleties in testing PR-Answer in one place.

Also adds a test for checking that switching feedback formats works correctly, but this is currently disabled.

Bug: webrtc:448848876
Change-Id: Ia52393b0585186bda1ada98a33ed9b3374ebcdec
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/413640
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45842}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index c0f2ff4..d12b50d 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -2470,6 +2470,7 @@
       "peer_connection_simulcast_unittest.cc",
       "peer_connection_stability_integrationtest.cc",
       "peer_connection_svc_integrationtest.cc",
+      "pranswer_switch_integrationtest.cc",
       "proxy_unittest.cc",
       "rtc_stats_collector_unittest.cc",
       "rtc_stats_integrationtest.cc",
diff --git a/pc/pranswer_switch_integrationtest.cc b/pc/pranswer_switch_integrationtest.cc
new file mode 100644
index 0000000..446a175
--- /dev/null
+++ b/pc/pranswer_switch_integrationtest.cc
@@ -0,0 +1,318 @@
+/*
+ *  Copyright 2025 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+// This file contains tests that verify the correct working of switching
+// to a different callee between PR-Answer and Answer.
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "api/data_channel_interface.h"
+#include "api/jsep.h"
+#include "api/peer_connection_interface.h"
+#include "api/rtc_error.h"
+#include "api/test/rtc_error_matchers.h"
+#include "p2p/test/test_turn_server.h"
+#include "pc/test/fake_rtc_certificate_generator.h"
+#include "pc/test/integration_test_helpers.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/task_queue_for_test.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/wait_until.h"
+
+namespace webrtc {
+
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Gt;
+using ::testing::HasSubstr;
+using ::testing::IsTrue;
+using ::testing::Ne;
+using ::testing::Not;
+
+class PeerConnectionPrAnswerSwitchTest
+    : public PeerConnectionIntegrationBaseTest {
+ public:
+  PeerConnectionPrAnswerSwitchTest()
+      : PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {}
+  std::unique_ptr<PeerConnectionIntegrationWrapper> SetupCallee2(
+      bool addTurn,
+      bool create_media_engine) {
+    RTCConfiguration config;
+    if (addTurn) {
+      static const SocketAddress turn_server_1_internal_address{"192.0.2.1",
+                                                                3478};
+      static const SocketAddress turn_server_1_external_address{"192.0.3.1", 0};
+      TestTurnServer* turn_server_1 = CreateTurnServer(
+          turn_server_1_internal_address, turn_server_1_external_address);
+
+      // Bypass permission check on received packets so media can be sent before
+      // the candidate is signaled.
+      SendTask(network_thread(), [turn_server_1] {
+        turn_server_1->set_enable_permission_checks(false);
+      });
+
+      PeerConnectionInterface::IceServer ice_server_1;
+      ice_server_1.urls.push_back("turn:192.0.2.1:3478");
+      ice_server_1.username = "test";
+      ice_server_1.password = "test";
+      config.servers.push_back(ice_server_1);
+      config.type = PeerConnectionInterface::kRelay;
+      config.presume_writable_when_fully_relayed = true;
+    }
+    CreatePeerConnectionWrappersWithConfig(config, config, create_media_engine);
+    PeerConnectionDependencies dependencies(nullptr);
+    // Ensure that the key of callee2 is different from the key of
+    // callee1, so that reconnection will trigger an ICE restart.
+    std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
+        new FakeRTCCertificateGenerator());
+    cert_generator->use_alternate_key();
+    dependencies.cert_generator = std::move(cert_generator);
+    auto callee2 = CreatePeerConnectionWrapper(
+        "Callee2", nullptr, &config, std::move(dependencies), nullptr,
+        /*reset_encoder_factory=*/false,
+        /*reset_decoder_factory=*/false, create_media_engine);
+    ConnectFakeSignaling();
+    callee2->set_signaling_message_receiver(caller());
+    return callee2;
+  }
+
+#ifdef WEBRTC_HAVE_SCTP
+  std::unique_ptr<PeerConnectionIntegrationWrapper> SetupCallee2AndDc(
+      bool addTurn) {
+    std::unique_ptr<PeerConnectionIntegrationWrapper> callee2 =
+        SetupCallee2(addTurn, /* create_media_engine= */ false);
+    DataChannelInit dc_init;
+    dc_init.negotiated = true;
+    dc_init.id = 77;
+    caller()->CreateDataChannel("label", &dc_init);
+    callee()->CreateDataChannel("label", &dc_init);
+    callee2->CreateDataChannel("label", &dc_init);
+
+    return callee2;
+  }
+#endif  // WEBRTC_HAVE_SCTP
+
+  void WaitConnected(bool prAnswer,
+                     PeerConnectionIntegrationWrapper* caller,
+                     PeerConnectionIntegrationWrapper* callee) {
+    if (prAnswer) {
+      EXPECT_EQ(caller->pc()->signaling_state(),
+                PeerConnectionInterface::kHaveRemotePrAnswer);
+      EXPECT_EQ(callee->pc()->signaling_state(),
+                PeerConnectionInterface::kHaveLocalPrAnswer);
+    } else {
+      EXPECT_EQ(caller->pc()->signaling_state(),
+                PeerConnectionInterface::kStable);
+      EXPECT_EQ(callee->pc()->signaling_state(),
+                PeerConnectionInterface::kStable);
+    }
+    ASSERT_THAT(
+        WaitUntil([&] { return caller->pc()->peer_connection_state(); },
+                  Eq(PeerConnectionInterface::PeerConnectionState::kConnected)),
+        IsRtcOk());
+    ASSERT_THAT(
+        WaitUntil([&] { return callee->pc()->peer_connection_state(); },
+                  Eq(PeerConnectionInterface::PeerConnectionState::kConnected)),
+        IsRtcOk());
+  }
+
+#ifdef WEBRTC_HAVE_SCTP
+  void WaitConnectedAndDcOpen(bool prAnswer,
+                              PeerConnectionIntegrationWrapper* caller,
+                              PeerConnectionIntegrationWrapper* callee) {
+    WaitConnected(prAnswer, caller, callee);
+    ASSERT_THAT(WaitUntil([&] { return caller->data_channel()->state(); },
+                          Eq(DataChannelInterface::kOpen)),
+                IsRtcOk());
+    ASSERT_THAT(WaitUntil([&] { return callee->data_channel()->state(); },
+                          Eq(DataChannelInterface::kOpen)),
+                IsRtcOk());
+  }
+
+  static void SendOnDatachannelWhenConnectedCallback(
+      PeerConnectionIntegrationWrapper* peer,
+      const std::string& data,
+      std::atomic<int>& signal) {
+    if (peer->pc()->peer_connection_state() ==
+            PeerConnectionInterface::PeerConnectionState::kConnected &&
+        peer->data_channel()->state() == DataChannelInterface::kOpen) {
+      peer->data_channel()->SendAsync(DataBuffer(data), [&](RTCError err) {
+        signal.store(err.ok() ? 1 : -1);
+      });
+    }
+  }
+  void VerifyReceivedDcMessages(PeerConnectionIntegrationWrapper* peer,
+                                const std::string& data,
+                                std::atomic<int>& signal) {
+    ASSERT_THAT(WaitUntil([&] { return signal.load(); }, Ne(0)), IsRtcOk());
+    EXPECT_THAT(WaitUntil([&] { return peer->data_observer()->last_message(); },
+                          Eq(data)),
+                IsRtcOk());
+  }
+#endif  // WEBRTC_HAVE_SCTP
+};
+
+#ifdef WEBRTC_HAVE_SCTP
+TEST_F(PeerConnectionPrAnswerSwitchTest, DtlsRestartOneCalleAtATime) {
+  auto callee2 = SetupCallee2AndDc(/* addTurn= */ false);
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  callee()->SetReceivedSdpMunger(
+      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        // Capture offer so that it can be sent to Callee2 too.
+        offer = sdp->Clone();
+      });
+  callee()->SetGeneratedSdpMunger(
+      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        // Modify offer to kPrAnswer
+        SetSdpType(sdp, SdpType::kPrAnswer);
+      });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_FALSE(HasFailure());
+  WaitConnectedAndDcOpen(/* prAnswer= */ true, caller(), callee());
+  ASSERT_FALSE(HasFailure());
+
+  std::atomic<int> caller_sent_on_dc(0);
+  std::atomic<int> callee2_sent_on_dc(0);
+  caller()->set_connection_change_callback([&](auto new_state) {
+    SendOnDatachannelWhenConnectedCallback(caller(), "KESO", caller_sent_on_dc);
+  });
+  // Install same cb on both connection_change_callback and
+  // data_observer->set_state_change_callback since they can fire in any order.
+  callee2->set_connection_change_callback([&](auto new_state) {
+    SendOnDatachannelWhenConnectedCallback(callee2.get(), "KENT",
+                                           callee2_sent_on_dc);
+  });
+  callee2->data_observer()->set_state_change_callback([&](auto new_state) {
+    SendOnDatachannelWhenConnectedCallback(callee2.get(), "KENT",
+                                           callee2_sent_on_dc);
+  });
+
+  // Now let callee2 get the offer, apply it and send the answer to caller.
+  std::string offer_sdp;
+  EXPECT_TRUE(offer->ToString(&offer_sdp));
+  callee2->ReceiveSdpMessage(SdpType::kOffer, offer_sdp);
+  WaitConnectedAndDcOpen(/* prAnswer= */ false, caller(), callee2.get());
+  ASSERT_FALSE(HasFailure());
+
+  VerifyReceivedDcMessages(caller(), "KENT", callee2_sent_on_dc);
+  VerifyReceivedDcMessages(callee2.get(), "KESO", caller_sent_on_dc);
+  ASSERT_FALSE(HasFailure());
+}
+#endif  // WEBRTC_HAVE_SCTP
+
+TEST_F(PeerConnectionPrAnswerSwitchTest, SendMediaNoDataChannel) {
+  std::unique_ptr<PeerConnectionIntegrationWrapper> second_callee =
+      SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true);
+  std::string saved_offer;
+  caller()->AddAudioVideoTracks();
+  caller()->SetGeneratedSdpMunger(
+      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        sdp->ToString(&saved_offer);
+      });
+  callee()->SetGeneratedSdpMunger(
+      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        SetSdpType(sdp, SdpType::kPrAnswer);
+      });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_THAT(WaitUntil(
+                  [&] {
+                    return caller()->pc()->signaling_state() ==
+                           PeerConnectionInterface::kHaveRemotePrAnswer;
+                  },
+                  IsTrue()),
+              IsRtcOk());
+  WaitConnected(/* prAnswer= */ true, caller(), callee());
+  MediaExpectations media_expectations;
+  media_expectations.CalleeExpectsSomeAudio();
+  media_expectations.CalleeExpectsSomeVideo();
+  ASSERT_TRUE(ExpectNewFrames(media_expectations));
+  // Send original offer to second callee and wait for settlement.
+  second_callee->ReceiveSdpMessage(SdpType::kOffer, saved_offer);
+  EXPECT_THAT(
+      WaitUntil([&] { return caller()->SignalingStateStable(); }, IsTrue()),
+      IsRtcOk());
+  WaitConnected(/* prAnswer= */ false, caller(), second_callee.get());
+  ASSERT_FALSE(HasFailure());
+}
+
+// This test completes, but is disabled because feedback type switching
+// does not work yet.
+// TODO: issues.webrtc.org/448848876 - enable when underlying issue fixed.
+TEST_F(PeerConnectionPrAnswerSwitchTest, DISABLED_MediaWithCcfbFirstThenTwcc) {
+  SetFieldTrials("WebRTC-RFC8888CongestionControlFeedback/Enabled,offer:true/");
+  SetFieldTrials("Callee2",
+                 "WebRTC-RFC8888CongestionControlFeedback/Disabled/");
+  std::unique_ptr<PeerConnectionIntegrationWrapper> second_callee =
+      SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true);
+  std::string saved_offer;
+  caller()->AddAudioVideoTracks();
+  caller()->SetGeneratedSdpMunger(
+      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        sdp->ToString(&saved_offer);
+      });
+  callee()->SetGeneratedSdpMunger(
+      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+        SetSdpType(sdp, SdpType::kPrAnswer);
+      });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_THAT(WaitUntil(
+                  [&] {
+                    return caller()->pc()->signaling_state() ==
+                           PeerConnectionInterface::kHaveRemotePrAnswer;
+                  },
+                  IsTrue()),
+              IsRtcOk());
+  WaitConnected(/* prAnswer= */ true, caller(), callee());
+  MediaExpectations media_expectations;
+  media_expectations.CalleeExpectsSomeAudio();
+  media_expectations.CalleeExpectsSomeVideo();
+  ASSERT_TRUE(ExpectNewFrames(media_expectations));
+  auto pc_internal = caller()->pc_internal();
+  EXPECT_THAT(
+      WaitUntil(
+          [&] {
+            return pc_internal->FeedbackAccordingToRfc8888CountForTesting();
+          },
+          Gt(0)),
+      IsRtcOk());
+  // There should be no transport-cc generated.
+  EXPECT_THAT(pc_internal->FeedbackAccordingToTransportCcCountForTesting(),
+              Eq(0));
+  // The final answer does TWCC.
+  second_callee->ReceiveSdpMessage(SdpType::kOffer, saved_offer);
+  EXPECT_THAT(
+      WaitUntil([&] { return caller()->SignalingStateStable(); }, IsTrue()),
+      IsRtcOk());
+  WaitConnected(/* prAnswer= */ false, caller(), second_callee.get());
+  ASSERT_FALSE(HasFailure());
+
+  int old_ccfb_count = pc_internal->FeedbackAccordingToRfc8888CountForTesting();
+  int old_twcc_count =
+      pc_internal->FeedbackAccordingToTransportCcCountForTesting();
+  EXPECT_THAT(
+      WaitUntil(
+          [&] {
+            return pc_internal->FeedbackAccordingToTransportCcCountForTesting();
+          },
+          Gt(old_twcc_count)),
+      IsRtcOk());
+  // These expects are easier to interpret than the WaitUntil log result.
+  EXPECT_THAT(pc_internal->FeedbackAccordingToTransportCcCountForTesting(),
+              Gt(old_twcc_count));
+  EXPECT_THAT(pc_internal->FeedbackAccordingToRfc8888CountForTesting(),
+              Eq(old_ccfb_count));
+}
+
+}  // namespace webrtc