xref: /aosp_15_r20/external/icing/icing/util/document-validator_test.cc (revision 8b6cd535a057e39b3b86660c4aa06c99747c2136)
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