bc218dfbbe
* feat: add workspace sources from files panel * fix: preserve worktree identity during recovery * feat: unify workspace source actions * fix: validate recovered workspace repositories * fix: harden workspace source filesystem boundaries * test: split workspace repository chip coverage * fix: restrict repository rescan recovery paths * fix: stabilize workspace source CI checks * test: canonicalize workspace source paths * test: align workspace source container paths * fix: populate direct workspace repository names * test: preserve git fixture rewrites across restarts * test: isolate backend git fixture config * fix: resume remote workspace executions before creation * fix: preserve ssh agent identity during resume * fix: preserve SSH workspace across backend restart * fix: simplify workspace actions trigger * fix: refine add workspace repositories UX * docs: narrow workspace source UX updates * fix: avoid phantom turn on session resume * test(web): normalize workspace source request headers * feat(web): streamline workspace source picker * docs: remove trailing spaces from workspace source plan * fix: prevent resume status from creating phantom turns * fix(web): stabilize workspace source attachment * refactor(web): extract workspace source store contract * fix: avoid phantom turns on idle agent resume * fix: address backend static analysis findings * fix: preserve agentctl readiness status value * fix: preserve resume messages without active turns * fix: resume agents from promoted workspace root * chore: merge origin/main into feature/add-new-repo-folder-d97 * feat(web): explain workspace source consequences * test(websocket): count session activity subscription
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { startHTTPGitFixture } from "../e2e/helpers/http-git-server";
|
|
import { execFileSync } from "node:child_process";
|
|
import { type Server } from "node:http";
|
|
import fs from "node:fs";
|
|
import net from "node:net";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
test("uses a trusted GitLab URL with isolated executor and backend Git rewrites", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "kandev-http-git-test-"));
|
|
try {
|
|
const fixture = await startHTTPGitFixture(root, "rewrite-source");
|
|
try {
|
|
expect(fixture.remoteURL).toBe("https://gitlab.com/fixture/rewrite-source.git");
|
|
expect(fixture.gitConfigEnvVars).toEqual([
|
|
{ key: "GIT_CONFIG_COUNT", value: "1" },
|
|
{
|
|
key: "GIT_CONFIG_KEY_0",
|
|
value: expect.stringMatching(/^url\.http:\/\/[^/]+\/.insteadOf$/),
|
|
},
|
|
{ key: "GIT_CONFIG_VALUE_0", value: "https://gitlab.com/" },
|
|
]);
|
|
expect(fixture.backendEnv).toEqual({
|
|
GIT_CONFIG_GLOBAL: expect.stringMatching(/rewrite-source\.gitconfig$/),
|
|
});
|
|
expect(fixture.backendEnv.GIT_CONFIG_GLOBAL.startsWith(root)).toBe(true);
|
|
const configuredRewrite = execFileSync(
|
|
"git",
|
|
["config", "--global", "--get-regexp", "^url\\..*\\.insteadOf$"],
|
|
{ env: { ...process.env, ...fixture.backendEnv }, encoding: "utf8" },
|
|
).trim();
|
|
expect(configuredRewrite).toMatch(
|
|
/^url\.http:\/\/[^/]+\/fixture\/rewrite-source\.git\.insteadof https:\/\/gitlab\.com\/fixture\/rewrite-source\.git$/,
|
|
);
|
|
} finally {
|
|
await fixture.close();
|
|
}
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("closes its listener when backend config setup fails after listening", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "kandev-http-git-test-"));
|
|
let port: number | undefined;
|
|
try {
|
|
await expect(
|
|
startHTTPGitFixture(root, "config-write-failure", {
|
|
onListening: (listeningPort) => {
|
|
port = listeningPort;
|
|
},
|
|
writeBackendGitConfig: () => {
|
|
throw new Error("config write failed");
|
|
},
|
|
}),
|
|
).rejects.toThrow("config write failed");
|
|
|
|
expect(port).toBeDefined();
|
|
expect(await canConnect(port!)).toBe(false);
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("aggregates setup and listener-close failures", async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "kandev-http-git-test-"));
|
|
const setupError = new Error("config write failed");
|
|
const closeError = new Error("listener close failed");
|
|
try {
|
|
await expect(
|
|
startHTTPGitFixture(root, "close-failure", {
|
|
writeBackendGitConfig: () => {
|
|
throw setupError;
|
|
},
|
|
closeServer: async (server) => {
|
|
await closeTestServer(server);
|
|
throw closeError;
|
|
},
|
|
}),
|
|
).rejects.toMatchObject({
|
|
errors: [setupError, closeError],
|
|
message: "HTTP Git fixture setup failed and its server did not close",
|
|
});
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function canConnect(port: number): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
const socket = net.connect(port, "127.0.0.1");
|
|
socket.once("connect", () => {
|
|
socket.destroy();
|
|
resolve(true);
|
|
});
|
|
socket.once("error", () => resolve(false));
|
|
});
|
|
}
|
|
|
|
function closeTestServer(server: Server): Promise<void> {
|
|
return new Promise((resolve, reject) =>
|
|
server.close((error) => (error ? reject(error) : resolve())),
|
|
);
|
|
}
|