From d323dc243452680d5705df1f32c7dabe4f4213e3 Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Mon, 28 Apr 2025 13:04:58 +0300 Subject: [PATCH] fix(test): Wait for Vault server in watch tests Fix flaky tests by polling Vault's /v1/sys/health endpoint. This ensures the server is ready before tests attempt to connect, preventing a race condition during Vault dev server startup. Signed-off-by: Ville Vesilehto --- watch/watch_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/watch/watch_test.go b/watch/watch_test.go index a967dc454..6ce55bd0c 100644 --- a/watch/watch_test.go +++ b/watch/watch_test.go @@ -5,20 +5,23 @@ package watch import ( "encoding/json" + "fmt" "io" "log" + "net" + "net/http" "os" "os/exec" "path/filepath" "strings" "testing" + "time" dep "github.com/hashicorp/consul-template/dependency" "github.com/hashicorp/vault/api" ) const ( - vaultAddr = "http://127.0.0.1:8200" vaultToken = "a_token" ) @@ -26,6 +29,7 @@ var ( testVault *vaultServer testClients *dep.ClientSet tokenRoleId string + vaultAddr string ) func TestMain(m *testing.M) { @@ -35,6 +39,14 @@ func TestMain(m *testing.M) { // sub-main so I can use defer func main(m *testing.M) int { log.SetOutput(io.Discard) + // Find a free port for the Vault server + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + panic(fmt.Sprintf("failed to find free port for vault: %v", err)) + } + vaultAddr = fmt.Sprintf("http://%s", listener.Addr().String()) + listener.Close() + testVault = newTestVault() defer func() { testVault.Stop() }() @@ -71,6 +83,7 @@ func newTestVault() *vaultServer { args := []string{ "server", "-dev", "-dev-root-token-id", vaultToken, "-dev-no-store-token", + "-dev-listen-address", strings.TrimPrefix(vaultAddr, "http://"), } cmd := exec.Command("vault", args...) cmd.Stdout = io.Discard @@ -79,6 +92,54 @@ func newTestVault() *vaultServer { if err := cmd.Start(); err != nil { panic("vault failed to start: " + err.Error()) } + + // Wait for Vault to become available + client := &http.Client{Timeout: 1 * time.Second} + healthURL := vaultAddr + "/v1/sys/health" + startTime := time.Now() + timeout := 30 * time.Second + + for { + if time.Since(startTime) > timeout { + panic("timed out waiting for vault dev server to start") + } + + resp, err := client.Get(healthURL) + if err != nil { + time.Sleep(200 * time.Millisecond) + continue + } + + if resp.StatusCode == http.StatusOK { + bodyBytes, readErr := io.ReadAll(resp.Body) + resp.Body.Close() + + if readErr != nil { + time.Sleep(200 * time.Millisecond) + continue + } + + var healthStatus map[string]interface{} + jsonErr := json.Unmarshal(bodyBytes, &healthStatus) + if jsonErr != nil { + time.Sleep(200 * time.Millisecond) + continue + } + + // Check for initialized and unsealed status + initialized, initOk := healthStatus["initialized"].(bool) + sealed, sealedOk := healthStatus["sealed"].(bool) + + if initOk && sealedOk && initialized && !sealed { + break + } + } else { + resp.Body.Close() + } + + time.Sleep(1 * time.Second) + } + return &vaultServer{ cmd: cmd, }