Add write roundtrip test (#8169)

* Refactor TestWriteInteraction to use a MessagingContext.

This allows us to test more details of the messages being sent.

* Add a roundtrip test to TestWriteInteraction.

Various fixups needed for that:

1) Make InteractionModelEngine::Shutdown actually remove the object as
   an unsolicited message handler.

2) Initialize WriteHandler's state properly so it's not random whether
   we think we have available WriteHandlers.

3) Fix TestWriteInteraction::AddAttributeDataElement to use the right
   tag (2, not 1) for its data.

4) Stop trying to run AppTests in QEMU, because of linking/compilation issues
   in that setup.
diff --git a/scripts/tests/esp32_qemu_tests.sh b/scripts/tests/esp32_qemu_tests.sh
index da4bc96..92bc114 100755
--- a/scripts/tests/esp32_qemu_tests.sh
+++ b/scripts/tests/esp32_qemu_tests.sh
@@ -42,16 +42,16 @@
 fi
 
 really_run_suite() {
-    idf scripts/tools/qemu_run_test.sh src/test_driver/esp32/build/chip "$1" "$2"
+    idf scripts/tools/qemu_run_test.sh src/test_driver/esp32/build/chip "$@"
 }
 
 run_suite() {
     if [[ -d "${log_dir}" ]]; then
         suite=${1%.a}
         suite=${suite#lib}
-        really_run_suite "$1" "$2" |& tee "$log_dir/$suite.log"
+        really_run_suite "$@" |& tee "$log_dir/$suite.log"
     else
-        really_run_suite "$1" "$2"
+        really_run_suite "$@"
     fi
 }
 
@@ -61,7 +61,15 @@
 SUITES=(
 )
 
-run_suite libAppTests.a
+# TODO: libAppTests depends on MessagingTestHelpers, which depends on
+# NetworkTestHelpers.  That sort of depends on InetTestHelpers or
+# equivalent (to provide gSystemLayer, gInet, InitNetwork(),
+# ShutdownNetwork()) but there's only a POSIX implementation of that
+# last, which does not compile on ESP32.  Need to figure out how to
+# make that work.  See comments below for the transport layer tests,
+# which have the same issue.
+# run_suite libAppTests.a -lMessagingTestHelpers -lNetworkTestHelpers
+
 run_suite libASN1Tests.a
 run_suite libBleLayerTests.a
 run_suite libCoreTests.a
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index 7697bfe..73b87bb 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -116,6 +116,8 @@
     }
 
     mpNextAvailableClusterInfo = nullptr;
+
+    mpExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id);
 }
 
 CHIP_ERROR InteractionModelEngine::NewCommandSender(CommandSender ** const apCommandSender)
diff --git a/src/app/WriteHandler.h b/src/app/WriteHandler.h
index cd731f9..2f47665 100644
--- a/src/app/WriteHandler.h
+++ b/src/app/WriteHandler.h
@@ -106,7 +106,7 @@
     InteractionModelDelegate * mpDelegate      = nullptr;
     WriteResponse::Builder mWriteResponseBuilder;
     System::PacketBufferTLVWriter mMessageWriter;
-    State mState;
+    State mState = State::Uninitialized;
 };
 } // namespace app
 } // namespace chip
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index 738f4dd..5305388 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -41,7 +41,9 @@
     "${chip_root}/src/app",
     "${chip_root}/src/app/util:device_callbacks_manager",
     "${chip_root}/src/lib/core",
+    "${chip_root}/src/messaging/tests:helpers",
     "${chip_root}/src/protocols",
+    "${chip_root}/src/transport/raw/tests:helpers",
     "${nlunit_test_root}:nlunit-test",
   ]
 }
diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp
index 3396786..84fcfc5 100644
--- a/src/app/tests/TestWriteInteraction.cpp
+++ b/src/app/tests/TestWriteInteraction.cpp
@@ -24,6 +24,7 @@
 #include <messaging/ExchangeContext.h>
 #include <messaging/ExchangeMgr.h>
 #include <messaging/Flags.h>
+#include <messaging/tests/MessagingContext.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <protocols/secure_channel/MessageCounterManager.h>
 #include <protocols/secure_channel/PASESession.h>
@@ -33,16 +34,17 @@
 #include <system/TLVPacketBufferBackingStore.h>
 #include <transport/SecureSessionMgr.h>
 #include <transport/raw/UDP.h>
+#include <transport/raw/tests/NetworkTestHelpers.h>
 
 #include <nlunit-test.h>
 
 namespace {
-chip::System::Layer gSystemLayer;
-chip::SecureSessionMgr gSessionManager;
-chip::Messaging::ExchangeManager gExchangeManager;
-chip::TransportMgr<chip::Transport::UDP> gTransportManager;
-chip::secure_channel::MessageCounterManager gMessageCounterManager;
-chip::Transport::AdminId gAdminId = 0;
+chip::TransportMgrBase gTransportManager;
+chip::Test::LoopbackTransport gLoopback;
+
+using TestContext = chip::Test::MessagingContext;
+TestContext sContext;
+
 } // namespace
 namespace chip {
 namespace app {
@@ -51,6 +53,7 @@
 public:
     static void TestWriteClient(nlTestSuite * apSuite, void * apContext);
     static void TestWriteHandler(nlTestSuite * apSuite, void * apContext);
+    static void TestWriteRoundtrip(nlTestSuite * apSuite, void * apContext);
 
 private:
     static void AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClient & aWriteClient);
@@ -86,7 +89,7 @@
 
     chip::TLV::TLVWriter * writer = aWriteClient.GetAttributeDataElementTLVWriter();
 
-    err = writer->PutBoolean(chip::TLV::ContextTag(1), true);
+    err = writer->PutBoolean(chip::TLV::ContextTag(chip::app::AttributeDataElement::kCsTag_Data), true);
     NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
 
     err = aWriteClient.FinishAttribute();
@@ -198,18 +201,21 @@
 
 void TestWriteInteraction::TestWriteClient(nlTestSuite * apSuite, void * apContext)
 {
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+
     CHIP_ERROR err = CHIP_NO_ERROR;
 
     app::WriteClient writeClient;
 
     chip::app::InteractionModelDelegate delegate;
     System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
-    err                            = writeClient.Init(&gExchangeManager, &delegate);
+    err                            = writeClient.Init(&ctx.GetExchangeManager(), &delegate);
     NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
     AddAttributeDataElement(apSuite, apContext, writeClient);
 
-    err = writeClient.SendWriteRequest(kTestDeviceNodeId, gAdminId, nullptr);
-    NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED);
+    SecureSessionHandle session = ctx.GetSessionLocalToPeer();
+    err                         = writeClient.SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetAdminId(), &session);
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
 
     GenerateWriteResponse(apSuite, apContext, buf);
 
@@ -221,6 +227,8 @@
 
 void TestWriteInteraction::TestWriteHandler(nlTestSuite * apSuite, void * apContext)
 {
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+
     CHIP_ERROR err = CHIP_NO_ERROR;
 
     app::WriteHandler writeHandler;
@@ -236,43 +244,72 @@
 
     AddAttributeStatus(apSuite, apContext, writeHandler);
 
-    writeHandler.mpExchangeCtx = gExchangeManager.NewContext({ 0, 0, 0 }, nullptr);
     TestExchangeDelegate delegate;
-    writeHandler.mpExchangeCtx->SetDelegate(&delegate);
-    err = writeHandler.SendWriteResponse();
-    NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED);
+    writeHandler.mpExchangeCtx = ctx.NewExchangeToLocal(&delegate);
+    err                        = writeHandler.SendWriteResponse();
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
 
     writeHandler.Shutdown();
 }
+
+CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
+{
+    return aWriteHandler->AddAttributeStatusCode(
+        AttributePathParams(aClusterInfo.mNodeId, aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mFieldId,
+                            aClusterInfo.mListIndex, AttributePathParams::Flags::kFieldIdValid),
+        Protocols::SecureChannel::GeneralStatusCode::kSuccess, Protocols::SecureChannel::Id,
+        Protocols::InteractionModel::ProtocolCode::Success);
+}
+
+class RoundtripDelegate : public chip::app::InteractionModelDelegate
+{
+public:
+    CHIP_ERROR WriteResponseStatus(const WriteClient * apWriteClient,
+                                   const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, const uint32_t aProtocolId,
+                                   const uint16_t aProtocolCode, AttributePathParams & aAttributePathParams,
+                                   uint8_t aCommandIndex) override
+    {
+        mGotResponse = true;
+        return CHIP_NO_ERROR;
+    }
+
+    bool mGotResponse = false;
+};
+
+void TestWriteInteraction::TestWriteRoundtrip(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    RoundtripDelegate delegate;
+    auto * engine = chip::app::InteractionModelEngine::GetInstance();
+    err           = engine->Init(&ctx.GetExchangeManager(), &delegate);
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+    app::WriteClient * writeClient;
+    err = engine->NewWriteClient(&writeClient);
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+    System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+    AddAttributeDataElement(apSuite, apContext, *writeClient);
+
+    NL_TEST_ASSERT(apSuite, !delegate.mGotResponse);
+
+    SecureSessionHandle session = ctx.GetSessionLocalToPeer();
+    err                         = writeClient->SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetAdminId(), &session);
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+    NL_TEST_ASSERT(apSuite, delegate.mGotResponse);
+
+    engine->Shutdown();
+}
+
 } // namespace app
 } // namespace chip
 
 namespace {
 
-void InitializeChip(nlTestSuite * apSuite)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-    chip::Optional<chip::Transport::PeerAddress> peer(chip::Transport::Type::kUndefined);
-    chip::Transport::AdminPairingTable admins;
-    chip::Transport::AdminPairingInfo * adminInfo = admins.AssignAdminId(gAdminId, chip::kTestDeviceNodeId);
-
-    NL_TEST_ASSERT(apSuite, adminInfo != nullptr);
-
-    err = chip::Platform::MemoryInit();
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
-
-    gSystemLayer.Init(nullptr);
-
-    err = gSessionManager.Init(chip::kTestDeviceNodeId, &gSystemLayer, &gTransportManager, &admins, &gMessageCounterManager);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
-
-    err = gExchangeManager.Init(&gSessionManager);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
-
-    err = gMessageCounterManager.Init(&gExchangeManager);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
-}
-
 /**
  *   Test Suite. It lists all the test functions.
  */
@@ -282,28 +319,59 @@
 {
         NL_TEST_DEF("CheckWriteClient", chip::app::TestWriteInteraction::TestWriteClient),
         NL_TEST_DEF("CheckWriteHandler", chip::app::TestWriteInteraction::TestWriteHandler),
+        NL_TEST_DEF("CheckWriteRoundtrip", chip::app::TestWriteInteraction::TestWriteRoundtrip),
         NL_TEST_SENTINEL()
 };
 // clang-format on
+
+int Initialize(void * aContext);
+int Finalize(void * aContext);
+
+// clang-format off
+nlTestSuite sSuite =
+{
+    "TestWriteInteraction",
+    &sTests[0],
+    Initialize,
+    Finalize
+};
+// clang-format on
+
+int Initialize(void * aContext)
+{
+    CHIP_ERROR err = chip::Platform::MemoryInit();
+    if (err != CHIP_NO_ERROR)
+    {
+        return FAILURE;
+    }
+
+    gTransportManager.Init(&gLoopback);
+
+    auto * ctx = static_cast<TestContext *>(aContext);
+    err        = ctx->Init(&sSuite, &gTransportManager);
+    if (err != CHIP_NO_ERROR)
+    {
+        return FAILURE;
+    }
+
+    gTransportManager.SetSecureSessionMgr(&ctx->GetSecureSessionManager());
+    return SUCCESS;
+}
+
+int Finalize(void * aContext)
+{
+    CHIP_ERROR err = reinterpret_cast<TestContext *>(aContext)->Shutdown();
+    chip::Platform::MemoryShutdown();
+    return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE;
+}
+
 } // namespace
 
 int TestWriteInteraction()
 {
-    // clang-format off
-    nlTestSuite theSuite =
-            {
-                    "TestWriteInteraction",
-                    &sTests[0],
-                    nullptr,
-                    nullptr
-            };
-    // clang-format on
+    nlTestRunner(&sSuite, &sContext);
 
-    InitializeChip(&theSuite);
-
-    nlTestRunner(&theSuite, nullptr);
-
-    return (nlTestRunnerStats(&theSuite));
+    return (nlTestRunnerStats(&sSuite));
 }
 
 CHIP_REGISTER_TEST_SUITE(TestWriteInteraction)