2121#include < atomic>
2222#include < chrono>
2323#include < condition_variable>
24+ #include < cstdlib>
2425#include < future>
2526#include < memory>
2627#include < mutex>
@@ -139,12 +140,30 @@ namespace {
139140class DisconnectTrackingDelegate : public RoomDelegate {
140141public:
141142 void onDisconnected (Room&, const DisconnectedEvent& ev) override {
142- ++count;
143- last_reason = ev.reason ;
143+ {
144+ const std::scoped_lock<std::mutex> lock (mutex_);
145+ last_reason_ = ev.reason ;
146+ }
147+ count.fetch_add (1 );
148+ cv_.notify_all ();
149+ }
150+
151+ bool waitForDisconnect (std::chrono::milliseconds timeout) {
152+ std::unique_lock<std::mutex> lock (mutex_);
153+ return cv_.wait_for (lock, timeout, [this ]() { return count.load () > 0 ; });
154+ }
155+
156+ DisconnectReason lastReason () const {
157+ const std::scoped_lock<std::mutex> lock (mutex_);
158+ return last_reason_;
144159 }
145160
146161 std::atomic<int > count{0 };
147- DisconnectReason last_reason = DisconnectReason::Unknown;
162+
163+ private:
164+ mutable std::mutex mutex_;
165+ std::condition_variable cv_;
166+ DisconnectReason last_reason_ = DisconnectReason::Unknown;
148167};
149168
150169class TokenRefreshTrackingDelegate : public RoomDelegate {
@@ -221,7 +240,7 @@ TEST_F(RoomTest, UserDisconnect) {
221240 EXPECT_EQ (room.connectionState (), ConnectionState::Disconnected);
222241 EXPECT_EQ (room.localParticipant ().lock (), nullptr ) << " local participant should be cleared after disconnect" ;
223242 EXPECT_EQ (delegate.count .load (), 1 ) << " onDisconnected should fire exactly once" ;
224- EXPECT_EQ (delegate.last_reason , DisconnectReason::ClientInitiated);
243+ EXPECT_EQ (delegate.lastReason () , DisconnectReason::ClientInitiated);
225244
226245 // Calling again on an already-disconnected room is a no-op
227246 EXPECT_NO_THROW (room.disconnect ()) << " second disconnect should not throw on an already-disconnected room" ;
@@ -243,7 +262,39 @@ TEST_F(RoomTest, DestructorDisconnect) {
243262 room.reset (); // invokes destructor which calls disconnect()
244263
245264 EXPECT_EQ (delegate.count .load (), 1 ) << " destructor should fire onDisconnected exactly once" ;
246- EXPECT_EQ (delegate.last_reason , DisconnectReason::ClientInitiated);
265+ EXPECT_EQ (delegate.lastReason (), DisconnectReason::ClientInitiated);
266+ }
267+
268+ // Case: server deletes the room while the client is connected.
269+ TEST_F (RoomTest, ServerDeletedRoomDisconnectsAndTearsDownLocally) {
270+ ASSERT_TRUE (server_available_) << " LIVEKIT_URL and LIVEKIT_TOKEN_A not set" ;
271+ if (server_url_ != kLocalTestLiveKitUrl ) {
272+ GTEST_SKIP () << " server-delete integration test requires local livekit-server" ;
273+ }
274+ const char * room_name = std::getenv (" LIVEKIT_ROOM" );
275+ ASSERT_NE (room_name, nullptr ) << " LIVEKIT_ROOM not set" ;
276+ ASSERT_NE (std::string (room_name), " " ) << " LIVEKIT_ROOM must be non-empty" ;
277+
278+ Room room;
279+ DisconnectTrackingDelegate delegate;
280+ room.setDelegate (&delegate);
281+
282+ RoomOptions options;
283+ ASSERT_TRUE (room.connect (server_url_, token_, options)) << " connect failed" ;
284+ ASSERT_EQ (room.connectionState (), ConnectionState::Connected);
285+ ASSERT_NE (room.localParticipant ().lock (), nullptr );
286+
287+ const std::string delete_command = std::string (" lk --dev room delete --yes " ) + room_name;
288+ ASSERT_EQ (std::system (delete_command.c_str ()), 0 ) << " failed to delete local test room" ;
289+
290+ ASSERT_TRUE (delegate.waitForDisconnect (10s)) << " server room deletion should disconnect the client" ;
291+ EXPECT_EQ (room.connectionState (), ConnectionState::Disconnected);
292+ EXPECT_EQ (room.localParticipant ().lock (), nullptr ) << " local participant should be cleared after server disconnect" ;
293+ EXPECT_EQ (delegate.count .load (), 1 ) << " onDisconnected should fire exactly once" ;
294+ EXPECT_EQ (delegate.lastReason (), DisconnectReason::RoomDeleted);
295+
296+ EXPECT_FALSE (room.disconnect ()) << " disconnect after server teardown should be a no-op" ;
297+ EXPECT_EQ (delegate.count .load (), 1 ) << " delegate must not double-fire" ;
247298}
248299
249300// Verifies that participant handles handed out by Room expire once the Room is
0 commit comments