aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/core/test/firebase/firestore/remote/serializer_test.cc')
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc241
1 files changed, 214 insertions, 27 deletions
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
index ba9ea47..1125fb4 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -146,13 +146,21 @@ class SerializerTest : public ::testing::Test {
*
* @param status the expected (failed) status. Only the code() is verified.
*/
- void ExpectFailedStatusDuringDecode(Status status,
- const std::vector<uint8_t>& bytes) {
+ void ExpectFailedStatusDuringFieldValueDecode(
+ Status status, const std::vector<uint8_t>& bytes) {
StatusOr<FieldValue> bad_status = serializer.DecodeFieldValue(bytes);
ASSERT_NOT_OK(bad_status);
EXPECT_EQ(status.code(), bad_status.status().code());
}
+ void ExpectFailedStatusDuringMaybeDocumentDecode(
+ Status status, const std::vector<uint8_t>& bytes) {
+ StatusOr<std::unique_ptr<MaybeDocument>> bad_status =
+ serializer.DecodeMaybeDocument(bytes);
+ ASSERT_NOT_OK(bad_status);
+ EXPECT_EQ(status.code(), bad_status.status().code());
+ }
+
v1beta1::Value ValueProto(nullptr_t) {
std::vector<uint8_t> bytes =
EncodeFieldValue(&serializer, FieldValue::NullValue());
@@ -230,20 +238,21 @@ class SerializerTest : public ::testing::Test {
/**
* Creates entries in the proto that we don't care about.
*
- * We ignore create time in our serializer. We never set it, and never read it
- * (other than to throw it away). But the server could (and probably does) set
- * it, so we need to be able to discard it properly. The ExpectRoundTrip deals
- * with this asymmetry.
+ * We ignore certain fields in our serializer. We never set them, and never
+ * read them (other than to throw them away). But the server could (and
+ * probably does) set them, so we need to be able to discard them properly.
+ * The ExpectRoundTrip deals with this asymmetry.
*
* This method adds these ignored fields to the proto.
*/
void TouchIgnoredBatchGetDocumentsResponseFields(
v1beta1::BatchGetDocumentsResponse* proto) {
+ proto->set_transaction("random bytes");
+
// TODO(rsgowman): This method currently assumes that this is a 'found'
// document. We (probably) will need to adjust this to work with NoDocuments
// too.
v1beta1::Document* doc_proto = proto->mutable_found();
-
google::protobuf::Timestamp* create_time_proto =
doc_proto->mutable_create_time();
create_time_proto->set_seconds(8765);
@@ -465,6 +474,63 @@ TEST_F(SerializerTest, EncodesNestedObjects) {
ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
+TEST_F(SerializerTest, EncodesFieldValuesWithRepeatedEntries) {
+ // Technically, serialized Value protos can contain multiple values. (The last
+ // one "wins".) However, well-behaved proto emitters (such as libprotobuf)
+ // won't generate that, so to test, we either need to use hand-crafted, raw
+ // bytes or use a proto message that's *almost* the same as the real one, such
+ // that when it's encoded, you can generate these repeated fields. (This is
+ // how libprotobuf tests itself.)
+ //
+ // Using libprotobuf for this purpose is mildly inconvenient for us, since we
+ // don't run protoc as part of the build process, so we'd need to either add
+ // these fake messages to our protos tree (Protos/testprotos?) and then check
+ // in the results (which isn't great when writing new tests). Fortunately, we
+ // have another alternative: nanopb.
+ //
+ // So we'll create a nanopb struct that *looks* like
+ // google_firestore_v1beta1_Value, and then populate and serialize it using
+ // the normal nanopb mechanisms. This should give us a wire-compatible Value
+ // message, but with multiple values set.
+
+ // Copy of the real one (from the nanopb generated document.pb.h), but with
+ // only boolean_value and integer_value.
+ struct google_firestore_v1beta1_Value_Fake {
+ bool boolean_value;
+ int64_t integer_value;
+ };
+
+ // Copy of the real one (from the nanopb generated document.pb.c), but with
+ // only boolean_value and integer_value.
+ const pb_field_t google_firestore_v1beta1_Value_fields_Fake[3] = {
+ PB_FIELD(1, BOOL, SINGULAR, STATIC, FIRST,
+ google_firestore_v1beta1_Value_Fake, boolean_value,
+ boolean_value, 0),
+ PB_FIELD(2, INT64, SINGULAR, STATIC, OTHER,
+ google_firestore_v1beta1_Value_Fake, integer_value,
+ boolean_value, 0),
+ PB_LAST_FIELD,
+ };
+
+ // Craft the bytes. boolean_value has a smaller tag, so it'll get encoded
+ // first. Implying integer_value should "win".
+ google_firestore_v1beta1_Value_Fake crafty_value{false, int64_t{42}};
+ std::vector<uint8_t> bytes(128);
+ pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size());
+ pb_encode(&stream, google_firestore_v1beta1_Value_fields_Fake, &crafty_value);
+ bytes.resize(stream.bytes_written);
+
+ // Decode the bytes into the model
+ StatusOr<FieldValue> actual_model_status = serializer.DecodeFieldValue(bytes);
+ EXPECT_OK(actual_model_status);
+ FieldValue actual_model = actual_model_status.ValueOrDie();
+
+ // Ensure the decoded model is as expected.
+ FieldValue expected_model = FieldValue::IntegerValue(42);
+ EXPECT_EQ(FieldValue::Type::Integer, actual_model.type());
+ EXPECT_EQ(expected_model, actual_model);
+}
+
TEST_F(SerializerTest, BadNullValue) {
std::vector<uint8_t> bytes =
EncodeFieldValue(&serializer, FieldValue::NullValue());
@@ -472,7 +538,7 @@ TEST_F(SerializerTest, BadNullValue) {
// Alter the null value from 0 to 1.
Mutate(&bytes[1], /*expected_initial_value=*/0, /*new_value=*/1);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -483,7 +549,7 @@ TEST_F(SerializerTest, BadBoolValue) {
// Alter the bool value from 1 to 2. (Value values are 0,1)
Mutate(&bytes[1], /*expected_initial_value=*/1, /*new_value=*/2);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -502,7 +568,7 @@ TEST_F(SerializerTest, BadIntegerValue) {
bytes.resize(12);
bytes[11] = 0x7f;
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -514,7 +580,7 @@ TEST_F(SerializerTest, BadStringValue) {
// used by the encoded tag.)
Mutate(&bytes[2], /*expected_initial_value=*/1, /*new_value=*/5);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -525,7 +591,7 @@ TEST_F(SerializerTest, BadTimestampValue_TooLarge) {
// Add some time, which should push us above the maximum allowed timestamp.
Mutate(&bytes[4], 0x82, 0x83);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -536,11 +602,16 @@ TEST_F(SerializerTest, BadTimestampValue_TooSmall) {
// Remove some time, which should push us below the minimum allowed timestamp.
Mutate(&bytes[4], 0x92, 0x91);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
-TEST_F(SerializerTest, BadTag) {
+TEST_F(SerializerTest, BadFieldValueTagAndNoOtherTagPresent) {
+ // A bad tag should be ignored. But if there are *no* valid tags, then we
+ // don't know the type of the FieldValue. Although it might be reasonable to
+ // assume some sort of default type in this situation, we've decided to fail
+ // the deserialization process in this case instead.
+
std::vector<uint8_t> bytes =
EncodeFieldValue(&serializer, FieldValue::NullValue());
@@ -550,15 +621,57 @@ TEST_F(SerializerTest, BadTag) {
// Specifically 31. 0xf8 represents field number 31 encoded as a varint.
Mutate(&bytes[0], /*expected_initial_value=*/0x58, /*new_value=*/0xf8);
- // TODO(rsgowman): The behaviour is *temporarily* slightly different during
- // development; this will cause a failed assertion rather than a failed
- // status. Remove this EXPECT_ANY_THROW statement (and reenable the
- // following commented out statement) once the corresponding assert has been
- // removed from serializer.cc.
- EXPECT_ANY_THROW(ExpectFailedStatusDuringDecode(
- Status(FirestoreErrorCode::DataLoss, "ignored"), bytes));
- // ExpectFailedStatusDuringDecode(
- // Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+ ExpectFailedStatusDuringFieldValueDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadFieldValueTagWithOtherValidTagsPresent) {
+ // A bad tag should be ignored, in which case, we should successfully
+ // deserialize the rest of the bytes as if it wasn't there. To craft these
+ // bytes, we'll use the same technique as
+ // EncodesFieldValuesWithRepeatedEntries (so go read the comments there
+ // first).
+
+ // Copy of the real one (from the nanopb generated document.pb.h), but with
+ // only boolean_value and integer_value.
+ struct google_firestore_v1beta1_Value_Fake {
+ bool boolean_value;
+ int64_t integer_value;
+ };
+
+ // Copy of the real one (from the nanopb generated document.pb.c), but with
+ // only boolean_value and integer_value. Also modified such that integer_value
+ // now has an invalid tag (instead of 2).
+ const int invalid_tag = 31;
+ const pb_field_t google_firestore_v1beta1_Value_fields_Fake[3] = {
+ PB_FIELD(1, BOOL, SINGULAR, STATIC, FIRST,
+ google_firestore_v1beta1_Value_Fake, boolean_value,
+ boolean_value, 0),
+ PB_FIELD(invalid_tag, INT64, SINGULAR, STATIC, OTHER,
+ google_firestore_v1beta1_Value_Fake, integer_value,
+ boolean_value, 0),
+ PB_LAST_FIELD,
+ };
+
+ // Craft the bytes. boolean_value has a smaller tag, so it'll get encoded
+ // first, normally implying integer_value should "win". Except that
+ // integer_value isn't a valid tag, so it should be ignored here.
+ google_firestore_v1beta1_Value_Fake crafty_value{true, int64_t{42}};
+ std::vector<uint8_t> bytes(128);
+ pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size());
+ pb_encode(&stream, google_firestore_v1beta1_Value_fields_Fake, &crafty_value);
+ bytes.resize(stream.bytes_written);
+
+ // Decode the bytes into the model
+ StatusOr<FieldValue> actual_model_status = serializer.DecodeFieldValue(bytes);
+ Status s = actual_model_status.status();
+ EXPECT_OK(actual_model_status);
+ FieldValue actual_model = actual_model_status.ValueOrDie();
+
+ // Ensure the decoded model is as expected.
+ FieldValue expected_model = FieldValue::BooleanValue(true);
+ EXPECT_EQ(FieldValue::Type::Boolean, actual_model.type());
+ EXPECT_EQ(expected_model, actual_model);
}
TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) {
@@ -570,7 +683,7 @@ TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) {
// would do.)
Mutate(&bytes[0], /*expected_initial_value=*/0x08, /*new_value=*/0x0a);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -581,7 +694,7 @@ TEST_F(SerializerTest, TagStringWiretypeVarintMismatch) {
// 0x88 represents a string value encoded as a varint.
Mutate(&bytes[0], /*expected_initial_value=*/0x8a, /*new_value=*/0x88);
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -594,13 +707,13 @@ TEST_F(SerializerTest, IncompleteFieldValue) {
ASSERT_EQ(0x00, bytes[1]);
bytes.pop_back();
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
TEST_F(SerializerTest, IncompleteTag) {
std::vector<uint8_t> bytes;
- ExpectFailedStatusDuringDecode(
+ ExpectFailedStatusDuringFieldValueDecode(
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
@@ -694,6 +807,69 @@ TEST_F(SerializerTest, EncodesNonEmptyDocument) {
ExpectRoundTrip(key, fields, update_time, proto);
}
+TEST_F(SerializerTest,
+ BadBatchGetDocumentsResponseTagWithOtherValidTagsPresent) {
+ // A bad tag should be ignored, in which case, we should successfully
+ // deserialize the rest of the bytes as if it wasn't there. To craft these
+ // bytes, we'll use the same technique as
+ // EncodesFieldValuesWithRepeatedEntries (so go read the comments there
+ // first).
+
+ // Copy of the real one (from the nanopb generated firestore.pb.h), but with
+ // only "missing" (a field from the original proto) and an extra_field.
+ struct google_firestore_v1beta1_BatchGetDocumentsResponse_Fake {
+ pb_callback_t missing;
+ int64_t extra_field;
+ };
+
+ // Copy of the real one (from the nanopb generated firestore.pb.c), but with
+ // only missing and an extra_field. Also modified such that extra_field
+ // now has a tag of 31.
+ const int invalid_tag = 31;
+ const pb_field_t
+ google_firestore_v1beta1_BatchGetDocumentsResponse_fields_Fake[3] = {
+ PB_FIELD(2, STRING, SINGULAR, CALLBACK, FIRST,
+ google_firestore_v1beta1_BatchGetDocumentsResponse_Fake,
+ missing, missing, 0),
+ PB_FIELD(invalid_tag, INT64, SINGULAR, STATIC, OTHER,
+ google_firestore_v1beta1_BatchGetDocumentsResponse_Fake,
+ extra_field, missing, 0),
+ PB_LAST_FIELD,
+ };
+
+ const char* missing_value = "projects/p/databases/d/documents/one/two";
+ google_firestore_v1beta1_BatchGetDocumentsResponse_Fake crafty_value;
+ crafty_value.missing.funcs.encode =
+ [](pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
+ const char* missing_value = static_cast<const char*>(*arg);
+ if (!pb_encode_tag_for_field(stream, field)) return false;
+ return pb_encode_string(stream,
+ reinterpret_cast<const uint8_t*>(missing_value),
+ strlen(missing_value));
+ };
+ crafty_value.missing.arg = const_cast<char*>(missing_value);
+ crafty_value.extra_field = 42;
+
+ std::vector<uint8_t> bytes(128);
+ pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size());
+ pb_encode(&stream,
+ google_firestore_v1beta1_BatchGetDocumentsResponse_fields_Fake,
+ &crafty_value);
+ bytes.resize(stream.bytes_written);
+
+ // Decode the bytes into the model
+ StatusOr<std::unique_ptr<MaybeDocument>> actual_model_status =
+ serializer.DecodeMaybeDocument(bytes);
+ EXPECT_OK(actual_model_status);
+ std::unique_ptr<MaybeDocument> actual_model =
+ std::move(actual_model_status).ValueOrDie();
+
+ // Ensure the decoded model is as expected.
+ NoDocument expected_model =
+ NoDocument(Key("one/two"), SnapshotVersion::None());
+ EXPECT_EQ(expected_model, *actual_model.get());
+}
+
TEST_F(SerializerTest, DecodesNoDocument) {
// We can't actually *encode* a NoDocument; the method exposed by the
// serializer requires both the document key and contents (as an ObjectValue,
@@ -713,5 +889,16 @@ TEST_F(SerializerTest, DecodesNoDocument) {
ExpectNoDocumentDeserializationRoundTrip(key, read_time, proto);
}
+TEST_F(SerializerTest, DecodeMaybeDocWithoutFoundOrMissingSetShouldFail) {
+ v1beta1::BatchGetDocumentsResponse proto;
+
+ std::vector<uint8_t> bytes(proto.ByteSizeLong());
+ bool status = proto.SerializeToArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(status);
+
+ ExpectFailedStatusDuringMaybeDocumentDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
// TODO(rsgowman): Test [en|de]coding multiple protos into the same output
// vector.