From cee950b6c8f946fd042763335614a6740979d718 Mon Sep 17 00:00:00 2001 From: Narciso Date: Mon, 31 Aug 2026 16:38:16 -0400 Subject: [PATCH] fix(test): wait for swarm convergence before idempotency re-check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setupMonitoring's "is idempotent" test calls it twice back to back. Swarm keeps converging a freshly created service (scheduling tasks, resolving endpoints), which bumps Version.Index on its own shortly after creation. If that bump lands between the second call's inspect() and update(), Docker rejects it with "update out of sequence" even though nothing else touched the service. This never happens in production — setupMonitoring is only triggered by a UI button click or once during server setup, both far enough apart in wall-clock time for Swarm to settle. So the fix stays in the test: wait for two consecutive Version.Index reads to agree before making the second call. --- .../__test__/setup/monitoring-setup.real.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/dokploy/__test__/setup/monitoring-setup.real.test.ts b/apps/dokploy/__test__/setup/monitoring-setup.real.test.ts index d16a75a40..89b8b390d 100644 --- a/apps/dokploy/__test__/setup/monitoring-setup.real.test.ts +++ b/apps/dokploy/__test__/setup/monitoring-setup.real.test.ts @@ -64,6 +64,21 @@ const serviceExists = async (name: string) => { } }; +// Swarm keeps converging a service for a bit after it's created (scheduling +// tasks, resolving endpoints), which bumps Version.Index on its own. Calling +// setupMonitoring again before that settles races that internal bump, so wait +// for two consecutive reads to agree before treating the service as stable. +const waitForServiceConvergence = async (name: string, timeoutMs = 5000) => { + const deadline = Date.now() + timeoutMs; + let lastIndex: string | null = null; + while (Date.now() < deadline) { + const inspect = await docker.getService(name).inspect(); + if (inspect.Version.Index === lastIndex) return; + lastIndex = inspect.Version.Index; + await new Promise((resolve) => setTimeout(resolve, 50)); + } +}; + const swarmTaskNames = async () => { const list = await docker.listContainers({ all: true }); return list @@ -193,6 +208,7 @@ describe.skipIf(hasRealMonitoring())( expect(await containerExists(SERVICE_NAME)).toBe(false); await expect(setupMonitoring("test-server")).resolves.not.toThrow(); + await waitForServiceConvergence(SERVICE_NAME); await expect(setupMonitoring("test-server")).resolves.not.toThrow(); expect(await serviceExists(SERVICE_NAME)).toBe(true);