@@ -938,10 +938,13 @@ CO_TEST_P_X(MoQSessionTest, SubscribeDuplicatesPendingPublish) {
938938
939939 // Use a baton to delay the PUBLISH_OK so the publish stays pending
940940 folly::coro::Baton publishOkBaton;
941+ bool publishMockCalled = false ;
941942 EXPECT_CALL (*serverSubscriber, publish (_, _))
942943 .WillOnce (
943- [&publishOkBaton](
944- PublishRequest actualPub, auto ) -> Subscriber::PublishResult {
944+ [&publishOkBaton, &publishMockCalled](
945+ const PublishRequest& actualPub,
946+ auto ) -> Subscriber::PublishResult {
947+ publishMockCalled = true ;
945948 auto consumer =
946949 std::make_shared<testing::NiceMock<MockTrackConsumer>>();
947950 ON_CALL (*consumer, setTrackAlias (_))
@@ -972,7 +975,12 @@ CO_TEST_P_X(MoQSessionTest, SubscribeDuplicatesPendingPublish) {
972975 auto publishResult =
973976 clientSession_->publish (std::move (pub), makePublishHandle ());
974977 EXPECT_TRUE (publishResult.hasValue ());
975- co_await folly::coro::co_reschedule_on_current_executor;
978+
979+ // Wait for the server to process the PUBLISH (handlePublish is dispatched
980+ // asynchronously via .start(), so multiple event loop iterations may be
981+ // needed)
982+ co_await rescheduleN (5 );
983+ EXPECT_TRUE (publishMockCalled);
976984
977985 // Server subscribes to client for the same track while publish is pending
978986 auto subRes = co_await serverSession_->subscribe (
@@ -988,3 +996,64 @@ CO_TEST_P_X(MoQSessionTest, SubscribeDuplicatesPendingPublish) {
988996
989997 clientSession_->close (SessionCloseErrorCode::NO_ERROR );
990998}
999+ CO_TEST_P_X (MoQSessionTest, PublishDuplicatesPendingSubscribe) {
1000+ co_await setupMoQSessionForPublish (initialMaxRequestID_);
1001+
1002+ FullTrackName ftn{TrackNamespace{{" test" }}, " test-track" };
1003+
1004+ // Use a baton to delay the SUBSCRIBE_OK so the subscribe stays pending
1005+ folly::coro::Baton subscribeOkBaton;
1006+ bool subscribeMockCalled = false ;
1007+ expectSubscribe (
1008+ [&subscribeOkBaton, &subscribeMockCalled](
1009+ auto sub, auto ) -> TaskSubscribeResult {
1010+ subscribeMockCalled = true ;
1011+ co_await subscribeOkBaton;
1012+ co_return makeSubscribeOkResult (sub);
1013+ });
1014+
1015+ // Client subscribes — start eagerly so it populates pendingSubscribeTracks_
1016+ auto subscribeConsumer =
1017+ std::make_shared<testing::NiceMock<MockTrackConsumer>>();
1018+ ON_CALL (*subscribeConsumer, setTrackAlias (_))
1019+ .WillByDefault (
1020+ testing::Return (
1021+ folly::Expected<folly::Unit, MoQPublishError>(folly::unit)));
1022+ ON_CALL (*subscribeConsumer, publishDone (_))
1023+ .WillByDefault (testing::Return (folly::unit));
1024+ auto subscribeSf =
1025+ clientSession_->subscribe (getSubscribe (ftn), subscribeConsumer)
1026+ .scheduleOn (&eventBase_)
1027+ .start ();
1028+
1029+ // Wait for the SUBSCRIBE to be delivered and processed by the server
1030+ co_await rescheduleN (5 );
1031+ EXPECT_TRUE (subscribeMockCalled);
1032+
1033+ // Server sends PUBLISH for the same track while subscribe is pending.
1034+ // Client should reject with DUPLICATE_SUBSCRIPTION.
1035+ auto handle = makePublishHandle ();
1036+ auto publishResult = serverSession_->publish (
1037+ PublishRequest{
1038+ RequestID (0 ),
1039+ ftn,
1040+ TrackAlias (100 ),
1041+ GroupOrder::Default,
1042+ AbsoluteLocation{0 , 100 },
1043+ true ,
1044+ },
1045+ handle);
1046+ EXPECT_TRUE (publishResult.hasValue ());
1047+
1048+ auto replyRes = co_await std::move (publishResult.value ().reply );
1049+ EXPECT_TRUE (replyRes.hasError ());
1050+ EXPECT_EQ (
1051+ replyRes.error ().errorCode , PublishErrorCode::DUPLICATE_SUBSCRIPTION );
1052+
1053+ // Let the SUBSCRIBE_OK through and complete the subscribe
1054+ subscribeOkBaton.post ();
1055+ auto subRes = co_await std::move (subscribeSf).via (&eventBase_);
1056+ EXPECT_TRUE (subRes.hasValue ());
1057+
1058+ clientSession_->close (SessionCloseErrorCode::NO_ERROR );
1059+ }
0 commit comments