Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions ANALYSIS.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,26 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
}

Object viewOn = params.get("viewOn");
if (viewOn == null || (viewOn instanceof String && ((String) viewOn).trim().isEmpty())) {
if (viewOn == null) {
errors.add(new TemplatePayloadValidationError("parameters.viewOn",
"CreateView operation requires 'viewOn' parameter"));
} else if (!(viewOn instanceof String)) {
errors.add(new TemplatePayloadValidationError("parameters.viewOn",
"'viewOn' must be a string"));
} else {
String viewOnStr = ((String) viewOn).trim();
if (viewOnStr.isEmpty()) {
errors.add(new TemplatePayloadValidationError("parameters.viewOn",
"'viewOn' cannot be empty"));
}
if (viewOnStr.contains("$")) {
errors.add(new TemplatePayloadValidationError("parameters.viewOn",
"'viewOn' collection name cannot contain '$': " + viewOnStr));
}
if (viewOnStr.contains("\0")) {
errors.add(new TemplatePayloadValidationError("parameters.viewOn",
"'viewOn' collection name cannot contain null character"));
}
}

Object pipeline = params.get("pipeline");
Expand All @@ -53,6 +70,9 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
} else if (!(pipeline instanceof List)) {
errors.add(new TemplatePayloadValidationError("parameters.pipeline",
"'pipeline' must be a list"));
} else {
errors.addAll(OperationValidator.checkListElementTypes(
(List<?>) pipeline, "parameters.pipeline", "Pipeline stage"));
}

Object options = params.get("options");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
"DropIndex operation requires either 'indexName' or 'keys' parameter"));
}

if (indexName != null && keys != null) {
errors.add(new TemplatePayloadValidationError("parameters",
"DropIndex operation requires either 'indexName' or 'keys', not both"));
}

if (indexName != null && !(indexName instanceof String)) {
errors.add(new TemplatePayloadValidationError("parameters.indexName",
"'indexName' must be a string"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
"'documents' cannot be empty"));
}

for (int i = 0; i < docList.size(); i++) {
if (docList.get(i) == null) {
errors.add(new TemplatePayloadValidationError("parameters.documents[" + i + "]",
"Document at index " + i + " is null"));
}
}
errors.addAll(OperationValidator.checkListElementTypes(docList, "parameters.documents", "Document"));

Object options = params.get("options");
if (options != null && !(options instanceof Map)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,22 @@ static List<TemplatePayloadValidationError> checkUnrecognizedKeys(
}
return errors;
}

static List<TemplatePayloadValidationError> checkListElementTypes(
List<?> list, String fieldPath, String elementName) {
List<TemplatePayloadValidationError> errors = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Object item = list.get(i);
if (item == null) {
errors.add(new TemplatePayloadValidationError(
fieldPath + "[" + i + "]",
elementName + " at index " + i + " is null"));
} else if (!(item instanceof Map)) {
errors.add(new TemplatePayloadValidationError(
fieldPath + "[" + i + "]",
elementName + " at index " + i + " must be a document (key-value map)"));
}
}
return errors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
"'update' must be a document"));
}

Object multi = params.get("multi");
if (multi != null && !(multi instanceof Boolean)) {
errors.add(new TemplatePayloadValidationError("parameters.multi",
"'multi' must be a boolean"));
}

Object options = params.get("options");
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,42 @@ void insertNullDocumentElementTest() {
assertTrue(errors.get(0).getMessage().contains("index 0 is null"));
}

@Test
@DisplayName("WHEN insert document item is not a map THEN validation fails")
void insertDocumentNotMapTest() {
MongoOperation op = new MongoOperation();
op.setType("insert");
op.setCollection("test");
Map<String, Object> params = new HashMap<>();
params.put("documents", Arrays.asList("hello"));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.documents[0]", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("must be a document"));
}

@Test
@DisplayName("WHEN insert documents have mixed types THEN only non-map items fail")
void insertDocumentMixedTypesTest() {
MongoOperation op = new MongoOperation();
op.setType("insert");
op.setCollection("test");
Map<String, Object> params = new HashMap<>();
Map<String, Object> validDoc = new HashMap<>();
validDoc.put("name", "ok");
params.put("documents", Arrays.asList(validDoc, 123));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.documents[1]", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("must be a document"));
}

@Test
@DisplayName("WHEN insert has documents as wrong type THEN validation fails")
void insertDocumentsWrongTypeTest() {
Expand Down Expand Up @@ -373,6 +409,27 @@ void updateOptionsWrongTypeTest() {
assertTrue(errors.get(0).getMessage().contains("must be a document"));
}

@Test
@DisplayName("WHEN update multi is wrong type THEN validation fails")
void updateMultiWrongTypeTest() {
MongoOperation op = new MongoOperation();
op.setType("update");
op.setCollection("test");
Map<String, Object> params = new HashMap<>();
params.put("filter", new HashMap<>());
Map<String, Object> update = new HashMap<>();
update.put("$set", new HashMap<>());
params.put("update", update);
params.put("multi", "yes");
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.multi", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("must be a boolean"));
}

@Test
@DisplayName("WHEN update missing both filter and update THEN both errors reported")
void updateMissingBothTest() {
Expand Down Expand Up @@ -654,6 +711,26 @@ void dropIndexIndexNameWrongTypeTest() {
assertTrue(errors.get(0).getMessage().contains("must be a string"));
}

@Test
@DisplayName("WHEN dropIndex has both indexName and keys THEN validation fails")
void dropIndexBothIndexNameAndKeysTest() {
MongoOperation op = new MongoOperation();
op.setType("dropIndex");
op.setCollection("test");
Map<String, Object> params = new HashMap<>();
params.put("indexName", "idx");
Map<String, Object> keys = new HashMap<>();
keys.put("field", 1);
params.put("keys", keys);
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("not both"));
}

@Test
@DisplayName("WHEN dropIndex has indexName THEN validation passes")
void dropIndexWithIndexNameTest() {
Expand Down Expand Up @@ -862,6 +939,78 @@ void createViewPipelineWrongTypeTest() {
assertTrue(errors.get(0).getMessage().contains("must be a list"));
}

@Test
@DisplayName("WHEN createView pipeline stage is not a map THEN validation fails")
void createViewPipelineStageNotMapTest() {
MongoOperation op = new MongoOperation();
op.setType("createView");
op.setCollection("testView");
Map<String, Object> params = new HashMap<>();
params.put("viewOn", "sourceCollection");
params.put("pipeline", Arrays.asList("invalid"));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.pipeline[0]", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("must be a document"));
}

@Test
@DisplayName("WHEN createView viewOn contains $ THEN validation fails")
void createViewViewOnContainsDollarTest() {
MongoOperation op = new MongoOperation();
op.setType("createView");
op.setCollection("testView");
Map<String, Object> params = new HashMap<>();
params.put("viewOn", "$cmd");
params.put("pipeline", Collections.singletonList(new HashMap<>()));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.viewOn", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("$"));
}

@Test
@DisplayName("WHEN createView viewOn contains null char THEN validation fails")
void createViewViewOnContainsNullCharTest() {
MongoOperation op = new MongoOperation();
op.setType("createView");
op.setCollection("testView");
Map<String, Object> params = new HashMap<>();
params.put("viewOn", "test\0collection");
params.put("pipeline", Collections.singletonList(new HashMap<>()));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.viewOn", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("null character"));
}

@Test
@DisplayName("WHEN createView viewOn is wrong type THEN validation fails")
void createViewViewOnWrongTypeTest() {
MongoOperation op = new MongoOperation();
op.setType("createView");
op.setCollection("testView");
Map<String, Object> params = new HashMap<>();
params.put("viewOn", 123);
params.put("pipeline", Collections.singletonList(new HashMap<>()));
op.setParameters(params);

List<TemplatePayloadValidationError> errors = op.validate();

assertEquals(1, errors.size());
assertEquals("parameters.viewOn", errors.get(0).getField());
assertTrue(errors.get(0).getMessage().contains("must be a string"));
}

@Test
@DisplayName("WHEN createView options is wrong type THEN validation fails")
void createViewOptionsWrongTypeTest() {
Expand Down
Loading