//go:build e2e package e2e import ( "encoding/json" "fmt" "io" "net/http" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDomainRouting_BasicRouting(t *testing.T) { env, err := LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("test-routing-%d", time.Now().Unix()) tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { DeleteDeployment(t, env, deploymentID) } }() // Wait for deployment to be active time.Sleep(2 * time.Second) // Get deployment details for debugging deployment := GetDeployment(t, env, deploymentID) t.Logf("Deployment created: ID=%s, CID=%s, Name=%s, Status=%s", deploymentID, deployment["content_cid"], deployment["name"], deployment["status"]) t.Run("Standard domain resolves", func(t *testing.T) { // Domain format: {deploymentName}.{baseDomain} domain := env.BuildDeploymentDomain(deploymentName) resp := TestDeploymentWithHostHeader(t, env, domain, "/") defer resp.Body.Close() 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") assert.Contains(t, string(body), "
", "Should serve React app") assert.Contains(t, resp.Header.Get("Content-Type"), "text/html", "Content-Type should be HTML") t.Logf("✓ Standard domain routing works: %s", domain) }) t.Run("Non-debros domain passes through", func(t *testing.T) { // Request with non-debros domain should not route to deployment resp := TestDeploymentWithHostHeader(t, env, "example.com", "/") defer resp.Body.Close() // Should either return 404 or pass to default handler assert.NotEqual(t, http.StatusOK, resp.StatusCode, "Non-debros domain should not route to deployment") t.Logf("✓ Non-debros domains correctly pass through (status: %d)", resp.StatusCode) }) t.Run("API paths bypass domain routing", func(t *testing.T) { // /v1/* paths should bypass domain routing and use API key auth domain := env.BuildDeploymentDomain(deploymentName) req, _ := http.NewRequest("GET", env.GatewayURL+"/v1/deployments/list", nil) req.Host = domain req.Header.Set("Authorization", "Bearer "+env.APIKey) resp, err := env.HTTPClient.Do(req) require.NoError(t, err, "Should execute request") defer resp.Body.Close() // Should return API response, not deployment content assert.Equal(t, http.StatusOK, resp.StatusCode, "API endpoint should work") var result map[string]interface{} bodyBytes, _ := io.ReadAll(resp.Body) err = json.Unmarshal(bodyBytes, &result) // Should be JSON API response assert.NoError(t, err, "Should decode JSON (API response)") assert.NotNil(t, result["deployments"], "Should have deployments field") t.Logf("✓ API paths correctly bypass domain routing") }) t.Run("Well-known paths bypass domain routing", func(t *testing.T) { domain := env.BuildDeploymentDomain(deploymentName) // /.well-known/ paths should bypass (used for ACME challenges, etc.) resp := TestDeploymentWithHostHeader(t, env, domain, "/.well-known/acme-challenge/test") defer resp.Body.Close() // Should not serve deployment content // Exact status depends on implementation, but shouldn't be deployment content body, _ := io.ReadAll(resp.Body) bodyStr := string(body) // Shouldn't contain React app content if resp.StatusCode == http.StatusOK { assert.NotContains(t, bodyStr, "
", "Well-known paths should not serve deployment content") } t.Logf("✓ Well-known paths bypass routing (status: %d)", resp.StatusCode) }) } func TestDomainRouting_MultipleDeployments(t *testing.T) { env, err := LoadTestEnv() require.NoError(t, err, "Failed to load test environment") tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") // Create multiple deployments deployment1Name := fmt.Sprintf("test-multi-1-%d", time.Now().Unix()) deployment2Name := fmt.Sprintf("test-multi-2-%d", time.Now().Unix()) deployment1ID := CreateTestDeployment(t, env, deployment1Name, tarballPath) time.Sleep(1 * time.Second) deployment2ID := CreateTestDeployment(t, env, deployment2Name, tarballPath) defer func() { if !env.SkipCleanup { DeleteDeployment(t, env, deployment1ID) DeleteDeployment(t, env, deployment2ID) } }() time.Sleep(2 * time.Second) t.Run("Each deployment routes independently", func(t *testing.T) { domain1 := env.BuildDeploymentDomain(deployment1Name) domain2 := env.BuildDeploymentDomain(deployment2Name) // Test deployment 1 resp1 := TestDeploymentWithHostHeader(t, env, domain1, "/") defer resp1.Body.Close() assert.Equal(t, http.StatusOK, resp1.StatusCode, "Deployment 1 should serve") // Test deployment 2 resp2 := TestDeploymentWithHostHeader(t, env, domain2, "/") defer resp2.Body.Close() assert.Equal(t, http.StatusOK, resp2.StatusCode, "Deployment 2 should serve") t.Logf("✓ Multiple deployments route independently") t.Logf(" - Domain 1: %s", domain1) t.Logf(" - Domain 2: %s", domain2) }) t.Run("Wrong domain returns 404", func(t *testing.T) { // Request with non-existent deployment subdomain fakeDeploymentDomain := env.BuildDeploymentDomain(fmt.Sprintf("nonexistent-deployment-%d", time.Now().Unix())) resp := TestDeploymentWithHostHeader(t, env, fakeDeploymentDomain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "Non-existent deployment should return 404") t.Logf("✓ Non-existent deployment returns 404") }) } func TestDomainRouting_ContentTypes(t *testing.T) { env, err := LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("test-content-types-%d", time.Now().Unix()) tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { DeleteDeployment(t, env, deploymentID) } }() time.Sleep(2 * time.Second) domain := env.BuildDeploymentDomain(deploymentName) contentTypeTests := []struct { path string shouldHave string description string }{ {"/", "text/html", "HTML root"}, {"/index.html", "text/html", "HTML file"}, } for _, test := range contentTypeTests { t.Run(test.description, func(t *testing.T) { resp := TestDeploymentWithHostHeader(t, env, domain, test.path) defer resp.Body.Close() if resp.StatusCode == http.StatusOK { contentType := resp.Header.Get("Content-Type") assert.Contains(t, contentType, test.shouldHave, "Content-Type for %s should contain %s", test.path, test.shouldHave) t.Logf("✓ %s: %s", test.description, contentType) } else { t.Logf("⚠ %s returned status %d", test.path, resp.StatusCode) } }) } } func TestDomainRouting_SPAFallback(t *testing.T) { env, err := LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("test-spa-%d", time.Now().Unix()) tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { DeleteDeployment(t, env, deploymentID) } }() time.Sleep(2 * time.Second) domain := env.BuildDeploymentDomain(deploymentName) t.Run("Unknown paths fall back to index.html", func(t *testing.T) { unknownPaths := []string{ "/about", "/users/123", "/settings/profile", "/some/deep/nested/path", } for _, path := range unknownPaths { resp := TestDeploymentWithHostHeader(t, env, domain, path) body, _ := io.ReadAll(resp.Body) resp.Body.Close() // Should return index.html for SPA routing assert.Equal(t, http.StatusOK, resp.StatusCode, "SPA fallback should return 200 for %s", path) assert.Contains(t, string(body), "
", "SPA fallback should return index.html for %s", path) } t.Logf("✓ SPA fallback routing verified for %d paths", len(unknownPaths)) }) } // TestDeployment_DomainFormat verifies that deployment URLs use the correct format: // - CORRECT: {name}.{baseDomain} (e.g., "myapp.dbrs.space") // - WRONG: {name}.node-{shortID}.{baseDomain} (should NOT exist) func TestDeployment_DomainFormat(t *testing.T) { env, err := LoadTestEnv() require.NoError(t, err, "Failed to load test environment") deploymentName := fmt.Sprintf("format-test-%d", time.Now().Unix()) tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { DeleteDeployment(t, env, deploymentID) } }() // Wait for deployment time.Sleep(2 * time.Second) t.Run("Deployment URL has correct format", func(t *testing.T) { deployment := GetDeployment(t, env, deploymentID) // Get the deployment URLs urls, ok := deployment["urls"].([]interface{}) if !ok || len(urls) == 0 { // Fall back to single url field if url, ok := deployment["url"].(string); ok && url != "" { urls = []interface{}{url} } } expectedDomain := env.BuildDeploymentDomain(deploymentName) t.Logf("Expected domain format: %s", expectedDomain) t.Logf("Deployment URLs: %v", urls) foundCorrectFormat := false for _, u := range urls { urlStr, ok := u.(string) if !ok { continue } // URL should contain the simple format: {name}.{baseDomain} if assert.Contains(t, urlStr, expectedDomain, "URL should contain %s", expectedDomain) { foundCorrectFormat = true } // URL should NOT contain node identifier pattern assert.NotContains(t, urlStr, ".node-", "URL should NOT have node identifier (got: %s)", urlStr) } if len(urls) > 0 { assert.True(t, foundCorrectFormat, "Should find URL with correct domain format") } t.Logf("✓ Domain format verification passed") t.Logf(" - Expected: %s", expectedDomain) }) t.Run("Domain resolves via Host header", func(t *testing.T) { // Test that the simple domain format works domain := env.BuildDeploymentDomain(deploymentName) resp := TestDeploymentWithHostHeader(t, env, domain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "Domain %s should resolve successfully", domain) body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(body), "
", "Should serve deployment content") t.Logf("✓ Domain %s resolves correctly", domain) }) }