test: preserve Windows PATH casing and exercise real fetch aborts
This commit is contained in:
+8
-3
@@ -8,10 +8,12 @@ import { clearDevinBinCache, findDevinBin, whichDevin, runDevin } from "../src/c
|
|||||||
|
|
||||||
function fixture(t) {
|
function fixture(t) {
|
||||||
const directory = mkdtempSync(join(tmpdir(), "pi devin cli "));
|
const directory = mkdtempSync(join(tmpdir(), "pi devin cli "));
|
||||||
const original = { ...process.env };
|
// Windows environment lookups ignore case; a spread object does not.
|
||||||
|
const keys = ["DEVIN_CLI", "LOCALAPPDATA", "ProgramFiles", "PATH"];
|
||||||
|
const original = Object.fromEntries(keys.map((key) => [key, process.env[key]]));
|
||||||
clearDevinBinCache();
|
clearDevinBinCache();
|
||||||
t.after(() => {
|
t.after(() => {
|
||||||
for (const key of ["DEVIN_CLI", "LOCALAPPDATA", "ProgramFiles", "PATH"]) {
|
for (const key of keys) {
|
||||||
if (original[key] === undefined) delete process.env[key]; else process.env[key] = original[key];
|
if (original[key] === undefined) delete process.env[key]; else process.env[key] = original[key];
|
||||||
}
|
}
|
||||||
clearDevinBinCache(); rmSync(directory, { recursive: true, force: true });
|
clearDevinBinCache(); rmSync(directory, { recursive: true, force: true });
|
||||||
@@ -47,10 +49,13 @@ test("Windows PATH lookup uses where.exe and reads multiple results", { skip: pr
|
|||||||
process.env.LOCALAPPDATA = directory; process.env.ProgramFiles = directory;
|
process.env.LOCALAPPDATA = directory; process.env.ProgramFiles = directory;
|
||||||
const bin = join(directory, "path bin"); mkdirSync(bin);
|
const bin = join(directory, "path bin"); mkdirSync(bin);
|
||||||
const binary = join(bin, "devin.exe"); writeFileSync(binary, "");
|
const binary = join(bin, "devin.exe"); writeFileSync(binary, "");
|
||||||
process.env.PATH = `${bin};${process.env.PATH}`;
|
const otherBin = join(directory, "other bin"); mkdirSync(otherBin);
|
||||||
|
writeFileSync(join(otherBin, "devin.exe"), "");
|
||||||
|
process.env.PATH = `${bin};${otherBin};${process.env.PATH}`;
|
||||||
const located = await whichDevin();
|
const located = await whichDevin();
|
||||||
const probe = spawnSync("where.exe", ["devin.exe"], { encoding: "utf8" });
|
const probe = spawnSync("where.exe", ["devin.exe"], { encoding: "utf8" });
|
||||||
assert.equal(located, binary, JSON.stringify({ status: probe.status, stdout: probe.stdout, stderr: probe.stderr, error: probe.error?.message }));
|
assert.equal(located, binary, JSON.stringify({ status: probe.status, stdout: probe.stdout, stderr: probe.stderr, error: probe.error?.message }));
|
||||||
|
assert.ok(probe.stdout.trim().split(/\r?\n/).length >= 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Windows batch wrappers work in paths with spaces, including inherited stdio", { skip: process.platform !== "win32" }, async (t) => {
|
test("Windows batch wrappers work in paths with spaces, including inherited stdio", { skip: process.platform !== "win32" }, async (t) => {
|
||||||
|
|||||||
@@ -67,3 +67,38 @@ test("aborting an in-flight stream reports aborted without leaking a rejection",
|
|||||||
assert.equal((await stream.result()).stopReason, "aborted");
|
assert.equal((await stream.result()).stopReason, "aborted");
|
||||||
assert.deepEqual(rejections, []);
|
assert.deepEqual(rejections, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("real fetch cancellation after a response chunk leaves the process usable", { timeout: 10_000 }, async (t) => {
|
||||||
|
const { createServer } = await import("node:http");
|
||||||
|
const { once } = await import("node:events");
|
||||||
|
const { encodeString, encodeVarintField, frameConnectStream } = await import("../src/wire.ts");
|
||||||
|
clearCachedUserJwt(); t.after(clearCachedUserJwt);
|
||||||
|
let chats = 0;
|
||||||
|
const server = createServer((req, res) => {
|
||||||
|
if (req.url.endsWith("/GetUserJwt")) {
|
||||||
|
res.end(encodeString(1, "eyJtest.jwt")); return;
|
||||||
|
}
|
||||||
|
chats++;
|
||||||
|
res.writeHead(200, { "Content-Type": "application/connect+proto" });
|
||||||
|
res.write(frameConnectStream(encodeString(3, "started"), false));
|
||||||
|
if (chats > 1) {
|
||||||
|
res.write(frameConnectStream(encodeVarintField(5, 0), false));
|
||||||
|
res.end(Buffer.from([2, 0, 0, 0, 2, 123, 125]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.after(() => { server.closeAllConnections(); server.close(); });
|
||||||
|
server.listen(0, "127.0.0.1"); await once(server, "listening");
|
||||||
|
const ac = new AbortController();
|
||||||
|
const options = { apiKey: "synthetic-test-key", env: { DEVIN_API_SERVER_URL: `http://127.0.0.1:${server.address().port}` } };
|
||||||
|
const stream = streamDevin(model, normalizeContext({ messages: [user("hello")] }), { ...options, signal: ac.signal });
|
||||||
|
let sawText = false;
|
||||||
|
for await (const event of stream) {
|
||||||
|
if (event.type === "text_delta") { sawText = true; ac.abort(); }
|
||||||
|
}
|
||||||
|
assert.equal(sawText, true);
|
||||||
|
assert.equal((await stream.result()).stopReason, "aborted");
|
||||||
|
const next = streamDevin(model, normalizeContext({ messages: [user("again")] }), options);
|
||||||
|
for await (const event of next) assert.notEqual(event.type, "error");
|
||||||
|
assert.equal((await next.result()).stopReason, "stop");
|
||||||
|
assert.equal(chats, 2);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user