/*
* Copyright 2016 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mongodb.morphia;
import com.mongodb.MongoCommandException;
import com.mongodb.MongoWriteException;
import com.mongodb.WriteConcernException;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.ValidationAction;
import com.mongodb.client.model.ValidationLevel;
import com.mongodb.client.model.ValidationOptions;
import org.bson.Document;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mongodb.morphia.annotations.Validation;
import org.mongodb.morphia.entities.DocumentValidation;
import org.mongodb.morphia.mapping.MappedClass;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.UpdateOperations;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TestDocumentValidation extends TestBase {
@Before
public void versionCheck() {
checkMinServerVersion(3.2);
}
@Test
public void createValidation() {
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
assertEquals(Document.parse(DocumentValidation.class.getAnnotation(Validation.class).value()), getValidator());
try {
getDs().save(new DocumentValidation("John", 1, new Date()));
fail("Document should have failed validation");
} catch (WriteConcernException e) {
assertTrue(e.getMessage().contains("Document failed validation"));
}
getDs().save(new DocumentValidation("Harold", 100, new Date()));
}
@Test
public void overwriteValidation() {
Document validator = Document.parse("{ jelly : { $ne : 'rhubarb' } }");
MongoDatabase database = addValidation(validator, "validation");
assertEquals(validator, getValidator());
Document rhubarb = new Document("jelly", "rhubarb").append("number", 20);
database.getCollection("validation").insertOne(new Document("jelly", "grape"));
try {
database.getCollection("validation").insertOne(rhubarb);
fail("Document should have failed validation");
} catch (MongoWriteException e) {
assertTrue(e.getMessage().contains("Document failed validation"));
}
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
assertEquals(Document.parse(DocumentValidation.class.getAnnotation(Validation.class).value()), getValidator());
try {
database.getCollection("validation").insertOne(rhubarb);
} catch (MongoWriteException e) {
assertFalse(e.getMessage().contains("Document failed validation"));
}
try {
getDs().save(new DocumentValidation("John", 1, new Date()));
fail("Document should have failed validation");
} catch (WriteConcernException e) {
assertTrue(e.getMessage().contains("Document failed validation"));
}
}
private MongoDatabase addValidation(final Document validator, final String collectionName) {
ValidationOptions options = new ValidationOptions()
.validator(validator)
.validationLevel(ValidationLevel.MODERATE)
.validationAction(ValidationAction.ERROR);
MongoDatabase database = getMongoClient().getDatabase(TEST_DB_NAME);
database.getCollection(collectionName).drop();
database.createCollection(collectionName, new CreateCollectionOptions().validationOptions(options));
return database;
}
@Test
public void validationDocuments() {
Document validator = Document.parse("{ jelly : { $ne : 'rhubarb' } }");
getMorphia().map(DocumentValidation.class);
MappedClass mappedClass = getMorphia().getMapper().getMappedClass(DocumentValidation.class);
for (ValidationLevel level : EnumSet.allOf(ValidationLevel.class)) {
for (ValidationAction action : EnumSet.allOf(ValidationAction.class)) {
checkValidation(validator, mappedClass, level, action);
}
}
}
@Test
public void findAndModify() {
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
getDs().save(new DocumentValidation("Harold", 100, new Date()));
Query<DocumentValidation> query = getDs().find(DocumentValidation.class);
UpdateOperations<DocumentValidation> updates = getDs().createUpdateOperations(DocumentValidation.class)
.set("number", 5);
FindAndModifyOptions options = new FindAndModifyOptions()
.bypassDocumentValidation(false);
try {
getDs().findAndModify(query, updates, options);
fail("Document validation should have complained");
} catch (MongoCommandException e) {
// expected
}
options.bypassDocumentValidation(true);
getDs().findAndModify(query, updates, options);
Assert.assertNotNull(query.field("number").equal(5).get());
}
@Test
public void update() {
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
getDs().save(new DocumentValidation("Harold", 100, new Date()));
Query<DocumentValidation> query = getDs().find(DocumentValidation.class);
UpdateOperations<DocumentValidation> updates = getDs().createUpdateOperations(DocumentValidation.class)
.set("number", 5);
UpdateOptions options = new UpdateOptions()
.bypassDocumentValidation(false);
try {
getDs().update(query, updates, options);
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
options.bypassDocumentValidation(true);
getDs().update(query, updates, options);
Assert.assertNotNull(query.field("number").equal(5).get());
}
@Test
public void save() {
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
try {
getDs().save(new DocumentValidation("Harold", 8, new Date()));
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
getDs().save(new DocumentValidation("Harold", 8, new Date()), new InsertOptions()
.bypassDocumentValidation(true));
Query<DocumentValidation> query = getDs().find(DocumentValidation.class)
.field("number").equal(8);
Assert.assertNotNull(query.get());
List<DocumentValidation> list = asList(new DocumentValidation("Harold", 8, new Date()),
new DocumentValidation("Harold", 8, new Date()),
new DocumentValidation("Harold", 8, new Date()),
new DocumentValidation("Harold", 8, new Date()),
new DocumentValidation("Harold", 8, new Date()));
try {
getDs().save(list);
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
getDs().save(list, new InsertOptions().bypassDocumentValidation(true));
Assert.assertFalse(query.field("number").equal(8).asList().isEmpty());
}
@Test
public void saveToNewCollection() {
getMorphia().map(DocumentValidation.class);
final Document validator = Document.parse("{ number : { $gt : 10 } }");
String collection = "newdocs";
addValidation(validator, collection);
try {
getAds().save(collection, new DocumentValidation("Harold", 8, new Date()));
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
getAds().save(collection, new DocumentValidation("Harold", 8, new Date()), new InsertOptions()
.bypassDocumentValidation(true));
Query<DocumentValidation> query = getAds().createQuery(collection, DocumentValidation.class)
.field("number").equal(8);
Assert.assertNotNull(query.get());
}
@Test
public void insert() {
getMorphia().map(DocumentValidation.class);
getDs().enableDocumentValidation();
try {
getAds().insert(new DocumentValidation("Harold", 8, new Date()));
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
getAds().insert(new DocumentValidation("Harold", 8, new Date()), new InsertOptions()
.bypassDocumentValidation(true));
Query<DocumentValidation> query = getDs().find(DocumentValidation.class)
.field("number").equal(8);
Assert.assertNotNull(query.get());
List<DocumentValidation> list = asList(new DocumentValidation("Harold", 8, new Date()),
new DocumentValidation("John", 8, new Date()),
new DocumentValidation("Sarah", 8, new Date()),
new DocumentValidation("Amy", 8, new Date()),
new DocumentValidation("James", 8, new Date()));
try {
getAds().insert(list);
fail("Document validation should have complained");
} catch (WriteConcernException e) {
// expected
}
getAds().insert(list, new InsertOptions()
.bypassDocumentValidation(true));
Assert.assertFalse(query.field("number").equal(8).asList().isEmpty());
}
private void checkValidation(final Document validator, final MappedClass mappedClass, final ValidationLevel level,
final ValidationAction action) {
updateValidation(mappedClass, level, action);
Document expected = new Document("validator", validator)
.append("validationLevel", level.getValue())
.append("validationAction", action.getValue());
Document validation = getValidation();
for (String key : expected.keySet()) {
assertEquals(expected.get(key), validation.get(key));
}
}
@SuppressWarnings("unchecked")
private Document getValidation() {
Document document = getMongoClient().getDatabase(TEST_DB_NAME)
.runCommand(new Document("listCollections", 1)
.append("filter", new Document("name", "validation")));
List<Document> firstBatch = (List<Document>) ((Document) document.get("cursor")).get("firstBatch");
return (Document) firstBatch.get(0).get("options");
}
@SuppressWarnings("unchecked")
private Document getValidator() {
return (Document) getValidation().get("validator");
}
@SuppressWarnings("deprecation")
private void updateValidation(final MappedClass mappedClass, final ValidationLevel level, final ValidationAction action) {
((DatastoreImpl) getDs()).process(mappedClass, new ValidationBuilder().value("{ jelly : { $ne : 'rhubarb' } }")
.level(level)
.action(action));
}
}