From 2221b8a99cb70d85a7a7527540f64151ca0c0bc9 Mon Sep 17 00:00:00 2001 From: Thai Nguyen Date: Tue, 18 Aug 2026 17:47:59 +0700 Subject: [PATCH] fix: preserve stack log selection on fetch failures --- .../services/docker-stack-containers.test.ts | 69 +++++++++++++++++++ packages/server/src/services/docker.ts | 10 +-- 2 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 apps/dokploy/__test__/services/docker-stack-containers.test.ts diff --git a/apps/dokploy/__test__/services/docker-stack-containers.test.ts b/apps/dokploy/__test__/services/docker-stack-containers.test.ts new file mode 100644 index 000000000..b8d06b4f9 --- /dev/null +++ b/apps/dokploy/__test__/services/docker-stack-containers.test.ts @@ -0,0 +1,69 @@ +import { + getContainersByAppNameMatch, + getStackContainersByAppName, +} from "@dokploy/server/services/docker"; +import { + execAsync, + execAsyncRemote, +} from "@dokploy/server/utils/process/execAsync"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@dokploy/server/utils/process/execAsync", async (importOriginal) => ({ + ...(await importOriginal< + typeof import("@dokploy/server/utils/process/execAsync") + >()), + execAsync: vi.fn(), + execAsyncRemote: vi.fn(), +})); + +const execAsyncMock = vi.mocked(execAsync); +const execAsyncRemoteMock = vi.mocked(execAsyncRemote); + +describe("stack container lookup results", () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + it("returns undefined when the native stack lookup fails", async () => { + execAsyncMock.mockRejectedValueOnce(new Error("Docker is unavailable")); + + await expect( + getContainersByAppNameMatch("farmgate-odoo", "stack"), + ).resolves.toBeUndefined(); + }); + + it("returns an empty list for a successful native lookup with no tasks", async () => { + execAsyncMock.mockResolvedValueOnce({ stdout: "", stderr: "" }); + + await expect( + getContainersByAppNameMatch("farmgate-odoo", "stack"), + ).resolves.toEqual([]); + }); + + it("returns undefined when the remote native stack lookup fails", async () => { + execAsyncRemoteMock.mockRejectedValueOnce(new Error("SSH is unavailable")); + + await expect( + getContainersByAppNameMatch("farmgate-odoo", "stack", "server-1"), + ).resolves.toBeUndefined(); + }); + + it("returns undefined when the Swarm stack lookup reports an error", async () => { + execAsyncRemoteMock.mockResolvedValueOnce({ + stdout: "", + stderr: "node is unavailable", + }); + + await expect( + getStackContainersByAppName("farmgate-odoo", "server-1"), + ).resolves.toBeUndefined(); + }); + + it("returns an empty list for a successful Swarm lookup with no tasks", async () => { + execAsyncMock.mockResolvedValueOnce({ stdout: "", stderr: "" }); + + await expect(getStackContainersByAppName("farmgate-odoo")).resolves.toEqual( + [], + ); + }); +}); diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 65eb29041..72d81fc3d 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -167,7 +167,7 @@ export const getContainersByAppNameMatch = async ( return containers || []; } catch {} - return []; + return appType === "stack" ? undefined : []; }; const getStackTaskContainers = async (appName: string, serverId?: string) => { @@ -227,7 +227,7 @@ const getStackTaskContainers = async (appName: string, serverId?: string) => { return containers; } catch {} - return []; + return undefined; }; export const getStackContainersByAppName = async ( @@ -243,7 +243,7 @@ export const getStackContainersByAppName = async ( const { stdout, stderr } = await execAsyncRemote(serverId, command); if (stderr) { - return []; + return undefined; } if (!stdout) return []; @@ -252,7 +252,7 @@ export const getStackContainersByAppName = async ( const { stdout, stderr } = await execAsync(command); if (stderr) { - return []; + return undefined; } if (!stdout) return []; @@ -292,7 +292,7 @@ export const getStackContainersByAppName = async ( return containers || []; } catch {} - return []; + return undefined; }; export const getServiceContainersByAppName = async (