blob: cfb6b4bfb0b4987cce2b0a3383b6e845e25a5852 [file] [edit]
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/moqt/moqt_track_status_stream.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/moqt/moqt_error.h"
#include "quiche/quic/moqt/moqt_fetch_task.h"
#include "quiche/quic/moqt/moqt_framer.h"
#include "quiche/quic/moqt/moqt_key_value_pair.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_names.h"
#include "quiche/quic/moqt/moqt_parser.h"
#include "quiche/quic/moqt/moqt_publisher.h"
#include "quiche/quic/moqt/moqt_session_interface.h"
#include "quiche/quic/moqt/moqt_types.h"
#include "quiche/quic/moqt/test_tools/mock_moqt_session.h"
#include "quiche/quic/moqt/test_tools/moqt_framer_utils.h"
#include "quiche/quic/moqt/test_tools/moqt_mock_visitor.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
#include "quiche/web_transport/test_tools/mock_web_transport.h"
#include "quiche/web_transport/web_transport.h"
namespace moqt::test {
namespace {
using ::quiche::test::StatusIs;
using ::testing::_;
using ::testing::IsEmpty;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrictMock;
class MoqtTrackStatusRequestStreamTest : public quiche::test::QuicheTest {
public:
MoqtTrackStatusRequestStreamTest()
: framer_(/*using_webtrans=*/true, quic::Perspective::IS_CLIENT),
message_parser_(kDefaultMoqtVersion, /*uses_web_transport=*/true,
quic::Perspective::IS_CLIENT),
track_name_("foo", "bar") {}
MoqtTrackStatusRequestStream CreateStream(
const MessageParameters& parameters = MessageParameters()) {
return MoqtTrackStatusRequestStream(&framer_, message_parser_, kRequestId,
track_name_, parameters,
session_error_callback_.AsStdFunction(),
response_callback_.AsStdFunction());
}
protected:
static constexpr uint64_t kRequestId = 2;
MoqtFramer framer_;
MoqtControlMessageParser message_parser_;
FullTrackName track_name_;
StrictMock<testing::MockFunction<void(MoqtError, absl::string_view)>>
session_error_callback_;
StrictMock<testing::MockFunction<void(
std::variant<MessageParameters, MoqtRequestErrorInfo>)>>
response_callback_;
StrictMock<webtransport::test::MockStream> mock_stream_;
};
TEST_F(MoqtTrackStatusRequestStreamTest, SendRequestOnStreamBound) {
MoqtTrackStatusRequestStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kTrackStatus), _));
EXPECT_CALL(response_callback_, Call)
.WillOnce([&](std::variant<MessageParameters, MoqtRequestErrorInfo> v) {
ASSERT_TRUE(std::holds_alternative<MoqtRequestErrorInfo>(v));
auto info = std::get<MoqtRequestErrorInfo>(v);
EXPECT_EQ(info.error_code, RequestErrorCode::kInternalError);
EXPECT_EQ(info.reason_phrase, "Stream closed");
});
stream.BindStream(&mock_stream_);
}
TEST_F(MoqtTrackStatusRequestStreamTest, SendRequestWithParameters) {
MessageParameters parameters;
parameters.delivery_timeout = quic::QuicTimeDelta::FromSeconds(5);
MoqtTrackStatusRequestStream stream = CreateStream(parameters);
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
MoqtTrackStatus expected_message;
expected_message.request_id = kRequestId;
expected_message.full_track_name = track_name_;
expected_message.parameters = parameters;
EXPECT_CALL(mock_stream_,
Writev(SerializedControlMessage(expected_message), _));
EXPECT_CALL(response_callback_, Call)
.WillOnce([&](std::variant<MessageParameters, MoqtRequestErrorInfo> v) {
ASSERT_TRUE(std::holds_alternative<MoqtRequestErrorInfo>(v));
auto info = std::get<MoqtRequestErrorInfo>(v);
EXPECT_EQ(info.error_code, RequestErrorCode::kInternalError);
EXPECT_EQ(info.reason_phrase, "Stream closed");
});
stream.BindStream(&mock_stream_);
}
TEST_F(MoqtTrackStatusRequestStreamTest, ReceiveOkResponse) {
MoqtTrackStatusRequestStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kTrackStatus), _));
stream.BindStream(&mock_stream_);
MessageParameters parameters;
parameters.expires = quic::QuicTimeDelta::FromSeconds(10);
parameters.largest_object = Location(1, 2);
EXPECT_CALL(response_callback_, Call)
.WillOnce([&](std::variant<MessageParameters, MoqtRequestErrorInfo> v) {
ASSERT_TRUE(std::holds_alternative<MessageParameters>(v));
auto params = std::get<MessageParameters>(v);
EXPECT_EQ(params.expires, parameters.expires);
EXPECT_EQ(params.largest_object, parameters.largest_object);
});
EXPECT_CALL(mock_stream_, Writev(testing::IsEmpty(), _));
MoqtRequestOk ok;
ok.request_id = kRequestId;
ok.parameters = parameters;
QUICHE_EXPECT_OK(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(ok)));
}
TEST_F(MoqtTrackStatusRequestStreamTest, ReceiveErrorResponse) {
MoqtTrackStatusRequestStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kTrackStatus), _));
stream.BindStream(&mock_stream_);
EXPECT_CALL(response_callback_, Call)
.WillOnce([&](std::variant<MessageParameters, MoqtRequestErrorInfo> v) {
ASSERT_TRUE(std::holds_alternative<MoqtRequestErrorInfo>(v));
auto info = std::get<MoqtRequestErrorInfo>(v);
EXPECT_EQ(info.error_code, RequestErrorCode::kDoesNotExist);
EXPECT_EQ(info.reason_phrase, "Track does not exist");
});
EXPECT_CALL(
mock_stream_,
Writev(IsEmpty(),
Property(&webtransport::StreamWriteOptions::send_fin, true)));
MoqtRequestError error;
error.request_id = kRequestId;
error.error_code = RequestErrorCode::kDoesNotExist;
error.reason_phrase = "Track does not exist";
QUICHE_EXPECT_OK(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(error)));
}
TEST_F(MoqtTrackStatusRequestStreamTest, DuplicateRequestOk) {
MoqtTrackStatusRequestStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kTrackStatus), _));
stream.BindStream(&mock_stream_);
EXPECT_CALL(response_callback_, Call);
EXPECT_CALL(mock_stream_, Writev(testing::IsEmpty(), _));
MoqtRequestOk ok;
ok.request_id = kRequestId;
QUICHE_EXPECT_OK(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(ok)));
EXPECT_THAT(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(ok)),
StatusIs(absl::StatusCode::kInvalidArgument, "Duplicate REQUEST_OK"));
}
TEST_F(MoqtTrackStatusRequestStreamTest, DuplicateRequestError) {
MoqtTrackStatusRequestStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kTrackStatus), _));
stream.BindStream(&mock_stream_);
EXPECT_CALL(response_callback_, Call);
EXPECT_CALL(
mock_stream_,
Writev(IsEmpty(),
Property(&webtransport::StreamWriteOptions::send_fin, true)));
MoqtRequestError error;
error.request_id = kRequestId;
error.error_code = RequestErrorCode::kDoesNotExist;
error.reason_phrase = "Track does not exist";
QUICHE_EXPECT_OK(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(error)));
EXPECT_THAT(
stream.OnRawControlMessage(GenericMessageToRawControlMessage(error)),
StatusIs(absl::StatusCode::kInvalidArgument, "Duplicate REQUEST_ERROR"));
}
class MoqtTrackStatusResponseStreamTest : public quiche::test::QuicheTest {
public:
MoqtTrackStatusResponseStreamTest()
: framer_(/*using_webtrans=*/true, quic::Perspective::IS_SERVER),
message_parser_(kDefaultMoqtVersion, /*uses_web_transport=*/true,
quic::Perspective::IS_SERVER),
track_name_("foo", "bar"),
mock_publisher_(track_name_) {}
MoqtTrackStatusResponseStream CreateStream() {
return MoqtTrackStatusResponseStream(
&framer_, message_parser_, session_error_callback_.AsStdFunction(),
session_.weak_ptr_factory_.Create());
}
protected:
static constexpr uint64_t kRequestId = 2;
MoqtFramer framer_;
MoqtControlMessageParser message_parser_;
FullTrackName track_name_;
StrictMock<testing::MockFunction<void(MoqtError, absl::string_view)>>
session_error_callback_;
MockSessionToPublisherInterface session_;
MockTrackPublisher mock_publisher_;
StrictMock<webtransport::test::MockStream> mock_stream_;
};
TEST_F(MoqtTrackStatusResponseStreamTest, ProcessTrackStatusSuccess) {
MoqtTrackStatusResponseStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
stream.BindStream(&mock_stream_);
EXPECT_CALL(session_, GetTrackPublisher(track_name_))
.WillOnce(Return(
std::shared_ptr<MoqtTrackPublisher>(&mock_publisher_, [](auto*) {})));
MoqtObjectListener* listener = nullptr;
EXPECT_CALL(mock_publisher_, AddObjectListener)
.WillOnce(testing::SaveArg<0>(&listener));
MoqtTrackStatus track_status;
track_status.request_id = kRequestId;
track_status.full_track_name = track_name_;
QUICHE_EXPECT_OK(stream.OnRawControlMessage(
GenericMessageToRawControlMessage(track_status)));
ASSERT_NE(listener, nullptr);
EXPECT_CALL(mock_publisher_, expiration)
.WillRepeatedly(Return(quic::QuicTimeDelta::FromSeconds(5)));
EXPECT_CALL(mock_publisher_, largest_location)
.WillRepeatedly(Return(Location(10, 20)));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kRequestOk), _));
EXPECT_CALL(mock_publisher_, RemoveObjectListener(listener));
listener->OnSubscribeAccepted();
}
TEST_F(MoqtTrackStatusResponseStreamTest, TrackDoesNotExist) {
MoqtTrackStatusResponseStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
stream.BindStream(&mock_stream_);
EXPECT_CALL(session_, GetTrackPublisher(track_name_))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kRequestError), _));
MoqtTrackStatus track_status;
track_status.request_id = kRequestId;
track_status.full_track_name = track_name_;
QUICHE_EXPECT_OK(stream.OnRawControlMessage(
GenericMessageToRawControlMessage(track_status)));
}
TEST_F(MoqtTrackStatusResponseStreamTest, DuplicateTrackStatus) {
MoqtTrackStatusResponseStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
stream.BindStream(&mock_stream_);
EXPECT_CALL(session_, GetTrackPublisher(track_name_))
.WillOnce(Return(
std::shared_ptr<MoqtTrackPublisher>(&mock_publisher_, [](auto*) {})));
MoqtObjectListener* listener = nullptr;
EXPECT_CALL(mock_publisher_, AddObjectListener)
.WillOnce(testing::SaveArg<0>(&listener));
MoqtTrackStatus track_status;
track_status.request_id = kRequestId;
track_status.full_track_name = track_name_;
QUICHE_EXPECT_OK(stream.OnRawControlMessage(
GenericMessageToRawControlMessage(track_status)));
ASSERT_NE(listener, nullptr);
EXPECT_THAT(stream.OnRawControlMessage(
GenericMessageToRawControlMessage(track_status)),
StatusIs(absl::StatusCode::kInvalidArgument,
"Duplicate TRACK_STATUS received"));
EXPECT_CALL(mock_publisher_, RemoveObjectListener(listener));
}
TEST_F(MoqtTrackStatusResponseStreamTest, TrackPublisherDestroyed) {
MoqtTrackStatusResponseStream stream = CreateStream();
EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true));
stream.BindStream(&mock_stream_);
EXPECT_CALL(session_, GetTrackPublisher(track_name_))
.WillOnce(Return(
std::shared_ptr<MoqtTrackPublisher>(&mock_publisher_, [](auto*) {})));
MoqtObjectListener* listener = nullptr;
EXPECT_CALL(mock_publisher_, AddObjectListener)
.WillOnce(testing::SaveArg<0>(&listener));
MoqtTrackStatus track_status;
track_status.request_id = kRequestId;
track_status.full_track_name = track_name_;
QUICHE_EXPECT_OK(stream.OnRawControlMessage(
GenericMessageToRawControlMessage(track_status)));
ASSERT_NE(listener, nullptr);
EXPECT_CALL(mock_stream_,
Writev(ControlMessageOfType(MoqtMessageType::kRequestError), _));
listener->OnTrackPublisherGone();
}
} // namespace
} // namespace moqt::test