Add getGitDiff() to DaemonClient + E2E tests
- Add getGitDiff(agentId) method to DaemonClient that sends git_diff_request and waits for git_diff_response - Add 3 E2E tests covering: 1. Modified file in git repo returns correct diff 2. Clean repo returns empty diff 3. Non-git directory returns error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
46ddb5a20c
commit
26ec7d20f0
@@ -1098,4 +1098,121 @@ describe("daemon E2E", () => {
|
|||||||
180000
|
180000
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getGitDiff", () => {
|
||||||
|
test(
|
||||||
|
"returns diff for modified file in git repo",
|
||||||
|
async () => {
|
||||||
|
const cwd = tmpCwd();
|
||||||
|
|
||||||
|
// Initialize git repo
|
||||||
|
const { execSync } = await import("child_process");
|
||||||
|
execSync("git init", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git config user.email 'test@test.com'", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git config user.name 'Test'", { cwd, stdio: "pipe" });
|
||||||
|
|
||||||
|
// Create and commit a file
|
||||||
|
const testFile = path.join(cwd, "test.txt");
|
||||||
|
writeFileSync(testFile, "original content\n");
|
||||||
|
execSync("git add test.txt", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git commit -m 'Initial commit'", { cwd, stdio: "pipe" });
|
||||||
|
|
||||||
|
// Modify the file (creates unstaged changes)
|
||||||
|
writeFileSync(testFile, "modified content\n");
|
||||||
|
|
||||||
|
// Create agent in the git repo
|
||||||
|
const agent = await ctx.client.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd,
|
||||||
|
title: "Git Diff Test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(agent.id).toBeTruthy();
|
||||||
|
expect(agent.status).toBe("idle");
|
||||||
|
|
||||||
|
// Get git diff
|
||||||
|
const result = await ctx.client.getGitDiff(agent.id);
|
||||||
|
|
||||||
|
// Verify diff returned without error
|
||||||
|
expect(result.error).toBeNull();
|
||||||
|
expect(result.diff).toBeTruthy();
|
||||||
|
expect(result.diff).toContain("test.txt");
|
||||||
|
expect(result.diff).toContain("-original content");
|
||||||
|
expect(result.diff).toContain("+modified content");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await ctx.client.deleteAgent(agent.id);
|
||||||
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
60000 // 1 minute timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"returns empty diff when no changes",
|
||||||
|
async () => {
|
||||||
|
const cwd = tmpCwd();
|
||||||
|
|
||||||
|
// Initialize git repo with clean state
|
||||||
|
const { execSync } = await import("child_process");
|
||||||
|
execSync("git init", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git config user.email 'test@test.com'", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git config user.name 'Test'", { cwd, stdio: "pipe" });
|
||||||
|
|
||||||
|
// Create and commit a file
|
||||||
|
const testFile = path.join(cwd, "test.txt");
|
||||||
|
writeFileSync(testFile, "content\n");
|
||||||
|
execSync("git add test.txt", { cwd, stdio: "pipe" });
|
||||||
|
execSync("git commit -m 'Initial commit'", { cwd, stdio: "pipe" });
|
||||||
|
|
||||||
|
// Create agent in the git repo (no modifications)
|
||||||
|
const agent = await ctx.client.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd,
|
||||||
|
title: "Git Diff Clean Test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(agent.id).toBeTruthy();
|
||||||
|
|
||||||
|
// Get git diff - should be empty
|
||||||
|
const result = await ctx.client.getGitDiff(agent.id);
|
||||||
|
|
||||||
|
expect(result.error).toBeNull();
|
||||||
|
expect(result.diff).toBe("");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await ctx.client.deleteAgent(agent.id);
|
||||||
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
60000 // 1 minute timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"returns error for non-git directory",
|
||||||
|
async () => {
|
||||||
|
const cwd = tmpCwd();
|
||||||
|
// Don't initialize git - just a regular directory
|
||||||
|
|
||||||
|
// Create agent in a non-git directory
|
||||||
|
const agent = await ctx.client.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd,
|
||||||
|
title: "Git Diff Non-Git Test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(agent.id).toBeTruthy();
|
||||||
|
|
||||||
|
// Get git diff - should return error
|
||||||
|
const result = await ctx.client.getGitDiff(agent.id);
|
||||||
|
|
||||||
|
expect(result.diff).toBe("");
|
||||||
|
expect(result.error).toBeTruthy();
|
||||||
|
expect(result.error).toContain("git");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await ctx.client.deleteAgent(agent.id);
|
||||||
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
60000 // 1 minute timeout
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -363,6 +363,32 @@ export class DaemonClient {
|
|||||||
this.send({ type: "set_agent_mode", agentId, modeId });
|
this.send({ type: "set_agent_mode", agentId, modeId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Git Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async getGitDiff(
|
||||||
|
agentId: string
|
||||||
|
): Promise<{ diff: string; error: string | null }> {
|
||||||
|
const startPosition = this.messageQueue.length;
|
||||||
|
|
||||||
|
this.send({ type: "git_diff_request", agentId });
|
||||||
|
|
||||||
|
return this.waitFor(
|
||||||
|
(msg) => {
|
||||||
|
if (
|
||||||
|
msg.type === "git_diff_response" &&
|
||||||
|
msg.payload.agentId === agentId
|
||||||
|
) {
|
||||||
|
return { diff: msg.payload.diff, error: msg.payload.error };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
10000,
|
||||||
|
{ skipQueueBefore: startPosition }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Permissions
|
// Permissions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -1174,7 +1174,7 @@ Goal: Expand DaemonClient to cover all daemon WebSocket capabilities so the app
|
|||||||
|
|
||||||
- **Done (2025-12-25 18:06)**: WHAT: Created shared `useTempClaudeConfigDir()` helper in `test-utils/claude-config.ts:22-49`, exported from `test-utils/index.ts:14`. Updated `daemon.e2e.test.ts:904-915` to add beforeAll/afterAll hooks using this helper and removed `.skip` from `describe("permission flow: Claude")`. Refactored `claude-agent.test.ts:19` to import shared helper and removed duplicate local implementation. RESULT: Both daemon Claude permission tests now pass. EVIDENCE: `npx vitest run --testNamePattern="permission flow: Claude"` shows: ✓ approves permission and executes command (8424ms), ✓ denies permission and prevents execution (7945ms). Direct `claude-agent.test.ts` permission tests also pass (4 tests). Typecheck passes.
|
- **Done (2025-12-25 18:06)**: WHAT: Created shared `useTempClaudeConfigDir()` helper in `test-utils/claude-config.ts:22-49`, exported from `test-utils/index.ts:14`. Updated `daemon.e2e.test.ts:904-915` to add beforeAll/afterAll hooks using this helper and removed `.skip` from `describe("permission flow: Claude")`. Refactored `claude-agent.test.ts:19` to import shared helper and removed duplicate local implementation. RESULT: Both daemon Claude permission tests now pass. EVIDENCE: `npx vitest run --testNamePattern="permission flow: Claude"` shows: ✓ approves permission and executes command (8424ms), ✓ denies permission and prevents execution (7945ms). Direct `claude-agent.test.ts` permission tests also pass (4 tests). Typecheck passes.
|
||||||
|
|
||||||
- [ ] **Implement**: Add `getGitDiff()` to DaemonClient + E2E test.
|
- [x] **Implement**: Add `getGitDiff()` to DaemonClient + E2E test.
|
||||||
|
|
||||||
**Context**: App calls `git_diff_request` to get file diffs. DaemonClient doesn't support this yet.
|
**Context**: App calls `git_diff_request` to get file diffs. DaemonClient doesn't support this yet.
|
||||||
|
|
||||||
@@ -1187,6 +1187,8 @@ Goal: Expand DaemonClient to cover all daemon WebSocket capabilities so the app
|
|||||||
- Method returns diff content
|
- Method returns diff content
|
||||||
- E2E test passes
|
- E2E test passes
|
||||||
|
|
||||||
|
- **Done (2025-12-25 19:33)**: WHAT: Added `getGitDiff(agentId: string)` method to `packages/server/src/server/test-utils/daemon-client.ts:370-390`. Added 3 E2E tests in `packages/server/src/server/daemon.e2e.test.ts:1102-1217`: (1) "returns diff for modified file in git repo" - creates temp git repo, commits file, modifies it, verifies diff contains expected content; (2) "returns empty diff when no changes" - creates clean repo, verifies empty diff; (3) "returns error for non-git directory" - verifies error message for non-git cwd. RESULT: All 3 tests pass. Method returns `{diff: string, error: string | null}` matching the server's `git_diff_response` payload structure. EVIDENCE: `npm run test --workspace=@paseo/server -- daemon.e2e.test.ts -t "getGitDiff"` shows 3 passed (12 skipped).
|
||||||
|
|
||||||
- [ ] **Implement**: Add `getGitRepoInfo()` to DaemonClient + E2E test.
|
- [ ] **Implement**: Add `getGitRepoInfo()` to DaemonClient + E2E test.
|
||||||
|
|
||||||
**Context**: App calls `git_repo_info_request` to get repo info (branch, status, etc).
|
**Context**: App calls `git_repo_info_request` to get repo info (branch, status, etc).
|
||||||
|
|||||||
Reference in New Issue
Block a user