CodeNomad/packages/server/src/workspaces/__tests__/git-clone.test.ts
Omer Cohen b2855f4312
fix: handle plugin base URL for host binding (#512)
## Summary
- Resolve the plugin fetch/SSE base URL from actual listener bindings
instead of always publishing 127.0.0.1.
- Preserve loopback plugin loading for default local, localhost,
wildcard, and mixed HTTP/HTTPS listener setups.
- Make the git clone destination test portable so server tests run with
zero skips.

## Validation
- npm run typecheck --workspace @neuralnomads/codenomad
- node --import tsx --test
packages/server/src/server/__tests__/listener-base-url.test.ts
packages/server/src/server/__tests__/network-addresses.test.ts
packages/server/src/workspaces/__tests__/git-clone.test.ts — 15 pass / 0
fail / 0 skipped
- shopt -s globstar && node --import tsx --test
packages/server/src/**/*.test.ts — 59 pass / 0 fail / 0 skipped

## Notes
- Nomad task/evidence artifacts intentionally excluded from the commit
per maintainer request.
2026-05-31 19:10:34 +01:00

186 lines
6.1 KiB
TypeScript

import assert from "node:assert/strict"
import { execFileSync } from "node:child_process"
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { homedir, tmpdir } from "node:os"
import path from "node:path"
import { describe, it } from "node:test"
import { cloneGitRepository } from "../git-clone"
function createBareRepository(repoPath: string): void {
execFileSync("git", ["init", "--bare", repoPath], { stdio: "ignore" })
}
async function expectCloneError(
callback: () => Promise<unknown>,
expectedStatusCode: number,
messagePattern: RegExp,
): Promise<void> {
await assert.rejects(callback, (error: unknown) => {
assert.equal(typeof error, "object")
assert.equal((error as { statusCode?: number }).statusCode, expectedStatusCode)
assert.match(String((error as { message?: string }).message ?? ""), messagePattern)
return true
})
}
describe("cloneGitRepository", () => {
it("clones into a missing destination", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
const destinationPath = path.join(temp, "cloned-repo")
try {
createBareRepository(sourceRepo)
const result = await cloneGitRepository({
repositoryUrl: sourceRepo,
destinationPath,
})
assert.equal(result.path, destinationPath)
assert.equal(existsSync(path.join(destinationPath, ".git")), true)
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("rejects non-empty destinations when cleanup is not enabled", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
const destinationPath = path.join(temp, "existing-destination")
const sentinelPath = path.join(destinationPath, "keep.txt")
try {
createBareRepository(sourceRepo)
mkdirSync(destinationPath, { recursive: true })
writeFileSync(sentinelPath, "keep")
await expectCloneError(
() => cloneGitRepository({ repositoryUrl: sourceRepo, destinationPath }),
409,
/Destination folder is not empty/,
)
assert.equal(readFileSync(sentinelPath, "utf8"), "keep")
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("preserves the existing destination when cleanup is enabled but clone fails", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const missingRepo = path.join(temp, "missing.git")
const destinationPath = path.join(temp, "existing-destination")
const sentinelPath = path.join(destinationPath, "keep.txt")
try {
mkdirSync(destinationPath, { recursive: true })
writeFileSync(sentinelPath, "keep")
await expectCloneError(
() => cloneGitRepository({ repositoryUrl: missingRepo, destinationPath, cleanup: true }),
409,
/does not appear to be a git repository|does not exist|not found/i,
)
assert.equal(readFileSync(sentinelPath, "utf8"), "keep")
assert.equal(existsSync(destinationPath), true)
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("replaces the existing destination only after a successful cleanup clone", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
const destinationPath = path.join(temp, "existing-destination")
const sentinelPath = path.join(destinationPath, "keep.txt")
try {
createBareRepository(sourceRepo)
mkdirSync(destinationPath, { recursive: true })
writeFileSync(sentinelPath, "keep")
const result = await cloneGitRepository({
repositoryUrl: sourceRepo,
destinationPath,
cleanup: true,
})
assert.equal(result.path, destinationPath)
assert.equal(existsSync(path.join(destinationPath, ".git")), true)
assert.equal(existsSync(sentinelPath), false)
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("rejects filesystem root destinations", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
try {
createBareRepository(sourceRepo)
await expectCloneError(
() =>
cloneGitRepository({
repositoryUrl: sourceRepo,
destinationPath: path.parse(process.cwd()).root,
cleanup: true,
}),
400,
/filesystem root/,
)
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("rejects home directory destinations", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
try {
createBareRepository(sourceRepo)
await expectCloneError(
() =>
cloneGitRepository({
repositoryUrl: sourceRepo,
destinationPath: homedir(),
cleanup: true,
}),
400,
/home folder/,
)
} finally {
rmSync(temp, { recursive: true, force: true })
}
})
it("supports destinations directly under a safe parent", async () => {
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-clone-"))
const sourceRepo = path.join(temp, "source.git")
const root = path.parse(process.cwd()).root
const parentPath = process.platform === "win32" ? root : temp
const destinationPath = path.join(parentPath, `codenomad-git-clone-root-${Date.now()}-${Math.random().toString(36).slice(2)}`)
try {
createBareRepository(sourceRepo)
rmSync(destinationPath, { recursive: true, force: true })
const result = await cloneGitRepository({
repositoryUrl: sourceRepo,
destinationPath,
})
assert.equal(result.path, destinationPath)
assert.equal(existsSync(destinationPath), true)
} finally {
rmSync(destinationPath, { recursive: true, force: true })
rmSync(temp, { recursive: true, force: true })
}
})
})