Skip to content

Commit 49a3dbd

Browse files
authored
Merge pull request #2 from gitdock-dev/feat/test-suite-ci-and-lib
test: CI suite, shared lib/, and release docs (no version bump)
2 parents 06d2ee9 + 52267af commit 49a3dbd

16 files changed

Lines changed: 907 additions & 209 deletions

.github/pull_request_template.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Summary
2+
3+
<!-- What does this PR change and why? -->
4+
5+
## Type of change
6+
7+
- [ ] Bug fix (user-visible)
8+
- [ ] Feature (user-visible)
9+
- [ ] Documentation / site only
10+
- [ ] Tests, CI, or refactor (no user-visible change)
11+
12+
## Checklist
13+
14+
- [ ] I ran `npm test` locally (required for code changes)
15+
- [ ] I smoke-tested with `npm start` if `dashboard.html` or API behavior changed
16+
- [ ] No secrets in commits (`config.json` tokens, `.env`, PATs)
17+
- [ ] Updated `CHANGELOG.md` under **Unreleased** when the change is notable
18+
19+
## Release
20+
21+
- [ ] **No GitHub Release needed** (default for tests, docs, CI-only)
22+
- [ ] **Release needed** — describe why users should download a new build:
23+
24+
<!-- e.g. packaged binary, breaking setup, critical security fix -->
25+
26+
## Test plan
27+
28+
<!-- Steps a reviewer can follow -->

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: "22"
18+
19+
- name: Install dependencies
20+
run: npm install
21+
22+
- name: Run test suite
23+
run: npm test

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Automated test suite (`npm test`) using Node.js built-in test runner and supertest.
13+
- Shared libraries under `lib/` for security validation, git status parsing, and dormant repo logic.
14+
- GitHub Actions workflow to run tests on push and pull requests to `main`.
15+
- `CHANGELOG.md`, pull request template, and release policy in `CONTRIBUTING.md`.
16+
17+
### Changed
18+
19+
- `server.js` imports validation helpers from `lib/` (behavior preserved; easier to test).
20+
- Account name validation now rejects names that require stripping unsafe characters (e.g. `bad name!`, `work;rm`).
21+
22+
### Security
23+
24+
- Stricter `sanitizeAccountName` so shell-like input cannot be silently normalized into a valid account id.
25+
26+
## [1.2.0] - 2026-05-21
27+
28+
### Added
29+
30+
- Five-step account setup with GitHub-aligned SSH and fine-grained PAT guidance.
31+
- Optional token storage in the OS credential store (not in `config.json`).
32+
- Official GitHub `known_hosts` fingerprints and SSH verify feedback in setup status.
33+
- Dormant repo filter and terminology (replaces stale/inactive).
34+
- Modal close on outside click and Escape; segmented repo action bar; cloned repo green border.
35+
36+
### Changed
37+
38+
- Activity Log removed; feedback via toasts and SSE.
39+
- README, privacy page, and site copy aligned with real token and dormant behavior.
40+
41+
[Unreleased]: https://github.com/gitdock-dev/gitdock/compare/v1.2.0...HEAD
42+
[1.2.0]: https://github.com/gitdock-dev/gitdock/releases/tag/v1.2.0

CONTRIBUTING.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ npm start
4444

4545
Hub runs at **http://localhost:3848** by default.
4646

47+
## Testing
48+
49+
GitDock uses the Node.js built-in test runner (`node --test`) plus [supertest](https://github.com/ladjs/supertest) for HTTP checks.
50+
51+
| Command | What it runs |
52+
|---------|----------------|
53+
| `npm test` | Full suite (security, git parsers, dormant logic, API integration) |
54+
| `npm run test:unit` | Pure library tests only (no server) |
55+
| `npm run test:api` | Express API integration tests in an isolated temp workspace |
56+
57+
Tests live under `test/`. Shared validation logic is in `lib/` so the server and tests stay aligned.
58+
59+
Before opening a PR, run `npm test` and ensure it passes.
60+
61+
## Releases and versioning
62+
63+
We use [Semantic Versioning](https://semver.org/). Not every merged PR becomes a GitHub Release.
64+
65+
| Change type | Version bump | GitHub Release (binaries) |
66+
|-------------|--------------|---------------------------|
67+
| Bug fix users depend on | Patch (e.g. 1.2.1) | Yes, when the fix matters for downloaded builds |
68+
| New UX or API behavior | Minor (e.g. 1.3.0) | Yes, when bundling several user-visible changes |
69+
| Tests, CI, docs, refactors | None required | No |
70+
71+
Track user-facing work in [CHANGELOG.md](CHANGELOG.md) under **Unreleased**. When cutting a release, move items to a version section, bump `package.json`, tag `vX.Y.Z`, and publish the release (that triggers the build workflow).
72+
4773
## Code style
4874

4975
- **Language:** All code and comments in English.
@@ -55,7 +81,7 @@ Hub runs at **http://localhost:3848** by default.
5581
## Submitting changes
5682

5783
1. **Fork** the repository and create a branch from `main` (e.g. `feat/your-feature` or `fix/issue-description`).
58-
2. **Make your changes** and test locally (`npm start` and, if relevant, the Hub).
84+
2. **Make your changes** and run the automated suite: `npm test` (unit + API integration). Also smoke-test the dashboard when UI changes: `npm start`.
5985
3. **Commit** with clear messages (e.g. `feat: add X`, `fix: resolve Y`).
6086
4. **Push** to your fork and open a **Pull Request** against `gitdock-dev/gitdock` `main`.
6187
5. Describe what you changed and why. Reference any related issues.

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ npm run dev
343343

344344
# Check if it's running
345345
curl http://127.0.0.1:3847/api/health
346+
347+
# Run automated tests (contributors)
348+
npm test
346349
```
347350

348351
### GitHub CLI
@@ -526,7 +529,7 @@ In the local GitDock dashboard use the dashboard’s **Configure Hub** to set th
526529

527530
## Contributing
528531

529-
We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style (vanilla JS, no frameworks), and how to submit pull requests and report issues.
532+
We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, `npm test`, release policy, code style (vanilla JS, no frameworks), and how to submit pull requests and report issues. See [CHANGELOG.md](CHANGELOG.md) for version history.
530533

531534
---
532535

lib/dormant.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Dormant repo detection (mirrors dashboard logic for tests and docs).
3+
*/
4+
5+
function isDormant(repo, dormantDays) {
6+
const days = Number(dormantDays);
7+
if (!Number.isFinite(days) || days < 1) return false;
8+
const refDate =
9+
repo.isCloned && repo.lastCommit && repo.lastCommit.date
10+
? repo.lastCommit.date
11+
: repo.updatedAt;
12+
if (!refDate) return false;
13+
const elapsed = Math.floor((Date.now() - new Date(refDate)) / 86400000);
14+
return elapsed > days;
15+
}
16+
17+
function dormantPeriodLabel(dormantDays) {
18+
const d = Number(dormantDays);
19+
if (d <= 30) return "1 month";
20+
if (d <= 90) return "3 months";
21+
if (d <= 180) return "6 months";
22+
return "1 year";
23+
}
24+
25+
module.exports = {
26+
isDormant,
27+
dormantPeriodLabel,
28+
};

lib/git-parse.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Pure parsers for git command output (used by server and tests).
3+
*/
4+
5+
function parseStatusPorcelain(output) {
6+
const lines = output ? output.split("\n").filter(Boolean) : [];
7+
const files = [];
8+
let stagedCount = 0;
9+
let unstagedCount = 0;
10+
let untrackedCount = 0;
11+
let conflictCount = 0;
12+
for (const line of lines) {
13+
const xy = line.slice(0, 2);
14+
const x = xy[0];
15+
const y = xy[1];
16+
const filePath = line.slice(3).trim().replace(/^["']|["']$/g, "");
17+
if (filePath) {
18+
let status = "modified";
19+
if (xy === "??") status = "untracked";
20+
else if (x === "A" || x === "M" || x === "D" || x === "R" || x === "C") status = "added";
21+
else if (y === "M" || y === "D") status = "modified";
22+
else if (x === "D" || y === "D") status = "deleted";
23+
else if (x === "U" || y === "U") status = "unmerged";
24+
files.push({ path: filePath, status });
25+
}
26+
if (xy === "??") {
27+
untrackedCount += 1;
28+
} else {
29+
if (x !== " " && x !== "?") stagedCount += 1;
30+
if (y !== " " && y !== "?") unstagedCount += 1;
31+
if (x === "U" || y === "U") conflictCount += 1;
32+
}
33+
}
34+
return { files, summary: { stagedCount, unstagedCount, untrackedCount, conflictCount } };
35+
}
36+
37+
/** Parse "git status -sb" first line for branch, upstream, ahead, behind */
38+
function parseStatusBranchLine(line) {
39+
if (!line || !line.startsWith("## ")) {
40+
return { branch: "unknown", hasUpstream: false, ahead: 0, behind: 0, upstreamRef: null };
41+
}
42+
const rest = line.slice(3).trim();
43+
const branchMatch = rest.match(/^([^\s.]+)(?:\.\.\.(\S+))?(?:\s+\[(.*)\])?/);
44+
const branch = branchMatch ? branchMatch[1] : "unknown";
45+
const upstreamRef = branchMatch && branchMatch[2] ? branchMatch[2] : null;
46+
const bracket = branchMatch && branchMatch[3] ? branchMatch[3] : "";
47+
let ahead = 0;
48+
let behind = 0;
49+
const aheadM = bracket.match(/ahead\s+(\d+)/);
50+
const behindM = bracket.match(/behind\s+(\d+)/);
51+
if (aheadM) ahead = parseInt(aheadM[1], 10) || 0;
52+
if (behindM) behind = parseInt(behindM[1], 10) || 0;
53+
return { branch, hasUpstream: !!upstreamRef, ahead, behind, upstreamRef };
54+
}
55+
56+
module.exports = {
57+
parseStatusPorcelain,
58+
parseStatusBranchLine,
59+
};

0 commit comments

Comments
 (0)