blob: a9bde6fec99ce2906bc79a1f3397741993ff7645 [file] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// JSON conformance tests for string and bytes fields: Unicode text, JSON
// escapes (including \u escapes, surrogate pairs and embedded NUL), rejected
// malformed escapes, unpaired surrogates, non-string values and single-quoted
// strings, and base64 / base64url bytes. This replaces the "String fields"
// and "Bytes fields" blocks (and the single-quote tests at the end) of the
// legacy
// BinaryAndJsonConformanceSuiteImpl<M>::RunJsonTestsForNonRepeatedTypes(); the
// test names and the requests sent to the testee are identical to the legacy
// ones.
//
// Like the legacy RunValidJsonTest(), each valid input is sent twice: once to
// be serialized as binary ("<name>.ProtobufOutput") and once as JSON
// ("<name>.JsonOutput"). ExpectParseFailureForJson() asked for JSON output
// under a name without an output suffix.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
#include "conformance/binary_test_util.h"
#include "conformance/matchers.h"
#include "conformance/message_type_fixtures.h"
#include "conformance/testee.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::testing::ValuesIn;
// ---------------------------------------------------------------------------
// String fields.
// ---------------------------------------------------------------------------
using JsonStringTest = MessageTypeConformanceTest;
TEST_P(JsonStringTest, StringField) {
constexpr absl::string_view kInput =
R"json({"optionalString": "Hello world!"})json";
constexpr absl::string_view kExpected =
R"pb(optional_string: "Hello world!")pb";
EXPECT_THAT(
Testee("StringField").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("StringField").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// Non-ASCII characters in the JSON text itself, as opposed to the \uXXXX
// escapes tested below. Note that kInput is deliberately not a raw string
// literal: the compiler resolves the \x escapes, so the payload holds the
// UTF-8 bytes of U+8C37 U+6B4C ("Google" in Chinese). In the raw string
// literals below, the \u escapes reach the testee's JSON parser verbatim.
TEST_P(JsonStringTest, StringFieldUnicode) {
constexpr absl::string_view kInput =
"{\"optionalString\": \"\xE8\xB0\xB7\xE6\xAD\x8C\"}";
constexpr absl::string_view kExpected =
R"pb(optional_string: "\xE8\xB0\xB7\xE6\xAD\x8C")pb";
EXPECT_THAT(Testee("StringFieldUnicode")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("StringFieldUnicode").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonStringTest, StringFieldEscape) {
constexpr absl::string_view kInput =
R"json({"optionalString": "\"\\\/\b\f\n\r\t"})json";
constexpr absl::string_view kExpected =
R"pb(optional_string: "\"\\/\b\f\n\r\t")pb";
EXPECT_THAT(Testee("StringFieldEscape")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(
Testee("StringFieldEscape").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonStringTest, StringFieldUnicodeEscape) {
constexpr absl::string_view kInput =
R"json({"optionalString": "\u8C37\u6B4C"})json";
constexpr absl::string_view kExpected =
R"pb(optional_string: "\xE8\xB0\xB7\xE6\xAD\x8C")pb";
EXPECT_THAT(Testee("StringFieldUnicodeEscape")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("StringFieldUnicodeEscape")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// U+1F601 (grinning face with smiling eyes).
TEST_P(JsonStringTest, StringFieldSurrogatePair) {
constexpr absl::string_view kInput =
R"json({"optionalString": "\uD83D\uDE01"})json";
constexpr absl::string_view kExpected =
R"pb(optional_string: "\xF0\x9F\x98\x81")pb";
EXPECT_THAT(Testee("StringFieldSurrogatePair")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("StringFieldSurrogatePair")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonStringTest, StringFieldEmbeddedNull) {
constexpr absl::string_view kInput =
R"json({"optionalString": "Hello\u0000world!"})json";
constexpr absl::string_view kExpected =
R"pb(optional_string: "Hello\000world!")pb";
EXPECT_THAT(Testee("StringFieldEmbeddedNull")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("StringFieldEmbeddedNull")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
// Unicode escapes must start with "\u" (lowercase u).
TEST_P(JsonStringTest, StringFieldUppercaseEscapeLetter) {
EXPECT_THAT(
Testee(kP1, "StringFieldUppercaseEscapeLetter")
.ParseJson(message(), "{\"optionalString\": \"\\U8C37\\U6b4C\"}")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldInvalidEscape) {
EXPECT_THAT(
Testee(kP1, "StringFieldInvalidEscape")
.ParseJson(message(), "{\"optionalString\": \"\\uXXXX\\u6B4C\"}")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldUnterminatedEscape) {
EXPECT_THAT(
Testee(kP1, "StringFieldUnterminatedEscape")
.ParseJson(message(), R"json({"optionalString": "\u8C3"})json")
.ParseOnly(),
Yields(IsParseError()));
}
// The unpaired-surrogate inputs below are ordinary (not raw) string literals:
// MSVC rejects a lone surrogate written as \uXXXX even inside a raw string
// literal (error C3850), so the backslash is escaped to keep the \u escape
// out of the compiler's hands and deliver it to the testee's JSON parser.
TEST_P(JsonStringTest, StringFieldUnpairedHighSurrogate) {
EXPECT_THAT(Testee(kP1, "StringFieldUnpairedHighSurrogate")
.ParseJson(message(), "{\"optionalString\": \"\\uD800\"}")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldUnpairedLowSurrogate) {
EXPECT_THAT(Testee(kP1, "StringFieldUnpairedLowSurrogate")
.ParseJson(message(), "{\"optionalString\": \"\\uDC00\"}")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldSurrogateInWrongOrder) {
EXPECT_THAT(
Testee(kP1, "StringFieldSurrogateInWrongOrder")
.ParseJson(message(), "{\"optionalString\": \"\\uDE01\\uD83D\"}")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldNotAString) {
EXPECT_THAT(Testee("StringFieldNotAString")
.ParseJson(message(), R"json({"optionalString": 12345})json")
.ParseOnly(),
Yields(IsParseError()));
}
// http://www.rfc-editor.org/rfc/rfc7159.txt says strings have to use double
// quotes.
TEST_P(JsonStringTest, StringFieldSingleQuoteKey) {
EXPECT_THAT(
Testee(kP1, "StringFieldSingleQuoteKey")
.ParseJson(message(), R"json({'optionalString': "Hello world!"})json")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldSingleQuoteValue) {
EXPECT_THAT(
Testee(kP1, "StringFieldSingleQuoteValue")
.ParseJson(message(), R"json({"optionalString": 'Hello world!'})json")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(JsonStringTest, StringFieldSingleQuoteBoth) {
EXPECT_THAT(
Testee(kP1, "StringFieldSingleQuoteBoth")
.ParseJson(message(), R"json({'optionalString': 'Hello world!'})json")
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(All, JsonStringTest, ValuesIn(AllTestMessageTypes()),
MessageTypeParamName);
// ---------------------------------------------------------------------------
// Bytes fields.
// ---------------------------------------------------------------------------
using JsonBytesTest = MessageTypeConformanceTest;
TEST_P(JsonBytesTest, BytesField) {
constexpr absl::string_view kInput = R"json({"optionalBytes": "AQI="})json";
constexpr absl::string_view kExpected = R"pb(optional_bytes: "\x01\x02")pb";
EXPECT_THAT(
Testee("BytesField").ParseJson(message(), kInput).SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee("BytesField").ParseJson(message(), kInput).SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
TEST_P(JsonBytesTest, BytesFieldBase64Url) {
constexpr absl::string_view kInput = R"json({"optionalBytes": "-_"})json";
constexpr absl::string_view kExpected = R"pb(optional_bytes: "\xfb")pb";
EXPECT_THAT(Testee(kP1, "BytesFieldBase64Url")
.ParseJson(message(), kInput)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
EXPECT_THAT(Testee(kP1, "BytesFieldBase64Url")
.ParseJson(message(), kInput)
.SerializeJson(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
INSTANTIATE_TEST_SUITE_P(All, JsonBytesTest, ValuesIn(AllTestMessageTypes()),
MessageTypeParamName);
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google