//go:build e2e package deployments_test import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "testing" "time" "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNodeJSDeployment_FullFlow(t *testing.T) { env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("test-nodejs-%d", time.Now().Unix()) tarballPath := filepath.Join("../../testdata/apps/node-api") var deploymentID string // Cleanup after test defer func() { if !env.SkipCleanup && deploymentID != "" { e2e.DeleteDeployment(t, env, deploymentID) } }() t.Run("Upload Node.js backend", func(t *testing.T) { deploymentID = createNodeJSDeployment(t, env, deploymentName, tarballPath) assert.NotEmpty(t, deploymentID, "Deployment ID should not be empty") t.Logf("Created deployment: %s (ID: %s)", deploymentName, deploymentID) }) t.Run("Wait for deployment to become healthy", func(t *testing.T) { healthy := e2e.WaitForHealthy(t, env, deploymentID, 90*time.Second) assert.True(t, healthy, "Deployment should become healthy within timeout") t.Logf("Deployment is healthy") }) t.Run("Test health endpoint", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) // Get the deployment URLs (can be array of strings or map) nodeURL := extractNodeURL(t, deployment) if nodeURL == "" { t.Skip("No node URL in deployment") } // Test via Host header (localhost testing) resp := e2e.TestDeploymentWithHostHeader(t, env, extractDomain(nodeURL), "/health") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "Health check should return 200") body, err := io.ReadAll(resp.Body) require.NoError(t, err) var health map[string]interface{} require.NoError(t, json.Unmarshal(body, &health)) assert.Contains(t, []string{"healthy", "ok"}, health["status"], "Health status should be 'healthy' or 'ok'") t.Logf("Health check passed: %v", health) }) t.Run("Test API endpoint", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) nodeURL := extractNodeURL(t, deployment) if nodeURL == "" { t.Skip("No node URL in deployment") } domain := extractDomain(nodeURL) // Test health endpoint (node-api app serves /health) resp := e2e.TestDeploymentWithHostHeader(t, env, domain, "/health") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) body, err := io.ReadAll(resp.Body) require.NoError(t, err) var result map[string]interface{} require.NoError(t, json.Unmarshal(body, &result)) assert.NotEmpty(t, result["service"]) t.Logf("API endpoint response: %v", result) }) } func createNodeJSDeployment(t *testing.T, env *e2e.E2ETestEnv, name, tarballPath string) string { t.Helper() var fileData []byte info, err := os.Stat(tarballPath) if err != nil { t.Fatalf("Failed to stat tarball path: %v", err) } if info.IsDir() { // Create tarball from directory tarData, err := exec.Command("tar", "-czf", "-", "-C", tarballPath, ".").Output() require.NoError(t, err, "Failed to create tarball from %s", tarballPath) fileData = tarData } else { file, err := os.Open(tarballPath) require.NoError(t, err, "Failed to open tarball: %s", tarballPath) defer file.Close() fileData, _ = io.ReadAll(file) } body := &bytes.Buffer{} boundary := "----WebKitFormBoundary7MA4YWxkTrZu0gW" body.WriteString("--" + boundary + "\r\n") body.WriteString("Content-Disposition: form-data; name=\"name\"\r\n\r\n") body.WriteString(name + "\r\n") body.WriteString("--" + boundary + "\r\n") body.WriteString("Content-Disposition: form-data; name=\"tarball\"; filename=\"app.tar.gz\"\r\n") body.WriteString("Content-Type: application/gzip\r\n\r\n") body.Write(fileData) body.WriteString("\r\n--" + boundary + "--\r\n") req, err := http.NewRequest("POST", env.GatewayURL+"/v1/deployments/nodejs/upload", body) require.NoError(t, err) req.Header.Set("Content-Type", "multipart/form-data; boundary="+boundary) req.Header.Set("Authorization", "Bearer "+env.APIKey) resp, err := env.HTTPClient.Do(req) require.NoError(t, err) defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { bodyBytes, _ := io.ReadAll(resp.Body) t.Fatalf("Deployment upload failed with status %d: %s", resp.StatusCode, string(bodyBytes)) } var result map[string]interface{} require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) if id, ok := result["deployment_id"].(string); ok { return id } if id, ok := result["id"].(string); ok { return id } t.Fatalf("Deployment response missing id field: %+v", result) return "" } // extractNodeURL gets the node URL from deployment response // Handles both array of strings and map formats func extractNodeURL(t *testing.T, deployment map[string]interface{}) string { t.Helper() // Try as array of strings first (new format) if urls, ok := deployment["urls"].([]interface{}); ok && len(urls) > 0 { if url, ok := urls[0].(string); ok { return url } } // Try as map (legacy format) if urls, ok := deployment["urls"].(map[string]interface{}); ok { if url, ok := urls["node"].(string); ok { return url } } return "" } func extractDomain(url string) string { // Extract domain from URL like "https://myapp.node-xyz.dbrs.space" // Remove protocol domain := url if len(url) > 8 && url[:8] == "https://" { domain = url[8:] } else if len(url) > 7 && url[:7] == "http://" { domain = url[7:] } // Remove trailing slash if len(domain) > 0 && domain[len(domain)-1] == '/' { domain = domain[:len(domain)-1] } return domain }