Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QObject>
#include <QTest>
#include <QtProtobuf/qprotobufjsonserializer.h>
#include <QtProtobuf/qprotobufserializer.h>
#include <proto2messages.qpb.h>
using namespace Qt::Literals::StringLiterals;
class QtProtobufProto2Test : public QObject
{
Q_OBJECT
private Q_SLOTS:
void serializeRequiredDefaultValues();
void serializeRequiredExplicitDefaultValues();
void serializeRequiredDefaultValuesJson();
void serializeRequiredExplicitDefaultValuesJson();
void serializeNestedRequiredDefaultValues();
void serializeNestedRequiredDefaultValuesJson();
void serializeRequiredCycle();
void deserializeRejectsMissingRequiredFields_data();
void deserializeRejectsMissingRequiredFields();
void deserializeAcceptsCompletePayload();
void deserializeJsonStaysLenient();
void deserializeChecksNestedRequiredFields();
};
// A proto2 required field has no implicit presence: it must appear on the wire even
// when it holds the type's default value, or a conformant parser rejects the message.
void QtProtobufProto2Test::serializeRequiredDefaultValues()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
QCOMPARE(msg.serialize(&serializer).toHex(), "080010001a00"_ba);
}
void QtProtobufProto2Test::serializeRequiredExplicitDefaultValues()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
msg.setTestFieldInt(0);
msg.setTestFieldBool(false);
msg.setTestFieldString(QString());
QCOMPARE(msg.serialize(&serializer).toHex(), "080010001a00"_ba);
}
void QtProtobufProto2Test::serializeRequiredDefaultValuesJson()
{
QProtobufJsonSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
QCOMPARE(msg.serialize(&serializer),
R"({"testFieldBool":false,"testFieldInt":0,"testFieldString":""})"_ba);
}
void QtProtobufProto2Test::serializeRequiredExplicitDefaultValuesJson()
{
QProtobufJsonSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
msg.setTestFieldInt(0);
msg.setTestFieldBool(false);
msg.setTestFieldString(QString());
QCOMPARE(msg.serialize(&serializer),
R"({"testFieldBool":false,"testFieldInt":0,"testFieldString":""})"_ba);
}
void QtProtobufProto2Test::serializeNestedRequiredDefaultValues()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::NestedRequiredMessage msg;
const QByteArray payload = msg.serialize(&serializer);
QCOMPARE(payload.toHex(), "0a06080010001a00"_ba);
QVERIFY(!msg.hasNested());
qtprotobufnamespace::proto2::tests::NestedRequiredMessage roundTripped;
QVERIFY(serializer.deserialize(&roundTripped, payload));
}
void QtProtobufProto2Test::serializeNestedRequiredDefaultValuesJson()
{
QProtobufJsonSerializer serializer;
qtprotobufnamespace::proto2::tests::NestedRequiredMessage msg;
QCOMPARE(msg.serialize(&serializer),
R"({"nested":{"testFieldBool":false,"testFieldInt":0,"testFieldString":""}})"_ba);
}
// No finite message satisfies a cycle of required fields, so the default
// submessage is written once instead of recursing without end.
void QtProtobufProto2Test::serializeRequiredCycle()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredCycle msg;
QCOMPARE(msg.serialize(&serializer).toHex(), "0a00"_ba);
}
void QtProtobufProto2Test::deserializeRejectsMissingRequiredFields_data()
{
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<QString>("missing");
QTest::newRow("all missing") << ""_ba << u"testFieldInt, testFieldBool, testFieldString"_s;
QTest::newRow("int present") << "0800"_ba << u"testFieldBool, testFieldString"_s;
QTest::newRow("string missing") << "08001000"_ba << u"testFieldString"_s;
// A scalar may repeat on the wire; counting occurrences would hide the missing field.
QTest::newRow("int repeated") << "08000800"_ba
<< u"testFieldBool, testFieldString"_s;
}
void QtProtobufProto2Test::deserializeRejectsMissingRequiredFields()
{
QFETCH(const QByteArray, payload);
QFETCH(const QString, missing);
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
QVERIFY(!serializer.deserialize(&msg, QByteArray::fromHex(payload)));
QCOMPARE(serializer.lastError(), QAbstractProtobufSerializer::Error::InvalidFormat);
QVERIFY2(serializer.lastErrorString().contains(missing),
qPrintable(serializer.lastErrorString()));
}
void QtProtobufProto2Test::deserializeAcceptsCompletePayload()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
QVERIFY(serializer.deserialize(&msg, QByteArray::fromHex("080010001a00"_ba)));
QCOMPARE(serializer.lastError(), QAbstractProtobufSerializer::Error::None);
}
// The reference implementation enforces required fields when parsing the binary
// format but not when parsing JSON. Being stricter would fail conformance.
void QtProtobufProto2Test::deserializeJsonStaysLenient()
{
QProtobufJsonSerializer serializer;
qtprotobufnamespace::proto2::tests::RequiredMessage msg;
QVERIFY(serializer.deserialize(&msg, "{}"_ba));
QCOMPARE(serializer.lastError(), QAbstractProtobufSerializer::Error::None);
}
void QtProtobufProto2Test::deserializeChecksNestedRequiredFields()
{
QProtobufSerializer serializer;
qtprotobufnamespace::proto2::tests::NestedRequiredMessage msg;
// Field 1, length 2: a RequiredMessage carrying only testFieldInt.
QVERIFY(!serializer.deserialize(&msg, QByteArray::fromHex("0a020800"_ba)));
QCOMPARE(serializer.lastError(), QAbstractProtobufSerializer::Error::InvalidFormat);
QVERIFY2(serializer.lastErrorString().contains(u"testFieldBool"_s),
qPrintable(serializer.lastErrorString()));
QVERIFY(serializer.deserialize(&msg, QByteArray::fromHex("0a06080010001a00"_ba)));
QCOMPARE(serializer.lastError(), QAbstractProtobufSerializer::Error::None);
}
QTEST_MAIN(QtProtobufProto2Test)
#include "tst_protobuf_proto2.moc"