homeio
PublicForked from doctor-io/homeio
local-sharing.test.ts
import { lstat, mkdtemp, mkdir, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let mockDataRoot = "";
vi.mock("@/lib/server/modules/files/path-resolver", () => {
class FilesPathError extends Error {
code: string;
statusCode: number;
constructor(
message: string,
options?: {
code?: string;
statusCode?: number;
},
) {
super(message);
this.code = options?.code ?? "internal_error";
this.statusCode = options?.statusCode ?? 500;
}
}
return {
FilesPathError,
resolvePathWithinFilesRoot: vi.fn(async (input: {
inputPath?: string;
requiredPrefix?: string;
allowMissingLeaf?: boolean;
}) => {
const cleaned = (input.inputPath ?? "").trim().replaceAll("\\", "/");
const normalized = cleaned ? path.posix.normalize(cleaned) : "";
if (normalized === ".." || normalized.startsWith("../")) {
throw new FilesPathError("Path escapes root", {
code: "path_outside_root",
statusCode: 400,
});
}
if (
input.requiredPrefix &&
normalized !== input.requiredPrefix &&
!normalized.startsWith(`${input.requiredPrefix}/`)
) {
throw new FilesPathError("Invalid path", {
code: "invalid_path",
statusCode: 400,
});
}
return {
rootPath: mockDataRoot,
relativePath: normalized,
absolutePath: path.resolve(mockDataRoot, normalized),
segments: normalized ? normalized.split("/") : [],
exists: true,
};
}),
};
});
vi.mock("@/lib/server/modules/files/local-shares-repository", () => ({
listLocalSharesFromDb: vi.fn(),
getLocalShareFromDb: vi.fn(),
getLocalShareBySourcePathFromDb: vi.fn(),
insertLocalShareInDb: vi.fn(),
deleteLocalShareFromDb: vi.fn(),
}));
vi.mock("node:child_process", () => ({
execFile: vi.fn(),
}));
import { execFile } from "node:child_process";
import {
addLocalFolderShare,
removeLocalFolderShare,
type LocalSharingError,
} from "@/lib/server/modules/files/local-sharing";
import {
deleteLocalShareFromDb,
getLocalShareBySourcePathFromDb,
getLocalShareFromDb,
insertLocalShareInDb,
} from "@/lib/server/modules/files/local-shares-repository";
type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void;
function getExecInvocation(restArgs: unknown[]) {
const callback = restArgs.at(-1);
if (typeof callback !== "function") {
throw new Error("Missing execFile callback");
}
const args = Array.isArray(restArgs[0]) ? (restArgs[0] as string[]) : [];
return {
args,
callback: callback as ExecFileCallback,
};
}
function uniqueViolation(constraint: string, detail: string) {
const error = new Error("duplicate key value violates unique constraint") as Error & {
code?: string;
constraint?: string;
detail?: string;
};
error.code = "23505";
error.constraint = constraint;
error.detail = detail;
return error;
}
describe("local sharing service", () => {
beforeEach(async () => {
mockDataRoot = await mkdtemp(path.join(os.tmpdir(), "home-server-local-sharing-"));
await mkdir(path.join(mockDataRoot, "Media"), { recursive: true });
await mkdir(path.join(mockDataRoot, "Shared"), { recursive: true });
vi.clearAllMocks();
});
afterEach(async () => {
if (mockDataRoot) {
await rm(mockDataRoot, { recursive: true, force: true });
}
});
it("rolls back reserved share and deletes db row when mount fails", async () => {
const reserved = {
id: "local-1",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
};
vi.mocked(getLocalShareBySourcePathFromDb).mockResolvedValueOnce(null);
vi.mocked(insertLocalShareInDb).mockResolvedValueOnce(reserved);
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce(reserved);
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "mount") {
const error = new Error("mount failed") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "permission denied";
callback(error, "", "permission denied");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "delete") {
const error = new Error("does not exist") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "usershare does not exist";
callback(error, "", "usershare does not exist");
return {} as never;
}
if (command === "umount") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
await expect(
addLocalFolderShare({
path: "Media",
}),
).rejects.toMatchObject<Partial<LocalSharingError>>({
code: "permission_denied",
statusCode: 403,
});
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-1");
});
it("maps source-path unique conflict to share_exists", async () => {
vi.mocked(getLocalShareBySourcePathFromDb).mockResolvedValueOnce(null);
vi.mocked(insertLocalShareInDb).mockRejectedValueOnce(
uniqueViolation(
"files_local_shares_source_path_idx",
"Key (source_path)=(Media) already exists.",
),
);
await expect(
addLocalFolderShare({
path: "Media",
}),
).rejects.toMatchObject<Partial<LocalSharingError>>({
code: "share_exists",
statusCode: 409,
});
expect(execFile).not.toHaveBeenCalled();
});
it("retries share-name conflicts and succeeds", async () => {
vi.mocked(getLocalShareBySourcePathFromDb).mockResolvedValue(null);
vi.mocked(insertLocalShareInDb)
.mockRejectedValueOnce(
uniqueViolation(
"files_local_shares_share_name_idx",
"Key (share_name)=(Media) already exists.",
),
)
.mockResolvedValueOnce({
id: "local-2",
shareName: "Media-2",
sourcePath: "Media",
sharedPath: "Shared/Media-2",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & { code?: number };
error.code = 1;
callback(error, "", "");
return {} as never;
}
if (command === "mount") {
callback(null, "", "");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "add") {
callback(null, "", "");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await addLocalFolderShare({
path: "Media",
name: "Media",
});
expect(result.shareName).toBe("Media-2");
expect(insertLocalShareInDb).toHaveBeenCalledTimes(2);
});
it("removes shared folder idempotently when usershare/mount are absent", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-1",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-1",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "delete") {
const error = new Error("usershare does not exist") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "usershare does not exist";
callback(error, "", "usershare does not exist");
return {} as never;
}
if (command === "umount") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-1");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-1");
});
it("skips umount when share is already unmounted", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-2",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-2",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "net" && args[0] === "usershare" && args[1] === "info") {
const error = new Error("usershare does not exist") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "usershare does not exist";
callback(error, "", "usershare does not exist");
return {} as never;
}
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "umount") {
callback(new Error("should not call umount"), "", "should not call umount");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-2");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-2");
expect(execFile).not.toHaveBeenCalledWith(
"umount",
expect.any(Array),
expect.any(Function),
);
});
it("removes stale db record when usershare delete is permission-denied but mount is absent", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-usershare-perm",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-usershare-perm",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
let usershareInfoCalls = 0;
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "info") {
usershareInfoCalls += 1;
callback(null, "share present", "");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "delete") {
const error = new Error("permission denied") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "permission denied";
callback(error, "", "permission denied");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-usershare-perm");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-usershare-perm");
expect(usershareInfoCalls).toBeGreaterThanOrEqual(1);
});
it("falls back to lazy umount when unmount target is busy", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-3",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-3",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
let mountpointCallCount = 0;
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "net" && args[0] === "usershare" && args[1] === "info") {
callback(null, "share present", "");
return {} as never;
}
if (command === "mountpoint") {
mountpointCallCount += 1;
if (mountpointCallCount === 1) {
callback(null, "", "");
return {} as never;
}
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "delete") {
callback(null, "", "");
return {} as never;
}
if (command === "umount" && args[0] === "-l") {
callback(null, "", "");
return {} as never;
}
if (command === "umount") {
const error = new Error("target is busy") as Error & {
code?: number;
stderr?: string;
};
error.code = 32;
error.stderr = "target is busy";
callback(error, "", "target is busy");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-3");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-3");
expect(execFile).toHaveBeenCalledWith(
"umount",
["-l", expect.stringContaining("Shared/Media")],
expect.anything(),
expect.any(Function),
);
});
it("removes db record when cleanup command fails but share is already inactive", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-4",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-4",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
let mountpointCallCount = 0;
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "net" && args[0] === "usershare" && args[1] === "info") {
const error = new Error("usershare does not exist") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "usershare does not exist";
callback(error, "", "usershare does not exist");
return {} as never;
}
if (command === "mountpoint") {
mountpointCallCount += 1;
if (mountpointCallCount === 1) {
callback(null, "", "");
return {} as never;
}
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "umount") {
const error = new Error("permission denied") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "permission denied";
callback(error, "", "permission denied");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-4");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-4");
});
it("removes stale db record when shared path is no longer valid", async () => {
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-stale",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Network/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-stale",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Network/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
const result = await removeLocalFolderShare("local-stale");
expect(result.removed).toBe(true);
expect(deleteLocalShareFromDb).toHaveBeenCalledWith("local-stale");
});
it("removes exported shared directory contents when unshare succeeds", async () => {
const exportedDir = path.join(mockDataRoot, "Shared", "Media");
await mkdir(exportedDir, { recursive: true });
await mkdir(path.join(exportedDir, "nested"), { recursive: true });
vi.mocked(getLocalShareFromDb).mockResolvedValueOnce({
id: "local-cleanup-dir",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(deleteLocalShareFromDb).mockResolvedValueOnce({
id: "local-cleanup-dir",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
});
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "net" && args[0] === "usershare" && args[1] === "info") {
callback(null, "share present", "");
return {} as never;
}
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & {
code?: number;
stderr?: string;
};
error.code = 1;
error.stderr = "not mounted";
callback(error, "", "not mounted");
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "delete") {
callback(null, "", "");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const result = await removeLocalFolderShare("local-cleanup-dir");
expect(result.removed).toBe(true);
await expect(lstat(exportedDir)).rejects.toMatchObject({ code: "ENOENT" });
});
it("serializes concurrent add requests for the same source path", async () => {
let sourceReserved = false;
vi.mocked(getLocalShareBySourcePathFromDb).mockImplementation(async () => {
if (!sourceReserved) return null;
return {
id: "local-1",
shareName: "Media",
sourcePath: "Media",
sharedPath: "Shared/Media",
createdAt: new Date(),
updatedAt: new Date(),
};
});
vi.mocked(insertLocalShareInDb).mockImplementation(async (input) => {
if (sourceReserved) {
throw uniqueViolation(
"files_local_shares_source_path_idx",
"Key (source_path)=(Media) already exists.",
);
}
sourceReserved = true;
return {
id: input.id,
shareName: input.shareName,
sourcePath: input.sourcePath,
sharedPath: input.sharedPath,
createdAt: new Date(),
updatedAt: new Date(),
};
});
vi.mocked(execFile).mockImplementation((command: string, ...restArgs: unknown[]) => {
const { args, callback } = getExecInvocation(restArgs);
if (command === "mountpoint") {
const error = new Error("not mounted") as Error & { code?: number };
error.code = 1;
callback(error, "", "");
return {} as never;
}
if (command === "mount") {
setTimeout(() => callback(null, "", ""), 30);
return {} as never;
}
if (command === "net" && args[0] === "usershare" && args[1] === "add") {
callback(null, "", "");
return {} as never;
}
callback(null, "", "");
return {} as never;
});
const results = await Promise.allSettled([
addLocalFolderShare({ path: "Media" }),
addLocalFolderShare({ path: "Media" }),
]);
const fulfilled = results.filter(
(result): result is PromiseFulfilledResult<Awaited<ReturnType<typeof addLocalFolderShare>>> =>
result.status === "fulfilled",
);
const rejected = results.filter(
(result): result is PromiseRejectedResult => result.status === "rejected",
);
expect(fulfilled).toHaveLength(1);
expect(rejected).toHaveLength(1);
expect(rejected[0]?.reason).toMatchObject({
code: "share_exists",
statusCode: 409,
});
});
});