//go:build e2e
package deployments_test
import (
"fmt"
"io"
"net/http"
"path/filepath"
"testing"
"time"
"github.com/DeBrosOfficial/network/e2e"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStaticDeployment_FullFlow(t *testing.T) {
env, err := e2e.LoadTestEnv()
require.NoError(t, err, "Failed to load test environment")
deploymentName := fmt.Sprintf("test-static-%d", time.Now().Unix())
tarballPath := 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("Upload static tarball", func(t *testing.T) {
deploymentID = e2e.CreateTestDeployment(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("Verify deployment in database", func(t *testing.T) {
deployment := e2e.GetDeployment(t, env, deploymentID)
assert.Equal(t, deploymentName, deployment["name"], "Deployment name should match")
assert.NotEmpty(t, deployment["content_cid"], "Content CID should not be empty")
// Status might be "deploying" or "active" depending on timing
status, ok := deployment["status"].(string)
require.True(t, ok, "Status should be a string")
assert.Contains(t, []string{"deploying", "active"}, status, "Status should be deploying or active")
t.Logf("✓ Deployment verified in database")
t.Logf(" - Name: %s", deployment["name"])
t.Logf(" - Status: %s", status)
t.Logf(" - CID: %s", deployment["content_cid"])
})
t.Run("Verify DNS record creation", func(t *testing.T) {
// Wait for deployment to become active
time.Sleep(2 * time.Second)
// Get the actual domain from deployment response
deployment := e2e.GetDeployment(t, env, deploymentID)
nodeURL := extractNodeURL(t, deployment)
require.NotEmpty(t, nodeURL, "Deployment should have a URL")
expectedDomain := extractDomain(nodeURL)
// Make request with Host header (localhost testing)
resp := e2e.TestDeploymentWithHostHeader(t, env, expectedDomain, "/")
defer resp.Body.Close()
// Should return 200 with React app HTML
assert.Equal(t, http.StatusOK, resp.StatusCode, "Should return 200 OK")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "Should read response body")
bodyStr := string(body)
// Verify React app content
assert.Contains(t, bodyStr, "
", "Should contain React root div")
assert.Contains(t, resp.Header.Get("Content-Type"), "text/html", "Content-Type should be text/html")
t.Logf("✓ Domain routing works")
t.Logf(" - Domain: %s", expectedDomain)
t.Logf(" - Status: %d", resp.StatusCode)
t.Logf(" - Content-Type: %s", resp.Header.Get("Content-Type"))
})
t.Run("Verify static assets serve correctly", func(t *testing.T) {
deployment := e2e.GetDeployment(t, env, deploymentID)
nodeURL := extractNodeURL(t, deployment)
require.NotEmpty(t, nodeURL, "Deployment should have a URL")
expectedDomain := extractDomain(nodeURL)
// Test CSS file (exact path depends on Vite build output)
// We'll just test a few common asset paths
assetPaths := []struct {
path string
contentType string
}{
{"/index.html", "text/html"},
// Note: Asset paths with hashes change on each build
// We'll test what we can
}
for _, asset := range assetPaths {
resp := e2e.TestDeploymentWithHostHeader(t, env, expectedDomain, asset.path)
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
assert.Contains(t, resp.Header.Get("Content-Type"), asset.contentType,
"Content-Type should be %s for %s", asset.contentType, asset.path)
t.Logf("✓ Asset served correctly: %s (%s)", asset.path, asset.contentType)
}
}
})
t.Run("Verify SPA fallback routing", func(t *testing.T) {
deployment := e2e.GetDeployment(t, env, deploymentID)
nodeURL := extractNodeURL(t, deployment)
require.NotEmpty(t, nodeURL, "Deployment should have a URL")
expectedDomain := extractDomain(nodeURL)
// Request unknown route (should return index.html for SPA)
resp := e2e.TestDeploymentWithHostHeader(t, env, expectedDomain, "/about/team")
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode, "SPA fallback should return 200")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "Should read response body")
assert.Contains(t, string(body), "
", "Should return index.html for unknown paths")
t.Logf("✓ SPA fallback routing works")
})
t.Run("List deployments", func(t *testing.T) {
req, err := http.NewRequest("GET", env.GatewayURL+"/v1/deployments/list", nil)
require.NoError(t, err, "Should create request")
req.Header.Set("Authorization", "Bearer "+env.APIKey)
resp, err := env.HTTPClient.Do(req)
require.NoError(t, err, "Should execute request")
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode, "List deployments should return 200")
var result map[string]interface{}
require.NoError(t, e2e.DecodeJSON(mustReadAll(t, resp.Body), &result), "Should decode JSON")
deployments, ok := result["deployments"].([]interface{})
require.True(t, ok, "Deployments should be an array")
assert.GreaterOrEqual(t, len(deployments), 1, "Should have at least one deployment")
// Find our deployment
found := false
for _, d := range deployments {
dep, ok := d.(map[string]interface{})
if !ok {
continue
}
if dep["name"] == deploymentName {
found = true
t.Logf("✓ Found deployment in list: %s", deploymentName)
break
}
}
assert.True(t, found, "Deployment should be in list")
})
t.Run("Delete deployment", func(t *testing.T) {
e2e.DeleteDeployment(t, env, deploymentID)
// Verify deletion - allow time for replication
time.Sleep(3 * time.Second)
req, _ := http.NewRequest("GET", env.GatewayURL+"/v1/deployments/get?id="+deploymentID, nil)
req.Header.Set("Authorization", "Bearer "+env.APIKey)
resp, err := env.HTTPClient.Do(req)
require.NoError(t, err, "Should execute request")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
t.Logf("Delete verification response: status=%d body=%s", resp.StatusCode, string(body))
// After deletion, either 404 (not found) or 200 with empty/error response is acceptable
if resp.StatusCode == http.StatusOK {
// If 200, check if the deployment is actually gone
t.Logf("Got 200 - this may indicate soft delete or eventual consistency")
}
t.Logf("✓ Deployment deleted successfully")
// Clear deploymentID so cleanup doesn't try to delete again
deploymentID = ""
})
}
func mustReadAll(t *testing.T, r io.Reader) []byte {
t.Helper()
data, err := io.ReadAll(r)
require.NoError(t, err, "Should read all data")
return data
}