diff --git a/app/server/modules/volumes/__tests__/volumes.service.test.ts b/app/server/modules/volumes/__tests__/volumes.service.test.ts index 264d7e4f..7b487801 100644 --- a/app/server/modules/volumes/__tests__/volumes.service.test.ts +++ b/app/server/modules/volumes/__tests__/volumes.service.test.ts @@ -262,4 +262,41 @@ describe("volumeService.testConnection", () => { expect(mount).toHaveBeenCalledOnce(); expect(unmount).toHaveBeenCalledOnce(); }); + + test("does not fail when backend unmount already removed the temp mount path", async () => { + const mount = vi.fn().mockResolvedValue({ status: "mounted" }); + let mountPath: string | undefined; + const unmount = vi.fn().mockImplementation(async () => { + await fs.rm(mountPath!, { recursive: true, force: true }); + return { status: "unmounted" }; + }); + + vi.spyOn(backendModule, "createVolumeBackend").mockImplementation((_volume, tempPath) => { + mountPath = tempPath; + return { + mount, + unmount, + checkHealth: vi.fn(), + }; + }); + + 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(mountPath).toEqual(expect.stringContaining(`${path.sep}zerobyte-test-`)); + await expect(fs.access(mountPath as string)).rejects.toThrow(); + expect(mount).toHaveBeenCalledOnce(); + expect(unmount).toHaveBeenCalledOnce(); + }); }); diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index 4a3c0efe..e5d7eaa3 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -232,36 +232,37 @@ const updateVolume = async (shortId: ShortId, volumeData: UpdateVolumeBody) => { const testConnection = async (backendConfig: BackendConfig) => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-test-")); - const encryptedConfig = await encryptVolumeConfig(backendConfig); + try { + const encryptedConfig = await encryptVolumeConfig(backendConfig); - const mockVolume = { - id: 0, - shortId: asShortId("test"), - name: "test-connection", - config: encryptedConfig, - createdAt: Date.now(), - updatedAt: Date.now(), - lastHealthCheck: Date.now(), - type: encryptedConfig.backend, - status: "unmounted" as const, - lastError: null, - provisioningId: null, - autoRemount: true, - organizationId: "test-org", - }; + const mockVolume = { + id: 0, + shortId: asShortId("test"), + name: "test-connection", + config: encryptedConfig, + createdAt: Date.now(), + updatedAt: Date.now(), + lastHealthCheck: Date.now(), + type: encryptedConfig.backend, + status: "unmounted" as const, + lastError: null, + provisioningId: null, + autoRemount: true, + organizationId: "test-org", + }; - const backend = createVolumeBackend(mockVolume, tempDir); - const { error } = await backend.mount(); + const backend = createVolumeBackend(mockVolume, tempDir); + const { error } = await backend.mount(); - await backend.unmount(); + await backend.unmount(); - await fs.access(tempDir); - await fs.rm(tempDir, { recursive: true, force: true }); - - return { - success: !error, - message: error ? toMessage(error) : "Connection successful", - }; + return { + success: !error, + message: error ? toMessage(error) : "Connection successful", + }; + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } }; const checkHealth = async (shortId: ShortId) => {