test: add unit tests for src/git/diff.ts#76
Conversation
Covers all exported functions: checkGitRepo, getStagedDiff, getUnstagedDiff, commit, and getRepoRoot. Each test creates isolated temporary git repos via mkdtempSync + git init.
404-Page-Found
left a comment
There was a problem hiding this comment.
Review: PR #76 — test: add unit tests for src/git/diff.ts
| Author | lb1192176991-lab |
| Base → Head | main → feat/git-diff-tests |
| Files changed | 2 (+165 / -2) |
| State | Open |
Overview ✅
The PR title and description clearly explain what (add unit tests for src/git/diff.ts) and why (git operations are central but had no coverage). Scope is focused — tests plus a minor refactor to the commit() return type. Size is manageable at 165 additions.
Correctness & Logic — ⚠️ Blocking
1. commit() return type change breaks all callers
The function signature changed from return string to return CommitResult (an object), but the two call sites in src/commands/suggest.ts were not updated:
- Line 149 (auto path):
result.trim()—.trim()on an object will throw at runtime. - Line 210 (interactive path):
result.trim()— same issue.
Both need to become result.raw.trim() (or result.raw).
2. Existing test will break
tests/commit-echo.test.mjs does console.log(out) where out is the return value of commit(). With the new object return type, this prints [object Object] instead of the raw git output string, causing assertions like res.stdout.includes(title) to fail. The test runner needs console.log(out.raw).
Code Quality & Maintainability — Comment
3. Regex parsing of git output is fragile
The regex /[\[\S+(?:\s+\([^)]+\))?\s+([a-f0-9]+)\]\s+(.+)/ targets the standard [branch hash] summary format, but git output varies by detached HEAD state, rebase/merge states, non-English locale, and custom format.pretty overrides. A mismatch silently produces hash: undefined, summary: undefined. Consider adding a fallback when the regex doesn't match.
Testing ✅
The test suite itself is well-structured:
- Isolated environments: Each test creates a temp dir, initializes a real git repo, and cleans up via
try/finally— clean and reliable. - Coverage: All 5 exported functions are tested (9 tests) covering happy paths and error paths.
- Error cases:
checkGitRepo()throws outside a repo,commit()throws when nothing is staged,getStagedDiff()returns empty when nothing is staged. - Conventions: Uses
node:test/node:assert/strictmatching existing project pattern. Imports compiled.jsfromdist/. ✅
Performance, Security, Documentation ✅
No concerns. No new dependencies, no security-sensitive changes.
Verdict: Request changes
Must fix before merge:
- Update
src/commands/suggest.tslines 149 and 210 to useresult.rawinstead of bareresult(since.trim()is called on the value). - Update
tests/commit-echo.test.mjsto handle the newCommitResultreturn type (changeconsole.log(out)toconsole.log(out.raw)).
Recommended (non-blocking):
3. Add a fallback in commit() when the regex doesn't match the git output format, so hash and summary are explicitly undefined rather than silently failing.
|
Hi @lb1192176991-lab, just checking in on this PR. Are you still planning to work on it, or has it been abandoned? |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 1 high |
🟢 Metrics 21 complexity · 0 duplication
Metric Results Complexity 21 Duplication 0
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
The PR introduces a comprehensive test suite and refactors git utility functions, but several issues prevent it from meeting quality standards. Codacy analysis indicates the PR is not up to standards due to new quality and security issues. Key concerns include a breaking change to the commit function API that is not explicitly documented as such, and the use of fragile regex for parsing human-readable git porcelain output which is susceptible to ReDoS and fails in detached HEAD states. Additionally, the test suite introduces global state side effects and resource leaks that could affect CI stability. Addressing these architectural and security concerns is required before merging.
About this PR
- Multiple functions rely on parsing human-readable git output (porcelain). This is fragile as output varies between Git versions and system locales. It is recommended to use plumbing commands (e.g.,
git rev-parse) which are designed for programmatic consumption. - The refactoring of the
commitfunction introduces a breaking change to the public API by changing the return type from astringto an object. This should be explicitly documented in the PR description to alert consumers.
2 comments outside of the diff
src/git/diff.ts
line 50🔴 HIGH RISK
The file path used inwriteFileSyncis constructed from a variable, which triggers a security audit for potential path traversal. Ensure the resulting path is strictly validated or contained within the intended temporary directory.
line 17🟡 MEDIUM RISK
Suggestion: Replace the logical OR (||) with the nullish coalescing operator (??) for safer default value assignment when handling errors. Note that ifstderris an empty string, the error message will now be empty.throw new Error(stderr ?? 'Not a git repository');
Test suggestions
- checkGitRepo returns successfully when inside a git repository
- checkGitRepo throws an error when executed outside a git repository
- getStagedDiff returns a DiffResult with content and hasChanges=true when changes are staged
- getStagedDiff returns empty string and hasChanges=false when no changes are staged
- getUnstagedDiff detects modifications to tracked files
- commit successfully creates a commit and returns the hash and summary line
- commit correctly incorporates an optional body into the full commit message
- commit throws an error when there are no changes to commit
- getRepoRoot returns the correct absolute path to the initialized repository
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| return result.stdout; | ||
| const raw = result.stdout; | ||
| // Parse "[branch hash] summary" or "[branch (extra) hash] summary" pattern | ||
| const match = raw.match(/\[\S+(?:\s+\([^)]+\))?\s+([a-f0-9]+)\]\s+(.+)/); |
There was a problem hiding this comment.
🔴 HIGH RISK
The regular expression used to parse git commit output is both fragile and insecure. It will fail to match branch descriptions in a 'detached HEAD' state (where branch names contain spaces) and is susceptible to ReDoS (Regular Expression Denial of Service) due to its structure. It is safer to use a non-greedy approach or, preferably, use plumbing commands like git rev-parse HEAD for reliable results.
| const match = raw.match(/\[\S+(?:\s+\([^)]+\))?\s+([a-f0-9]+)\]\s+(.+)/); | |
| const match = raw.match(/\[[^\]]+?\s+([a-f0-9]+)\]\s+(.+)/); |
|
|
||
| import { checkGitRepo, getStagedDiff, getUnstagedDiff, commit, getRepoRoot } from '../dist/git/diff.js'; | ||
|
|
||
| function createGitRepo() { |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: Using process.chdir() modifies the global state of the Node.js process, which prevents parallel test execution and can cause race conditions. Additionally, temporary directories created with mkdtempSync are not cleaned up, leading to resource leaks. Consider refactoring functions to accept a cwd parameter and using a finally block with fs.rmSync for cleanup.
| process.chdir(dir); | ||
| const result = commit('feat: add test file'); | ||
| assert.ok(result.hash, 'commit should return a hash'); | ||
| assert.equal(result.hash.length, 7); |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: Asserting that the hash length is exactly 7 is fragile as short hashes can vary in length. Validate that it is a valid hex string of at least 7 characters instead.
| assert.equal(result.hash.length, 7); | |
| assert.ok(/^[a-f0-9]{7,}$/.test(result.hash), 'commit should return a valid hex hash'); |
What
Added comprehensive unit tests for all exported functions in
src/git/diff.tsusing Node's built-innode:testandnode:assert/strict.Why
Git operations are central to the tool but had no test coverage. These tests validate the core git interaction layer in isolated temporary repositories.
Test coverage
checkGitRepo()— returns successfully inside a git repo, throws outsidegetStagedDiff()— returns diff when staged, empty when nothing stagedgetUnstagedDiff()— detects unstaged changes on tracked filescommit()— returns hash and summary, works with body, throws on empty commitgetRepoRoot()— returns the absolute path of the repository rootTesting
npm run build && node --test tests/git-diff.test.mjs— all 9 tests pass