[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
"use strict";
const assert = require("node:assert/strict");
const test = require("node:test");
const { Readable } = require("node:stream");
const { EventEmitter } = require("node:events");
const { createFileOpsApi } = require("./sftpBridge/fileOps.cjs");
const MAX_IN_MEMORY_READ_BYTES = 10 * 1024 * 1024;
function createApi(client, overrides = {}) {
return createFileOpsApi({
sftpClients: new Map([["sftp-1", client]]),
resolveEncodingForRequest: () => "utf-8",
encodePath: (value) => value,
Buffer,
...overrides,
});
}
test("SFTP text reads reject oversized files before opening the data stream", async () => {
let streamOpened = false;
const sftp = {
createReadStream() {
streamOpened = true;
throw new Error("oversized file stream should not open");
},
};
const api = createApi({}, {
requireSftpChannel: async () => sftp,
statAsync: async () => ({ size: MAX_IN_MEMORY_READ_BYTES + 1 }),
});
await assert.rejects(
api.readSftp(null, { sftpId: "sftp-1", path: "/large.log" }),
/10 MB.*download.*external app/i,
);
assert.equal(streamOpened, false);
});
test("SFTP text reads still stream ordinary files", async () => {
const sftp = {
createReadStream() {
return Readable.from([Buffer.from("hel"), Buffer.from("lo")]);
},
};
const api = createApi({}, {
requireSftpChannel: async () => sftp,
statAsync: async () => ({ size: 5 }),
});
assert.equal(
await api.readSftp(null, { sftpId: "sftp-1", path: "/small.txt" }),
"hello",
);
});
test("SFTP binary reads reject oversized files before opening the data stream", async () => {
let streamOpened = false;
const sftp = {
createReadStream() {
streamOpened = true;
throw new Error("oversized file stream should not open");
},
};
const api = createApi({}, {
requireSftpChannel: async () => sftp,
statAsync: async () => ({ size: MAX_IN_MEMORY_READ_BYTES + 1 }),
});
await assert.rejects(
api.readSftpBinary(null, { sftpId: "sftp-1", path: "/large.bin" }),
/10 MB.*download.*external app/i,
);
assert.equal(streamOpened, false);
});
test("SFTP reads stop at the byte limit when the remote file grows after stat", async () => {
let legacyGetCalled = false;
const stream = Readable.from([
Buffer.alloc(MAX_IN_MEMORY_READ_BYTES),
Buffer.from("x"),
]);
const sftp = {
createReadStream() {
return stream;
},
};
const api = createApi({
async get() {
legacyGetCalled = true;
return Buffer.alloc(MAX_IN_MEMORY_READ_BYTES + 1);
},
}, {
requireSftpChannel: async () => sftp,
statAsync: async () => ({ size: 1 }),
});
await assert.rejects(
api.readSftp(null, { sftpId: "sftp-1", path: "/growing.log" }),
/10 MB.*download.*external app/i,
);
assert.equal(legacyGetCalled, false);
assert.equal(stream.destroyed, true);
assert.equal(stream.listenerCount("data"), 0);
assert.equal(stream.listenerCount("end"), 0);
assert.equal(stream.listenerCount("error"), 0);
assert.equal(stream.listenerCount("close"), 0);
});
test("SCP reads reject oversized files before starting the transfer", async () => {
let readStarted = false;
const backend = {
async stat() {
return { size: MAX_IN_MEMORY_READ_BYTES + 1 };
},
async readFile() {
readStarted = true;
throw new Error("oversized SCP transfer should not start");
},
};
const api = createApi({
__netcattyFileProtocol: "scp",
__netcattyScpBackend: backend,
});
await assert.rejects(
api.readSftp(null, { sftpId: "sftp-1", path: "/large.log" }),
/10 MB.*download.*external app/i,
);
assert.equal(readStarted, false);
});
test("the total in-memory SFTP read deadline also bounds a stalled stat", async () => {
const api = createApi({}, {
requireSftpChannel: async () => ({}),
statAsync: async () => new Promise(() => {}),
});
await assert.rejects(
Promise.race([
api.readSftp(null, { sftpId: "sftp-1", path: "/stalled.txt", timeoutMs: 5 }),
new Promise((_, reject) => setTimeout(() => reject(new Error("test watchdog")), 100)),
]),
/SFTP in-memory read timed out/i,
);
});
test("a timed-out in-memory SFTP stream is destroyed and releases every listener", async () => {
const stream = new EventEmitter();
let destroyed = 0;
stream.destroy = () => { destroyed += 1; };
stream.close = () => { destroyed += 1; };
const api = createApi({}, {
requireSftpChannel: async () => ({ createReadStream: () => stream }),
statAsync: async () => ({ size: 1 }),
});
await assert.rejects(
Promise.race([
api.readSftpBinary(null, { sftpId: "sftp-1", path: "/stalled.bin", timeoutMs: 5 }),
new Promise((_, reject) => setTimeout(() => reject(new Error("test watchdog")), 100)),
]),
/SFTP in-memory read timed out/i,
);
assert.ok(destroyed > 0);
assert.equal(stream.listenerCount("data"), 0);
assert.equal(stream.listenerCount("end"), 0);
assert.equal(stream.listenerCount("error"), 0);
assert.equal(stream.listenerCount("close"), 0);
});
test("in-memory SFTP stream error and premature close both release listeners", async () => {
for (const terminalEvent of ["error", "close"]) {
const stream = new EventEmitter();
stream.destroy = () => {};
stream.close = () => {};
const api = createApi({}, {
requireSftpChannel: async () => ({ createReadStream: () => stream }),
statAsync: async () => ({ size: 1 }),
});
const reading = api.readSftp(null, { sftpId: "sftp-1", path: "/broken.txt", timeoutMs: 100 });
process.nextTick(() => {
if (terminalEvent === "error") stream.emit("error", new Error("read failed"));
else stream.emit("close");
});
await assert.rejects(reading, /read failed|closed before/i);
assert.equal(stream.listenerCount("data"), 0);
assert.equal(stream.listenerCount("end"), 0);
assert.equal(stream.listenerCount("error"), 0);
assert.equal(stream.listenerCount("close"), 0);
}
});