Some checks failed
Release Workflow / integration-tests (push) Has been cancelled
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Release Workflow / request-docs-version-update (push) Has been cancelled
This reverts commit 885ea10f2a.
289 lines
9.8 KiB
TypeScript
289 lines
9.8 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
const agentManagerMock = vi.hoisted(() => ({
|
|
runVolumeCommand: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../../agents/agents-manager", () => ({
|
|
agentManager: agentManagerMock,
|
|
}));
|
|
|
|
import { volumeService } from "../volume.service";
|
|
import { db } from "~/server/db/db";
|
|
import { volumesTable } from "~/server/db/schema";
|
|
import { randomUUID } from "node:crypto";
|
|
import { createTestSession } from "~/test/helpers/auth";
|
|
import { withContext } from "~/server/core/request-context";
|
|
import { asShortId } from "~/server/utils/branded";
|
|
import { createTestVolume } from "~/test/helpers/volume";
|
|
import { config } from "~/server/core/config";
|
|
|
|
afterEach(() => {
|
|
config.flags.enableLocalAgent = false;
|
|
vi.restoreAllMocks();
|
|
agentManagerMock.runVolumeCommand.mockReset();
|
|
});
|
|
|
|
describe("volumeService.getVolume", () => {
|
|
test("should find volume by shortId", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
|
|
const [volume] = await db
|
|
.insert(volumesTable)
|
|
.values({
|
|
shortId: asShortId(randomUUID().slice(0, 8)),
|
|
name: `test-vol-${randomUUID().slice(0, 8)}`,
|
|
type: "directory",
|
|
status: "mounted",
|
|
config: { backend: "directory", path: "/" },
|
|
autoRemount: true,
|
|
organizationId,
|
|
})
|
|
.returning();
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.getVolume(volume.shortId);
|
|
expect(result.volume.id).toBe(volume.id);
|
|
expect(result.volume.shortId).toBe(volume.shortId);
|
|
});
|
|
});
|
|
|
|
test("should find volume by shortId from literal input", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
|
|
const [volume] = await db
|
|
.insert(volumesTable)
|
|
.values({
|
|
shortId: asShortId("test1234"),
|
|
name: `test-vol-${randomUUID().slice(0, 8)}`,
|
|
type: "directory",
|
|
status: "mounted",
|
|
config: { backend: "directory", path: "/" },
|
|
autoRemount: true,
|
|
organizationId,
|
|
})
|
|
.returning();
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.getVolume(volume.shortId);
|
|
expect(result.volume.id).toBe(volume.id);
|
|
expect(result.volume.shortId).toBe(volume.shortId);
|
|
});
|
|
});
|
|
|
|
test("should find volume by numeric-looking shortId", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
|
|
const [volume] = await db
|
|
.insert(volumesTable)
|
|
.values({
|
|
shortId: asShortId("499780"),
|
|
name: `test-vol-${randomUUID().slice(0, 8)}`,
|
|
type: "directory",
|
|
status: "mounted",
|
|
config: { backend: "directory", path: "/" },
|
|
autoRemount: true,
|
|
organizationId,
|
|
})
|
|
.returning();
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.getVolume(asShortId("499780"));
|
|
expect(result.volume.id).toBe(volume.id);
|
|
expect(result.volume.shortId).toBe(asShortId("499780"));
|
|
});
|
|
});
|
|
|
|
test("should throw NotFoundError for non-existent volume", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
await expect(volumeService.getVolume(asShortId("nonexistent"))).rejects.toThrow("Volume not found");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("volumeService.listFiles security", () => {
|
|
test("should reject traversal outside the volume root in listFiles", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
agentManagerMock.runVolumeCommand.mockRejectedValue(new Error("Invalid path"));
|
|
|
|
const [volume] = await db
|
|
.insert(volumesTable)
|
|
.values({
|
|
shortId: asShortId(randomUUID().slice(0, 8)),
|
|
name: `test-vol-${randomUUID().slice(0, 8)}`,
|
|
type: "directory",
|
|
status: "mounted",
|
|
config: { backend: "directory", path: "/tmp/volume" },
|
|
autoRemount: true,
|
|
organizationId,
|
|
})
|
|
.returning();
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
await expect(volumeService.listFiles(volume.shortId, "../volume-secret")).rejects.toThrow("Invalid path");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("volumeService.mountVolume", () => {
|
|
test("routes unmount and mount to the owning agent before updating state", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
const volume = await createTestVolume({ organizationId, status: "mounted", agentId: "agent-1" });
|
|
agentManagerMock.runVolumeCommand
|
|
.mockResolvedValueOnce({ name: "volume.unmount", result: { status: "unmounted" } })
|
|
.mockResolvedValueOnce({ name: "volume.mount", result: { status: "mounted" } });
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.mountVolume(volume.shortId);
|
|
|
|
expect(result.status).toBe("mounted");
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenNthCalledWith(
|
|
1,
|
|
volume.agentId,
|
|
expect.objectContaining({ name: "volume.unmount", volume: expect.objectContaining({ id: volume.id }) }),
|
|
);
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenNthCalledWith(
|
|
2,
|
|
volume.agentId,
|
|
expect.objectContaining({ name: "volume.mount", volume: expect.objectContaining({ id: volume.id }) }),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("volumeService.unmountVolume", () => {
|
|
test("persists the unmounted status for normal unmount requests", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
const volume = await createTestVolume({ organizationId, status: "mounted", agentId: "agent-1" });
|
|
agentManagerMock.runVolumeCommand.mockResolvedValueOnce({
|
|
name: "volume.unmount",
|
|
result: { status: "unmounted" },
|
|
});
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.unmountVolume(volume.shortId);
|
|
|
|
expect(result.status).toBe("unmounted");
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledWith(
|
|
volume.agentId,
|
|
expect.objectContaining({ name: "volume.unmount", volume: expect.objectContaining({ id: volume.id }) }),
|
|
);
|
|
});
|
|
|
|
const updatedVolume = await db.query.volumesTable.findFirst({ where: { id: volume.id } });
|
|
expect(updatedVolume?.status).toBe("unmounted");
|
|
});
|
|
});
|
|
|
|
describe("volumeService.ensureHealthyVolume", () => {
|
|
test("returns ready when the mounted volume passes its health check", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
const volume = await createTestVolume({ organizationId, status: "mounted", agentId: "agent-1" });
|
|
agentManagerMock.runVolumeCommand.mockResolvedValue({
|
|
name: "volume.checkHealth",
|
|
result: { status: "mounted" },
|
|
});
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.ensureHealthyVolume(volume.shortId);
|
|
|
|
expect(result).toEqual({
|
|
ready: true,
|
|
volume: expect.objectContaining({ id: volume.id, status: "mounted", lastError: null }),
|
|
remounted: false,
|
|
});
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledOnce();
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledWith(
|
|
volume.agentId,
|
|
expect.objectContaining({
|
|
name: "volume.checkHealth",
|
|
volume: expect.objectContaining({ id: volume.id }),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test("auto-remounts when the mounted volume fails its health check", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
const volume = await createTestVolume({
|
|
organizationId,
|
|
status: "mounted",
|
|
autoRemount: true,
|
|
agentId: "agent-1",
|
|
});
|
|
agentManagerMock.runVolumeCommand
|
|
.mockResolvedValueOnce({ name: "volume.checkHealth", result: { status: "error", error: "stale mount" } })
|
|
.mockResolvedValueOnce({ name: "volume.unmount", result: { status: "unmounted" } })
|
|
.mockResolvedValueOnce({ name: "volume.mount", result: { status: "mounted" } });
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.ensureHealthyVolume(volume.shortId);
|
|
|
|
expect(result).toEqual({
|
|
ready: true,
|
|
volume: expect.objectContaining({ id: volume.id, status: "mounted", lastError: null }),
|
|
remounted: true,
|
|
});
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledTimes(3);
|
|
|
|
const updatedVolume = await db.query.volumesTable.findFirst({ where: { id: volume.id } });
|
|
expect(updatedVolume?.status).toBe("mounted");
|
|
expect(updatedVolume?.lastError).toBeNull();
|
|
});
|
|
});
|
|
|
|
test("returns not ready when the health check fails and auto-remount is disabled", async () => {
|
|
const { organizationId, user } = await createTestSession();
|
|
const volume = await createTestVolume({
|
|
organizationId,
|
|
status: "mounted",
|
|
autoRemount: false,
|
|
agentId: "agent-1",
|
|
});
|
|
agentManagerMock.runVolumeCommand.mockResolvedValue({
|
|
name: "volume.checkHealth",
|
|
result: { status: "error", error: "stale mount" },
|
|
});
|
|
|
|
await withContext({ organizationId, userId: user.id }, async () => {
|
|
const result = await volumeService.ensureHealthyVolume(volume.shortId);
|
|
|
|
expect(result).toEqual({
|
|
ready: false,
|
|
volume: expect.objectContaining({ id: volume.id, status: "error", lastError: "stale mount" }),
|
|
reason: "stale mount",
|
|
});
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("volumeService.testConnection", () => {
|
|
test("routes test connections to the local agent", async () => {
|
|
config.flags.enableLocalAgent = true;
|
|
agentManagerMock.runVolumeCommand.mockResolvedValue({
|
|
name: "volume.testConnection",
|
|
result: { success: true, message: "Connection successful" },
|
|
});
|
|
|
|
await expect(
|
|
volumeService.testConnection({
|
|
backend: "nfs",
|
|
server: "127.0.0.1",
|
|
exportPath: "/exports/test",
|
|
version: "4",
|
|
port: 2049,
|
|
readOnly: false,
|
|
}),
|
|
).resolves.toEqual({
|
|
success: true,
|
|
message: "Connection successful",
|
|
});
|
|
|
|
expect(agentManagerMock.runVolumeCommand).toHaveBeenCalledWith(
|
|
"local",
|
|
expect.objectContaining({ name: "volume.testConnection" }),
|
|
);
|
|
});
|
|
});
|