From db1ccaa94662b860e54393b18e6796eed3370741 Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Wed, 3 Jun 2026 17:35:02 +0530 Subject: [PATCH] fix(test): deploy happy-path poll on terminal status, not provider_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestDeployNew_SourceImage_FlagOn_Accepted (and its git sibling) polled the row until provider_id was non-empty, then asserted status=="healthy". But runDeploy writes provider_id BEFORE status in two separate UPDATEs, so the poll could break in the window between them and read a transient "building" — a real race in the test, not the compute path. It passed in ci.yml build-and-test but lost the race in deploy.yml's gate, which (now that P3 merged) blocks the api auto-deploy (prod stuck on the P2 SHA). Poll on the terminal status (healthy/failed) so the wait covers the final write. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../handlers/deploy_source_git_integration_test.go | 10 +++++----- .../handlers/deploy_source_image_integration_test.go | 9 ++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/handlers/deploy_source_git_integration_test.go b/internal/handlers/deploy_source_git_integration_test.go index c50a4d91..f0cbf771 100644 --- a/internal/handlers/deploy_source_git_integration_test.go +++ b/internal/handlers/deploy_source_git_integration_test.go @@ -161,17 +161,17 @@ func TestDeployNew_SourceGit_FlagOn_Accepted(t *testing.T) { assert.Empty(t, env.Item.GitToken, "git token must NEVER be echoed back") require.NotEmpty(t, env.Item.ID) - // runDeploy is async — poll the row until the noop provider stamps it - // healthy with a provider id (proves applyGitSourceOpts → compute.Deploy ran). - // Generous deadline: the async runDeploy goroutine does DB writes that can - // be slow under `-race -p 1` with the full suite loaded. + // runDeploy is async — poll the row until the noop provider drives it to a + // terminal status (proves applyGitSourceOpts → compute.Deploy ran). Poll on + // status, NOT provider_id: runDeploy writes provider_id before status in two + // UPDATEs, so breaking on provider_id races the status write. 30s ceiling. deadline := time.Now().Add(30 * time.Second) var status, providerID string var gitURLCol sql.NullString for time.Now().Before(deadline) { row := db.QueryRow(`SELECT status, COALESCE(provider_id,''), git_url FROM deployments WHERE id = $1`, env.Item.ID) require.NoError(t, row.Scan(&status, &providerID, &gitURLCol)) - if providerID != "" { + if status == "healthy" || status == "failed" { break } time.Sleep(50 * time.Millisecond) diff --git a/internal/handlers/deploy_source_image_integration_test.go b/internal/handlers/deploy_source_image_integration_test.go index b37e2427..fbfbe4db 100644 --- a/internal/handlers/deploy_source_image_integration_test.go +++ b/internal/handlers/deploy_source_image_integration_test.go @@ -230,8 +230,11 @@ func TestDeployNew_SourceImage_FlagOn_Accepted(t *testing.T) { // runDeploy is async — poll the row until the (noop) compute provider has // stamped it healthy with a provider id. This deterministically waits for // the applyImageSourceOpts → compute.Deploy path to execute. - // Generous deadline: the async runDeploy goroutine does DB writes that can - // be slow under `-race -p 1` with the full suite loaded (was 5s → flaked). + // + // Poll on the TERMINAL status (runDeploy writes provider_id BEFORE status in + // two separate UPDATEs — breaking on provider_id races the status write and + // reads a transient "building"). 30s ceiling for slow `-race -p 1` runs; + // early-breaks the instant a terminal status appears. deadline := time.Now().Add(30 * time.Second) var status, providerID string var imageRef sql.NullString @@ -240,7 +243,7 @@ func TestDeployNew_SourceImage_FlagOn_Accepted(t *testing.T) { `SELECT status, COALESCE(provider_id,''), image_ref FROM deployments WHERE id = $1`, env.Item.ID) require.NoError(t, row.Scan(&status, &providerID, &imageRef)) - if providerID != "" { + if status == "healthy" || status == "failed" { break } time.Sleep(50 * time.Millisecond)