//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" ) // TestDeploymentRollback_FullFlow tests the complete rollback workflow: // 1. Deploy v1 // 2. Update to v2 // 3. Verify v2 content // 4. Rollback to v1 // 5. Verify v1 content is restored func TestDeploymentRollback_FullFlow(t *testing.T) { env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("rollback-test-%d", time.Now().Unix()) tarballPathV1 := filepath.Join("../../testdata/apps/react-app") var deploymentID string // Cleanup after test defer func() { if !env.SkipCleanup && deploymentID != "" { e2e.DeleteDeployment(t, env, deploymentID) } }() t.Run("Deploy v1", func(t *testing.T) { deploymentID = e2e.CreateTestDeployment(t, env, deploymentName, tarballPathV1) require.NotEmpty(t, deploymentID, "Deployment ID should not be empty") t.Logf("Created deployment v1: %s (ID: %s)", deploymentName, deploymentID) }) t.Run("Verify v1 deployment", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) version, ok := deployment["version"].(float64) require.True(t, ok, "Version should be a number") assert.Equal(t, float64(1), version, "Initial version should be 1") contentCID, ok := deployment["content_cid"].(string) require.True(t, ok, "Content CID should be a string") assert.NotEmpty(t, contentCID, "Content CID should not be empty") t.Logf("v1 version: %v, CID: %s", version, contentCID) }) var v1CID string t.Run("Save v1 CID", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) v1CID = deployment["content_cid"].(string) t.Logf("Saved v1 CID: %s", v1CID) }) t.Run("Update to v2", func(t *testing.T) { // Update the deployment with the same tarball (simulates a new version) updateDeployment(t, env, deploymentName, tarballPathV1) // Wait for update to complete time.Sleep(2 * time.Second) }) t.Run("Verify v2 deployment", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) version, ok := deployment["version"].(float64) require.True(t, ok, "Version should be a number") assert.Equal(t, float64(2), version, "Version should be 2 after update") t.Logf("v2 version: %v", version) }) t.Run("List deployment versions", func(t *testing.T) { versions := listVersions(t, env, deploymentName) t.Logf("Available versions: %+v", versions) // Should have at least 2 versions in history assert.GreaterOrEqual(t, len(versions), 1, "Should have version history") }) t.Run("Rollback to v1", func(t *testing.T) { rollbackDeployment(t, env, deploymentName, 1) // Wait for rollback to complete time.Sleep(2 * time.Second) }) t.Run("Verify rollback succeeded", func(t *testing.T) { deployment := e2e.GetDeployment(t, env, deploymentID) version, ok := deployment["version"].(float64) require.True(t, ok, "Version should be a number") // Note: Version number increases even on rollback (it's a new deployment version) // But the content_cid should be the same as v1 t.Logf("Post-rollback version: %v", version) contentCID, ok := deployment["content_cid"].(string) require.True(t, ok, "Content CID should be a string") assert.Equal(t, v1CID, contentCID, "Content CID should match v1 after rollback") t.Logf("Rollback verified - content CID matches v1: %s", contentCID) }) } // updateDeployment updates an existing static deployment func updateDeployment(t *testing.T, env *e2e.E2ETestEnv, name, tarballPath string) { t.Helper() var fileData []byte info, err := os.Stat(tarballPath) require.NoError(t, err) if info.IsDir() { fileData, err = exec.Command("tar", "-czf", "-", "-C", tarballPath, ".").Output() require.NoError(t, err) } else { file, err := os.Open(tarballPath) require.NoError(t, err, "Failed to open tarball") defer file.Close() fileData, _ = io.ReadAll(file) } // Create multipart form body := &bytes.Buffer{} boundary := "----WebKitFormBoundary7MA4YWxkTrZu0gW" // Write name field body.WriteString("--" + boundary + "\r\n") body.WriteString("Content-Disposition: form-data; name=\"name\"\r\n\r\n") body.WriteString(name + "\r\n") // Write tarball file 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/static/update", body) require.NoError(t, err, "Failed to create request") 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, "Failed to execute request") defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) t.Fatalf("Update failed with status %d: %s", resp.StatusCode, string(bodyBytes)) } var result map[string]interface{} require.NoError(t, json.NewDecoder(resp.Body).Decode(&result), "Failed to decode response") t.Logf("Update response: %+v", result) } // listVersions lists available versions for a deployment func listVersions(t *testing.T, env *e2e.E2ETestEnv, name string) []map[string]interface{} { t.Helper() req, err := http.NewRequest("GET", env.GatewayURL+"/v1/deployments/versions?name="+name, nil) require.NoError(t, err, "Failed to create request") req.Header.Set("Authorization", "Bearer "+env.APIKey) resp, err := env.HTTPClient.Do(req) require.NoError(t, err, "Failed to execute request") defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) t.Logf("List versions returned status %d: %s", resp.StatusCode, string(bodyBytes)) return nil } var result struct { Versions []map[string]interface{} `json:"versions"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { t.Logf("Failed to decode versions: %v", err) return nil } return result.Versions } // rollbackDeployment triggers a rollback to a specific version func rollbackDeployment(t *testing.T, env *e2e.E2ETestEnv, name string, targetVersion int) { t.Helper() reqBody := map[string]interface{}{ "name": name, "version": targetVersion, } bodyBytes, _ := json.Marshal(reqBody) req, err := http.NewRequest("POST", env.GatewayURL+"/v1/deployments/rollback", bytes.NewBuffer(bodyBytes)) require.NoError(t, err, "Failed to create request") req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+env.APIKey) resp, err := env.HTTPClient.Do(req) require.NoError(t, err, "Failed to execute request") defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) t.Fatalf("Rollback failed with status %d: %s", resp.StatusCode, string(bodyBytes)) } var result map[string]interface{} require.NoError(t, json.NewDecoder(resp.Body).Decode(&result), "Failed to decode response") t.Logf("Rollback response: %+v", result) }