Skip to content

Commit 3a4a81d

Browse files
committed
chore: Improve unit test coverage
1 parent ffe2c94 commit 3a4a81d

13 files changed

Lines changed: 846 additions & 140 deletions

src/main/java/dev/dochia/cli/core/command/InitSkillsCommand.java

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -59,67 +59,20 @@ public class InitSkillsCommand implements Runnable, CommandLine.IExitCodeGenerat
5959
"dochia-explain"
6060
);
6161

62-
private static final Map<String, List<String>> SKILL_EXTRA_FILES = Map.of(
63-
"dochia-test", List.of("references/report-output.md")
64-
);
62+
private static final Map<String, List<String>> SKILL_EXTRA_FILES =
63+
Map.of("dochia-test", List.of("references/report-output.md"));
6564

6665
@Override
6766
public void run() {
6867
try {
6968
Path baseDir = Path.of(directory).toAbsolutePath().normalize();
7069
logger.info("Generating Dochia agent skills in {}", baseDir);
7170

72-
int created = 0;
73-
int skipped = 0;
74-
75-
for (String skillName : SKILL_NAMES) {
76-
Path skillDir = baseDir.resolve(".agents").resolve("skills").resolve(skillName);
77-
Path skillFile = skillDir.resolve("SKILL.md");
78-
String resourcePath = "skills/" + skillName + "/SKILL.md";
79-
80-
if (writeResourceFile(resourcePath, skillFile)) {
81-
created++;
82-
} else {
83-
skipped++;
84-
}
85-
86-
for (String extraFile : SKILL_EXTRA_FILES.getOrDefault(skillName, List.of())) {
87-
Path extraTarget = skillDir.resolve(extraFile);
88-
String extraResource = "skills/" + skillName + "/" + extraFile;
89-
if (writeResourceFile(extraResource, extraTarget)) {
90-
created++;
91-
} else {
92-
skipped++;
93-
}
94-
}
95-
}
96-
97-
logger.noFormat("");
98-
logger.info("Done! {} files created, {} skipped (already exist).", created, skipped);
99-
100-
if (created > 0) {
101-
logger.noFormat("");
102-
logger.info("Generated files:");
103-
for (String skillName : SKILL_NAMES) {
104-
Path skillFile = baseDir.resolve(".agents").resolve("skills").resolve(skillName).resolve("SKILL.md");
105-
if (Files.exists(skillFile)) {
106-
logger.noFormat(" .agents/skills/{}/SKILL.md", skillName);
107-
}
108-
}
109-
logger.noFormat("");
110-
logger.info("These files are automatically discovered by:");
111-
logger.noFormat(" - Windsurf (Cascade)");
112-
logger.noFormat(" - Cursor");
113-
logger.noFormat(" - Claude Code");
114-
logger.noFormat(" - OpenAI Codex");
115-
logger.noFormat("");
116-
logger.info("Commit them to your repository so your team benefits too.");
117-
}
118-
119-
if (skipped > 0 && !force) {
120-
logger.note("Use --force to overwrite existing files.");
121-
}
71+
int[] counts = processAllSkills(baseDir);
72+
int created = counts[0];
73+
int skipped = counts[1];
12274

75+
logSummary(baseDir, created, skipped);
12376
exitCode = 0;
12477
} catch (Exception e) {
12578
logger.error("Failed to generate agent skills: {}", e.getMessage());
@@ -128,6 +81,74 @@ public void run() {
12881
}
12982
}
13083

84+
private int[] processAllSkills(Path baseDir) throws IOException {
85+
int created = 0;
86+
int skipped = 0;
87+
88+
for (String skillName : SKILL_NAMES) {
89+
int[] counts = processSkill(baseDir, skillName);
90+
created += counts[0];
91+
skipped += counts[1];
92+
}
93+
return new int[]{created, skipped};
94+
}
95+
96+
private int[] processSkill(Path baseDir, String skillName) throws IOException {
97+
int created = 0;
98+
int skipped = 0;
99+
100+
Path skillDir = baseDir.resolve(".agents").resolve("skills").resolve(skillName);
101+
String resourcePath = "skills/" + skillName + "/SKILL.md";
102+
103+
if (writeResourceFile(resourcePath, skillDir.resolve("SKILL.md"))) {
104+
created++;
105+
} else {
106+
skipped++;
107+
}
108+
109+
for (String extraFile : SKILL_EXTRA_FILES.getOrDefault(skillName, List.of())) {
110+
String extraResource = "skills/" + skillName + "/" + extraFile;
111+
if (writeResourceFile(extraResource, skillDir.resolve(extraFile))) {
112+
created++;
113+
} else {
114+
skipped++;
115+
}
116+
}
117+
return new int[]{created, skipped};
118+
}
119+
120+
private void logSummary(Path baseDir, int created, int skipped) {
121+
logger.noFormat("");
122+
logger.info("Done! {} files created, {} skipped (already exist).", created, skipped);
123+
124+
if (created > 0) {
125+
logGeneratedFiles(baseDir);
126+
}
127+
128+
if (skipped > 0 && !force) {
129+
logger.note("Use --force to overwrite existing files.");
130+
}
131+
}
132+
133+
private void logGeneratedFiles(Path baseDir) {
134+
logger.noFormat("");
135+
logger.info("Generated files:");
136+
for (String skillName : SKILL_NAMES) {
137+
Path skillFile = baseDir.resolve(".agents").resolve("skills").resolve(skillName).resolve("SKILL.md");
138+
if (Files.exists(skillFile)) {
139+
logger.noFormat(" .agents/skills/{}/SKILL.md", skillName);
140+
}
141+
}
142+
logger.noFormat("");
143+
logger.info("These files are automatically discovered by:");
144+
logger.noFormat(" - Windsurf (Cascade)");
145+
logger.noFormat(" - Cursor");
146+
logger.noFormat(" - Claude Code");
147+
logger.noFormat(" - OpenAI Codex");
148+
logger.noFormat("");
149+
logger.info("Commit them to your repository so your team benefits too.");
150+
}
151+
131152
/**
132153
* Writes a classpath resource to a target file path.
133154
*

src/main/java/dev/dochia/cli/core/util/DochiaModelUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import io.swagger.v3.oas.models.media.DateSchema;
1010
import io.swagger.v3.oas.models.media.DateTimeSchema;
1111
import io.swagger.v3.oas.models.media.IntegerSchema;
12-
import io.swagger.v3.oas.models.media.JsonSchema;
1312
import io.swagger.v3.oas.models.media.MapSchema;
1413
import io.swagger.v3.oas.models.media.NumberSchema;
1514
import io.swagger.v3.oas.models.media.ObjectSchema;

src/test/java/dev/dochia/cli/core/command/DochiaCommandTest.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,24 @@ void shouldDisplayLicenseWhenLicensesOption() {
6363
}
6464

6565
@Test
66-
void shouldReturnZeroAsExitCode() {
67-
int exitCode = dochiaCommand.getExitCode();
66+
void shouldReturnZeroExitCodeByDefault() {
67+
assertThat(dochiaCommand.getExitCode()).isZero();
68+
}
69+
70+
@Test
71+
void shouldReturnZeroExitCodeAfterDisplayingHelp() {
72+
dochiaCommand.run();
73+
74+
assertThat(dochiaCommand.getExitCode()).isZero();
75+
}
6876

69-
assertThat(exitCode).isZero();
77+
@Test
78+
void shouldSetExitCodeAfterLicensesRun() {
79+
dochiaCommand.licenses = true;
80+
dochiaCommand.run();
81+
82+
assertThat(dochiaCommand.getExitCode()).isZero();
83+
assertThat(outContent.toString(StandardCharsets.UTF_8)).isNotEmpty();
7084
}
7185

7286
@Test
@@ -76,4 +90,30 @@ void shouldDisplayLicensesSuccessfully() {
7690
assertThat(result).isZero();
7791
assertThat(outContent.toString(StandardCharsets.UTF_8)).isNotEmpty();
7892
}
93+
94+
@Test
95+
void shouldReturnNonZeroWhenLicenseFileNotFound() {
96+
// Use a custom classloader that won't find the resource
97+
Thread currentThread = Thread.currentThread();
98+
ClassLoader original = currentThread.getContextClassLoader();
99+
try {
100+
currentThread.setContextClassLoader(new ClassLoader(null) {});
101+
int result = dochiaCommand.displayLicenses();
102+
103+
assertThat(result).isEqualTo(1);
104+
assertThat(errContent.toString(StandardCharsets.UTF_8))
105+
.contains("License file not found.");
106+
} finally {
107+
currentThread.setContextClassLoader(original);
108+
}
109+
}
110+
111+
@Test
112+
void shouldNotPrintHelpWhenLicensesFlagIsSet() {
113+
dochiaCommand.licenses = true;
114+
dochiaCommand.run();
115+
116+
assertThat(outContent.toString(StandardCharsets.UTF_8))
117+
.doesNotContain("dochia – Bringing chaos with love!");
118+
}
79119
}

src/test/java/dev/dochia/cli/core/command/InitSkillsCommandTest.java

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import org.junit.jupiter.api.io.TempDir;
66
import picocli.CommandLine;
77

8+
import java.io.IOException;
89
import java.nio.file.Files;
910
import java.nio.file.Path;
1011

11-
import static org.junit.jupiter.api.Assertions.*;
12+
import static org.assertj.core.api.Assertions.assertThat;
1213

1314
@QuarkusTest
1415
class InitSkillsCommandTest {
@@ -18,14 +19,14 @@ void shouldGenerateAllSkillFiles(@TempDir Path tempDir) {
1819
CommandLine commandLine = new CommandLine(new InitSkillsCommand());
1920
int exitCode = commandLine.execute("--dir", tempDir.toString());
2021

21-
assertEquals(0, exitCode);
22-
assertFalse(Files.exists(tempDir.resolve("AGENTS.md")));
23-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-test/SKILL.md")));
24-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-fuzz/SKILL.md")));
25-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-replay/SKILL.md")));
26-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-list/SKILL.md")));
27-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-explain/SKILL.md")));
28-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-test/references/report-output.md")));
22+
assertThat(exitCode).isZero();
23+
assertThat(tempDir.resolve("AGENTS.md")).doesNotExist();
24+
assertThat(tempDir.resolve(".agents/skills/dochia-test/SKILL.md")).exists();
25+
assertThat(tempDir.resolve(".agents/skills/dochia-fuzz/SKILL.md")).exists();
26+
assertThat(tempDir.resolve(".agents/skills/dochia-replay/SKILL.md")).exists();
27+
assertThat(tempDir.resolve(".agents/skills/dochia-list/SKILL.md")).exists();
28+
assertThat(tempDir.resolve(".agents/skills/dochia-explain/SKILL.md")).exists();
29+
assertThat(tempDir.resolve(".agents/skills/dochia-test/references/report-output.md")).exists();
2930
}
3031

3132
@Test
@@ -37,10 +38,9 @@ void shouldNotOverwriteExistingFilesWithoutForce(@TempDir Path tempDir) throws E
3738
CommandLine commandLine = new CommandLine(new InitSkillsCommand());
3839
int exitCode = commandLine.execute("--dir", tempDir.toString());
3940

40-
assertEquals(0, exitCode);
41-
assertEquals("existing content", Files.readString(skillFile));
42-
// Other skills should still be created
43-
assertTrue(Files.exists(tempDir.resolve(".agents/skills/dochia-fuzz/SKILL.md")));
41+
assertThat(exitCode).isZero();
42+
assertThat(Files.readString(skillFile)).isEqualTo("existing content");
43+
assertThat(tempDir.resolve(".agents/skills/dochia-fuzz/SKILL.md")).exists();
4444
}
4545

4646
@Test
@@ -52,10 +52,25 @@ void shouldOverwriteExistingFilesWithForce(@TempDir Path tempDir) throws Excepti
5252
CommandLine commandLine = new CommandLine(new InitSkillsCommand());
5353
int exitCode = commandLine.execute("--dir", tempDir.toString(), "--force");
5454

55-
assertEquals(0, exitCode);
56-
String content = Files.readString(skillFile);
57-
assertNotEquals("existing content", content);
58-
assertTrue(content.contains("dochia-test"));
55+
assertThat(exitCode).isZero();
56+
assertThat(Files.readString(skillFile))
57+
.isNotEqualTo("existing content")
58+
.contains("dochia-test");
59+
}
60+
61+
@Test
62+
void shouldOverwriteExistingFilesWithShortForceFlag(@TempDir Path tempDir) throws Exception {
63+
Path skillFile = tempDir.resolve(".agents/skills/dochia-test/SKILL.md");
64+
Files.createDirectories(skillFile.getParent());
65+
Files.writeString(skillFile, "existing content");
66+
67+
CommandLine commandLine = new CommandLine(new InitSkillsCommand());
68+
int exitCode = commandLine.execute("--dir", tempDir.toString(), "-f");
69+
70+
assertThat(exitCode).isZero();
71+
assertThat(Files.readString(skillFile))
72+
.isNotEqualTo("existing content")
73+
.contains("dochia-test");
5974
}
6075

6176
@Test
@@ -64,23 +79,77 @@ void shouldContainValidSkillFrontmatter(@TempDir Path tempDir) throws Exception
6479
commandLine.execute("--dir", tempDir.toString());
6580

6681
String testSkill = Files.readString(tempDir.resolve(".agents/skills/dochia-test/SKILL.md"));
67-
assertTrue(testSkill.startsWith("---"));
68-
assertTrue(testSkill.contains("name: dochia-test"));
69-
assertTrue(testSkill.contains("description:"));
70-
assertTrue(testSkill.contains("metadata:"));
71-
assertTrue(testSkill.contains("triggers:"));
72-
assertTrue(testSkill.contains("examples:"));
82+
assertThat(testSkill)
83+
.startsWith("---")
84+
.contains("name: dochia-test")
85+
.contains("description:")
86+
.contains("metadata:")
87+
.contains("triggers:")
88+
.contains("examples:");
7389

7490
String fuzzSkill = Files.readString(tempDir.resolve(".agents/skills/dochia-fuzz/SKILL.md"));
75-
assertTrue(fuzzSkill.contains("name: dochia-fuzz"));
76-
assertTrue(fuzzSkill.contains("metadata:"));
91+
assertThat(fuzzSkill)
92+
.contains("name: dochia-fuzz")
93+
.contains("metadata:");
7794
}
7895

7996
@Test
8097
void shouldReadResources() {
8198
InitSkillsCommand command = new InitSkillsCommand();
8299
String content = command.readResource("skills/dochia-test/SKILL.md");
83-
assertNotNull(content);
84-
assertTrue(content.contains("dochia-test"));
100+
101+
assertThat(content).isNotNull().contains("dochia-test");
102+
}
103+
104+
@Test
105+
void shouldReturnNullForNonexistentResource() {
106+
InitSkillsCommand command = new InitSkillsCommand();
107+
String content = command.readResource("nonexistent/resource.md");
108+
109+
assertThat(content).isNull();
110+
}
111+
112+
@Test
113+
void shouldReturnFalseWhenResourceNotFound(@TempDir Path tempDir) throws IOException {
114+
InitSkillsCommand command = new InitSkillsCommand();
115+
Path target = tempDir.resolve("nonexistent/SKILL.md");
116+
117+
boolean result = command.writeResourceFile("nonexistent/resource.md", target);
118+
119+
assertThat(result).isFalse();
120+
assertThat(target).doesNotExist();
121+
}
122+
123+
@Test
124+
void shouldReturnZeroExitCodeOnSuccess(@TempDir Path tempDir) {
125+
InitSkillsCommand command = new InitSkillsCommand();
126+
CommandLine commandLine = new CommandLine(command);
127+
commandLine.execute("--dir", tempDir.toString());
128+
129+
assertThat(command.getExitCode()).isZero();
130+
}
131+
132+
@Test
133+
void shouldUseShortDirFlag(@TempDir Path tempDir) {
134+
CommandLine commandLine = new CommandLine(new InitSkillsCommand());
135+
int exitCode = commandLine.execute("-d", tempDir.toString());
136+
137+
assertThat(exitCode).isZero();
138+
assertThat(tempDir.resolve(".agents/skills/dochia-test/SKILL.md")).exists();
139+
}
140+
141+
@Test
142+
void shouldBeIdempotentWhenRunTwiceWithForce(@TempDir Path tempDir) throws Exception {
143+
CommandLine commandLine1 = new CommandLine(new InitSkillsCommand());
144+
commandLine1.execute("--dir", tempDir.toString());
145+
146+
String firstRun = Files.readString(tempDir.resolve(".agents/skills/dochia-test/SKILL.md"));
147+
148+
CommandLine commandLine2 = new CommandLine(new InitSkillsCommand());
149+
commandLine2.execute("--dir", tempDir.toString(), "--force");
150+
151+
String secondRun = Files.readString(tempDir.resolve(".agents/skills/dochia-test/SKILL.md"));
152+
153+
assertThat(secondRun).isEqualTo(firstRun);
85154
}
86155
}

0 commit comments

Comments
 (0)