diff --git a/Makefile b/Makefile index 020ee5b..e791a23 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ test: # Gateway-focused E2E tests assume gateway and nodes are already running # Auto-discovers configuration from ~/.orama and queries database for API key # No environment variables required -.PHONY: test-e2e test-e2e-deployments test-e2e-fullstack test-e2e-https test-e2e-quick test-e2e-local test-e2e-prod +.PHONY: test-e2e test-e2e-deployments test-e2e-fullstack test-e2e-https test-e2e-quick test-e2e-local test-e2e-prod test-e2e-shared test-e2e-cluster test-e2e-integration test-e2e-production # Check if gateway is running (helper) .PHONY: check-gateway @@ -32,15 +32,15 @@ test-e2e-local: check-gateway @echo "Running E2E tests against local dev environment..." go test -v -tags e2e -timeout 30m ./e2e/... -# Production E2E tests - requires ORAMA_GATEWAY_URL to be set +# Production E2E tests - includes production-only tests test-e2e-prod: @if [ -z "$$ORAMA_GATEWAY_URL" ]; then \ echo "❌ ORAMA_GATEWAY_URL not set"; \ echo "Usage: ORAMA_GATEWAY_URL=http://VPS-IP:6001 make test-e2e-prod"; \ exit 1; \ fi - @echo "Running E2E tests against $$ORAMA_GATEWAY_URL..." - go test -v -tags e2e -timeout 30m ./e2e/... + @echo "Running E2E tests (including production-only) against $$ORAMA_GATEWAY_URL..." + go test -v -tags "e2e production" -timeout 30m ./e2e/... # Generic e2e target (works with both local and production) test-e2e: @@ -61,6 +61,22 @@ test-e2e-https: @echo "Running HTTPS/external access E2E tests..." go test -v -tags e2e -timeout 10m -run "TestHTTPS" ./e2e/... +test-e2e-shared: + @echo "Running shared E2E tests..." + go test -v -tags e2e -timeout 10m ./e2e/shared/... + +test-e2e-cluster: + @echo "Running cluster E2E tests..." + go test -v -tags e2e -timeout 15m ./e2e/cluster/... + +test-e2e-integration: + @echo "Running integration E2E tests..." + go test -v -tags e2e -timeout 20m ./e2e/integration/... + +test-e2e-production: + @echo "Running production-only E2E tests..." + go test -v -tags "e2e production" -timeout 15m ./e2e/production/... + test-e2e-quick: @echo "Running quick E2E smoke tests..." go test -v -tags e2e -timeout 5m -run "TestStatic|TestHealth" ./e2e/... @@ -155,10 +171,15 @@ help: @echo " make kill - Force kill all development services (use if stop fails)" @echo "" @echo "E2E Testing:" - @echo " make test-e2e-local - Run E2E tests against local dev (checks gateway first)" - @echo " make test-e2e-prod - Run E2E tests against production (needs ORAMA_GATEWAY_URL)" - @echo " make test-e2e-quick - Quick smoke tests (static deploys, health checks)" - @echo " make test-e2e - Generic E2E tests (auto-discovers config)" + @echo " make test-e2e-local - Run E2E tests against local dev (checks gateway first)" + @echo " make test-e2e-prod - Run all E2E tests incl. production-only (needs ORAMA_GATEWAY_URL)" + @echo " make test-e2e-shared - Run shared E2E tests (cache, storage, pubsub, auth)" + @echo " make test-e2e-cluster - Run cluster E2E tests (libp2p, olric, rqlite, namespace)" + @echo " make test-e2e-integration - Run integration E2E tests (fullstack, persistence, concurrency)" + @echo " make test-e2e-deployments - Run deployment E2E tests" + @echo " make test-e2e-production - Run production-only E2E tests (DNS, HTTPS, cross-node)" + @echo " make test-e2e-quick - Quick smoke tests (static deploys, health checks)" + @echo " make test-e2e - Generic E2E tests (auto-discovers config)" @echo "" @echo " Example production test:" @echo " ORAMA_GATEWAY_URL=http://141.227.165.168:6001 make test-e2e-prod" diff --git a/e2e/ipfs_cluster_test.go b/e2e/cluster/ipfs_cluster_test.go similarity index 87% rename from e2e/ipfs_cluster_test.go rename to e2e/cluster/ipfs_cluster_test.go index a0d4812..76c01fd 100644 --- a/e2e/ipfs_cluster_test.go +++ b/e2e/cluster/ipfs_cluster_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package cluster_test import ( "bytes" @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/DeBrosOfficial/network/pkg/ipfs" ) @@ -18,13 +19,13 @@ import ( // For production testing, use storage_http_test.go which uses gateway endpoints. func TestIPFSCluster_Health(t *testing.T) { - SkipIfProduction(t) // Direct IPFS connection not available in production + e2e.SkipIfProduction(t) // Direct IPFS connection not available in production ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 10 * time.Second, } @@ -40,13 +41,13 @@ func TestIPFSCluster_Health(t *testing.T) { } func TestIPFSCluster_GetPeerCount(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 10 * time.Second, } @@ -68,13 +69,13 @@ func TestIPFSCluster_GetPeerCount(t *testing.T) { } func TestIPFSCluster_AddFile(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -101,13 +102,13 @@ func TestIPFSCluster_AddFile(t *testing.T) { } func TestIPFSCluster_PinFile(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -139,13 +140,13 @@ func TestIPFSCluster_PinFile(t *testing.T) { } func TestIPFSCluster_PinStatus(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -173,7 +174,7 @@ func TestIPFSCluster_PinStatus(t *testing.T) { } // Give pin time to propagate - Delay(1000) + e2e.Delay(1000) // Get status status, err := client.PinStatus(ctx, cid) @@ -197,13 +198,13 @@ func TestIPFSCluster_PinStatus(t *testing.T) { } func TestIPFSCluster_UnpinFile(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -236,13 +237,13 @@ func TestIPFSCluster_UnpinFile(t *testing.T) { } func TestIPFSCluster_GetFile(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -261,10 +262,10 @@ func TestIPFSCluster_GetFile(t *testing.T) { cid := addResult.Cid // Give time for propagation - Delay(1000) + e2e.Delay(1000) // Get file - rc, err := client.Get(ctx, cid, GetIPFSAPIURL()) + rc, err := client.Get(ctx, cid, e2e.GetIPFSAPIURL()) if err != nil { t.Fatalf("get file failed: %v", err) } @@ -283,13 +284,13 @@ func TestIPFSCluster_GetFile(t *testing.T) { } func TestIPFSCluster_LargeFile(t *testing.T) { - SkipIfProduction(t) + e2e.SkipIfProduction(t) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 60 * time.Second, } @@ -317,13 +318,13 @@ func TestIPFSCluster_LargeFile(t *testing.T) { } func TestIPFSCluster_ReplicationFactor(t *testing.T) { - SkipIfProduction(t) // Direct IPFS connection not available in production + e2e.SkipIfProduction(t) // Direct IPFS connection not available in production ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } @@ -353,7 +354,7 @@ func TestIPFSCluster_ReplicationFactor(t *testing.T) { } // Give time for replication - Delay(2000) + e2e.Delay(2000) // Check status status, err := client.PinStatus(ctx, cid) @@ -365,13 +366,13 @@ func TestIPFSCluster_ReplicationFactor(t *testing.T) { } func TestIPFSCluster_MultipleFiles(t *testing.T) { - SkipIfProduction(t) // Direct IPFS connection not available in production + e2e.SkipIfProduction(t) // Direct IPFS connection not available in production ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() - logger := NewTestLogger(t) + logger := e2e.NewTestLogger(t) cfg := ipfs.Config{ - ClusterAPIURL: GetIPFSClusterURL(), + ClusterAPIURL: e2e.GetIPFSClusterURL(), Timeout: 30 * time.Second, } diff --git a/e2e/libp2p_connectivity_test.go b/e2e/cluster/libp2p_connectivity_test.go similarity index 79% rename from e2e/libp2p_connectivity_test.go rename to e2e/cluster/libp2p_connectivity_test.go index 0a6408a..225c751 100644 --- a/e2e/libp2p_connectivity_test.go +++ b/e2e/cluster/libp2p_connectivity_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package cluster_test import ( "context" @@ -8,25 +8,27 @@ import ( "strings" "testing" "time" + + "github.com/DeBrosOfficial/network/e2e" ) func TestLibP2P_PeerConnectivity(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create and connect client - c := NewNetworkClient(t) + c := e2e.NewNetworkClient(t) if err := c.Connect(); err != nil { t.Fatalf("connect failed: %v", err) } defer c.Disconnect() // Verify peer connectivity through the gateway - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/peers", + URL: e2e.GetGatewayURL() + "/v1/network/peers", } body, status, err := req.Do(ctx) @@ -39,7 +41,7 @@ func TestLibP2P_PeerConnectivity(t *testing.T) { } var resp map[string]interface{} - if err := DecodeJSON(body, &resp); err != nil { + if err := e2e.DecodeJSON(body, &resp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -50,30 +52,30 @@ func TestLibP2P_PeerConnectivity(t *testing.T) { } func TestLibP2P_BootstrapPeers(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - bootstrapPeers := GetBootstrapPeers() + bootstrapPeers := e2e.GetBootstrapPeers() if len(bootstrapPeers) == 0 { t.Skipf("E2E_BOOTSTRAP_PEERS not set; skipping") } // Create client with bootstrap peers explicitly set - c := NewNetworkClient(t) + c := e2e.NewNetworkClient(t) if err := c.Connect(); err != nil { t.Fatalf("connect failed: %v", err) } defer c.Disconnect() // Give peer discovery time - Delay(2000) + e2e.Delay(2000) // Verify we're connected (check via gateway status) - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/status", + URL: e2e.GetGatewayURL() + "/v1/network/status", } body, status, err := req.Do(ctx) @@ -86,7 +88,7 @@ func TestLibP2P_BootstrapPeers(t *testing.T) { } var resp map[string]interface{} - if err := DecodeJSON(body, &resp); err != nil { + if err := e2e.DecodeJSON(body, &resp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -96,15 +98,15 @@ func TestLibP2P_BootstrapPeers(t *testing.T) { } func TestLibP2P_MultipleClientConnections(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create multiple clients - c1 := NewNetworkClient(t) - c2 := NewNetworkClient(t) - c3 := NewNetworkClient(t) + c1 := e2e.NewNetworkClient(t) + c2 := e2e.NewNetworkClient(t) + c3 := e2e.NewNetworkClient(t) if err := c1.Connect(); err != nil { t.Fatalf("c1 connect failed: %v", err) @@ -122,12 +124,12 @@ func TestLibP2P_MultipleClientConnections(t *testing.T) { defer c3.Disconnect() // Give peer discovery time - Delay(2000) + e2e.Delay(2000) // Verify gateway sees multiple peers - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/peers", + URL: e2e.GetGatewayURL() + "/v1/network/peers", } body, status, err := req.Do(ctx) @@ -140,7 +142,7 @@ func TestLibP2P_MultipleClientConnections(t *testing.T) { } var resp map[string]interface{} - if err := DecodeJSON(body, &resp); err != nil { + if err := e2e.DecodeJSON(body, &resp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -151,12 +153,12 @@ func TestLibP2P_MultipleClientConnections(t *testing.T) { } func TestLibP2P_ReconnectAfterDisconnect(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - c := NewNetworkClient(t) + c := e2e.NewNetworkClient(t) // Connect if err := c.Connect(); err != nil { @@ -164,9 +166,9 @@ func TestLibP2P_ReconnectAfterDisconnect(t *testing.T) { } // Verify connected via gateway - req1 := &HTTPRequest{ + req1 := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/status", + URL: e2e.GetGatewayURL() + "/v1/network/status", } _, status1, err := req1.Do(ctx) @@ -180,7 +182,7 @@ func TestLibP2P_ReconnectAfterDisconnect(t *testing.T) { } // Give time for disconnect to propagate - Delay(500) + e2e.Delay(500) // Reconnect if err := c.Connect(); err != nil { @@ -189,9 +191,9 @@ func TestLibP2P_ReconnectAfterDisconnect(t *testing.T) { defer c.Disconnect() // Verify connected via gateway again - req2 := &HTTPRequest{ + req2 := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/status", + URL: e2e.GetGatewayURL() + "/v1/network/status", } _, status2, err := req2.Do(ctx) @@ -201,25 +203,25 @@ func TestLibP2P_ReconnectAfterDisconnect(t *testing.T) { } func TestLibP2P_PeerDiscovery(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create client - c := NewNetworkClient(t) + c := e2e.NewNetworkClient(t) if err := c.Connect(); err != nil { t.Fatalf("connect failed: %v", err) } defer c.Disconnect() // Give peer discovery time - Delay(3000) + e2e.Delay(3000) // Get peer list - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/peers", + URL: e2e.GetGatewayURL() + "/v1/network/peers", } body, status, err := req.Do(ctx) @@ -232,7 +234,7 @@ func TestLibP2P_PeerDiscovery(t *testing.T) { } var resp map[string]interface{} - if err := DecodeJSON(body, &resp); err != nil { + if err := e2e.DecodeJSON(body, &resp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -251,22 +253,22 @@ func TestLibP2P_PeerDiscovery(t *testing.T) { } func TestLibP2P_PeerAddressFormat(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create client - c := NewNetworkClient(t) + c := e2e.NewNetworkClient(t) if err := c.Connect(); err != nil { t.Fatalf("connect failed: %v", err) } defer c.Disconnect() // Get peer list - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/peers", + URL: e2e.GetGatewayURL() + "/v1/network/peers", } body, status, err := req.Do(ctx) @@ -279,7 +281,7 @@ func TestLibP2P_PeerAddressFormat(t *testing.T) { } var resp map[string]interface{} - if err := DecodeJSON(body, &resp); err != nil { + if err := e2e.DecodeJSON(body, &resp); err != nil { t.Fatalf("failed to decode response: %v", err) } diff --git a/e2e/namespace_cluster_test.go b/e2e/cluster/namespace_cluster_test.go similarity index 96% rename from e2e/namespace_cluster_test.go rename to e2e/cluster/namespace_cluster_test.go index 646087a..c3e58ba 100644 --- a/e2e/namespace_cluster_test.go +++ b/e2e/cluster/namespace_cluster_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package cluster_test import ( "context" @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/require" ) @@ -30,7 +31,7 @@ func TestNamespaceCluster_FullProvisioning(t *testing.T) { // Generate unique namespace name newNamespace := fmt.Sprintf("e2e-cluster-%d", time.Now().UnixNano()) - env, err := LoadTestEnvWithNamespace(newNamespace) + env, err := e2e.LoadTestEnvWithNamespace(newNamespace) require.NoError(t, err, "FATAL: Failed to create test environment for namespace %s", newNamespace) require.NotEmpty(t, env.APIKey, "FATAL: No API key received - namespace provisioning failed") @@ -70,7 +71,7 @@ func TestNamespaceCluster_FullProvisioning(t *testing.T) { } deploymentName := fmt.Sprintf("cluster-test-%d", time.Now().Unix()) - deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) require.NotEmpty(t, deploymentID, "FAIL: Deployment creation failed on namespace cluster") t.Logf("Created deployment %s (ID: %s) on namespace %s", deploymentName, deploymentID, newNamespace) @@ -78,7 +79,7 @@ func TestNamespaceCluster_FullProvisioning(t *testing.T) { // Cleanup defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() @@ -246,7 +247,7 @@ func TestNamespaceCluster_ProvisioningCreatesProcesses(t *testing.T) { t.Logf("Ports in use before provisioning: %v", portsBefore) // Create namespace - env, err := LoadTestEnvWithNamespace(newNamespace) + env, err := e2e.LoadTestEnvWithNamespace(newNamespace) require.NoError(t, err, "FATAL: Failed to create namespace") require.NotEmpty(t, env.APIKey, "FATAL: No API key - provisioning failed") @@ -330,7 +331,7 @@ func TestNamespaceCluster_ProvisioningCreatesProcesses(t *testing.T) { // TestNamespaceCluster_StatusEndpoint tests the /v1/namespace/status endpoint func TestNamespaceCluster_StatusEndpoint(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") t.Run("Status endpoint returns 404 for non-existent cluster", func(t *testing.T) { @@ -351,10 +352,10 @@ func TestNamespaceCluster_CrossNamespaceAccess(t *testing.T) { nsA := fmt.Sprintf("ns-a-%d", time.Now().Unix()) nsB := fmt.Sprintf("ns-b-%d", time.Now().Unix()) - envA, err := LoadTestEnvWithNamespace(nsA) + envA, err := e2e.LoadTestEnvWithNamespace(nsA) require.NoError(t, err, "FAIL: Cannot create namespace A") - envB, err := LoadTestEnvWithNamespace(nsB) + envB, err := e2e.LoadTestEnvWithNamespace(nsB) require.NoError(t, err, "FAIL: Cannot create namespace B") // Verify both namespaces have different API keys @@ -392,7 +393,7 @@ func TestNamespaceCluster_CrossNamespaceAccess(t *testing.T) { // TestDeployment_SubdomainFormat tests deployment subdomain format func TestDeployment_SubdomainFormat(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") @@ -401,12 +402,12 @@ func TestDeployment_SubdomainFormat(t *testing.T) { } deploymentName := fmt.Sprintf("subdomain-test-%d", time.Now().UnixNano()) - deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) require.NotEmpty(t, deploymentID, "FAIL: Deployment creation failed") defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() diff --git a/e2e/namespace_isolation_test.go b/e2e/cluster/namespace_isolation_test.go similarity index 91% rename from e2e/namespace_isolation_test.go rename to e2e/cluster/namespace_isolation_test.go index 584af45..79241ea 100644 --- a/e2e/namespace_isolation_test.go +++ b/e2e/cluster/namespace_isolation_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package cluster_test import ( "bytes" @@ -12,35 +12,36 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNamespaceIsolation_Deployments(t *testing.T) { // Setup two test environments with different namespaces - envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) + envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Failed to create namespace A environment") - envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) + envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Failed to create namespace B environment") tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") // Create deployment in namespace-a deploymentNameA := "test-app-ns-a" - deploymentIDA := CreateTestDeployment(t, envA, deploymentNameA, tarballPath) + deploymentIDA := e2e.CreateTestDeployment(t, envA, deploymentNameA, tarballPath) defer func() { if !envA.SkipCleanup { - DeleteDeployment(t, envA, deploymentIDA) + e2e.DeleteDeployment(t, envA, deploymentIDA) } }() // Create deployment in namespace-b deploymentNameB := "test-app-ns-b" - deploymentIDB := CreateTestDeployment(t, envB, deploymentNameB, tarballPath) + deploymentIDB := e2e.CreateTestDeployment(t, envB, deploymentNameB, tarballPath) defer func() { if !envB.SkipCleanup { - DeleteDeployment(t, envB, deploymentIDB) + e2e.DeleteDeployment(t, envB, deploymentIDB) } }() @@ -112,27 +113,27 @@ func TestNamespaceIsolation_Deployments(t *testing.T) { } func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) { - envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) + envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-a") - envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) + envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-b") // Create database in namespace-a dbNameA := "users-db-a" - CreateSQLiteDB(t, envA, dbNameA) + e2e.CreateSQLiteDB(t, envA, dbNameA) defer func() { if !envA.SkipCleanup { - DeleteSQLiteDB(t, envA, dbNameA) + e2e.DeleteSQLiteDB(t, envA, dbNameA) } }() // Create database in namespace-b dbNameB := "users-db-b" - CreateSQLiteDB(t, envB, dbNameB) + e2e.CreateSQLiteDB(t, envB, dbNameB) defer func() { if !envB.SkipCleanup { - DeleteSQLiteDB(t, envB, dbNameB) + e2e.DeleteSQLiteDB(t, envB, dbNameB) } }() @@ -201,17 +202,17 @@ func TestNamespaceIsolation_SQLiteDatabases(t *testing.T) { } func TestNamespaceIsolation_IPFSContent(t *testing.T) { - envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) + envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-a") - envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) + envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-b") // Upload file in namespace-a - cidA := UploadTestFile(t, envA, "test-file-a.txt", "Content from namespace A") + cidA := e2e.UploadTestFile(t, envA, "test-file-a.txt", "Content from namespace A") defer func() { if !envA.SkipCleanup { - UnpinFile(t, envA, cidA) + e2e.UnpinFile(t, envA, cidA) } }() @@ -273,10 +274,10 @@ func TestNamespaceIsolation_IPFSContent(t *testing.T) { } func TestNamespaceIsolation_OlricCache(t *testing.T) { - envA, err := LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) + envA, err := e2e.LoadTestEnvWithNamespace("namespace-a-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-a") - envB, err := LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) + envB, err := e2e.LoadTestEnvWithNamespace("namespace-b-" + fmt.Sprintf("%d", time.Now().Unix())) require.NoError(t, err, "Should create test environment for namespace-b") dmap := "test-cache" diff --git a/e2e/olric_cluster_test.go b/e2e/cluster/olric_cluster_test.go similarity index 74% rename from e2e/olric_cluster_test.go rename to e2e/cluster/olric_cluster_test.go index f8d4032..e6359d8 100644 --- a/e2e/olric_cluster_test.go +++ b/e2e/cluster/olric_cluster_test.go @@ -1,17 +1,17 @@ //go:build e2e -package e2e +package cluster_test import ( "encoding/json" "fmt" - "io" "net" "net/http" "strings" "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/require" ) @@ -31,86 +31,10 @@ func getOlricNodeAddresses() []string { } } -// putToOlric stores a key-value pair in Olric via HTTP API -func putToOlric(gatewayURL, apiKey, dmap, key, value string) error { - reqBody := map[string]interface{}{ - "dmap": dmap, - "key": key, - "value": value, - } - bodyBytes, _ := json.Marshal(reqBody) - - req, err := http.NewRequest("POST", gatewayURL+"/v1/cache/put", strings.NewReader(string(bodyBytes))) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+apiKey) - - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("put failed with status %d: %s", resp.StatusCode, string(body)) - } - return nil -} - -// getFromOlric retrieves a value from Olric via HTTP API -func getFromOlric(gatewayURL, apiKey, dmap, key string) (string, error) { - reqBody := map[string]interface{}{ - "dmap": dmap, - "key": key, - } - bodyBytes, _ := json.Marshal(reqBody) - - req, err := http.NewRequest("POST", gatewayURL+"/v1/cache/get", strings.NewReader(string(bodyBytes))) - if err != nil { - return "", err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+apiKey) - - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - return "", fmt.Errorf("key not found") - } - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return "", fmt.Errorf("get failed with status %d: %s", resp.StatusCode, string(body)) - } - - body, _ := io.ReadAll(resp.Body) - var result map[string]interface{} - if err := json.Unmarshal(body, &result); err != nil { - return "", err - } - - if value, ok := result["value"].(string); ok { - return value, nil - } - // Value might be in a different format - if value, ok := result["value"]; ok { - return fmt.Sprintf("%v", value), nil - } - return "", fmt.Errorf("value not found in response") -} - // TestOlric_BasicDistribution verifies cache operations work across the cluster. func TestOlric_BasicDistribution(t *testing.T) { // Note: Not using SkipIfMissingGateway() since LoadTestEnv() creates its own API key - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") require.NotEmpty(t, env.APIKey, "FAIL: No API key available") @@ -121,11 +45,11 @@ func TestOlric_BasicDistribution(t *testing.T) { value := fmt.Sprintf("value_%d", time.Now().UnixNano()) // Put - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not put value to cache") // Get - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get value from cache") require.Equal(t, value, retrieved, "FAIL: Retrieved value doesn't match") @@ -140,7 +64,7 @@ func TestOlric_BasicDistribution(t *testing.T) { value := fmt.Sprintf("dist_value_%d", i) keys[key] = value - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not put key %s", key) } @@ -148,7 +72,7 @@ func TestOlric_BasicDistribution(t *testing.T) { // Verify all keys are retrievable for key, expectedValue := range keys { - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get key %s", key) require.Equal(t, expectedValue, retrieved, "FAIL: Value mismatch for key %s", key) } @@ -159,7 +83,7 @@ func TestOlric_BasicDistribution(t *testing.T) { // TestOlric_ConcurrentAccess verifies cache handles concurrent operations correctly. func TestOlric_ConcurrentAccess(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") dmap := fmt.Sprintf("concurrent_test_%d", time.Now().UnixNano()) @@ -172,7 +96,7 @@ func TestOlric_ConcurrentAccess(t *testing.T) { for i := 0; i < 10; i++ { go func(idx int) { value := fmt.Sprintf("concurrent_value_%d", idx) - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) done <- err }(i) } @@ -188,7 +112,7 @@ func TestOlric_ConcurrentAccess(t *testing.T) { require.Empty(t, errors, "FAIL: %d concurrent writes failed: %v", len(errors), errors) // The key should have ONE of the values (last write wins) - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get key after concurrent writes") require.Contains(t, retrieved, "concurrent_value_", "FAIL: Value doesn't match expected pattern") @@ -200,7 +124,7 @@ func TestOlric_ConcurrentAccess(t *testing.T) { initialValue := "initial_value" // Set initial value - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, initialValue) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, initialValue) require.NoError(t, err, "FAIL: Could not set initial value") // Launch concurrent readers and writers @@ -209,7 +133,7 @@ func TestOlric_ConcurrentAccess(t *testing.T) { // 10 readers for i := 0; i < 10; i++ { go func() { - _, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + _, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) done <- err }() } @@ -218,7 +142,7 @@ func TestOlric_ConcurrentAccess(t *testing.T) { for i := 0; i < 10; i++ { go func(idx int) { value := fmt.Sprintf("updated_value_%d", idx) - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) done <- err }(i) } @@ -247,7 +171,7 @@ func TestOlric_NamespaceClusterCache(t *testing.T) { // Create a new namespace namespace := fmt.Sprintf("cache-test-%d", time.Now().UnixNano()) - env, err := LoadTestEnvWithNamespace(namespace) + env, err := e2e.LoadTestEnvWithNamespace(namespace) require.NoError(t, err, "FAIL: Could not create namespace for cache test") require.NotEmpty(t, env.APIKey, "FAIL: No API key") @@ -260,11 +184,11 @@ func TestOlric_NamespaceClusterCache(t *testing.T) { value := fmt.Sprintf("ns_value_%d", time.Now().UnixNano()) // Put using namespace API key - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not put value in namespace cache") // Get - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get value from namespace cache") require.Equal(t, value, retrieved, "FAIL: Value mismatch in namespace cache") @@ -298,7 +222,7 @@ func TestOlric_NamespaceClusterCache(t *testing.T) { // TestOlric_DataConsistency verifies data remains consistent across operations. func TestOlric_DataConsistency(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") dmap := fmt.Sprintf("consistency_test_%d", time.Now().UnixNano()) @@ -309,12 +233,12 @@ func TestOlric_DataConsistency(t *testing.T) { // Write multiple times for i := 1; i <= 5; i++ { value := fmt.Sprintf("version_%d", i) - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not update key to version %d", i) } // Final read should return latest version - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not read final value") require.Equal(t, "version_5", retrieved, "FAIL: Latest version not preserved") @@ -326,11 +250,11 @@ func TestOlric_DataConsistency(t *testing.T) { value := "to_be_deleted" // Put - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not put value") // Verify it exists - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get value before delete") require.Equal(t, value, retrieved) @@ -351,7 +275,7 @@ func TestOlric_DataConsistency(t *testing.T) { "FAIL: Delete returned unexpected status %d", resp.StatusCode) // Verify key is gone - _, err = getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + _, err = e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.Error(t, err, "FAIL: Key should not exist after delete") require.Contains(t, err.Error(), "not found", "FAIL: Expected 'not found' error") @@ -365,7 +289,7 @@ func TestOlric_DataConsistency(t *testing.T) { func TestOlric_TTLExpiration(t *testing.T) { t.Skip("TTL support not yet implemented in cache handler - see set_handler.go lines 88-98") - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") dmap := fmt.Sprintf("ttl_test_%d", time.Now().UnixNano()) @@ -396,7 +320,7 @@ func TestOlric_TTLExpiration(t *testing.T) { "FAIL: Put returned status %d", resp.StatusCode) // Verify key exists immediately - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Could not get key immediately after put") require.Equal(t, value, retrieved) t.Logf(" Key exists immediately after put") @@ -405,7 +329,7 @@ func TestOlric_TTLExpiration(t *testing.T) { time.Sleep(time.Duration(ttlSeconds+2) * time.Second) // Key should be gone - _, err = getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + _, err = e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.Error(t, err, "FAIL: Key should have expired after %d seconds", ttlSeconds) require.Contains(t, err.Error(), "not found", "FAIL: Expected 'not found' error after TTL") diff --git a/e2e/rqlite_cluster_test.go b/e2e/cluster/rqlite_cluster_test.go similarity index 85% rename from e2e/rqlite_cluster_test.go rename to e2e/cluster/rqlite_cluster_test.go index 04dd606..8f8e43a 100644 --- a/e2e/rqlite_cluster_test.go +++ b/e2e/cluster/rqlite_cluster_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package cluster_test import ( "context" @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/require" ) @@ -21,15 +22,15 @@ import ( // TestRQLite_ClusterHealth verifies the RQLite cluster is healthy and operational. func TestRQLite_ClusterHealth(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Check RQLite schema endpoint (proves cluster is reachable) - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/rqlite/schema", + URL: e2e.GetGatewayURL() + "/v1/rqlite/schema", } body, status, err := req.Do(ctx) @@ -37,7 +38,7 @@ func TestRQLite_ClusterHealth(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: RQLite schema endpoint returned %d: %s", status, string(body)) var schemaResp map[string]interface{} - err = DecodeJSON(body, &schemaResp) + err = e2e.DecodeJSON(body, &schemaResp) require.NoError(t, err, "FAIL: Could not decode RQLite schema response") // Schema endpoint should return tables array @@ -49,27 +50,27 @@ func TestRQLite_ClusterHealth(t *testing.T) { // TestRQLite_WriteReadConsistency verifies data written can be read back consistently. func TestRQLite_WriteReadConsistency(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, } dropReq.Do(context.Background()) }() // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": fmt.Sprintf( "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, value TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)", @@ -88,9 +89,9 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { uniqueValue := fmt.Sprintf("test_value_%d", time.Now().UnixNano()) // Insert - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("INSERT INTO %s (value) VALUES ('%s')", table, uniqueValue), @@ -103,9 +104,9 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Insert returned status %d", status) // Read back - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT value FROM %s WHERE value = '%s'", table, uniqueValue), }, @@ -116,7 +117,7 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Query returned status %d", status) var queryResp map[string]interface{} - err = DecodeJSON(body, &queryResp) + err = e2e.DecodeJSON(body, &queryResp) require.NoError(t, err, "FAIL: Could not decode query response") // Verify we got our value back @@ -135,9 +136,9 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { fmt.Sprintf("INSERT INTO %s (value) VALUES ('batch_%d')", table, i)) } - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": statements, }, @@ -148,9 +149,9 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Batch insert returned status %d", status) // Count all batch rows - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) as cnt FROM %s WHERE value LIKE 'batch_%%'", table), }, @@ -161,7 +162,7 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Count query returned status %d", status) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) @@ -175,27 +176,27 @@ func TestRQLite_WriteReadConsistency(t *testing.T) { // TestRQLite_TransactionAtomicity verifies transactions are atomic. func TestRQLite_TransactionAtomicity(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, } dropReq.Do(context.Background()) }() // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": fmt.Sprintf( "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, value TEXT UNIQUE)", @@ -210,9 +211,9 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { "FAIL: Create table returned status %d", status) t.Run("Successful_transaction_commits_all", func(t *testing.T) { - txReq := &HTTPRequest{ + txReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("INSERT INTO %s (value) VALUES ('tx_val_1')", table), @@ -227,9 +228,9 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Transaction returned status %d", status) // Verify all 3 rows exist - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE value LIKE 'tx_val_%%'", table), }, @@ -237,7 +238,7 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { body, _, _ := queryReq.Do(ctx) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) @@ -250,9 +251,9 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { t.Run("Updates_preserve_consistency", func(t *testing.T) { // Update a value - updateReq := &HTTPRequest{ + updateReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("UPDATE %s SET value = 'tx_val_1_updated' WHERE value = 'tx_val_1'", table), @@ -265,9 +266,9 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Update returned status %d", status) // Verify update took effect - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT value FROM %s WHERE value = 'tx_val_1_updated'", table), }, @@ -275,7 +276,7 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { body, _, _ := queryReq.Do(ctx) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) count, _ := queryResp["count"].(float64) require.Equal(t, float64(1), count, "FAIL: Update didn't take effect") @@ -286,27 +287,27 @@ func TestRQLite_TransactionAtomicity(t *testing.T) { // TestRQLite_ConcurrentWrites verifies the cluster handles concurrent writes correctly. func TestRQLite_ConcurrentWrites(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, } dropReq.Do(context.Background()) }() // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": fmt.Sprintf( "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, worker INTEGER, seq INTEGER)", @@ -333,9 +334,9 @@ func TestRQLite_ConcurrentWrites(t *testing.T) { go func(workerID int) { defer wg.Done() for i := 0; i < insertsPerWorker; i++ { - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("INSERT INTO %s (worker, seq) VALUES (%d, %d)", table, workerID, i), @@ -367,9 +368,9 @@ func TestRQLite_ConcurrentWrites(t *testing.T) { require.Empty(t, errors, "FAIL: %d concurrent inserts failed: %v", len(errors), errors) // Verify total count - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) FROM %s", table), }, @@ -377,7 +378,7 @@ func TestRQLite_ConcurrentWrites(t *testing.T) { body, _, _ := queryReq.Do(ctx) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) @@ -395,7 +396,7 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { // Create a new namespace namespace := fmt.Sprintf("rqlite-test-%d", time.Now().UnixNano()) - env, err := LoadTestEnvWithNamespace(namespace) + env, err := e2e.LoadTestEnvWithNamespace(namespace) require.NoError(t, err, "FAIL: Could not create namespace for RQLite test") require.NotEmpty(t, env.APIKey, "FAIL: No API key - namespace provisioning failed") @@ -404,11 +405,11 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, @@ -419,7 +420,7 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { t.Run("Namespace_RQLite_create_insert_query", func(t *testing.T) { // Create table in namespace cluster - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/create-table", Headers: map[string]string{"Authorization": "Bearer " + env.APIKey}, @@ -438,7 +439,7 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { // Insert data uniqueValue := fmt.Sprintf("ns_value_%d", time.Now().UnixNano()) - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/transaction", Headers: map[string]string{"Authorization": "Bearer " + env.APIKey}, @@ -454,7 +455,7 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Insert returned status %d", status) // Query data - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/query", Headers: map[string]string{"Authorization": "Bearer " + env.APIKey}, @@ -468,7 +469,7 @@ func TestRQLite_NamespaceClusterOperations(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Query returned status %d", status) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) count, _ := queryResp["count"].(float64) require.Equal(t, float64(1), count, "FAIL: Data not found in namespace cluster") diff --git a/e2e/env.go b/e2e/env.go index a105ef8..8f90338 100644 --- a/e2e/env.go +++ b/e2e/env.go @@ -1536,6 +1536,81 @@ func TestDeploymentWithHostHeader(t *testing.T, env *E2ETestEnv, host, path stri return resp } +// PutToOlric stores a key-value pair in Olric via the gateway HTTP API +func PutToOlric(gatewayURL, apiKey, dmap, key, value string) error { + reqBody := map[string]interface{}{ + "dmap": dmap, + "key": key, + "value": value, + } + bodyBytes, _ := json.Marshal(reqBody) + + req, err := http.NewRequest("POST", gatewayURL+"/v1/cache/put", strings.NewReader(string(bodyBytes))) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("put failed with status %d: %s", resp.StatusCode, string(body)) + } + return nil +} + +// GetFromOlric retrieves a value from Olric via the gateway HTTP API +func GetFromOlric(gatewayURL, apiKey, dmap, key string) (string, error) { + reqBody := map[string]interface{}{ + "dmap": dmap, + "key": key, + } + bodyBytes, _ := json.Marshal(reqBody) + + req, err := http.NewRequest("POST", gatewayURL+"/v1/cache/get", strings.NewReader(string(bodyBytes))) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return "", fmt.Errorf("key not found") + } + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("get failed with status %d: %s", resp.StatusCode, string(body)) + } + + body, _ := io.ReadAll(resp.Body) + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return "", err + } + + if value, ok := result["value"].(string); ok { + return value, nil + } + if value, ok := result["value"]; ok { + return fmt.Sprintf("%v", value), nil + } + return "", fmt.Errorf("value not found in response") +} + // WaitForHealthy waits for a deployment to become healthy func WaitForHealthy(t *testing.T, env *E2ETestEnv, deploymentID string, timeout time.Duration) bool { t.Helper() diff --git a/e2e/concurrency_test.go b/e2e/integration/concurrency_test.go similarity index 81% rename from e2e/concurrency_test.go rename to e2e/integration/concurrency_test.go index 4312842..967825d 100644 --- a/e2e/concurrency_test.go +++ b/e2e/integration/concurrency_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package integration_test import ( "context" @@ -10,16 +10,18 @@ import ( "sync/atomic" "testing" "time" + + "github.com/DeBrosOfficial/network/e2e" ) // TestCache_ConcurrentWrites tests concurrent cache writes func TestCache_ConcurrentWrites(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() numGoroutines := 10 var wg sync.WaitGroup var errorCount int32 @@ -32,9 +34,9 @@ func TestCache_ConcurrentWrites(t *testing.T) { key := fmt.Sprintf("key-%d", idx) value := fmt.Sprintf("value-%d", idx) - putReq := &HTTPRequest{ + putReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/put", + URL: e2e.GetGatewayURL() + "/v1/cache/put", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -56,9 +58,9 @@ func TestCache_ConcurrentWrites(t *testing.T) { } // Verify all values exist - scanReq := &HTTPRequest{ + scanReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/scan", + URL: e2e.GetGatewayURL() + "/v1/cache/scan", Body: map[string]interface{}{ "dmap": dmap, }, @@ -70,7 +72,7 @@ func TestCache_ConcurrentWrites(t *testing.T) { } var scanResp map[string]interface{} - if err := DecodeJSON(body, &scanResp); err != nil { + if err := e2e.DecodeJSON(body, &scanResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -82,19 +84,19 @@ func TestCache_ConcurrentWrites(t *testing.T) { // TestCache_ConcurrentReads tests concurrent cache reads func TestCache_ConcurrentReads(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "shared-key" value := "shared-value" // Put value first - putReq := &HTTPRequest{ + putReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/put", + URL: e2e.GetGatewayURL() + "/v1/cache/put", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -117,9 +119,9 @@ func TestCache_ConcurrentReads(t *testing.T) { go func() { defer wg.Done() - getReq := &HTTPRequest{ + getReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/get", + URL: e2e.GetGatewayURL() + "/v1/cache/get", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -133,7 +135,7 @@ func TestCache_ConcurrentReads(t *testing.T) { } var getResp map[string]interface{} - if err := DecodeJSON(body, &getResp); err != nil { + if err := e2e.DecodeJSON(body, &getResp); err != nil { atomic.AddInt32(&errorCount, 1) return } @@ -153,12 +155,12 @@ func TestCache_ConcurrentReads(t *testing.T) { // TestCache_ConcurrentDeleteAndWrite tests concurrent delete and write func TestCache_ConcurrentDeleteAndWrite(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() var wg sync.WaitGroup var errorCount int32 @@ -174,9 +176,9 @@ func TestCache_ConcurrentDeleteAndWrite(t *testing.T) { key := fmt.Sprintf("key-%d", idx) value := fmt.Sprintf("value-%d", idx) - putReq := &HTTPRequest{ + putReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/put", + URL: e2e.GetGatewayURL() + "/v1/cache/put", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -201,9 +203,9 @@ func TestCache_ConcurrentDeleteAndWrite(t *testing.T) { key := fmt.Sprintf("key-%d", idx) - deleteReq := &HTTPRequest{ + deleteReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/delete", + URL: e2e.GetGatewayURL() + "/v1/cache/delete", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -226,18 +228,18 @@ func TestCache_ConcurrentDeleteAndWrite(t *testing.T) { // TestRQLite_ConcurrentInserts tests concurrent database inserts func TestRQLite_ConcurrentInserts(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup table after test defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, } dropReq.Do(context.Background()) @@ -249,9 +251,9 @@ func TestRQLite_ConcurrentInserts(t *testing.T) { ) // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": schema, }, @@ -272,9 +274,9 @@ func TestRQLite_ConcurrentInserts(t *testing.T) { go func(idx int) { defer wg.Done() - txReq := &HTTPRequest{ + txReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("INSERT INTO %s(value) VALUES (%d)", table, idx), @@ -296,9 +298,9 @@ func TestRQLite_ConcurrentInserts(t *testing.T) { } // Verify count - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) as count FROM %s", table), }, @@ -310,7 +312,7 @@ func TestRQLite_ConcurrentInserts(t *testing.T) { } var countResp map[string]interface{} - if err := DecodeJSON(body, &countResp); err != nil { + if err := e2e.DecodeJSON(body, &countResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -325,18 +327,18 @@ func TestRQLite_ConcurrentInserts(t *testing.T) { // TestRQLite_LargeBatchTransaction tests a large transaction with many statements func TestRQLite_LargeBatchTransaction(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() // Cleanup table after test defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": table}, } dropReq.Do(context.Background()) @@ -348,9 +350,9 @@ func TestRQLite_LargeBatchTransaction(t *testing.T) { ) // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": schema, }, @@ -370,9 +372,9 @@ func TestRQLite_LargeBatchTransaction(t *testing.T) { }) } - txReq := &HTTPRequest{ + txReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "ops": ops, }, @@ -384,9 +386,9 @@ func TestRQLite_LargeBatchTransaction(t *testing.T) { } // Verify count - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) as count FROM %s", table), }, @@ -398,7 +400,7 @@ func TestRQLite_LargeBatchTransaction(t *testing.T) { } var countResp map[string]interface{} - if err := DecodeJSON(body, &countResp); err != nil { + if err := e2e.DecodeJSON(body, &countResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -412,19 +414,19 @@ func TestRQLite_LargeBatchTransaction(t *testing.T) { // TestCache_TTLExpiryWithSleep tests TTL expiry with a controlled sleep func TestCache_TTLExpiryWithSleep(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "ttl-expiry-key" value := "ttl-expiry-value" // Put value with 2 second TTL - putReq := &HTTPRequest{ + putReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/put", + URL: e2e.GetGatewayURL() + "/v1/cache/put", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -439,9 +441,9 @@ func TestCache_TTLExpiryWithSleep(t *testing.T) { } // Verify exists immediately - getReq := &HTTPRequest{ + getReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/get", + URL: e2e.GetGatewayURL() + "/v1/cache/get", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -454,7 +456,7 @@ func TestCache_TTLExpiryWithSleep(t *testing.T) { } // Sleep for TTL duration + buffer - Delay(2500) + e2e.Delay(2500) // Try to get after TTL expires _, status, err = getReq.Do(ctx) @@ -465,21 +467,21 @@ func TestCache_TTLExpiryWithSleep(t *testing.T) { // TestCache_ConcurrentWriteAndDelete tests concurrent writes and deletes on same key func TestCache_ConcurrentWriteAndDelete(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "contested-key" // Alternate between writes and deletes numIterations := 5 for i := 0; i < numIterations; i++ { // Write - putReq := &HTTPRequest{ + putReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/put", + URL: e2e.GetGatewayURL() + "/v1/cache/put", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -493,9 +495,9 @@ func TestCache_ConcurrentWriteAndDelete(t *testing.T) { } // Read - getReq := &HTTPRequest{ + getReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/get", + URL: e2e.GetGatewayURL() + "/v1/cache/get", Body: map[string]interface{}{ "dmap": dmap, "key": key, @@ -508,9 +510,9 @@ func TestCache_ConcurrentWriteAndDelete(t *testing.T) { } // Delete - deleteReq := &HTTPRequest{ + deleteReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/delete", + URL: e2e.GetGatewayURL() + "/v1/cache/delete", Body: map[string]interface{}{ "dmap": dmap, "key": key, diff --git a/e2e/data_persistence_test.go b/e2e/integration/data_persistence_test.go similarity index 89% rename from e2e/data_persistence_test.go rename to e2e/integration/data_persistence_test.go index e185255..2ae0688 100644 --- a/e2e/data_persistence_test.go +++ b/e2e/integration/data_persistence_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package integration_test import ( "context" @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/require" ) @@ -23,7 +24,7 @@ import ( // TestRQLite_DataPersistence verifies that RQLite data is persisted through the gateway. func TestRQLite_DataPersistence(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -32,18 +33,18 @@ func TestRQLite_DataPersistence(t *testing.T) { // Cleanup defer func() { - dropReq := &HTTPRequest{ + dropReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": tableName}, } dropReq.Do(context.Background()) }() // Create table - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": fmt.Sprintf( "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, value TEXT, version INTEGER)", @@ -65,9 +66,9 @@ func TestRQLite_DataPersistence(t *testing.T) { fmt.Sprintf("INSERT INTO %s (value, version) VALUES ('item_%d', %d)", tableName, i, i)) } - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{"statements": statements}, } @@ -76,9 +77,9 @@ func TestRQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Insert returned status %d", status) // Verify all data exists - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName), }, @@ -89,7 +90,7 @@ func TestRQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Count query returned status %d", status) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) @@ -98,9 +99,9 @@ func TestRQLite_DataPersistence(t *testing.T) { } // Update data - updateReq := &HTTPRequest{ + updateReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("UPDATE %s SET version = version + 100 WHERE version <= 5", tableName), @@ -113,9 +114,9 @@ func TestRQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Update returned status %d", status) // Verify updates persisted - queryUpdatedReq := &HTTPRequest{ + queryUpdatedReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE version > 100", tableName), }, @@ -125,7 +126,7 @@ func TestRQLite_DataPersistence(t *testing.T) { require.NoError(t, err, "FAIL: Could not count updated rows") require.Equal(t, http.StatusOK, status, "FAIL: Count updated query returned status %d", status) - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) count := int(row[0].(float64)) @@ -137,9 +138,9 @@ func TestRQLite_DataPersistence(t *testing.T) { t.Run("Deletes_are_persisted", func(t *testing.T) { // Delete some rows - deleteReq := &HTTPRequest{ + deleteReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf("DELETE FROM %s WHERE version > 100", tableName), @@ -152,9 +153,9 @@ func TestRQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Delete returned status %d", status) // Verify deletes persisted - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName), }, @@ -165,7 +166,7 @@ func TestRQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Count query returned status %d", status) var queryResp map[string]interface{} - DecodeJSON(body, &queryResp) + e2e.DecodeJSON(body, &queryResp) if rows, ok := queryResp["rows"].([]interface{}); ok && len(rows) > 0 { row := rows[0].([]interface{}) @@ -213,7 +214,7 @@ func TestRQLite_DataFilesExist(t *testing.T) { // TestOlric_DataPersistence verifies Olric cache data persistence. // Note: Olric is an in-memory cache, so this tests data survival during runtime. func TestOlric_DataPersistence(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") dmap := fmt.Sprintf("persist_cache_%d", time.Now().UnixNano()) @@ -226,17 +227,17 @@ func TestOlric_DataPersistence(t *testing.T) { value := fmt.Sprintf("persist_value_%d", i) keys[key] = value - err := putToOlric(env.GatewayURL, env.APIKey, dmap, key, value) + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, key, value) require.NoError(t, err, "FAIL: Could not put key %s", key) } // Perform other operations - err := putToOlric(env.GatewayURL, env.APIKey, dmap, "other_key", "other_value") + err := e2e.PutToOlric(env.GatewayURL, env.APIKey, dmap, "other_key", "other_value") require.NoError(t, err, "FAIL: Could not put other key") // Verify original keys still exist for key, expectedValue := range keys { - retrieved, err := getFromOlric(env.GatewayURL, env.APIKey, dmap, key) + retrieved, err := e2e.GetFromOlric(env.GatewayURL, env.APIKey, dmap, key) require.NoError(t, err, "FAIL: Key %s not found after other operations", key) require.Equal(t, expectedValue, retrieved, "FAIL: Value mismatch for key %s", key) } @@ -249,7 +250,7 @@ func TestOlric_DataPersistence(t *testing.T) { func TestNamespaceCluster_DataPersistence(t *testing.T) { // Create namespace namespace := fmt.Sprintf("persist-ns-%d", time.Now().UnixNano()) - env, err := LoadTestEnvWithNamespace(namespace) + env, err := e2e.LoadTestEnvWithNamespace(namespace) require.NoError(t, err, "FAIL: Could not create namespace") t.Logf("Created namespace: %s", namespace) @@ -261,7 +262,7 @@ func TestNamespaceCluster_DataPersistence(t *testing.T) { // Create data via gateway API tableName := fmt.Sprintf("ns_data_%d", time.Now().UnixNano()) - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/create-table", Headers: map[string]string{ @@ -278,7 +279,7 @@ func TestNamespaceCluster_DataPersistence(t *testing.T) { "FAIL: Create table returned status %d", status) // Insert data - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/transaction", Headers: map[string]string{ @@ -296,7 +297,7 @@ func TestNamespaceCluster_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Insert returned status %d", status) // Verify data exists - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/rqlite/query", Headers: map[string]string{ @@ -323,7 +324,7 @@ func TestNamespaceCluster_DataPersistence(t *testing.T) { // TestIPFS_DataPersistence verifies IPFS content is persisted and pinned. // Note: Detailed IPFS tests are in storage_http_test.go. This test uses the helper from env.go. func TestIPFS_DataPersistence(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -332,12 +333,12 @@ func TestIPFS_DataPersistence(t *testing.T) { t.Run("Uploaded_content_persists", func(t *testing.T) { // Use helper function to upload content via multipart form content := fmt.Sprintf("persistent content %d", time.Now().UnixNano()) - cid := UploadTestFile(t, env, "persist_test.txt", content) + cid := e2e.UploadTestFile(t, env, "persist_test.txt", content) require.NotEmpty(t, cid, "FAIL: No CID returned from upload") t.Logf(" Uploaded content with CID: %s", cid) // Verify content can be retrieved - getReq := &HTTPRequest{ + getReq := &e2e.HTTPRequest{ Method: http.MethodGet, URL: env.GatewayURL + "/v1/storage/get/" + cid, Headers: map[string]string{ @@ -357,7 +358,7 @@ func TestIPFS_DataPersistence(t *testing.T) { // TestSQLite_DataPersistence verifies per-deployment SQLite databases persist. func TestSQLite_DataPersistence(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "FAIL: Could not load test environment") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -367,7 +368,7 @@ func TestSQLite_DataPersistence(t *testing.T) { t.Run("SQLite_database_persists", func(t *testing.T) { // Create database - createReq := &HTTPRequest{ + createReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/db/sqlite/create", Headers: map[string]string{ @@ -385,7 +386,7 @@ func TestSQLite_DataPersistence(t *testing.T) { t.Logf(" Created SQLite database: %s", dbName) // Create table and insert data - queryReq := &HTTPRequest{ + queryReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/db/sqlite/query", Headers: map[string]string{ @@ -402,7 +403,7 @@ func TestSQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Create table returned status %d", status) // Insert data - insertReq := &HTTPRequest{ + insertReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/db/sqlite/query", Headers: map[string]string{ @@ -419,7 +420,7 @@ func TestSQLite_DataPersistence(t *testing.T) { require.Equal(t, http.StatusOK, status, "FAIL: Insert returned status %d", status) // Verify data persists - selectReq := &HTTPRequest{ + selectReq := &e2e.HTTPRequest{ Method: http.MethodPost, URL: env.GatewayURL + "/v1/db/sqlite/query", Headers: map[string]string{ @@ -442,7 +443,7 @@ func TestSQLite_DataPersistence(t *testing.T) { t.Run("SQLite_database_listed", func(t *testing.T) { // List databases to verify it was persisted - listReq := &HTTPRequest{ + listReq := &e2e.HTTPRequest{ Method: http.MethodGet, URL: env.GatewayURL + "/v1/db/sqlite/list", Headers: map[string]string{ diff --git a/e2e/domain_routing_test.go b/e2e/integration/domain_routing_test.go similarity index 84% rename from e2e/domain_routing_test.go rename to e2e/integration/domain_routing_test.go index 864285b..3ad0e23 100644 --- a/e2e/domain_routing_test.go +++ b/e2e/integration/domain_routing_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package integration_test import ( "encoding/json" @@ -12,21 +12,22 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDomainRouting_BasicRouting(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.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) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() @@ -34,7 +35,7 @@ func TestDomainRouting_BasicRouting(t *testing.T) { time.Sleep(2 * time.Second) // Get deployment details for debugging - deployment := GetDeployment(t, env, deploymentID) + deployment := e2e.GetDeployment(t, env, deploymentID) t.Logf("Deployment created: ID=%s, CID=%s, Name=%s, Status=%s", deploymentID, deployment["content_cid"], deployment["name"], deployment["status"]) @@ -42,7 +43,7 @@ func TestDomainRouting_BasicRouting(t *testing.T) { // Domain format: {deploymentName}.{baseDomain} domain := env.BuildDeploymentDomain(deploymentName) - resp := TestDeploymentWithHostHeader(t, env, domain, "/") + resp := e2e.TestDeploymentWithHostHeader(t, env, domain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "Should return 200 OK") @@ -58,7 +59,7 @@ func TestDomainRouting_BasicRouting(t *testing.T) { 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", "/") + resp := e2e.TestDeploymentWithHostHeader(t, env, "example.com", "/") defer resp.Body.Close() // Should either return 404 or pass to default handler @@ -98,7 +99,7 @@ func TestDomainRouting_BasicRouting(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") + resp := e2e.TestDeploymentWithHostHeader(t, env, domain, "/.well-known/acme-challenge/test") defer resp.Body.Close() // Should not serve deployment content @@ -117,7 +118,7 @@ func TestDomainRouting_BasicRouting(t *testing.T) { } func TestDomainRouting_MultipleDeployments(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") @@ -126,14 +127,14 @@ func TestDomainRouting_MultipleDeployments(t *testing.T) { 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) + deployment1ID := e2e.CreateTestDeployment(t, env, deployment1Name, tarballPath) time.Sleep(1 * time.Second) - deployment2ID := CreateTestDeployment(t, env, deployment2Name, tarballPath) + deployment2ID := e2e.CreateTestDeployment(t, env, deployment2Name, tarballPath) defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deployment1ID) - DeleteDeployment(t, env, deployment2ID) + e2e.DeleteDeployment(t, env, deployment1ID) + e2e.DeleteDeployment(t, env, deployment2ID) } }() @@ -144,13 +145,13 @@ func TestDomainRouting_MultipleDeployments(t *testing.T) { domain2 := env.BuildDeploymentDomain(deployment2Name) // Test deployment 1 - resp1 := TestDeploymentWithHostHeader(t, env, domain1, "/") + resp1 := e2e.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, "/") + resp2 := e2e.TestDeploymentWithHostHeader(t, env, domain2, "/") defer resp2.Body.Close() assert.Equal(t, http.StatusOK, resp2.StatusCode, "Deployment 2 should serve") @@ -164,7 +165,7 @@ func TestDomainRouting_MultipleDeployments(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, "/") + resp := e2e.TestDeploymentWithHostHeader(t, env, fakeDeploymentDomain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, @@ -175,16 +176,16 @@ func TestDomainRouting_MultipleDeployments(t *testing.T) { } func TestDomainRouting_ContentTypes(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.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) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() @@ -203,7 +204,7 @@ func TestDomainRouting_ContentTypes(t *testing.T) { for _, test := range contentTypeTests { t.Run(test.description, func(t *testing.T) { - resp := TestDeploymentWithHostHeader(t, env, domain, test.path) + resp := e2e.TestDeploymentWithHostHeader(t, env, domain, test.path) defer resp.Body.Close() if resp.StatusCode == http.StatusOK { @@ -220,16 +221,16 @@ func TestDomainRouting_ContentTypes(t *testing.T) { } func TestDomainRouting_SPAFallback(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.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) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() @@ -246,7 +247,7 @@ func TestDomainRouting_SPAFallback(t *testing.T) { } for _, path := range unknownPaths { - resp := TestDeploymentWithHostHeader(t, env, domain, path) + resp := e2e.TestDeploymentWithHostHeader(t, env, domain, path) body, _ := io.ReadAll(resp.Body) resp.Body.Close() @@ -266,16 +267,16 @@ func TestDomainRouting_SPAFallback(t *testing.T) { // - CORRECT: {name}-{random}.{baseDomain} (e.g., "myapp-f3o4if.dbrs.space") // - WRONG: {name}.node-{shortID}.{baseDomain} (should NOT exist) func TestDeployment_DomainFormat(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.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) + deploymentID := e2e.CreateTestDeployment(t, env, deploymentName, tarballPath) defer func() { if !env.SkipCleanup { - DeleteDeployment(t, env, deploymentID) + e2e.DeleteDeployment(t, env, deploymentID) } }() @@ -283,7 +284,7 @@ func TestDeployment_DomainFormat(t *testing.T) { time.Sleep(2 * time.Second) t.Run("Deployment URL has correct format", func(t *testing.T) { - deployment := GetDeployment(t, env, deploymentID) + deployment := e2e.GetDeployment(t, env, deploymentID) // Get the deployment URLs urls, ok := deployment["urls"].([]interface{}) @@ -331,14 +332,14 @@ func TestDeployment_DomainFormat(t *testing.T) { t.Run("Domain resolves via Host header", func(t *testing.T) { // Get the actual subdomain from the deployment - deployment := GetDeployment(t, env, deploymentID) + deployment := e2e.GetDeployment(t, env, deploymentID) subdomain, _ := deployment["subdomain"].(string) if subdomain == "" { t.Skip("No subdomain set, skipping host header test") } domain := subdomain + "." + env.BaseDomain - resp := TestDeploymentWithHostHeader(t, env, domain, "/") + resp := e2e.TestDeploymentWithHostHeader(t, env, domain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, diff --git a/e2e/fullstack_integration_test.go b/e2e/integration/fullstack_integration_test.go similarity index 81% rename from e2e/fullstack_integration_test.go rename to e2e/integration/fullstack_integration_test.go index b1f8f03..7101fc1 100644 --- a/e2e/fullstack_integration_test.go +++ b/e2e/integration/fullstack_integration_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package integration_test import ( "bytes" @@ -12,12 +12,13 @@ import ( "testing" "time" + "github.com/DeBrosOfficial/network/e2e" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestFullStack_GoAPI_SQLite(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") appName := fmt.Sprintf("fullstack-app-%d", time.Now().Unix()) @@ -29,15 +30,15 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { defer func() { if !env.SkipCleanup { if backendID != "" { - DeleteDeployment(t, env, backendID) + e2e.DeleteDeployment(t, env, backendID) } - DeleteSQLiteDB(t, env, dbName) + e2e.DeleteSQLiteDB(t, env, dbName) } }() // Step 1: Create SQLite database t.Run("Create SQLite database", func(t *testing.T) { - CreateSQLiteDB(t, env, dbName) + e2e.CreateSQLiteDB(t, env, dbName) // Create users table query := `CREATE TABLE users ( @@ -46,11 +47,11 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { email TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )` - ExecuteSQLQuery(t, env, dbName, query) + e2e.ExecuteSQLQuery(t, env, dbName, query) // Insert test data insertQuery := `INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')` - result := ExecuteSQLQuery(t, env, dbName, insertQuery) + result := e2e.ExecuteSQLQuery(t, env, dbName, insertQuery) assert.NotNil(t, result, "Should execute INSERT successfully") t.Logf("✓ Database created with users table") @@ -64,7 +65,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { // Note: In a real implementation, we would pass DATABASE_NAME env var // For now, we just test the deployment mechanism - backendID = CreateTestDeployment(t, env, backendName, tarballPath) + backendID = e2e.CreateTestDeployment(t, env, backendName, tarballPath) assert.NotEmpty(t, backendID, "Backend deployment ID should not be empty") t.Logf("✓ Go backend deployed: %s", backendName) @@ -77,10 +78,10 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { t.Run("Test database CRUD operations", func(t *testing.T) { // INSERT insertQuery := `INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')` - ExecuteSQLQuery(t, env, dbName, insertQuery) + e2e.ExecuteSQLQuery(t, env, dbName, insertQuery) // SELECT - users := QuerySQLite(t, env, dbName, "SELECT * FROM users ORDER BY id") + users := e2e.QuerySQLite(t, env, dbName, "SELECT * FROM users ORDER BY id") require.GreaterOrEqual(t, len(users), 2, "Should have at least 2 users") assert.Equal(t, "Alice", users[0]["name"], "First user should be Alice") @@ -91,14 +92,14 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { // UPDATE updateQuery := `UPDATE users SET email = 'alice.new@example.com' WHERE name = 'Alice'` - result := ExecuteSQLQuery(t, env, dbName, updateQuery) + result := e2e.ExecuteSQLQuery(t, env, dbName, updateQuery) rowsAffected, ok := result["rows_affected"].(float64) require.True(t, ok, "Should have rows_affected") assert.Equal(t, float64(1), rowsAffected, "Should update 1 row") // Verify update - updated := QuerySQLite(t, env, dbName, "SELECT email FROM users WHERE name = 'Alice'") + updated := e2e.QuerySQLite(t, env, dbName, "SELECT email FROM users WHERE name = 'Alice'") require.Len(t, updated, 1, "Should find Alice") assert.Equal(t, "alice.new@example.com", updated[0]["email"], "Email should be updated") @@ -106,14 +107,14 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { // DELETE deleteQuery := `DELETE FROM users WHERE name = 'Bob'` - result = ExecuteSQLQuery(t, env, dbName, deleteQuery) + result = e2e.ExecuteSQLQuery(t, env, dbName, deleteQuery) rowsAffected, ok = result["rows_affected"].(float64) require.True(t, ok, "Should have rows_affected") assert.Equal(t, float64(1), rowsAffected, "Should delete 1 row") // Verify deletion - remaining := QuerySQLite(t, env, dbName, "SELECT * FROM users") + remaining := e2e.QuerySQLite(t, env, dbName, "SELECT * FROM users") assert.Equal(t, 1, len(remaining), "Should have 1 user remaining") t.Logf("✓ DELETE operation verified") @@ -121,7 +122,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { // Step 4: Test backend API endpoints (if deployment is active) t.Run("Test backend API endpoints", func(t *testing.T) { - deployment := GetDeployment(t, env, backendID) + deployment := e2e.GetDeployment(t, env, backendID) status, ok := deployment["status"].(string) if !ok || status != "active" { @@ -132,7 +133,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { backendDomain := env.BuildDeploymentDomain(backendName) // Test health endpoint - resp := TestDeploymentWithHostHeader(t, env, backendDomain, "/health") + resp := e2e.TestDeploymentWithHostHeader(t, env, backendDomain, "/health") defer resp.Body.Close() if resp.StatusCode == http.StatusOK { @@ -149,7 +150,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { } // Test users API endpoint - resp2 := TestDeploymentWithHostHeader(t, env, backendDomain, "/api/users") + resp2 := e2e.TestDeploymentWithHostHeader(t, env, backendDomain, "/api/users") defer resp2.Body.Close() if resp2.StatusCode == http.StatusOK { @@ -205,7 +206,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { for i := 0; i < 5; i++ { go func(idx int) { - users := QuerySQLite(t, env, dbName, "SELECT * FROM users") + users := e2e.QuerySQLite(t, env, dbName, "SELECT * FROM users") assert.GreaterOrEqual(t, len(users), 0, "Should query successfully") done <- true }(i) @@ -226,7 +227,7 @@ func TestFullStack_GoAPI_SQLite(t *testing.T) { } func TestFullStack_StaticSite_SQLite(t *testing.T) { - env, err := LoadTestEnv() + env, err := e2e.LoadTestEnv() require.NoError(t, err, "Failed to load test environment") appName := fmt.Sprintf("fullstack-static-%d", time.Now().Unix()) @@ -238,21 +239,21 @@ func TestFullStack_StaticSite_SQLite(t *testing.T) { defer func() { if !env.SkipCleanup { if frontendID != "" { - DeleteDeployment(t, env, frontendID) + e2e.DeleteDeployment(t, env, frontendID) } - DeleteSQLiteDB(t, env, dbName) + e2e.DeleteSQLiteDB(t, env, dbName) } }() t.Run("Deploy static site and create database", func(t *testing.T) { // Create database - CreateSQLiteDB(t, env, dbName) - ExecuteSQLQuery(t, env, dbName, "CREATE TABLE page_views (id INTEGER PRIMARY KEY, page TEXT, count INTEGER)") - ExecuteSQLQuery(t, env, dbName, "INSERT INTO page_views (page, count) VALUES ('home', 0)") + e2e.CreateSQLiteDB(t, env, dbName) + e2e.ExecuteSQLQuery(t, env, dbName, "CREATE TABLE page_views (id INTEGER PRIMARY KEY, page TEXT, count INTEGER)") + e2e.ExecuteSQLQuery(t, env, dbName, "INSERT INTO page_views (page, count) VALUES ('home', 0)") // Deploy frontend tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz") - frontendID = CreateTestDeployment(t, env, frontendName, tarballPath) + frontendID = e2e.CreateTestDeployment(t, env, frontendName, tarballPath) assert.NotEmpty(t, frontendID, "Frontend deployment should succeed") t.Logf("✓ Static site deployed with SQLite backend") @@ -265,7 +266,7 @@ func TestFullStack_StaticSite_SQLite(t *testing.T) { frontendDomain := env.BuildDeploymentDomain(frontendName) // Test frontend - resp := TestDeploymentWithHostHeader(t, env, frontendDomain, "/") + resp := e2e.TestDeploymentWithHostHeader(t, env, frontendDomain, "/") defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "Frontend should serve") @@ -274,10 +275,10 @@ func TestFullStack_StaticSite_SQLite(t *testing.T) { assert.Contains(t, string(body), "