1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/util/document-validator.h"
16
17 #include <cstdint>
18 #include <limits>
19 #include <memory>
20 #include <string>
21
22 #include "icing/text_classifier/lib3/utils/base/status.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include "icing/document-builder.h"
26 #include "icing/feature-flags.h"
27 #include "icing/file/filesystem.h"
28 #include "icing/proto/document.pb.h"
29 #include "icing/proto/schema.pb.h"
30 #include "icing/schema-builder.h"
31 #include "icing/schema/schema-store.h"
32 #include "icing/testing/common-matchers.h"
33 #include "icing/testing/fake-clock.h"
34 #include "icing/testing/test-feature-flags.h"
35 #include "icing/testing/tmp-directory.h"
36
37 namespace icing {
38 namespace lib {
39
40 namespace {
41
42 using ::testing::HasSubstr;
43
44 // type and property names of EmailMessage and EmailMessageWithNote
45 constexpr char kTypeEmail[] = "EmailMessage";
46 constexpr char kTypeEmailWithNote[] = "EmailMessageWithNote";
47 constexpr char kPropertySubject[] = "subject";
48 constexpr char kPropertyText[] = "text";
49 constexpr char kPropertyRecipients[] = "recipients";
50 constexpr char kPropertyNote[] = "note";
51 constexpr char kPropertyNoteEmbedding[] = "noteEmbedding";
52 // type and property names of Conversation
53 constexpr char kTypeConversation[] = "Conversation";
54 constexpr char kTypeConversationWithEmailNote[] = "ConversationWithEmailNote";
55 constexpr char kPropertyName[] = "name";
56 constexpr char kPropertyEmails[] = "emails";
57 // Other values
58 constexpr char kDefaultNamespace[] = "icing";
59 constexpr char kDefaultString[] = "This is a string.";
60
61 class DocumentValidatorTest : public ::testing::Test {
62 protected:
DocumentValidatorTest()63 DocumentValidatorTest() {}
64
SetUp()65 void SetUp() override {
66 feature_flags_ = std::make_unique<FeatureFlags>(GetTestFeatureFlags());
67
68 SchemaProto schema =
69 SchemaBuilder()
70 .AddType(
71 SchemaTypeConfigBuilder()
72 .SetType(kTypeEmail)
73 .AddProperty(PropertyConfigBuilder()
74 .SetName(kPropertySubject)
75 .SetDataType(TYPE_STRING)
76 .SetCardinality(CARDINALITY_REQUIRED))
77 .AddProperty(PropertyConfigBuilder()
78 .SetName(kPropertyText)
79 .SetDataType(TYPE_STRING)
80 .SetCardinality(CARDINALITY_OPTIONAL))
81 .AddProperty(PropertyConfigBuilder()
82 .SetName(kPropertyRecipients)
83 .SetDataType(TYPE_STRING)
84 .SetCardinality(CARDINALITY_REPEATED)))
85 .AddType(
86 SchemaTypeConfigBuilder()
87 .SetType(kTypeEmailWithNote)
88 .AddParentType(kTypeEmail)
89 .AddProperty(PropertyConfigBuilder()
90 .SetName(kPropertySubject)
91 .SetDataType(TYPE_STRING)
92 .SetCardinality(CARDINALITY_REQUIRED))
93 .AddProperty(PropertyConfigBuilder()
94 .SetName(kPropertyText)
95 .SetDataType(TYPE_STRING)
96 .SetCardinality(CARDINALITY_OPTIONAL))
97 .AddProperty(PropertyConfigBuilder()
98 .SetName(kPropertyRecipients)
99 .SetDataType(TYPE_STRING)
100 .SetCardinality(CARDINALITY_REPEATED))
101 .AddProperty(PropertyConfigBuilder()
102 .SetName(kPropertyNote)
103 .SetDataType(TYPE_STRING)
104 .SetCardinality(CARDINALITY_OPTIONAL))
105 .AddProperty(PropertyConfigBuilder()
106 .SetName(kPropertyNoteEmbedding)
107 .SetDataType(TYPE_VECTOR)
108 .SetCardinality(CARDINALITY_OPTIONAL)))
109 .AddType(
110 SchemaTypeConfigBuilder()
111 .SetType(kTypeConversation)
112 .AddProperty(PropertyConfigBuilder()
113 .SetName(kPropertyName)
114 .SetDataType(TYPE_STRING)
115 .SetCardinality(CARDINALITY_REQUIRED))
116 .AddProperty(
117 PropertyConfigBuilder()
118 .SetName(kPropertyEmails)
119 .SetDataTypeDocument(
120 kTypeEmail, /*index_nested_properties=*/true)
121 .SetCardinality(CARDINALITY_REPEATED)))
122 .AddType(
123 SchemaTypeConfigBuilder()
124 .SetType(kTypeConversationWithEmailNote)
125 .AddProperty(PropertyConfigBuilder()
126 .SetName(kPropertyName)
127 .SetDataType(TYPE_STRING)
128 .SetCardinality(CARDINALITY_REQUIRED))
129 .AddProperty(PropertyConfigBuilder()
130 .SetName(kPropertyEmails)
131 .SetDataTypeDocument(
132 kTypeEmailWithNote,
133 /*index_nested_properties=*/true)
134 .SetCardinality(CARDINALITY_REPEATED)))
135 .Build();
136
137 schema_dir_ = GetTestTempDir() + "/schema_store";
138 ASSERT_TRUE(filesystem_.CreateDirectory(schema_dir_.c_str()));
139 ICING_ASSERT_OK_AND_ASSIGN(
140 schema_store_, SchemaStore::Create(&filesystem_, schema_dir_,
141 &fake_clock_, feature_flags_.get()));
142 ASSERT_THAT(schema_store_->SetSchema(
143 schema, /*ignore_errors_and_delete_documents=*/false,
144 /*allow_circular_schema_definitions=*/false),
145 IsOk());
146
147 document_validator_ =
148 std::make_unique<DocumentValidator>(schema_store_.get());
149 }
150
SimpleEmailBuilder()151 DocumentBuilder SimpleEmailBuilder() {
152 return DocumentBuilder()
153 .SetKey(kDefaultNamespace, "email/1")
154 .SetSchema(kTypeEmail)
155 .AddStringProperty(kPropertySubject, kDefaultString)
156 .AddStringProperty(kPropertyText, kDefaultString)
157 .AddStringProperty(kPropertyRecipients, kDefaultString, kDefaultString,
158 kDefaultString);
159 }
160
SimpleEmailWithNoteBuilder()161 DocumentBuilder SimpleEmailWithNoteBuilder() {
162 PropertyProto::VectorProto vector;
163 vector.add_values(0.1);
164 vector.add_values(0.2);
165 vector.add_values(0.3);
166 vector.set_model_signature("my_model");
167
168 return DocumentBuilder()
169 .SetKey(kDefaultNamespace, "email_with_note/1")
170 .SetSchema(kTypeEmailWithNote)
171 .AddStringProperty(kPropertySubject, kDefaultString)
172 .AddStringProperty(kPropertyText, kDefaultString)
173 .AddStringProperty(kPropertyRecipients, kDefaultString, kDefaultString,
174 kDefaultString)
175 .AddStringProperty(kPropertyNote, kDefaultString)
176 .AddVectorProperty(kPropertyNoteEmbedding, vector);
177 }
178
SimpleConversationBuilder()179 DocumentBuilder SimpleConversationBuilder() {
180 return DocumentBuilder()
181 .SetKey(kDefaultNamespace, "conversation/1")
182 .SetSchema(kTypeConversation)
183 .AddStringProperty(kPropertyName, kDefaultString)
184 .AddDocumentProperty(kPropertyEmails, SimpleEmailBuilder().Build(),
185 SimpleEmailBuilder().Build(),
186 SimpleEmailBuilder().Build());
187 }
188
189 std::unique_ptr<FeatureFlags> feature_flags_;
190 std::string schema_dir_;
191 Filesystem filesystem_;
192 FakeClock fake_clock_;
193 std::unique_ptr<SchemaStore> schema_store_;
194 std::unique_ptr<DocumentValidator> document_validator_;
195 };
196
TEST_F(DocumentValidatorTest,ValidateSimpleSchemasOk)197 TEST_F(DocumentValidatorTest, ValidateSimpleSchemasOk) {
198 DocumentProto email = SimpleEmailBuilder().Build();
199 EXPECT_THAT(document_validator_->Validate(email), IsOk());
200
201 DocumentProto conversation = SimpleConversationBuilder().Build();
202 EXPECT_THAT(document_validator_->Validate(conversation), IsOk());
203 }
204
TEST_F(DocumentValidatorTest,ValidateEmptyNamespaceInvalid)205 TEST_F(DocumentValidatorTest, ValidateEmptyNamespaceInvalid) {
206 DocumentProto email = SimpleEmailBuilder().SetNamespace("").Build();
207 EXPECT_THAT(document_validator_->Validate(email),
208 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
209 HasSubstr("'namespace' is empty")));
210 }
211
TEST_F(DocumentValidatorTest,ValidateTopLevelEmptyUriInvalid)212 TEST_F(DocumentValidatorTest, ValidateTopLevelEmptyUriInvalid) {
213 DocumentProto email = SimpleEmailBuilder().SetUri("").Build();
214 EXPECT_THAT(document_validator_->Validate(email),
215 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
216 HasSubstr("'uri' is empty")));
217 }
218
TEST_F(DocumentValidatorTest,ValidateNestedEmptyUriValid)219 TEST_F(DocumentValidatorTest, ValidateNestedEmptyUriValid) {
220 DocumentProto conversation =
221 SimpleConversationBuilder()
222 .ClearProperties()
223 .AddStringProperty(kPropertyName, kDefaultString)
224 .AddDocumentProperty(kPropertyEmails,
225 SimpleEmailBuilder()
226 .SetUri("") // Empty nested uri
227 .Build())
228 .Build();
229
230 EXPECT_THAT(document_validator_->Validate(conversation), IsOk());
231 }
232
TEST_F(DocumentValidatorTest,ValidateEmptySchemaInvalid)233 TEST_F(DocumentValidatorTest, ValidateEmptySchemaInvalid) {
234 DocumentProto email = SimpleEmailBuilder().SetSchema("").Build();
235 EXPECT_THAT(document_validator_->Validate(email),
236 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
237 HasSubstr("'schema' is empty")));
238 }
239
TEST_F(DocumentValidatorTest,ValidateNonexistentSchemaNotFound)240 TEST_F(DocumentValidatorTest, ValidateNonexistentSchemaNotFound) {
241 DocumentProto email =
242 SimpleEmailBuilder().SetSchema("WrongEmailType").Build();
243 EXPECT_THAT(document_validator_->Validate(email),
244 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND,
245 HasSubstr("'WrongEmailType' not found")));
246 }
247
TEST_F(DocumentValidatorTest,ValidateEmptyPropertyInvalid)248 TEST_F(DocumentValidatorTest, ValidateEmptyPropertyInvalid) {
249 DocumentProto email =
250 SimpleEmailBuilder().AddStringProperty("", kDefaultString).Build();
251
252 EXPECT_THAT(document_validator_->Validate(email),
253 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
254 HasSubstr("'name' is empty")));
255 }
256
TEST_F(DocumentValidatorTest,ValidateDuplicatePropertyAlreadyExists)257 TEST_F(DocumentValidatorTest, ValidateDuplicatePropertyAlreadyExists) {
258 DocumentProto email = SimpleEmailBuilder()
259 .ClearProperties()
260 .AddStringProperty(kPropertySubject, kDefaultString)
261 .AddStringProperty(kPropertySubject, kDefaultString)
262 .Build();
263
264 EXPECT_THAT(document_validator_->Validate(email),
265 StatusIs(libtextclassifier3::StatusCode::ALREADY_EXISTS,
266 HasSubstr("'subject' already exists")));
267 }
268
TEST_F(DocumentValidatorTest,ValidateNonexistentPropertyNotFound)269 TEST_F(DocumentValidatorTest, ValidateNonexistentPropertyNotFound) {
270 DocumentProto email =
271 SimpleEmailBuilder()
272 .AddStringProperty("WrongPropertyName", kDefaultString)
273 .Build();
274
275 EXPECT_THAT(document_validator_->Validate(email),
276 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND,
277 HasSubstr("'WrongPropertyName' not found")));
278 }
279
TEST_F(DocumentValidatorTest,ValidateExactlyOneRequiredValueOk)280 TEST_F(DocumentValidatorTest, ValidateExactlyOneRequiredValueOk) {
281 // Required property should have exactly 1 value
282 DocumentProto email =
283 SimpleEmailBuilder()
284 .ClearProperties()
285 .AddStringProperty(kPropertySubject, kDefaultString) // 1 value
286 .Build();
287
288 EXPECT_THAT(document_validator_->Validate(email), IsOk());
289 }
290
TEST_F(DocumentValidatorTest,ValidateInvalidNumberOfRequiredValues)291 TEST_F(DocumentValidatorTest, ValidateInvalidNumberOfRequiredValues) {
292 // Required property should have exactly 1 value
293 DocumentProto email = SimpleEmailBuilder()
294 .ClearProperties()
295 .AddStringProperty(kPropertySubject) // 0 values
296 .Build();
297
298 EXPECT_THAT(document_validator_->Validate(email),
299 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
300 HasSubstr("'subject' with only 1 value is required "
301 "but 0 elements are found")));
302
303 email =
304 SimpleEmailBuilder()
305 .ClearProperties()
306 .AddStringProperty(kPropertySubject, kDefaultString, kDefaultString)
307 .Build();
308
309 EXPECT_THAT(document_validator_->Validate(email),
310 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
311 HasSubstr("'subject' with only 1 value is required "
312 "but 2 elements are found")));
313 }
314
TEST_F(DocumentValidatorTest,ValidateZeroOrOneOptionalValueOk)315 TEST_F(DocumentValidatorTest, ValidateZeroOrOneOptionalValueOk) {
316 DocumentProto email = SimpleEmailBuilder()
317 .ClearProperties()
318 .AddStringProperty(kPropertySubject, kDefaultString)
319 .AddStringProperty(kPropertyText) // 0 values
320 .Build();
321
322 EXPECT_THAT(document_validator_->Validate(email), IsOk());
323
324 email = SimpleEmailBuilder()
325 .ClearProperties()
326 .AddStringProperty(kPropertySubject, kDefaultString)
327 .AddStringProperty(kPropertyText, kDefaultString) // 1 value
328 .Build();
329
330 EXPECT_THAT(document_validator_->Validate(email), IsOk());
331 }
332
TEST_F(DocumentValidatorTest,ValidateInvalidNumberOfOptionalValues)333 TEST_F(DocumentValidatorTest, ValidateInvalidNumberOfOptionalValues) {
334 DocumentProto email =
335 SimpleEmailBuilder()
336 .ClearProperties()
337 .AddStringProperty(kPropertySubject, kDefaultString)
338 .AddStringProperty(kPropertyText, kDefaultString, kDefaultString)
339 .Build();
340
341 EXPECT_THAT(
342 document_validator_->Validate(email),
343 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
344 HasSubstr("'text' is optional but 2 elements are found")));
345 }
346
TEST_F(DocumentValidatorTest,ValidateMissingRequiredPropertyInvalid)347 TEST_F(DocumentValidatorTest, ValidateMissingRequiredPropertyInvalid) {
348 // All required properties should be present in document
349 DocumentProto email = SimpleEmailBuilder()
350 .ClearProperties()
351 .AddStringProperty(kPropertyText, kDefaultString)
352 .Build();
353
354 // The required property 'subject' isn't added in email.
355 EXPECT_THAT(document_validator_->Validate(email),
356 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
357 HasSubstr("One or more required fields missing")));
358 }
359
TEST_F(DocumentValidatorTest,ValidateNestedPropertyDoesntMatchSchemaTypeInvalid)360 TEST_F(DocumentValidatorTest,
361 ValidateNestedPropertyDoesntMatchSchemaTypeInvalid) {
362 // Nested DocumentProto should have the expected schema type
363 DocumentProto conversation =
364 SimpleConversationBuilder()
365 .ClearProperties()
366 .AddStringProperty(kPropertyName, kDefaultString)
367 .AddDocumentProperty(
368 kPropertyEmails, SimpleEmailBuilder().Build(),
369 SimpleConversationBuilder().Build(), // Wrong document type
370 SimpleEmailBuilder().Build())
371 .Build();
372
373 EXPECT_THAT(
374 document_validator_->Validate(conversation),
375 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
376 HasSubstr("'emails' should be type or subtype of 'EmailMessage' "
377 "but actual value has type 'Conversation'")));
378 }
379
TEST_F(DocumentValidatorTest,ValidateNestedPropertyMatchSubtypeOk)380 TEST_F(DocumentValidatorTest, ValidateNestedPropertyMatchSubtypeOk) {
381 DocumentProto conversation =
382 DocumentBuilder()
383 .SetKey(kDefaultNamespace, "conversation/1")
384 .SetSchema(kTypeConversation)
385 .AddStringProperty(kPropertyName, kDefaultString)
386 .AddDocumentProperty(kPropertyEmails, SimpleEmailBuilder().Build(),
387 // This is a subtype, which is ok.
388 SimpleEmailWithNoteBuilder().Build(),
389 SimpleEmailBuilder().Build())
390 .Build();
391
392 EXPECT_THAT(document_validator_->Validate(conversation), IsOk());
393 }
394
TEST_F(DocumentValidatorTest,ValidateNestedPropertyNonexistentTypeInvalid)395 TEST_F(DocumentValidatorTest, ValidateNestedPropertyNonexistentTypeInvalid) {
396 DocumentProto conversation =
397 DocumentBuilder()
398 .SetKey(kDefaultNamespace, "conversation/1")
399 .SetSchema(kTypeConversation)
400 .AddStringProperty(kPropertyName, kDefaultString)
401 .AddDocumentProperty(
402 kPropertyEmails, SimpleEmailBuilder().Build(),
403 // Nonexistent type is not allowed
404 DocumentBuilder()
405 .SetKey(kDefaultNamespace, "email_with_note/1")
406 .SetSchema("Nonexistent")
407 .Build(),
408 SimpleEmailBuilder().Build())
409 .Build();
410
411 EXPECT_THAT(
412 document_validator_->Validate(conversation),
413 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
414 HasSubstr("'emails' should be type or subtype of 'EmailMessage' "
415 "but actual value has type 'Nonexistent'")));
416 }
417
TEST_F(DocumentValidatorTest,ValidateNestedPropertyMatchSuperTypeInvalid)418 TEST_F(DocumentValidatorTest, ValidateNestedPropertyMatchSuperTypeInvalid) {
419 DocumentProto conversation1 =
420 DocumentBuilder()
421 .SetKey(kDefaultNamespace, "conversation_with_email_note/1")
422 .SetSchema(kTypeConversationWithEmailNote)
423 .AddStringProperty(kPropertyName, kDefaultString)
424 .AddDocumentProperty(kPropertyEmails,
425 SimpleEmailWithNoteBuilder().Build(),
426 SimpleEmailWithNoteBuilder().Build(),
427 SimpleEmailWithNoteBuilder().Build())
428 .Build();
429 EXPECT_THAT(document_validator_->Validate(conversation1), IsOk());
430
431 DocumentProto conversation2 =
432 DocumentBuilder()
433 .SetKey(kDefaultNamespace, "conversation_with_email_note/2")
434 .SetSchema(kTypeConversationWithEmailNote)
435 .AddStringProperty(kPropertyName, kDefaultString)
436 .AddDocumentProperty(kPropertyEmails,
437 SimpleEmailWithNoteBuilder().Build(),
438 // This is a super type, which is not ok.
439 SimpleEmailBuilder().Build(),
440 SimpleEmailWithNoteBuilder().Build())
441 .Build();
442 EXPECT_THAT(
443 document_validator_->Validate(conversation2),
444 StatusIs(
445 libtextclassifier3::StatusCode::INVALID_ARGUMENT,
446 HasSubstr(
447 "'emails' should be type or subtype of 'EmailMessageWithNote' "
448 "but actual value has type 'EmailMessage'")));
449 }
450
TEST_F(DocumentValidatorTest,ValidateNestedPropertyInvalid)451 TEST_F(DocumentValidatorTest, ValidateNestedPropertyInvalid) {
452 // Issues in nested DocumentProto should be detected
453 DocumentProto conversation =
454 SimpleConversationBuilder()
455 .ClearProperties()
456 .AddStringProperty(kPropertyName, kDefaultString)
457 .AddDocumentProperty(kPropertyEmails,
458 SimpleEmailBuilder()
459 .SetNamespace("")
460 .Build()) // Bad nested document
461 .Build();
462
463 EXPECT_THAT(document_validator_->Validate(conversation),
464 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
465 HasSubstr("'namespace' is empty")));
466 }
467
TEST_F(DocumentValidatorTest,HandleTypeConfigMapChangesOk)468 TEST_F(DocumentValidatorTest, HandleTypeConfigMapChangesOk) {
469 SchemaProto email_schema =
470 SchemaBuilder()
471 .AddType(SchemaTypeConfigBuilder()
472 .SetType(kTypeEmail)
473 .AddProperty(PropertyConfigBuilder()
474 .SetName(kPropertySubject)
475 .SetDataType(TYPE_STRING)
476 .SetCardinality(CARDINALITY_REQUIRED))
477 .AddProperty(PropertyConfigBuilder()
478 .SetName(kPropertyText)
479 .SetDataType(TYPE_STRING)
480 .SetCardinality(CARDINALITY_OPTIONAL))
481 .AddProperty(PropertyConfigBuilder()
482 .SetName(kPropertyRecipients)
483 .SetDataType(TYPE_STRING)
484 .SetCardinality(CARDINALITY_REPEATED)))
485 .Build();
486
487 // Create a custom directory so we don't collide
488 // with the test's preset schema in SetUp
489 const std::string custom_schema_dir = GetTestTempDir() + "/custom_schema";
490 filesystem_.DeleteDirectoryRecursively(custom_schema_dir.c_str());
491 filesystem_.CreateDirectoryRecursively(custom_schema_dir.c_str());
492
493 // Set a schema with only the 'Email' type
494 ICING_ASSERT_OK_AND_ASSIGN(
495 std::unique_ptr<SchemaStore> schema_store,
496 SchemaStore::Create(&filesystem_, custom_schema_dir, &fake_clock_,
497 feature_flags_.get()));
498 ASSERT_THAT(schema_store->SetSchema(
499 email_schema, /*ignore_errors_and_delete_documents=*/false,
500 /*allow_circular_schema_definitions=*/false),
501 IsOk());
502
503 DocumentValidator document_validator(schema_store.get());
504
505 DocumentProto conversation = SimpleConversationBuilder().Build();
506
507 // Schema doesn't know about the 'Conversation' type yet
508 EXPECT_THAT(document_validator.Validate(conversation),
509 StatusIs(libtextclassifier3::StatusCode::NOT_FOUND,
510 HasSubstr("'Conversation' not found")));
511
512 // Add the 'Conversation' type
513 SchemaProto email_and_conversation_schema =
514 SchemaBuilder(email_schema)
515 .AddType(SchemaTypeConfigBuilder()
516 .SetType(kTypeConversation)
517 .AddProperty(PropertyConfigBuilder()
518 .SetName(kPropertyName)
519 .SetDataType(TYPE_STRING)
520 .SetCardinality(CARDINALITY_REQUIRED))
521 .AddProperty(
522 PropertyConfigBuilder()
523 .SetName(kPropertyEmails)
524 .SetDataTypeDocument(
525 kTypeEmail, /*index_nested_properties=*/true)
526 .SetCardinality(CARDINALITY_REPEATED)))
527 .Build();
528
529 // DocumentValidator should be able to handle the SchemaStore getting updated
530 // separately
531 ASSERT_THAT(
532 schema_store->SetSchema(email_and_conversation_schema,
533 /*ignore_errors_and_delete_documents=*/false,
534 /*allow_circular_schema_definitions=*/false),
535 IsOk());
536
537 ICING_EXPECT_OK(document_validator.Validate(conversation));
538 }
539
TEST_F(DocumentValidatorTest,PositiveDocumentScoreOk)540 TEST_F(DocumentValidatorTest, PositiveDocumentScoreOk) {
541 DocumentProto email = SimpleEmailBuilder().SetScore(1).Build();
542 ICING_EXPECT_OK(document_validator_->Validate(email));
543
544 email = SimpleEmailBuilder()
545 .SetScore(std::numeric_limits<int32_t>::max())
546 .Build();
547 ICING_EXPECT_OK(document_validator_->Validate(email));
548 }
549
TEST_F(DocumentValidatorTest,NegativeDocumentScoreInvalid)550 TEST_F(DocumentValidatorTest, NegativeDocumentScoreInvalid) {
551 DocumentProto email = SimpleEmailBuilder().SetScore(-1).Build();
552 EXPECT_THAT(document_validator_->Validate(email),
553 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
554 HasSubstr("is negative")));
555
556 email = SimpleEmailBuilder()
557 .SetScore(std::numeric_limits<int32_t>::min())
558 .Build();
559 EXPECT_THAT(document_validator_->Validate(email),
560 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
561 HasSubstr("is negative")));
562 }
563
TEST_F(DocumentValidatorTest,PositiveDocumentCreationTimestampMsOk)564 TEST_F(DocumentValidatorTest, PositiveDocumentCreationTimestampMsOk) {
565 DocumentProto email = SimpleEmailBuilder().SetCreationTimestampMs(1).Build();
566 ICING_EXPECT_OK(document_validator_->Validate(email));
567
568 email = SimpleEmailBuilder()
569 .SetCreationTimestampMs(std::numeric_limits<int32_t>::max())
570 .Build();
571 ICING_EXPECT_OK(document_validator_->Validate(email));
572 }
573
TEST_F(DocumentValidatorTest,ZeroDocumentCreationTimestampMsOk)574 TEST_F(DocumentValidatorTest, ZeroDocumentCreationTimestampMsOk) {
575 DocumentProto email = SimpleEmailBuilder().SetCreationTimestampMs(0).Build();
576 ICING_EXPECT_OK(document_validator_->Validate(email));
577 }
578
TEST_F(DocumentValidatorTest,NegativeDocumentCreationTimestampMsInvalid)579 TEST_F(DocumentValidatorTest, NegativeDocumentCreationTimestampMsInvalid) {
580 DocumentProto email = SimpleEmailBuilder().SetCreationTimestampMs(-1).Build();
581 EXPECT_THAT(document_validator_->Validate(email),
582 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
583 HasSubstr("is negative")));
584
585 email = SimpleEmailBuilder()
586 .SetCreationTimestampMs(std::numeric_limits<int32_t>::min())
587 .Build();
588 EXPECT_THAT(document_validator_->Validate(email),
589 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
590 HasSubstr("is negative")));
591 }
592
TEST_F(DocumentValidatorTest,PositiveDocumentTtlMsOk)593 TEST_F(DocumentValidatorTest, PositiveDocumentTtlMsOk) {
594 DocumentProto email = SimpleEmailBuilder().SetTtlMs(1).Build();
595 ICING_EXPECT_OK(document_validator_->Validate(email));
596
597 email = SimpleEmailBuilder()
598 .SetTtlMs(std::numeric_limits<int32_t>::max())
599 .Build();
600 ICING_EXPECT_OK(document_validator_->Validate(email));
601 }
602
TEST_F(DocumentValidatorTest,ZeroDocumentTtlMsOk)603 TEST_F(DocumentValidatorTest, ZeroDocumentTtlMsOk) {
604 DocumentProto email = SimpleEmailBuilder().SetTtlMs(0).Build();
605 ICING_EXPECT_OK(document_validator_->Validate(email));
606 }
607
TEST_F(DocumentValidatorTest,NegativeDocumentTtlMsInvalid)608 TEST_F(DocumentValidatorTest, NegativeDocumentTtlMsInvalid) {
609 DocumentProto email = SimpleEmailBuilder().SetTtlMs(-1).Build();
610 EXPECT_THAT(document_validator_->Validate(email),
611 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
612 HasSubstr("is negative")));
613
614 email = SimpleEmailBuilder()
615 .SetTtlMs(std::numeric_limits<int32_t>::min())
616 .Build();
617 EXPECT_THAT(document_validator_->Validate(email),
618 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
619 HasSubstr("is negative")));
620 }
621
TEST_F(DocumentValidatorTest,ValidateEmbeddingZeroDimensionInvalid)622 TEST_F(DocumentValidatorTest, ValidateEmbeddingZeroDimensionInvalid) {
623 PropertyProto::VectorProto vector;
624 vector.set_model_signature("my_model");
625 DocumentProto email =
626 DocumentBuilder()
627 .SetKey(kDefaultNamespace, "email_with_note/1")
628 .SetSchema(kTypeEmailWithNote)
629 .AddStringProperty(kPropertySubject, kDefaultString)
630 .AddStringProperty(kPropertyText, kDefaultString)
631 .AddStringProperty(kPropertyRecipients, kDefaultString,
632 kDefaultString, kDefaultString)
633 .AddStringProperty(kPropertyNote, kDefaultString)
634 .AddVectorProperty(kPropertyNoteEmbedding, vector)
635 .Build();
636 EXPECT_THAT(document_validator_->Validate(email),
637 StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT,
638 HasSubstr("contains empty vectors")));
639 }
640
TEST_F(DocumentValidatorTest,ValidateEmbeddingEmptySignatureOk)641 TEST_F(DocumentValidatorTest, ValidateEmbeddingEmptySignatureOk) {
642 PropertyProto::VectorProto vector;
643 vector.add_values(0.1);
644 vector.add_values(0.2);
645 vector.add_values(0.3);
646 vector.set_model_signature("");
647 DocumentProto email =
648 DocumentBuilder()
649 .SetKey(kDefaultNamespace, "email_with_note/1")
650 .SetSchema(kTypeEmailWithNote)
651 .AddStringProperty(kPropertySubject, kDefaultString)
652 .AddStringProperty(kPropertyText, kDefaultString)
653 .AddStringProperty(kPropertyRecipients, kDefaultString,
654 kDefaultString, kDefaultString)
655 .AddStringProperty(kPropertyNote, kDefaultString)
656 .AddVectorProperty(kPropertyNoteEmbedding, vector)
657 .Build();
658 ICING_EXPECT_OK(document_validator_->Validate(email));
659 }
660
TEST_F(DocumentValidatorTest,ValidateEmbeddingNoSignatureOk)661 TEST_F(DocumentValidatorTest, ValidateEmbeddingNoSignatureOk) {
662 PropertyProto::VectorProto vector;
663 vector.add_values(0.1);
664 vector.add_values(0.2);
665 vector.add_values(0.3);
666 DocumentProto email =
667 DocumentBuilder()
668 .SetKey(kDefaultNamespace, "email_with_note/1")
669 .SetSchema(kTypeEmailWithNote)
670 .AddStringProperty(kPropertySubject, kDefaultString)
671 .AddStringProperty(kPropertyText, kDefaultString)
672 .AddStringProperty(kPropertyRecipients, kDefaultString,
673 kDefaultString, kDefaultString)
674 .AddStringProperty(kPropertyNote, kDefaultString)
675 .AddVectorProperty(kPropertyNoteEmbedding, vector)
676 .Build();
677 ICING_EXPECT_OK(document_validator_->Validate(email));
678 }
679
680 } // namespace
681
682 } // namespace lib
683 } // namespace icing
684