Skip to content

Commit f03ead8

Browse files
committed
test: align test class generation to PascalCase
- Expect generated test file CodegenDemoApplicationTests.java - Verify content includes @SpringBootTest and class name - Matches NameConverter-based generation across CI (case-sensitive)
1 parent 371d624 commit f03ead8

File tree

10 files changed

+144
-126
lines changed

10 files changed

+144
-126
lines changed

README.md

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
</p>
1414

1515
**A customizable project generator for Spring Boot.**
16-
Quickly scaffold a new Java application with predefined structure, configuration, and tests — no repetitive setup required.
16+
Quickly scaffold a new Java application with predefined structure, configuration, and tests — no repetitive setup
17+
required.
1718

1819
---
1920

@@ -66,7 +67,8 @@ mvn spring-boot:run -Dspring-boot.run.profiles=cli \
6667
-Dspring-boot.run.arguments="--groupId=com.example --artifactId=demo-app --packageName=com.example.demo --outputDir=./target/generated-projects --overwrite=true"
6768
```
6869

69-
✅ This generates a new project as a zip archive under the specified output directory (default: `./target/generated-projects`):
70+
✅ This generates a new project as a zip archive under the specified output directory (default:
71+
`./target/generated-projects`):
7072

7173
```
7274
[OK] Project archive generated at: /.../target/generated-projects/demo-app/demo-app.zip
@@ -77,8 +79,8 @@ mvn spring-boot:run -Dspring-boot.run.profiles=cli \
7779
* If you don’t provide `--outputDir`, the project will be created under the default path `target/generated-projects`.
7880
* If the target directory already exists:
7981

80-
* By default, the generator will **fail-fast** with a clear error.
81-
* Add `--overwrite=true` to **delete and regenerate** the project in the same directory.
82+
* By default, the generator will **fail-fast** with a clear error.
83+
* Add `--overwrite=true` to **delete and regenerate** the project in the same directory.
8284

8385
---
8486

@@ -99,32 +101,34 @@ import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGene
99101
// Assume ProjectGenerationService is injected or obtained from Spring context
100102
ProjectGenerationService service = /* @Autowired or ApplicationContext.getBean(...) */;
101103

102-
var depWeb = new Dependency.DependencyBuilder()
103-
.groupId("org.springframework.boot")
104-
.artifactId("spring-boot-starter-web")
105-
.build();
106-
107-
var depTest = new Dependency.DependencyBuilder()
108-
.groupId("org.springframework.boot")
109-
.artifactId("spring-boot-starter-test")
110-
.scope("test")
111-
.build();
112-
113-
var metadata = new SpringBootJavaProjectMetadataBuilder()
114-
.springBootVersion("3.5.5")
115-
.javaVersion("21")
116-
.groupId("com.example")
117-
.artifactId("demo-app")
118-
.name("demo-app")
119-
.description("Generated by codegen-initializr-core")
120-
.packageName("com.example.demo")
121-
.dependencies(List.of(depWeb, depTest))
122-
.build();
123-
124-
var type = new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA);
125-
126-
Path zip = service.generateProject(type, metadata);
127-
System.out.println("Archive generated at: " + zip.toAbsolutePath());
104+
var depWeb = new Dependency.DependencyBuilder()
105+
.groupId("org.springframework.boot")
106+
.artifactId("spring-boot-starter-web")
107+
.build();
108+
109+
var depTest = new Dependency.DependencyBuilder()
110+
.groupId("org.springframework.boot")
111+
.artifactId("spring-boot-starter-test")
112+
.scope("test")
113+
.build();
114+
115+
var metadata = new SpringBootJavaProjectMetadataBuilder()
116+
.springBootVersion("3.5.5")
117+
.javaVersion("21")
118+
.groupId("com.example")
119+
.artifactId("demo-app")
120+
.name("demo-app")
121+
.description("Generated by codegen-initializr-core")
122+
.packageName("com.example.demo")
123+
.dependencies(List.of(depWeb, depTest))
124+
.build();
125+
126+
var type = new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA);
127+
128+
Path zip = service.generateProject(type, metadata);
129+
System.out.
130+
131+
println("Archive generated at: "+zip.toAbsolutePath());
128132
```
129133

130134
---
@@ -177,14 +181,16 @@ Run the test suite:
177181
mvn test
178182
```
179183

180-
The generator components (`pom.xml`, `.gitignore`, `application.yml`, layout, archiver) are fully covered with unit & integration tests.
184+
The generator components (`pom.xml`, `.gitignore`, `application.yml`, layout, archiver) are fully covered with unit &
185+
integration tests.
181186

182187
---
183188

184189
## 📖 Related Work
185190

186191
This tool is inspired by the need to automate repetitive **Spring Boot project initialization** tasks.
187-
It works well alongside other repositories like [spring-boot-openapi-generics-clients](https://github.com/bsayli/spring-boot-openapi-generics-clients).
192+
It works well alongside other repositories
193+
like [spring-boot-openapi-generics-clients](https://github.com/bsayli/spring-boot-openapi-generics-clients).
188194

189195
---
190196

src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,25 @@ public class CliRunner implements ApplicationRunner {
2727

2828
private static final Logger log = LoggerFactory.getLogger(CliRunner.class);
2929

30-
private static final String ARG_GROUP_ID = "groupId";
31-
private static final String ARG_ARTIFACT_ID = "artifactId";
32-
private static final String ARG_NAME = "name";
30+
private static final String ARG_GROUP_ID = "groupId";
31+
private static final String ARG_ARTIFACT_ID = "artifactId";
32+
private static final String ARG_NAME = "name";
3333
private static final String ARG_PACKAGE_NAME = "packageName";
3434
private static final String ARG_JAVA_VERSION = "javaVersion";
3535
private static final String ARG_BOOT_VERSION = "springBootVersion";
36-
private static final String ARG_OUTPUT_DIR = "outputDir";
37-
private static final String ARG_OVERWRITE = "overwrite";
36+
private static final String ARG_OUTPUT_DIR = "outputDir";
37+
private static final String ARG_OVERWRITE = "overwrite";
3838

39-
private static final String DEF_GROUP_ID = "com.example";
40-
private static final String DEF_ARTIFACT_ID = "demo-app";
39+
private static final String DEF_GROUP_ID = "com.example";
40+
private static final String DEF_ARTIFACT_ID = "demo-app";
4141
private static final String DEF_PACKAGE_NAME = "com.example.demo";
4242
private static final String DEF_JAVA_VERSION = "21";
4343
private static final String DEF_BOOT_VERSION = "3.5.5";
44-
private static final String DEF_DESCRIPTION = "Generated by codegen-initializr-core";
45-
private static final boolean DEF_OVERWRITE = false;
44+
private static final String DEF_DESCRIPTION = "Generated by codegen-initializr-core";
45+
private static final boolean DEF_OVERWRITE = false;
4646

4747
private static final Path DEFAULT_OUTPUT_ROOT =
48-
Paths.get(System.getProperty("user.dir"), "target", "generated-projects");
48+
Paths.get(System.getProperty("user.dir"), "target", "generated-projects");
4949

5050
private final ProjectGenerationService service;
5151

@@ -55,40 +55,46 @@ public CliRunner(ProjectGenerationService service) {
5555

5656
@Override
5757
public void run(ApplicationArguments args) throws Exception {
58-
String groupId = argOrDefault(args, ARG_GROUP_ID, DEF_GROUP_ID);
59-
String artifact = argOrDefault(args, ARG_ARTIFACT_ID, DEF_ARTIFACT_ID);
60-
String name = argOrDefault(args, ARG_NAME, artifact);
61-
String pkg = argOrDefault(args, ARG_PACKAGE_NAME, DEF_PACKAGE_NAME);
62-
String javaVer = argOrDefault(args, ARG_JAVA_VERSION, DEF_JAVA_VERSION);
63-
String bootVer = argOrDefault(args, ARG_BOOT_VERSION, DEF_BOOT_VERSION);
64-
Path outputRoot = argPath(args, ARG_OUTPUT_DIR).orElse(DEFAULT_OUTPUT_ROOT);
58+
String groupId = argOrDefault(args, ARG_GROUP_ID, DEF_GROUP_ID);
59+
String artifact = argOrDefault(args, ARG_ARTIFACT_ID, DEF_ARTIFACT_ID);
60+
String name = argOrDefault(args, ARG_NAME, artifact);
61+
String pkg = argOrDefault(args, ARG_PACKAGE_NAME, DEF_PACKAGE_NAME);
62+
String javaVer = argOrDefault(args, ARG_JAVA_VERSION, DEF_JAVA_VERSION);
63+
String bootVer = argOrDefault(args, ARG_BOOT_VERSION, DEF_BOOT_VERSION);
64+
Path outputRoot = argPath(args, ARG_OUTPUT_DIR).orElse(DEFAULT_OUTPUT_ROOT);
6565
boolean overwrite = argBoolean(args, ARG_OVERWRITE, DEF_OVERWRITE);
6666

6767
Path projectDir = outputRoot.resolve(artifact);
6868

6969
if (Files.exists(projectDir)) {
7070
if (overwrite) {
71-
log.info("♻️ Existing directory found. Deleting before regeneration: {}", projectDir.toAbsolutePath());
71+
log.info(
72+
"♻️ Existing directory found. Deleting before regeneration: {}",
73+
projectDir.toAbsolutePath());
7274
deleteRecursively(projectDir);
7375
} else {
7476
throw new IllegalStateException(
75-
"Target directory already exists: " + projectDir.toAbsolutePath()
76-
+ " (use --overwrite=true to replace it)");
77+
"Target directory already exists: "
78+
+ projectDir.toAbsolutePath()
79+
+ " (use --overwrite=true to replace it)");
7780
}
7881
}
7982

80-
var depWeb = new Dependency.DependencyBuilder()
83+
var depWeb =
84+
new Dependency.DependencyBuilder()
8185
.groupId("org.springframework.boot")
8286
.artifactId("spring-boot-starter-web")
8387
.build();
8488

85-
var depTest = new Dependency.DependencyBuilder()
89+
var depTest =
90+
new Dependency.DependencyBuilder()
8691
.groupId("org.springframework.boot")
8792
.artifactId("spring-boot-starter-test")
8893
.scope("test")
8994
.build();
9095

91-
var metadata = new SpringBootJavaProjectMetadataBuilder()
96+
var metadata =
97+
new SpringBootJavaProjectMetadataBuilder()
9298
.springBootVersion(bootVer)
9399
.javaVersion(javaVer)
94100
.groupId(groupId)
@@ -139,8 +145,10 @@ private void deleteRecursively(Path dir) {
139145
if (!Files.exists(dir)) return;
140146

141147
try (Stream<Path> paths = Files.walk(dir)) {
142-
paths.sorted(Comparator.reverseOrder())
143-
.forEach(p -> {
148+
paths
149+
.sorted(Comparator.reverseOrder())
150+
.forEach(
151+
p -> {
144152
try {
145153
Files.deleteIfExists(p);
146154
} catch (IOException e) {
@@ -151,4 +159,4 @@ private void deleteRecursively(Path dir) {
151159
throw new UncheckedIOException("Failed to clean directory: " + dir.toAbsolutePath(), e);
152160
}
153161
}
154-
}
162+
}

src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGenerator.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.MavenJavaSourceFolderProperties;
88
import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata;
9+
import io.github.bsayli.codegen.initializr.projectgeneration.naming.NameConverter;
910
import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkProjectStarterClassGenerator;
1011
import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine;
1112
import java.io.File;
@@ -19,11 +20,15 @@ public class SpringBootJavaMainClassGenerator implements FrameworkProjectStarter
1920

2021
private final TemplateEngine freeMarkerTemplateEngine;
2122
private final MavenJavaSourceFolderProperties sourceFolder;
23+
private final NameConverter nameConverter;
2224

2325
public SpringBootJavaMainClassGenerator(
24-
TemplateEngine freeMarkerTemplateEngine, MavenJavaSourceFolderProperties sourceFolder) {
26+
TemplateEngine freeMarkerTemplateEngine,
27+
MavenJavaSourceFolderProperties sourceFolder,
28+
NameConverter nameConverter) {
2529
this.sourceFolder = sourceFolder;
2630
this.freeMarkerTemplateEngine = freeMarkerTemplateEngine;
31+
this.nameConverter = nameConverter;
2732
}
2833

2934
@Override
@@ -33,9 +38,9 @@ public void generateProjectStarterClass(File projectDestination, ProjectMetadata
3338
Map<String, Object> mainClassModel = new HashMap<>();
3439
mainClassModel.put("projectPackageName", projectMetadata.getPackageName());
3540

36-
String className = sanitizeClassName(projectMetadata.getName());
37-
className =
38-
className.substring(0, 1).toUpperCase() + className.substring(1) + FILE_NAME_POSTFIX;
41+
// e.g. "codegen-demo" -> "CodegenDemo" + "Application"
42+
String classBase = nameConverter.toPascalCase(projectMetadata.getName());
43+
String className = classBase + FILE_NAME_POSTFIX;
3944
mainClassModel.put("className", className);
4045

4146
String basePackagePath = projectMetadata.getPackageName().replace(".", "/");
@@ -47,17 +52,4 @@ public void generateProjectStarterClass(File projectDestination, ProjectMetadata
4752
freeMarkerTemplateEngine.generateFileFromTemplate(
4853
TEMPLATE_NAME, fileName, mainClassModel, mainClassFileDestination);
4954
}
50-
51-
private String sanitizeClassName(String className) {
52-
StringBuilder sanitizedName = new StringBuilder();
53-
className
54-
.chars()
55-
.forEach(
56-
c -> {
57-
if (Character.isLetterOrDigit(c) || c == '_') {
58-
sanitizedName.append((char) c);
59-
}
60-
});
61-
return sanitizedName.toString();
62-
}
6355
}

src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGenerator.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.MavenJavaSourceFolderProperties;
66
import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata;
7+
import io.github.bsayli.codegen.initializr.projectgeneration.naming.NameConverter;
78
import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkSpecificTestUnitGenerator;
89
import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine;
910
import java.io.File;
@@ -17,22 +18,26 @@ public class SpringBootJavaTestClassGenerator implements FrameworkSpecificTestUn
1718

1819
private final TemplateEngine freeMarkerTemplateEngine;
1920
private final MavenJavaSourceFolderProperties sourceFolder;
21+
private final NameConverter nameConverter;
2022

2123
public SpringBootJavaTestClassGenerator(
22-
TemplateEngine freeMarkerTemplateEngine, MavenJavaSourceFolderProperties sourceFolder) {
24+
TemplateEngine freeMarkerTemplateEngine,
25+
MavenJavaSourceFolderProperties sourceFolder,
26+
NameConverter nameConverter) {
2327
this.sourceFolder = sourceFolder;
2428
this.freeMarkerTemplateEngine = freeMarkerTemplateEngine;
29+
this.nameConverter = nameConverter;
2530
}
2631

2732
@Override
2833
public void generateTestClass(File projectDestination, ProjectMetadata projectMetadata)
2934
throws IOException {
35+
3036
Map<String, Object> testClassModel = new HashMap<>();
3137
testClassModel.put("projectPackageName", projectMetadata.getPackageName());
3238

33-
String className = projectMetadata.getName().replace("-", "");
34-
className =
35-
className.substring(0, 1).toUpperCase() + className.substring(1) + FILE_NAME_POSTFIX;
39+
String classBase = nameConverter.toPascalCase(projectMetadata.getName());
40+
String className = classBase + FILE_NAME_POSTFIX;
3641
testClassModel.put("className", className);
3742

3843
String basePackagePath = projectMetadata.getPackageName().replace(".", "/");

src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngine.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating;
22

3-
import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType;
4-
import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine;
53
import freemarker.template.Configuration;
64
import freemarker.template.Template;
75
import freemarker.template.TemplateException;
6+
import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType;
7+
import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine;
88
import java.io.File;
99
import java.io.FileWriter;
1010
import java.io.IOException;

src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.bsayli.codegen.initializr.projectgeneration.configuration;
22

3-
import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.FreeMarkerTemplateProperties;
43
import freemarker.template.TemplateExceptionHandler;
4+
import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.FreeMarkerTemplateProperties;
55
import java.io.Serial;
66
import org.springframework.boot.context.properties.EnableConfigurationProperties;
77
import org.springframework.context.annotation.Bean;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.github.bsayli.codegen.initializr.projectgeneration.naming;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
@Component
6+
public class NameConverter {
7+
8+
/**
9+
* Converts a raw project name into Java-friendly PascalCase. Examples: "codegen-demo" ->
10+
* "CodegenDemo" "my_service.core" -> "MyServiceCore" "123-metric" -> "App123Metric"
11+
*/
12+
public String toPascalCase(String raw) {
13+
if (raw == null || raw.isBlank()) return "";
14+
String[] parts = raw.split("[^A-Za-z0-9]+");
15+
StringBuilder sb = new StringBuilder();
16+
for (String p : parts) {
17+
if (p.isBlank()) continue;
18+
String lower = p.toLowerCase();
19+
sb.append(Character.toUpperCase(lower.charAt(0)));
20+
if (lower.length() > 1) {
21+
sb.append(lower.substring(1));
22+
}
23+
}
24+
String result = sb.toString();
25+
if (!result.isEmpty() && Character.isDigit(result.charAt(0))) {
26+
result = "App" + result;
27+
}
28+
return result;
29+
}
30+
}

0 commit comments

Comments
 (0)