test/git-summary.test.js
Rama main · réplica de lectura del repositorio original.
// The cheap repository summary that backs the sidebar placard. It must be
// narrower than the panel's full status: no per-file payload, no filesystem
// path, no credentials, and identity derived here rather than by the client.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var { execFileSync } = require("child_process");
var gitCli = require("../lib/git-cli");
var projectHttpSource = fs.readFileSync(path.join(__dirname, "../lib/project-http.js"), "utf8");
var gitCliSource = fs.readFileSync(path.join(__dirname, "../lib/git-cli.js"), "utf8");
function git(cwd, args) {
return execFileSync("git", args, { cwd: cwd, encoding: "utf8" });
}
function createRepository(t) {
var repo = fs.mkdtempSync(path.join(os.tmpdir(), "clay-git-summary-"));
t.after(function () { fs.rmSync(repo, { recursive: true, force: true }); });
git(repo, ["init", "-q", "-b", "main"]);
git(repo, ["config", "user.name", "Clay Test"]);
git(repo, ["config", "user.email", "clay@example.test"]);
fs.writeFileSync(path.join(repo, "tracked.txt"), "before\n");
git(repo, ["add", "tracked.txt"]);
git(repo, ["commit", "-qm", "initial"]);
return repo;
}
test("a non-repository directory reports exactly that and nothing else", async function (t) {
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "clay-git-plain-"));
t.after(function () { fs.rmSync(dir, { recursive: true, force: true }); });
var summary = gitCli.getSummary(dir);
assert.deepEqual(summary, { isRepository: false },
"the client has nothing to render, so nothing is described");
});
test("the summary reports identity, branch and bounded counts", async function (t) {
var repo = createRepository(t);
fs.writeFileSync(path.join(repo, "tracked.txt"), "after\n");
fs.writeFileSync(path.join(repo, "staged.txt"), "staged\n");
fs.writeFileSync(path.join(repo, "untracked.txt"), "new\n");
git(repo, ["add", "staged.txt"]);
var summary = gitCli.getSummary(repo);
assert.equal(summary.isRepository, true);
assert.equal(summary.name, path.basename(repo), "identity is derived server-side");
assert.equal(summary.branch, "main");
assert.equal(summary.detached, false);
assert.equal(summary.changed, 3);
assert.equal(summary.staged, 1);
assert.equal(summary.unstaged, 1, "the untracked file is not counted as unstaged");
assert.equal(summary.untracked, 1);
assert.equal(summary.conflicted, 0);
assert.equal(summary.hasUpstream, false, "no upstream means no sync claim");
assert.equal(summary.ahead, 0);
assert.equal(summary.behind, 0);
assert.equal(summary.isWorktree, false);
assert.equal(summary.remote, null);
assert.equal(summary.shortOid.length, 8);
});
test("the summary never returns a path or a file list", async function (t) {
var repo = createRepository(t);
fs.writeFileSync(path.join(repo, "secret-name.txt"), "x\n");
var summary = gitCli.getSummary(repo);
var keys = Object.keys(summary);
assert.equal(keys.indexOf("root"), -1);
assert.equal(keys.indexOf("gitDir"), -1);
assert.equal(keys.indexOf("commonDir"), -1);
assert.equal(keys.indexOf("mainWorktree"), -1);
assert.equal(keys.indexOf("worktrees"), -1);
assert.equal(keys.indexOf("files"), -1);
assert.equal(keys.indexOf("upstream"), -1, "only the boolean hasUpstream is exposed");
assert.equal(keys.indexOf("origin"), -1, "only the shortened label is exposed");
var serialized = JSON.stringify(summary);
assert.equal(serialized.indexOf(repo), -1, "the repository path does not leak");
assert.equal(serialized.indexOf("secret-name.txt"), -1, "changed paths do not leak");
assert.equal(summary.changed, 1, "the count is still reported");
});
test("a detached head is reported without inventing a branch name", async function (t) {
var repo = createRepository(t);
var head = git(repo, ["rev-parse", "HEAD"]).trim();
git(repo, ["checkout", "-q", "--detach", head]);
var summary = gitCli.getSummary(repo);
assert.equal(summary.detached, true);
assert.equal(summary.branch, null);
assert.equal(summary.shortOid, head.slice(0, 8));
});
test("a linked worktree is identified without exposing the main checkout path", async function (t) {
var repo = createRepository(t);
var worktree = path.join(repo, "..", path.basename(repo) + "-wt");
git(repo, ["worktree", "add", "-q", "-b", "feat/placard", worktree]);
t.after(function () {
try { git(repo, ["worktree", "remove", "--force", worktree]); } catch (e) {}
fs.rmSync(worktree, { recursive: true, force: true });
});
var summary = gitCli.getSummary(worktree);
assert.equal(summary.isRepository, true);
assert.equal(summary.isWorktree, true);
assert.equal(summary.branch, "feat/placard");
assert.equal(JSON.stringify(summary).indexOf(repo), -1);
var main = gitCli.getSummary(repo);
assert.equal(main.isWorktree, false);
assert.equal(main.branch, "main");
});
test("worktree detection survives a symlinked working directory", async function (t) {
// macOS puts temp dirs behind /var -> /private/var. git answers
// --absolute-git-dir resolved and --git-common-dir relative, so comparing
// the raw strings reported every ordinary repository as a linked worktree.
var repo = createRepository(t);
assert.equal(gitCli.getSummary(repo).isWorktree, false);
assert.equal(gitCli.getStatus(repo).isWorktree, false,
"the full status agrees, so the panel and the placard cannot disagree");
});
test("the remote label is bounded and credential-free", function () {
var label = gitCli.shortRemoteLabel;
assert.equal(label("git@github.com:ThroughLineCare/throughline-portal.git"),
"github.com/ThroughLineCare/throughline-portal");
assert.equal(label("https://github.com/ThroughLineCare/throughline-portal.git"),
"github.com/ThroughLineCare/throughline-portal");
assert.equal(label("ssh://git@example.com:2222/team/repo.git"),
"example.com:2222/team/repo");
assert.equal(label("https://someone:ghp_secrettoken@github.com/team/repo.git"),
"github.com/team/repo", "an embedded token is dropped, not merely hidden");
assert.equal(label("https://github.com/team/repo/"), "github.com/team/repo");
assert.equal(label(null), null);
assert.equal(label(""), null);
var long = label("https://github.com/" + "x".repeat(200) + "/repo.git");
assert.ok(long.length <= 64, "the label is bounded");
assert.match(long, /…$/, "truncation is visible rather than silent");
});
test("the summary is cheaper than the full status by construction", function () {
var summary = gitCliSource.slice(gitCliSource.indexOf("function getSummary(cwd, osUserInfo)"));
summary = summary.slice(0, summary.indexOf("function resolveChangedPaths"));
// One combined rev-parse, one status, one remote lookup.
assert.equal((summary.match(/runGitSync\(/g) || []).length, 3,
"three git invocations, against the full status's six");
assert.match(summary, /"rev-parse", "--is-inside-work-tree", "--show-toplevel",\s*\n?\s*"--absolute-git-dir", "--git-common-dir",/,
"the identity probe is a single invocation");
assert.equal(/worktree", "list|"diff"|"show"/.test(summary), false,
"no worktree enumeration and no diff work");
assert.equal(/decorateStatus/.test(summary), false, "no session attribution");
});
test("the endpoint is registered behind the same permission gate as the panel", function () {
assert.match(projectHttpSource,
/var isGitPanelRequest = urlPath === "\/api\/git\/status" \|\| urlPath === "\/api\/git\/summary"/,
"the summary is gated exactly like the rest of the Git surface");
var gate = projectHttpSource.slice(projectHttpSource.indexOf("if (isGitPanelRequest && usersModule.isMultiUser())"));
gate = gate.slice(0, gate.indexOf("if (req.method === \"GET\" && urlPath === \"/api/git/status\")"));
assert.match(gate, /!gitPermissions\.fileBrowser/);
assert.match(gate, /Git access is not permitted/);
var handler = projectHttpSource.slice(projectHttpSource.indexOf('urlPath === "/api/git/summary") {'));
handler = handler.slice(0, handler.indexOf('urlPath.indexOf("/api/git/file-diff?")'));
assert.match(handler, /gitCli\.getSummary\(cwd, getOsUserInfoForReq\(req\)\)/,
"the OS user identity is applied, same as every other Git read");
assert.match(handler, /"Cache-Control": "no-store"/);
assert.match(handler, /res\.writeHead\(500/, "a failure is reported rather than faked as an empty repo");
});