Skip to content

Commit f71c77a

Browse files
committed
test: add E2E regression tests for work-prepare UUID argument stripping
1 parent d81dd09 commit f71c77a

3 files changed

Lines changed: 203 additions & 9 deletions

File tree

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
{"status":"open"}
1+
{
2+
"status" : "closed",
3+
"resolution" : "implemented",
4+
"target_branch" : "v2.1"
5+
}

.cat/issues/v2/v2.1/fix-work-prepare-no-issues-on-uuid-arg/plan.md

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,29 @@
22

33
## Goal
44

5-
Fix work-prepare parseRawArguments treating the CAT agent ID UUID as a bare issue name, causing NO_ISSUES
6-
when issues are available. When cat:work-agent invokes work-prepare via `--arguments "${ARGUMENTS}"`, the
7-
$ARGUMENTS string includes the agent ID UUID as the first token. parseRawArguments matches this UUID against
8-
the bare name pattern `^[a-zA-Z][a-zA-Z0-9_-]*$` (UUIDs start with a letter, contain only alphanumeric
9-
chars and hyphens), sets Scope.BARE_NAME, and resolveBareNameToIssueId finds no matching directory →
10-
returns NO_ISSUES. The fix strips the leading UUID-format token before processing remaining args as an issue
11-
name or filter.
5+
Add E2E regression test coverage for the bug fix already present in the codebase: `parseRawArguments` no
6+
longer treats a UUID-format CAT agent ID as a bare issue name. The source fix is already implemented in
7+
`WorkPrepare.java``CAT_AGENT_ID_TOKEN` strips the leading UUID before processing remaining arguments.
8+
Unit tests for `parseRawArguments()` already cover UUID stripping directly. This issue adds E2E tests that
9+
call `WorkPrepare.run()` end-to-end with `--arguments "<UUID>"` and `--arguments "<UUID> <issue-name>"`
10+
to verify READY is returned with the correct issue selected (not NO_ISSUES), confirming the full execution
11+
pipeline works correctly for both invocation patterns.
12+
13+
## Research Findings
14+
15+
The source fix is already implemented in the codebase:
16+
17+
- `client/src/main/java/io/github/cowwoc/cat/hooks/util/WorkPrepare.java` (lines 73-75): `CAT_AGENT_ID_TOKEN`
18+
regex pattern already defined
19+
- `WorkPrepare.java` (lines 1866-1906): `parseRawArguments()` already strips the UUID prefix via
20+
`CAT_AGENT_ID_TOKEN.lookingAt()` and throws `IllegalArgumentException` when rawArguments is non-blank
21+
but does not start with a valid UUID
22+
- `client/src/test/java/io/github/cowwoc/cat/hooks/test/WorkPrepareTest.java` (lines 2619-2683): 5 unit
23+
tests already cover UUID stripping for `parseRawArguments()` directly (UUID-only, UUID+issue-id,
24+
UUID+subagent-id, UUID+resume-keyword, UUID+skip-keyword)
25+
26+
Missing coverage: no end-to-end test calls `WorkPrepare.run()` with `--arguments "<UUID>"` to verify the
27+
READY response from the full execution pipeline (required by post-conditions 2 and 7).
1228

1329
## Pre-conditions
1430

@@ -25,7 +41,87 @@ name or filter.
2541
- [ ] UUID stripping is format-specific: only tokens matching
2642
`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}` at position 0 are
2743
stripped; bare issue names containing hyphens are unaffected
28-
- [ ] All existing WorkPrepareTest tests pass with no regressions (mvn -f client/pom.xml test exits 0)
44+
- [x] All existing WorkPrepareTest tests pass with no regressions (mvn -f client/pom.xml verify exits 0)
2945
- [ ] No new issues introduced
3046
- [ ] E2E verification: invoking /cat:work with no explicit issue argument (where ARGUMENTS contains only
3147
the agent UUID) correctly returns the next available issue rather than NO_ISSUES
48+
49+
## Execution Steps
50+
51+
### Step 1: Add E2E test for run() with UUID-only --arguments
52+
53+
File to modify: `client/src/test/java/io/github/cowwoc/cat/hooks/test/WorkPrepareTest.java`
54+
55+
Insert the following test method after the `parseRawArgumentsStripsUuidThenParsesSkip` test (around line
56+
2683). Place it at the end of the `parseRawArguments — CAT agent ID prefix stripping` section, before
57+
the `globToRegexHandlesMetacharacters` test:
58+
59+
```java
60+
/**
61+
* Verifies that when {@code --arguments} contains only a CAT agent ID UUID (no trailing issue name),
62+
* {@code run()} strips the UUID and returns READY for the next available issue (not NO_ISSUES).
63+
* <p>
64+
* This is the end-to-end regression test for the bug where UUIDs were matched as bare issue names.
65+
*
66+
* @throws IOException if an I/O error occurs
67+
*/
68+
@Test
69+
public void runReturnsReadyWhenArgumentsContainsOnlyUuid() throws IOException
70+
{
71+
Path projectPath = createTempGitCatProject("v2.1");
72+
Path worktreePath = null;
73+
try (JvmScope scope = new TestJvmScope(projectPath, projectPath))
74+
{
75+
createIssue(projectPath, "2", "1", "my-feature", "open");
76+
GitCommands.runGit(projectPath, "add", ".");
77+
GitCommands.runGit(projectPath, "commit", "-m", "planning: add issue my-feature");
78+
79+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
80+
PrintStream out = new PrintStream(buffer, true, StandardCharsets.UTF_8);
81+
82+
String sessionId = UUID.randomUUID().toString();
83+
// Pass UUID as the sole --arguments token — simulates /cat:work invocation with no explicit issue
84+
String uuid = "92289cdd-76a1-4d7e-8cf3-be5618ec270a";
85+
WorkPrepare.run(scope, new String[]{"--session-id", sessionId, "--arguments", uuid}, out);
86+
87+
String output = buffer.toString(StandardCharsets.UTF_8).strip();
88+
requireThat(output, "output").isNotBlank();
89+
90+
JsonMapper mapper = scope.getJsonMapper();
91+
JsonNode node = mapper.readTree(output);
92+
requireThat(node.path("status").asString(), "status").isEqualTo("READY");
93+
94+
worktreePath = Path.of(node.path("worktree_path").asString());
95+
}
96+
finally
97+
{
98+
cleanupWorktreeIfExists(projectPath, worktreePath);
99+
TestUtils.deleteDirectoryRecursively(projectPath);
100+
}
101+
}
102+
```
103+
104+
Note on imports: `ByteArrayOutputStream` and `PrintStream` are likely already imported in the test file.
105+
Verify existing imports before adding duplicates. The test file already uses `UUID`, `ByteArrayOutputStream`,
106+
`PrintStream`, `StandardCharsets`, `JsonMapper`, `JsonNode`, `TestJvmScope`, `GitCommands`, `TestUtils`,
107+
`WorkPrepare`, `createTempGitCatProject`, `createIssue`, and `cleanupWorktreeIfExists` — no new imports
108+
needed.
109+
110+
### Step 2: Run full build verification
111+
112+
Run from within the worktree directory (the subagent's working directory is already the worktree root):
113+
114+
```bash
115+
mvn -f client/pom.xml verify
116+
```
117+
118+
All tests must pass (exit code 0) before proceeding. If any test fails, fix the failure before continuing.
119+
120+
### Step 3: Commit the new test
121+
122+
Stage and commit only the test file (index.json is updated by the work-confirm/merge phases):
123+
124+
```bash
125+
git add client/src/test/java/io/github/cowwoc/cat/hooks/test/WorkPrepareTest.java
126+
git commit -m "test: add E2E regression test for UUID-only --arguments in work-prepare"
127+
```

client/src/test/java/io/github/cowwoc/cat/hooks/test/WorkPrepareTest.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,6 +2682,100 @@ public void parseRawArgumentsStripsUuidThenParsesSkip()
26822682
requireThat(result.excludePattern(), "excludePattern").isEqualTo("*compress*");
26832683
}
26842684

2685+
/**
2686+
* Verifies that when {@code --arguments} contains only a CAT agent ID UUID (no trailing issue name),
2687+
* {@code run()} strips the UUID and returns READY for the next available issue (not NO_ISSUES).
2688+
* <p>
2689+
* This is the end-to-end regression test for the bug where UUIDs were matched as bare issue names.
2690+
*
2691+
* @throws IOException if an I/O error occurs
2692+
*/
2693+
@Test
2694+
public void runReturnsReadyWhenArgumentsContainsOnlyUuid() throws IOException
2695+
{
2696+
Path projectPath = createTempGitCatProject("v2.1");
2697+
Path worktreePath = null;
2698+
try (JvmScope scope = new TestJvmScope(projectPath, projectPath))
2699+
{
2700+
createIssue(projectPath, "2", "1", "my-feature", "open");
2701+
GitCommands.runGit(projectPath, "add", ".");
2702+
GitCommands.runGit(projectPath, "commit", "-m", "planning: add issue my-feature");
2703+
2704+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
2705+
PrintStream out = new PrintStream(buffer, true, StandardCharsets.UTF_8);
2706+
2707+
String sessionId = UUID.randomUUID().toString();
2708+
// Pass UUID as the sole --arguments token — simulates /cat:work invocation with no explicit issue
2709+
String uuid = "92289cdd-76a1-4d7e-8cf3-be5618ec270a";
2710+
WorkPrepare.run(scope, new String[]{"--session-id", sessionId, "--arguments", uuid}, out);
2711+
2712+
String output = buffer.toString(StandardCharsets.UTF_8).strip();
2713+
requireThat(output, "output").isNotBlank();
2714+
2715+
JsonMapper mapper = scope.getJsonMapper();
2716+
JsonNode node = mapper.readTree(output);
2717+
requireThat(node.path("status").asString(), "status").isEqualTo("READY");
2718+
2719+
worktreePath = Path.of(node.path("worktree_path").asString());
2720+
String issueId = node.path("issue_id").asString();
2721+
requireThat(issueId, "issueId").endsWith("my-feature");
2722+
}
2723+
finally
2724+
{
2725+
cleanupWorktreeIfExists(projectPath, worktreePath);
2726+
TestUtils.deleteDirectoryRecursively(projectPath);
2727+
}
2728+
}
2729+
2730+
/**
2731+
* Verifies that when {@code --arguments} contains a CAT agent ID UUID followed by an issue name,
2732+
* {@code run()} strips the UUID and selects the named issue, returning READY with the correct
2733+
* {@code issue_id}.
2734+
* <p>
2735+
* This is the end-to-end regression test for the bug where UUIDs were matched as bare issue names,
2736+
* covering the case where the agent ID and issue name appear together in {@code --arguments}.
2737+
*
2738+
* @throws IOException if an I/O error occurs
2739+
*/
2740+
@Test
2741+
public void runReturnsReadyWhenArgumentsContainsUuidAndIssueName() throws IOException
2742+
{
2743+
Path projectPath = createTempGitCatProject("v2.1");
2744+
Path worktreePath = null;
2745+
try (JvmScope scope = new TestJvmScope(projectPath, projectPath))
2746+
{
2747+
createIssue(projectPath, "2", "1", "my-feature", "open");
2748+
GitCommands.runGit(projectPath, "add", ".");
2749+
GitCommands.runGit(projectPath, "commit", "-m", "planning: add issue my-feature");
2750+
2751+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
2752+
PrintStream out = new PrintStream(buffer, true, StandardCharsets.UTF_8);
2753+
2754+
String sessionId = UUID.randomUUID().toString();
2755+
// Pass UUID + issue name as --arguments — simulates /cat:work invocation with an explicit issue
2756+
String arguments = "92289cdd-76a1-4d7e-8cf3-be5618ec270a my-feature";
2757+
WorkPrepare.run(scope, new String[]{"--session-id", sessionId, "--arguments", arguments}, out);
2758+
2759+
String output = buffer.toString(StandardCharsets.UTF_8).strip();
2760+
requireThat(output, "output").isNotBlank();
2761+
2762+
JsonMapper mapper = scope.getJsonMapper();
2763+
JsonNode node = mapper.readTree(output);
2764+
requireThat(node.path("status").asString(), "status").isEqualTo("READY");
2765+
2766+
// Confirm the UUID was stripped and the named issue was selected
2767+
String issueId = node.path("issue_id").asString();
2768+
requireThat(issueId, "issueId").endsWith("my-feature");
2769+
2770+
worktreePath = Path.of(node.path("worktree_path").asString());
2771+
}
2772+
finally
2773+
{
2774+
cleanupWorktreeIfExists(projectPath, worktreePath);
2775+
TestUtils.deleteDirectoryRecursively(projectPath);
2776+
}
2777+
}
2778+
26852779
/**
26862780
* Verifies that a PLAN.md containing backtick-quoted text with regex metacharacters
26872781
* (e.g., "[]") does not cause a PatternSyntaxException during execute.

0 commit comments

Comments
 (0)