Skip to content

Commit dde662d

Browse files
committed
updates with tests
1 parent 6c70ef0 commit dde662d

15 files changed

Lines changed: 371 additions & 83 deletions

File tree

cddjava/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@
4141
<version>4.11</version>
4242
<scope>test</scope>
4343
</dependency>
44+
<dependency>
45+
<groupId>com.squareup.okhttp3</groupId>
46+
<artifactId>okhttp</artifactId>
47+
<version>5.0.0-alpha.10</version>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>com.google.code.gson</groupId>
52+
<artifactId>gson</artifactId>
53+
<version>2.9.0</version>
54+
<scope>test</scope>
55+
</dependency>
56+
<dependency>
57+
<groupId>com.github.javafaker</groupId>
58+
<artifactId>javafaker</artifactId>
59+
<version>1.0.2</version>
60+
</dependency>
4461
</dependencies>
4562

4663
</project>

cddjava/src/main/java/org/offscale/Create.java

Lines changed: 132 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
package org.offscale;
22

3-
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
4-
import com.github.javaparser.ast.body.FieldDeclaration;
5-
import com.github.javaparser.ast.body.MethodDeclaration;
3+
import com.github.javafaker.Faker;
4+
import com.github.javaparser.ast.Modifier;
5+
import com.github.javaparser.ast.body.*;
6+
import com.github.javaparser.ast.expr.MethodCallExpr;
67
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
7-
import com.google.common.annotations.VisibleForTesting;
8+
import com.github.javaparser.ast.stmt.BlockStmt;
89
import com.google.common.collect.ImmutableMap;
910
import com.google.common.collect.Lists;
1011
import org.json.JSONArray;
1112
import org.json.JSONObject;
12-
import org.yaml.snakeyaml.Yaml;
13-
import java.io.InputStream;
13+
1414
import java.util.ArrayList;
1515
import java.util.HashMap;
1616
import java.util.List;
17-
import java.util.Map;
17+
import java.util.Optional;
1818
import java.util.regex.Matcher;
1919
import java.util.regex.Pattern;
2020

@@ -24,27 +24,27 @@
2424
*/
2525
public class Create {
2626
private final JSONObject jo;
27+
Faker faker = new Faker();
2728
private static final ImmutableMap<String, String> OPEN_API_TO_JAVA = Utils.getOpenAPIToJavaTypes();
28-
29-
private class Parameter {
29+
private static final String GET_METHOD_METHOD_NAME = "run";
30+
private class Schema {
3031
private String type;
3132
private String name;
32-
3333
private String description;
34-
3534
private String strictType;
3635

37-
public Parameter (String type) {
36+
public Schema (final String type) {
3837
this.type = type;
3938
}
4039

41-
public Parameter(String type, String strictType) {
40+
public Schema(String type, String strictType) {
4241
this.type = type;
4342
this.strictType = strictType;
4443
}
4544

46-
public void setName(String name) {
45+
public Schema setName(String name) {
4746
this.name = name;
47+
return this;
4848
}
4949

5050
public void setType(String type) {
@@ -68,7 +68,7 @@ public ImmutableMap<String, String> generateComponents() {
6868
HashMap<String, String> generatedComponents = new HashMap<>();
6969
JSONObject joSchemas = jo.getJSONObject("components").getJSONObject("schemas");
7070
List<String> schemas = Lists.newArrayList(joSchemas.keys());
71-
schemas.forEach((schema) -> generatedComponents.put(schema, generateComponent(joSchemas.getJSONObject(schema), schema)));
71+
schemas.forEach((schema) -> generatedComponents.put(schema, generateComponent(joSchemas.getJSONObject(schema), schema).toString()));
7272
return ImmutableMap.copyOf(generatedComponents);
7373
}
7474

@@ -78,35 +78,116 @@ public ImmutableMap<String, String> generateComponents() {
7878
* @param componentName name of the component to generate.
7979
* @return a String containing the generated code for a component.
8080
*/
81-
private String generateComponent(JSONObject joComponent, String componentName) {
81+
private ClassOrInterfaceDeclaration generateComponent(JSONObject joComponent, String componentName) {
8282
JSONObject joProperties = joComponent.getJSONObject("properties");
8383
List<String> properties = Lists.newArrayList(joProperties.keys());
8484
ClassOrInterfaceDeclaration myComponent = new ClassOrInterfaceDeclaration();
8585

8686
myComponent.setName(componentName);
8787
properties.forEach((property) -> {
88-
Parameter parameter = parseSchema(joProperties.getJSONObject(property));
89-
FieldDeclaration field = myComponent.addField(parameter.type, property);
90-
field.setJavadocComment("Type of " + parameter.strictType);
88+
Schema parameter = parseSchema(joProperties.getJSONObject(property));
89+
if (parameter.type.equals("Object")) {
90+
myComponent.addMember(generateComponent(joProperties.getJSONObject(property), Utils.capitalizeFirstLetter(property)));
91+
FieldDeclaration field = myComponent.addField(Utils.capitalizeFirstLetter(property), property);
92+
} else {
93+
FieldDeclaration field = myComponent.addField(parameter.type, property);
94+
field.setJavadocComment("Type of " + parameter.strictType);
95+
}
9196
});
92-
return myComponent.toString();
97+
return myComponent;
9398
}
9499

95100
/**
96101
* @return Routes interface containing routes from OpenAPI Spec
97102
*/
98-
public String generateRoutes() {
103+
public ImmutableMap<String, String> generateRoutesAndTests() {
104+
HashMap<String, String> routesAndTests = new HashMap<>();
99105
JSONObject joPaths = jo.getJSONObject("paths");
100106
List<String> paths = Lists.newArrayList(joPaths.keys());
101-
ClassOrInterfaceDeclaration myInterface = new ClassOrInterfaceDeclaration();
102-
myInterface.setInterface(true);
103-
myInterface.setName("Routes");
107+
ClassOrInterfaceDeclaration routesInterface = new ClassOrInterfaceDeclaration()
108+
.setInterface(true).setName("Routes");
109+
ClassOrInterfaceDeclaration testsClass = new ClassOrInterfaceDeclaration().setName("Tests");
110+
testsClass.addField("OkHttpClient", "client",Modifier.Keyword.PRIVATE, Modifier.Keyword.FINAL).getVariable(0).setInitializer("new OkHttpClient()");
111+
MethodDeclaration runMethod = testsClass.addMethod(GET_METHOD_METHOD_NAME);
112+
MethodDeclaration runMethodWithBody = Utils.generateGetRequestMethod();
113+
runMethod.setParameters(runMethodWithBody.getParameters());
114+
runMethod.setType(runMethodWithBody.getType());
115+
runMethod.setBody(runMethodWithBody.getBody().get());
116+
104117
for (String path : paths) {
105118
for (String operation : Lists.newArrayList(joPaths.getJSONObject(path).keys())) {
106-
generateRoute(myInterface, joPaths.getJSONObject(path).getJSONObject(operation), path, operation);
119+
generateRoute(routesInterface, joPaths.getJSONObject(path).getJSONObject(operation), path, operation);
120+
generateTest(testsClass, joPaths.getJSONObject(path).getJSONObject(operation));
107121
}
108122
}
109-
return myInterface.toString();
123+
routesAndTests.put("routes", routesInterface.toString());
124+
routesAndTests.put("tests", testsClass.toString());
125+
return ImmutableMap.copyOf(routesAndTests);
126+
}
127+
128+
private String getBaseURL() {
129+
return this.jo.getJSONArray("servers").getJSONObject(0).getString("url");
130+
}
131+
132+
private void generateTest(ClassOrInterfaceDeclaration routesInterface, JSONObject joRoute) {
133+
String classType = generateRouteType(joRoute.getJSONObject("responses")).type;
134+
MethodDeclaration methodDeclaration = routesInterface.addMethod(joRoute.getString("operationId") + "Test");
135+
methodDeclaration.addAnnotation("Test");
136+
StringBuilder getURL = new StringBuilder("\"" + getBaseURL() + "?");
137+
if (joRoute.has("parameters")) {
138+
generateRouteParameters(joRoute.getJSONArray("parameters"))
139+
.forEach(parameter -> getURL.append(generateMockDataForType(parameter)));
140+
}
141+
getURL.append("\"");
142+
BlockStmt methodBody = new BlockStmt();
143+
MethodCallExpr runCall = new MethodCallExpr();
144+
runCall.setName(GET_METHOD_METHOD_NAME);
145+
runCall.addArgument(getURL.toString());
146+
FieldDeclaration getResponse = new FieldDeclaration();
147+
if (!classType.equals("void")) {
148+
FieldDeclaration gson = new FieldDeclaration();
149+
FieldDeclaration parsedResponse = new FieldDeclaration();
150+
151+
Utils.initializeField(getResponse, "String", "getResponse", runCall.toString() + ".body().string()");
152+
Utils.initializeField(gson, "Gson", "gson", "new GsonBuilder().create()");
153+
Utils.initializeField(parsedResponse, Utils.getPrimitivesToClassTypes(classType),
154+
"response", "gson.fromJson(getResponse, " + classType + ".class)");
155+
Utils.addDeclarationsToBlock(methodBody, getResponse, gson, parsedResponse);
156+
} else {
157+
Utils.initializeField(getResponse, "int", "statusCode", runCall.toString() + ".code()");
158+
MethodCallExpr assertEqualsCall = new MethodCallExpr();
159+
assertEqualsCall.setName("assertEquals");
160+
assertEqualsCall.addArgument("statusCode");
161+
assertEqualsCall.addArgument("200");
162+
Utils.addDeclarationsToBlock(methodBody, getResponse);
163+
methodBody.addStatement(assertEqualsCall);
164+
}
165+
methodDeclaration.setBody(methodBody);
166+
}
167+
168+
private String generateMockDataForUnrecognizedName(String type) {
169+
if (type.equals("String")) {
170+
return faker.food().fruit();
171+
} else if (type.equals("long") || type.equals("int")) {
172+
return String.valueOf(faker.number().numberBetween(1, 100));
173+
} else if (type.equals("boolean")) {
174+
return "true";
175+
} else {
176+
return "UNKNOWN";
177+
}
178+
}
179+
180+
private String generateMockDataForType(Schema schema) {
181+
String parameter = schema.name + "=";
182+
switch (schema.name) {
183+
case "name": return parameter + faker.name().name();
184+
case "fullname": return parameter + faker.name().fullName();
185+
case "firstname": return parameter + faker.name().firstName();
186+
case "lastname": return parameter + faker.name().lastName();
187+
case "address": return parameter + faker.address().fullAddress();
188+
default: return parameter +
189+
generateMockDataForUnrecognizedName(schema.type);
190+
}
110191
}
111192

112193
/**
@@ -117,12 +198,17 @@ public String generateRoutes() {
117198
* @param operation
118199
*/
119200
private MethodDeclaration generateRoute(ClassOrInterfaceDeclaration routesInterface, JSONObject joRoute, String routeName, String operation) {
120-
MethodDeclaration methodDeclaration = routesInterface.addMethod(joRoute.getString("operationId"));
121-
methodDeclaration.setJavadocComment(generateJavadocForRoute(joRoute, routeName, operation));
201+
MethodDeclaration methodDeclaration = routesInterface.addMethod(joRoute.getString("operationId"))
202+
.removeBody()
203+
.setJavadocComment(generateJavadocForRoute(joRoute, routeName, operation));
122204
NormalAnnotationExpr expr = methodDeclaration.addAndGetAnnotation(operation.toUpperCase());
123205
expr.addPair("path", "\"" + routeName + "\"");
124-
methodDeclaration.removeBody();
125-
methodDeclaration.setType(generateRouteType(joRoute.getJSONObject("responses")));
206+
String routeType = generateRouteType(joRoute.getJSONObject("responses")).type;
207+
if (routeType.equals("void")) {
208+
methodDeclaration.setType("void");
209+
} else {
210+
methodDeclaration.setType("Call<" + routeType + ">");
211+
}
126212
if (joRoute.has("parameters")) {
127213
generateRouteParameters(joRoute.getJSONArray("parameters")).forEach(param -> methodDeclaration.addParameter(param.type, param.name));
128214
}
@@ -134,11 +220,11 @@ private MethodDeclaration generateRoute(ClassOrInterfaceDeclaration routesInterf
134220
* @param joRouteParameters the openAPI representation of the route parameters
135221
* @return a List of route parameters
136222
*/
137-
private List<Parameter> generateRouteParameters(JSONArray joRouteParameters) {
138-
List<Parameter> routeParameters = new ArrayList<>();
223+
private List<Schema> generateRouteParameters(JSONArray joRouteParameters) {
224+
List<Schema> routeParameters = new ArrayList<>();
139225
for (int i = 0; i < joRouteParameters.length(); i++) {
140226
JSONObject joParameter = joRouteParameters.getJSONObject(i);
141-
Parameter parameter = parseSchema(joParameter.getJSONObject("schema"));
227+
Schema parameter = parseSchema(joParameter.getJSONObject("schema"));
142228
parameter.setName(joParameter.getString("name"));
143229
parameter.setDescription(joParameter.getString("description"));
144230
routeParameters.add(parameter);
@@ -151,42 +237,33 @@ private List<Parameter> generateRouteParameters(JSONArray joRouteParameters) {
151237
* @param joRouteResponse the OpenAPI representation of the route response.
152238
* @return the type of the route
153239
*/
154-
private String generateRouteType(JSONObject joRouteResponse) {
155-
StringBuilder type = new StringBuilder();
240+
private Schema generateRouteType(JSONObject joRouteResponse) {
156241
List<String> responses = Lists.newArrayList(joRouteResponse.keys());
157-
responses.forEach(response -> {
158-
if (!response.equals("default")) {
159-
if (joRouteResponse.getJSONObject(response).has("content")) {
160-
Parameter parameter = parseSchema(joRouteResponse.getJSONObject(response).getJSONObject("content").getJSONObject("application/json").getJSONObject("schema"));
161-
parameter.setType("Call<" + parameter.type + ">");
162-
type.append(parameter.type);
163-
}
164-
}
165-
});
166-
if (type.isEmpty()) {
167-
return "void";
168-
} else {
169-
return type.toString();
242+
Optional<String> response = responses.stream().filter(r -> r.equals("200")).findFirst();
243+
if (!response.isEmpty() && joRouteResponse.getJSONObject(response.get()).has("content")) {
244+
return parseSchema(joRouteResponse.getJSONObject(response.get()).getJSONObject("content").getJSONObject("application/json").getJSONObject("schema"));
170245
}
246+
return new Schema("void");
171247
}
172248

173249
/**
174250
*
175-
* @param schema which is essentially a type
251+
* @param joSchema which is essentially a type
176252
* @return a Parameter with type information
177253
*/
178-
private Parameter parseSchema(JSONObject schema) {
179-
if (schema.has("$ref")) {
180-
return new Parameter(parseSchemaRef(schema.getString("$ref")));
254+
private Schema parseSchema(JSONObject joSchema) {
255+
if (joSchema.has("$ref")) {
256+
return new Schema(parseSchemaRef(joSchema.getString("$ref")));
181257
}
182258

183-
if (schema.has("format")) {
184-
return new Parameter(Utils.getOpenAPIToJavaTypes().get(schema.get("format")), schema.getString("format"));
259+
if (joSchema.has("format")) {
260+
return new Schema(Utils.getOpenAPIToJavaTypes().get(joSchema.get("format")), joSchema.getString("format"));
185261
}
186262

187-
return new Parameter(Utils.getOpenAPIToJavaTypes().get(schema.get("type")), schema.getString("type"));
263+
return new Schema(Utils.getOpenAPIToJavaTypes().get(joSchema.get("type")), joSchema.getString("type"));
188264
}
189265

266+
190267
/**
191268
* Uses regex to parse out the component name in the reference.
192269
* @param ref of a schema, maps to a component

cddjava/src/main/java/org/offscale/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
public class Main {
55
public static void main(String[] args) {
66
Create create = new Create("OpenAPISpec1/openapi.yaml");
7-
System.out.println(create.generateRoutes());
7+
System.out.println(create.generateRoutesAndTests());
88
}
99
}

cddjava/src/main/java/org/offscale/Merge.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import java.util.Map;
1010
import java.util.Optional;
1111

12+
/**
13+
* Given existing code and an openAPI spec, updates code based on openAP spec.
14+
*/
1215
public class Merge {
1316
private final ImmutableMap<String, String> components;
1417
private final String routes;
@@ -23,13 +26,24 @@ public Merge(ImmutableMap<String, String> components, String routes, String file
2326
this.create = new Create(filePath);
2427
}
2528

29+
/**
30+
* Updates the Components code with the openAPI components.
31+
* When there is a conflict, uses openAPI as the source of truth.
32+
* @return a map where the keys are the component names and the values
33+
* are the component code.
34+
*/
2635
public ImmutableMap<String, String> mergeComponents() {
2736
ImmutableMap<String, String> openAPIComponents = create.generateComponents();
2837
Map<String, String> mergedComponents = new HashMap<>();
2938
openAPIComponents.forEach((key, value) -> mergedComponents.put(key, mergeComponent(value, key)));
3039
return ImmutableMap.copyOf(mergedComponents);
3140
}
3241

42+
/**
43+
* @param componentCode the openAPI component code
44+
* @param componentName the openAPI component name
45+
* @return the merged code for a component
46+
*/
3347
public String mergeComponent(String componentCode, String componentName) {
3448
if (!this.components.containsKey(componentName)) {
3549
return componentCode;
@@ -57,8 +71,11 @@ public String mergeComponent(String componentCode, String componentName) {
5771
return javaCodeComponent.toString();
5872
}
5973

74+
/**
75+
* @return merged routes between existing code and openAPI spec.
76+
*/
6077
public String mergeRoutes() {
61-
String openAPIRoutes = create.generateRoutes();
78+
String openAPIRoutes = create.generateRoutesAndTests().get("routes");
6279
CompilationUnit cuOpenAPIRoutes = StaticJavaParser.parse(openAPIRoutes);
6380
CompilationUnit cuJavaCodeRoutes = StaticJavaParser.parse(this.routes);
6481
ClassOrInterfaceDeclaration javaCodeInterface = cuJavaCodeRoutes.getInterfaceByName("Routes").get();

0 commit comments

Comments
 (0)