0ddae4f91e
remove query from codebase part 1 * remove query * fix localstack run
154 lines
5.5 KiB
Go
154 lines
5.5 KiB
Go
// Package test provides configuration utilities for test infrastructure.
|
|
// This file centralizes timeout configuration for CI/CD stability.
|
|
package test
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// GetHTTPTimeout returns the HTTP client timeout for API calls.
|
|
// Default is 30 seconds, configurable via E2E_HTTP_TIMEOUT environment variable.
|
|
// In CI environments, this timeout ensures that stalled HTTP calls don't block
|
|
// indefinitely, which was a root cause of test hangs.
|
|
func GetHTTPTimeout() time.Duration {
|
|
if t := os.Getenv("E2E_HTTP_TIMEOUT"); t != "" {
|
|
if d, err := time.ParseDuration(t); err == nil {
|
|
return d
|
|
}
|
|
}
|
|
return 30 * time.Second
|
|
}
|
|
|
|
// GetPollingTimeout returns the timeout for polling operations like waiting
|
|
// for client status or mock endpoint verification.
|
|
// Default is 60 seconds locally, 180 seconds in CI (detected via CI or
|
|
// BITBUCKET_PIPELINE_UUID environment variables).
|
|
// Configurable via E2E_WAIT_TIMEOUT environment variable.
|
|
func GetPollingTimeout() time.Duration {
|
|
if t := os.Getenv("E2E_WAIT_TIMEOUT"); t != "" {
|
|
if d, err := time.ParseDuration(t); err == nil {
|
|
return d
|
|
}
|
|
}
|
|
if isCI() {
|
|
return 180 * time.Second // 3 minutes in CI
|
|
}
|
|
return 60 * time.Second // 1 minute locally
|
|
}
|
|
|
|
// GetPollInterval returns the interval between polling attempts.
|
|
// Default is 500ms, configurable via E2E_POLL_INTERVAL environment variable.
|
|
func GetPollInterval() time.Duration {
|
|
if t := os.Getenv("E2E_POLL_INTERVAL"); t != "" {
|
|
if d, err := time.ParseDuration(t); err == nil {
|
|
return d
|
|
}
|
|
}
|
|
return 500 * time.Millisecond
|
|
}
|
|
|
|
// GetHTTPClient returns an HTTP client with the configured timeout.
|
|
// This should be used instead of &http.Client{} to ensure all HTTP operations
|
|
// have bounded timeouts and won't hang indefinitely.
|
|
func GetHTTPClient() *http.Client {
|
|
return &http.Client{
|
|
Timeout: GetHTTPTimeout(),
|
|
}
|
|
}
|
|
|
|
// isCI returns true if running in a CI environment.
|
|
// Detects CI via the CI or BITBUCKET_PIPELINE_UUID environment variables.
|
|
func isCI() bool {
|
|
return os.Getenv("CI") != "" || os.Getenv("BITBUCKET_PIPELINE_UUID") != ""
|
|
}
|
|
|
|
// WaitForExternalReadiness waits for a service to be reachable from outside Docker.
|
|
// This addresses the race condition where a container's internal health check passes
|
|
// but the external port mapping isn't yet ready. This is particularly important in
|
|
// Docker-in-Docker (DinD) environments like Bitbucket Pipelines.
|
|
//
|
|
// Parameters:
|
|
// - uri: The base URI of the service (e.g., "http://localhost:32768")
|
|
// - healthPath: The health check endpoint path (e.g., "/health" or "/liveness/probe")
|
|
// - timeout: Maximum time to wait for the service to become reachable
|
|
//
|
|
// Returns an error if the service is not reachable within the timeout period.
|
|
func WaitForExternalReadiness(uri string, healthPath string, timeout time.Duration) error {
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
deadline := time.Now().Add(timeout)
|
|
|
|
fullURL := fmt.Sprintf("%s%s", uri, healthPath)
|
|
slog.Info("Waiting for external readiness", "url", fullURL, "timeout", timeout)
|
|
|
|
var lastErr error
|
|
attempts := 0
|
|
for time.Now().Before(deadline) {
|
|
attempts++
|
|
resp, err := client.Get(fullURL)
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
|
resp.Body.Close()
|
|
slog.Info("Service is externally reachable", "url", fullURL, "attempts", attempts)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
lastErr = err
|
|
} else {
|
|
lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
resp.Body.Close()
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
return fmt.Errorf("service not reachable from test client after %v (%d attempts): %w", timeout, attempts, lastErr)
|
|
}
|
|
|
|
// WaitForAPIRoutes waits for API routes to be fully registered.
|
|
// This addresses a race condition where the health endpoint returns 200 but other
|
|
// API routes haven't been registered yet, causing "404 page not found" errors.
|
|
// This is particularly important in slow CI environments where route registration
|
|
// may take longer after the health endpoint becomes available.
|
|
//
|
|
// Parameters:
|
|
// - uri: The base URI of the service (e.g., "http://localhost:32768")
|
|
// - timeout: Maximum time to wait for routes to be registered
|
|
//
|
|
// Returns an error if routes are not available within the timeout period.
|
|
func WaitForAPIRoutes(uri string, timeout time.Duration) error {
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
deadline := time.Now().Add(timeout)
|
|
|
|
// Check /clients endpoint - should return a response when routes are ready
|
|
// A "404 page not found" means routes aren't registered yet
|
|
// Note: /query endpoint was removed as part of query functionality removal
|
|
routeURL := fmt.Sprintf("%s/clients", uri)
|
|
slog.Info("Waiting for API routes to be registered", "url", routeURL, "timeout", timeout)
|
|
|
|
var lastErr error
|
|
attempts := 0
|
|
for time.Now().Before(deadline) {
|
|
attempts++
|
|
resp, err := client.Get(routeURL)
|
|
if err == nil {
|
|
// Any response other than connection error means routes are registered
|
|
// 200 = success, 401 = auth required (but route exists), etc.
|
|
// Only "404 page not found" indicates routes not yet registered
|
|
if resp.StatusCode != http.StatusNotFound {
|
|
resp.Body.Close()
|
|
slog.Info("API routes are registered", "url", routeURL, "attempts", attempts, "status", resp.StatusCode)
|
|
return nil
|
|
}
|
|
lastErr = fmt.Errorf("routes not registered yet: %d", resp.StatusCode)
|
|
resp.Body.Close()
|
|
} else {
|
|
lastErr = err
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
return fmt.Errorf("API routes not registered after %v (%d attempts): %w", timeout, attempts, lastErr)
|
|
}
|