Files
NetMesh/domain/sftpTransferConflicts.test.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

318 lines
8.1 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { TransferTask } from "./models";
import {
findActivePathConflict,
pathConflictMessage,
} from "./sftpTransferConflicts";
const base = (overrides: Partial<TransferTask> = {}): TransferTask => ({
id: "a",
fileName: "sing-box",
sourcePath: "/root/sing-box",
targetPath: "/Users/me/Desktop/sing-box",
sourceConnectionId: "remote",
targetConnectionId: "local",
direction: "download",
status: "transferring",
totalBytes: 100,
transferredBytes: 10,
speed: 1,
startTime: 1,
isDirectory: false,
...overrides,
});
test("findActivePathConflict matches same destination among active rows", () => {
const tasks = [
base({ id: "live", status: "transferring" }),
base({ id: "done", status: "completed", transferredBytes: 100 }),
base({ id: "other-path", targetPath: "/tmp/other" }),
];
assert.equal(
findActivePathConflict(tasks, {
id: "new",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "local",
})?.id,
"live",
);
assert.equal(
findActivePathConflict(tasks, {
id: "live",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "local",
}),
undefined,
);
});
test("findActivePathConflict ignores identical paths on different endpoints", () => {
const tasks = [
base({
id: "host-a",
direction: "upload",
sourcePath: "/local/file",
targetPath: "/remote/file",
sourceConnectionId: "local",
targetConnectionId: "conn-a",
targetHostId: "host-a",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "host-b",
targetPath: "/remote/file",
targetConnectionId: "conn-b",
targetHostId: "host-b",
}),
undefined,
);
});
test("findActivePathConflict collides different sources writing one destination", () => {
const tasks = [
base({
id: "from-a",
sourcePath: "/root/a",
targetPath: "/Users/me/Desktop/out.bin",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "from-b",
targetPath: "/Users/me/Desktop/out.bin",
targetConnectionId: "local",
})?.id,
"from-a",
);
});
test("findActivePathConflict requires local endpoint on the candidate (downloadToLocal regression)", () => {
// createDirectDownloadTransferTask stores targetConnectionId:"local".
// A bare { targetPath } candidate is not isLocalTransferDestination — it
// used to miss every active download and allow duplicate concurrent writers.
const tasks = [
base({
id: "active-download",
targetPath: "/Users/me/Downloads/frpc.tar",
targetConnectionId: "local",
targetHostLabel: "Local",
status: "transferring",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "broken-candidate",
targetPath: "/Users/me/Downloads/frpc.tar",
}),
undefined,
"missing local endpoint must not false-match (documents the bug shape)",
);
assert.equal(
findActivePathConflict(tasks, {
id: "fixed-candidate",
targetPath: "/Users/me/Downloads/frpc.tar",
targetConnectionId: "local",
targetHostLabel: "Local",
})?.id,
"active-download",
);
});
test("findActivePathConflict treats local pane id and local sentinel as one endpoint", () => {
const tasks = [
base({
id: "dual-pane",
targetConnectionId: "right-1710000000000",
targetHostLabel: "Local",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "save-as",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "local",
})?.id,
"dual-pane",
);
// Dual-pane enqueue normalizes local candidates to the "local" sentinel + label.
assert.equal(
findActivePathConflict(tasks, {
id: "other-pane",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "local",
targetHostLabel: "Local",
})?.id,
"dual-pane",
);
// Two stored dual-pane local rows (ephemeral pane ids) still collide.
assert.equal(
findActivePathConflict(
[
...tasks,
base({
id: "left-pane",
targetConnectionId: "left-1710000000001",
targetHostLabel: "Local",
status: "pending",
}),
],
{
id: "left-pane",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "left-1710000000001",
targetHostLabel: "Local",
},
)?.id,
"dual-pane",
);
});
test("interrupted is not an active path conflict (resume may claim the path)", () => {
const tasks = [base({ id: "dead", status: "interrupted" })];
assert.equal(
findActivePathConflict(tasks, {
id: "resume",
targetPath: "/Users/me/Desktop/sing-box",
targetConnectionId: "local",
}),
undefined,
);
});
test("findActivePathConflict includes active directory child transfers", () => {
const tasks = [
base({
id: "dir-parent",
fileName: "bundle",
targetPath: "/Users/me/Desktop/bundle",
isDirectory: true,
status: "transferring",
}),
base({
id: "dir-child",
fileName: "out.bin",
targetPath: "/Users/me/Desktop/bundle/out.bin",
parentTaskId: "dir-parent",
status: "transferring",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "standalone",
targetPath: "/Users/me/Desktop/bundle/out.bin",
targetConnectionId: "local",
})?.id,
"dir-child",
);
// Parent directory path alone is not the same destination as a nested file.
assert.equal(
findActivePathConflict(tasks, {
id: "standalone-dir",
targetPath: "/Users/me/Desktop/bundle",
targetConnectionId: "local",
})?.id,
"dir-parent",
);
});
test("findActivePathConflict reserves descendants of active directory transfers", () => {
const tasks = [
base({
id: "dir-parent",
fileName: "bundle",
targetPath: "/Users/me/Desktop/bundle",
isDirectory: true,
status: "pending",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "standalone",
targetPath: "/Users/me/Desktop/bundle/out.bin",
targetConnectionId: "local",
})?.id,
"dir-parent",
);
assert.equal(
findActivePathConflict(tasks, {
id: "sibling-prefix",
targetPath: "/Users/me/Desktop/bundle-old/out.bin",
targetConnectionId: "local",
}),
undefined,
);
});
test("findActivePathConflict refuses directory that would cover an active descendant", () => {
const tasks = [
base({
id: "live-file",
fileName: "out.bin",
targetPath: "/Users/me/Desktop/bundle/out.bin",
status: "transferring",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "incoming-dir",
targetPath: "/Users/me/Desktop/bundle",
targetConnectionId: "local",
isDirectory: true,
})?.id,
"live-file",
);
// Without isDirectory, a file-named path does not reserve descendants.
assert.equal(
findActivePathConflict(tasks, {
id: "incoming-file",
targetPath: "/Users/me/Desktop/bundle",
targetConnectionId: "local",
isDirectory: false,
}),
undefined,
);
assert.equal(
findActivePathConflict(tasks, {
id: "sibling-dir",
targetPath: "/Users/me/Desktop/bundle-old",
targetConnectionId: "local",
isDirectory: true,
}),
undefined,
);
});
test("findActivePathConflict compares Windows local paths canonically", () => {
const tasks = [
base({
id: "live",
targetPath: "C:\\Downloads\\out.bin",
status: "transferring",
}),
];
assert.equal(
findActivePathConflict(tasks, {
id: "save-as",
targetPath: "c:/downloads/out.bin",
targetConnectionId: "local",
})?.id,
"live",
);
assert.equal(
findActivePathConflict(tasks, {
id: "other",
targetPath: "C:\\Downloads\\other.bin",
targetConnectionId: "local",
}),
undefined,
);
});
test("pathConflictMessage distinguishes paused vs running", () => {
assert.match(pathConflictMessage({ fileName: "x", status: "paused" }), /paused/i);
assert.match(pathConflictMessage({ fileName: "x", status: "transferring" }), /in progress/i);
});