Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add negative tests for constraint mapper #1559

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class Constants {
public static final String MEDIA_TYPE = "mediaType";
public static final String TUPLE = "tuple";
public static final String REGEX_INTERPOLATION_PATTERN = "^(?!.*\\$\\{).+$";
public static final String DATE_CONSTRAINT_ANNOTATION = "constraint:Date";

/**
* Enum to select the Ballerina Type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ public enum DiagnosticMessages {
OAS_CONVERTOR_118("OAS_CONVERTOR_118", "Generated OpenAPI definition does not contain variable " +
"assignment '%s' in constraint validation.", DiagnosticSeverity.WARNING),
OAS_CONVERTOR_119("OAS_CONVERTOR_119", "Given REGEX pattern '%s' is not supported by the OpenAPI " +
"tool, it may also not support interpolation within the REGEX pattern.", DiagnosticSeverity.WARNING);
"tool, it may also not support interpolation within the REGEX pattern.", DiagnosticSeverity.WARNING),
OAS_CONVERTOR_120("OAS_CONVERTER_120", "Ballerina Date constraints might not be reflected in the " +
"OpenAPI definition", DiagnosticSeverity.WARNING);

private final String code;
private final String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import static io.ballerina.openapi.converter.Constants.HTTP;
import static io.ballerina.openapi.converter.Constants.HTTP_CODES;
import static io.ballerina.openapi.converter.Constants.REGEX_INTERPOLATION_PATTERN;
import static io.ballerina.openapi.converter.Constants.DATE_CONSTRAINT_ANNOTATION;

/**
* This util class for processing the mapping in between ballerina record and openAPI object schema.
Expand Down Expand Up @@ -832,7 +833,6 @@ private void setConstraintValueToSchema(ConstraintAnnotation constraintAnnot, Sc
}
}


/**
* This util is used to extract the annotation values in `@constraint` and store it in builder.
*/
Expand All @@ -843,6 +843,13 @@ private void extractedConstraintAnnotation(MetadataNode metadata,
.filter(this::isConstraintAnnotation)
.filter(annotation -> annotation.annotValue().isPresent())
.forEach(annotation -> {
if (isDateConstraint(annotation)) {
DiagnosticMessages errorMsg = DiagnosticMessages.OAS_CONVERTOR_120;
IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic(errorMsg,
annotation.location(), annotation.toString());
diagnostics.add(error);
return;
}
MappingConstructorExpressionNode annotationValue = annotation.annotValue().get();
annotationValue.fields().stream()
.filter(field -> SyntaxKind.SPECIFIC_FIELD.equals(field.kind()))
Expand All @@ -863,6 +870,16 @@ private boolean isConstraintAnnotation(AnnotationNode annotation) {
return false;
}

/*
* This util is used to check whether an annotation is a constraint:Date annotation.
* Currently, we don't have support for mapping Date constraints to OAS hence we skip them.
* {@link <a href="https://github.com/ballerina-platform/ballerina-standard-library/issues/5049">...</a>}
* Once the above improvement is completed this method should be removed!
*/
private boolean isDateConstraint(AnnotationNode annotation) {
return annotation.annotReference().toString().trim().equals(DATE_CONSTRAINT_ANNOTATION);
}

/**
* This util is used to process the content of a constraint annotation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@

import io.ballerina.openapi.cmd.OASContractGenerator;
import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic;
import io.ballerina.projects.DiagnosticResult;
import io.ballerina.projects.Project;
import io.ballerina.projects.ProjectException;
import io.ballerina.projects.directory.ProjectLoader;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;
import org.testng.Assert;
import org.testng.annotations.Test;

Expand All @@ -28,6 +33,8 @@
import java.util.Arrays;
import java.util.List;

import static io.ballerina.openapi.generators.openapi.TestUtils.getCompilation;

/**
* This test class for the covering the negative tests for constraint
* {@link io.ballerina.openapi.converter.service.ConstraintAnnotation} scenarios.
Expand All @@ -37,7 +44,7 @@ public class NegativeConstraintTests {
private static final Path RES_DIR = Paths.get("src/test/resources/ballerina-to-openapi").toAbsolutePath();

@Test(description = "When the string constraint has incompatible REGEX patterns with OAS")
public void testInvalidRegexPatterns() throws IOException {
public void testInterpolationInRegexPatterns() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint-negative/negative_patternInterpolation.bal");
List<OpenAPIConverterDiagnostic> errors = TestUtils.compareWithGeneratedFile(new OASContractGenerator(),
ballerinaFilePath, "constraint-negative/negative_patternInterpolation.yaml");
Expand All @@ -49,4 +56,64 @@ public void testInvalidRegexPatterns() throws IOException {
"REGEX pattern.");
}
}

@Test(description = "When a constraint has values referenced with variables")
public void testConstNameRef() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint-negative/negative_constNameRef.bal");
List<OpenAPIConverterDiagnostic> errors = TestUtils.compareWithGeneratedFile(new OASContractGenerator(),
ballerinaFilePath, "constraint-negative/negative_constNameRef.yaml");
List<String> expectedVariables = Arrays.asList("maxVal", "5 + minVal", "Value");
for (int i = 0; i < errors.size(); i++) {
Assert.assertEquals(errors.get(i).getMessage(), "Generated OpenAPI definition does not contain" +
" variable assignment '" + expectedVariables.get(i) + "' in constraint validation.");
}
}

/*
* This test is used to check whether it gives warnings when '@constraint:Date' is being used.
* Currently, we don't have support for mapping Date constraints to OAS hence we skip them.
* TODO: <a href="https://github.com/ballerina-platform/ballerina-standard-library/issues/5049">...</a>
* Once the above improvement is completed this Negative test should be removed and should add as a Unit test!
*/
@Test(description = "when the record field has time:Date record type")
public void testDateType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint-negative/negative_date.bal");
List<OpenAPIConverterDiagnostic> errors = TestUtils.compareWithGeneratedFile(new OASContractGenerator(),
ballerinaFilePath, "constraint-negative/negative_date.yaml");
errors.forEach(error -> Assert.assertEquals(error.getMessage(), "Ballerina Date constraints might " +
"not be reflected in the OpenAPI definition"));
}

/*
* This test is used to check whether it gives compilation errors when invalid REGEX patterns are given.
* To get more context go through the referenced links.
* By fixing below Bug will make this test fail.
* {@link <a href="https://github.com/ballerina-platform/ballerina-lang/issues/41492">...</a>}
* TODO: <a href="https://github.com/ballerina-platform/ballerina-standard-library/issues/5048">...</a>
*/
@Test(description = "When String constraint has compilation errors (REGEX pattern with invalid format)")
public void testInvalidRegexPattern() throws ProjectException {
Path ballerinaFilePath = RES_DIR.resolve("constraint-negative/invalidRegexPattern.bal");
Project project = ProjectLoader.loadProject(ballerinaFilePath);
DiagnosticResult diagnostic = getCompilation(project);
Object[] errors = diagnostic.diagnostics().stream().filter(d ->
DiagnosticSeverity.ERROR == d.diagnosticInfo().severity()).toArray();
Assert.assertEquals(errors.length, 2);
Assert.assertTrue(errors[0].toString().contains("ERROR [invalidRegexPattern.bal:(21:20,21:20)] " +
"invalid char after backslash"));
Assert.assertTrue(errors[1].toString().contains("ERROR [invalidRegexPattern.bal:(21:22,21:22)] " +
"missing backslash"));
}

@Test(description = "When Integer constraint has float value which is invalid")
public void testInvalidInteger() throws ProjectException {
Path ballerinaFilePath = RES_DIR.resolve("constraint-negative/invalidInteger.bal");
Project project = ProjectLoader.loadProject(ballerinaFilePath);
DiagnosticResult diagnostic = getCompilation(project);
Object[] errors = diagnostic.diagnostics().stream().filter(d ->
DiagnosticSeverity.ERROR == d.diagnosticInfo().severity()).toArray();
Assert.assertEquals(errors.length, 1);
Assert.assertTrue(errors[0].toString().contains("incompatible types: expected '(int|record {| int value; " +
"string message; |})?', found 'float'"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import io.ballerina.openapi.cmd.OASContractGenerator;
import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic;
import io.ballerina.projects.DiagnosticResult;
import io.ballerina.projects.Package;
import io.ballerina.projects.Project;
import org.testng.Assert;

import java.io.File;
Expand Down Expand Up @@ -84,6 +87,11 @@ public static List<OpenAPIConverterDiagnostic> compareWithGeneratedFile(OASContr
}
}

public static DiagnosticResult getCompilation(Project project) {
Package cPackage = project.currentPackage();
return cPackage.getCompilation().diagnosticResult();
}

public static void deleteDirectory(Path path) {
if (!Files.exists(path)) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org).
//
// WSO2 LLC. licenses this file to you 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.

import ballerina/http;
import ballerina/constraint;

@constraint:Int{
minValueExclusive: 10.5
}
public type Distance int;

public type City record{
Distance distance;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload City body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org).
//
// WSO2 LLC. licenses this file to you 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.

import ballerina/http;
import ballerina/constraint;

@constraint:String {
pattern: re `^\${1,2}[a-z]+$`
}
public type Position string;

public type Child record {
Position position;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload Child body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org).
//
// WSO2 LLC. licenses this file to you 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.

import ballerina/http;
import ballerina/constraint;

int maxVal = 100;
int minVal = 5;
const float Value = 10.5;
@constraint:Int {
maxValueExclusive: maxVal,
minValue: {
value: 5 + minVal,
message: "Min value exceeded!"
}
}
public type St_ID int;

public type StudentRecord record {
St_ID id;
@constraint:Float { minValue: Value}
float num?;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload StudentRecord body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org).
//
// WSO2 LLC. licenses this file to you 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.

import ballerina/http;
import ballerina/constraint;
import ballerina/time;

@constraint:Date {
option: constraint:FUTURE,
message: "Event date cannot be past!"
}
public type MyDate time:Date;

type RegDate record {
MyDate date;
@constraint:Date {
option: {
value: constraint:PAST,
message: "Last login should be past!"
},
message: "Invalid date found for last login!"
}
time:Date lastLogin?;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(RegDate body) returns error? {
return;
}
}
Loading
Loading