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), "
", "Should contain React app") // Simulate page view tracking - ExecuteSQLQuery(t, env, dbName, "UPDATE page_views SET count = count + 1 WHERE page = 'home'") + e2e.ExecuteSQLQuery(t, env, dbName, "UPDATE page_views SET count = count + 1 WHERE page = 'home'") // Verify count - views := QuerySQLite(t, env, dbName, "SELECT count FROM page_views WHERE page = 'home'") + views := e2e.QuerySQLite(t, env, dbName, "SELECT count FROM page_views WHERE page = 'home'") require.Len(t, views, 1, "Should have page view record") count, ok := views[0]["count"].(float64) diff --git a/e2e/production/cross_node_proxy_test.go b/e2e/production/cross_node_proxy_test.go index bbd7309..cc22174 100644 --- a/e2e/production/cross_node_proxy_test.go +++ b/e2e/production/cross_node_proxy_test.go @@ -1,4 +1,4 @@ -//go:build e2e +//go:build e2e && production package production diff --git a/e2e/production/dns_resolution_test.go b/e2e/production/dns_resolution_test.go index c5d2a95..b90d511 100644 --- a/e2e/production/dns_resolution_test.go +++ b/e2e/production/dns_resolution_test.go @@ -1,4 +1,4 @@ -//go:build e2e +//go:build e2e && production package production diff --git a/e2e/production/https_certificate_test.go b/e2e/production/https_certificate_test.go index 942178e..196cd53 100644 --- a/e2e/production/https_certificate_test.go +++ b/e2e/production/https_certificate_test.go @@ -1,4 +1,4 @@ -//go:build e2e +//go:build e2e && production package production diff --git a/e2e/deployments/https_external_test.go b/e2e/production/https_external_test.go similarity index 87% rename from e2e/deployments/https_external_test.go rename to e2e/production/https_external_test.go index 4db9481..9bca4d8 100644 --- a/e2e/deployments/https_external_test.go +++ b/e2e/production/https_external_test.go @@ -1,6 +1,6 @@ -//go:build e2e +//go:build e2e && production -package deployments_test +package production import ( "crypto/tls" @@ -24,7 +24,7 @@ import ( // This test requires: // - Orama deployed on a VPS with a real domain // - DNS properly configured -// - Run with: go test -v -tags e2e -run TestHTTPS ./e2e/deployments/... +// - Run with: go test -v -tags "e2e production" -run TestHTTPS ./e2e/production/... func TestHTTPS_ExternalAccess(t *testing.T) { // Skip if not configured for external testing externalURL := os.Getenv("ORAMA_EXTERNAL_URL") @@ -171,3 +171,34 @@ func TestHTTPS_DomainFormat(t *testing.T) { } }) } + +func extractNodeURL(t *testing.T, deployment map[string]interface{}) string { + t.Helper() + + if urls, ok := deployment["urls"].([]interface{}); ok && len(urls) > 0 { + if url, ok := urls[0].(string); ok { + return url + } + } + + if urls, ok := deployment["urls"].(map[string]interface{}); ok { + if url, ok := urls["node"].(string); ok { + return url + } + } + + return "" +} + +func extractDomain(url string) string { + domain := url + if len(url) > 8 && url[:8] == "https://" { + domain = url[8:] + } else if len(url) > 7 && url[:7] == "http://" { + domain = url[7:] + } + if len(domain) > 0 && domain[len(domain)-1] == '/' { + domain = domain[:len(domain)-1] + } + return domain +} diff --git a/e2e/production/nameserver_test.go b/e2e/production/nameserver_test.go index ee4ff69..9705918 100644 --- a/e2e/production/nameserver_test.go +++ b/e2e/production/nameserver_test.go @@ -1,4 +1,4 @@ -//go:build e2e +//go:build e2e && production package production diff --git a/e2e/auth_negative_test.go b/e2e/shared/auth_negative_test.go similarity index 84% rename from e2e/auth_negative_test.go rename to e2e/shared/auth_negative_test.go index fc6684c..8a01286 100644 --- a/e2e/auth_negative_test.go +++ b/e2e/shared/auth_negative_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "context" @@ -9,6 +9,8 @@ import ( "time" "unicode" + e2e "github.com/DeBrosOfficial/network/e2e" + "github.com/stretchr/testify/require" ) @@ -23,10 +25,10 @@ func TestAuth_MissingAPIKey(t *testing.T) { defer cancel() // Request protected endpoint without auth headers - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -42,12 +44,12 @@ func TestAuth_InvalidAPIKey(t *testing.T) { defer cancel() // Request with invalid API key - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") req.Header.Set("Authorization", "Bearer invalid-key-xyz-123456789") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -63,9 +65,9 @@ func TestAuth_CacheWithoutAuth(t *testing.T) { defer cancel() // Request cache endpoint without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/cache/health", + URL: e2e.GetGatewayURL() + "/v1/cache/health", SkipAuth: true, } @@ -83,9 +85,9 @@ func TestAuth_StorageWithoutAuth(t *testing.T) { defer cancel() // Request storage endpoint without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/storage/status/QmTest", + URL: e2e.GetGatewayURL() + "/v1/storage/status/QmTest", SkipAuth: true, } @@ -103,9 +105,9 @@ func TestAuth_RQLiteWithoutAuth(t *testing.T) { defer cancel() // Request rqlite endpoint without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/rqlite/schema", + URL: e2e.GetGatewayURL() + "/v1/rqlite/schema", SkipAuth: true, } @@ -123,12 +125,12 @@ func TestAuth_MalformedBearerToken(t *testing.T) { defer cancel() // Request with malformed bearer token (missing "Bearer " prefix) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") req.Header.Set("Authorization", "invalid-token-format-no-bearer") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -144,12 +146,12 @@ func TestAuth_ExpiredJWT(t *testing.T) { defer cancel() // Test with a clearly invalid JWT structure - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") req.Header.Set("Authorization", "Bearer expired.jwt.token.invalid") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -165,12 +167,12 @@ func TestAuth_EmptyBearerToken(t *testing.T) { defer cancel() // Request with empty bearer token - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") req.Header.Set("Authorization", "Bearer ") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -182,7 +184,7 @@ func TestAuth_EmptyBearerToken(t *testing.T) { } func TestAuth_DuplicateAuthHeaders(t *testing.T) { - if GetAPIKey() == "" { + if e2e.GetAPIKey() == "" { t.Skip("No API key configured") } @@ -190,12 +192,12 @@ func TestAuth_DuplicateAuthHeaders(t *testing.T) { defer cancel() // Request with both valid API key in Authorization header - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/cache/health", + URL: e2e.GetGatewayURL() + "/v1/cache/health", Headers: map[string]string{ - "Authorization": "Bearer " + GetAPIKey(), - "X-API-Key": GetAPIKey(), + "Authorization": "Bearer " + e2e.GetAPIKey(), + "X-API-Key": e2e.GetAPIKey(), }, } @@ -209,7 +211,7 @@ func TestAuth_DuplicateAuthHeaders(t *testing.T) { } func TestAuth_CaseSensitiveAPIKey(t *testing.T) { - apiKey := GetAPIKey() + apiKey := e2e.GetAPIKey() if apiKey == "" { t.Skip("No API key configured") } @@ -236,12 +238,12 @@ func TestAuth_CaseSensitiveAPIKey(t *testing.T) { t.Skip("API key has no letters to change case") } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/cache/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil) require.NoError(t, err, "FAIL: Could not create request") req.Header.Set("Authorization", "Bearer "+incorrectKey) - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -257,10 +259,10 @@ func TestAuth_HealthEndpointNoAuth(t *testing.T) { defer cancel() // Health endpoint at /v1/health should NOT require auth - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/health", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/health", nil) require.NoError(t, err, "FAIL: Could not create request") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -276,10 +278,10 @@ func TestAuth_StatusEndpointNoAuth(t *testing.T) { defer cancel() // Status endpoint at /v1/status should NOT require auth - req, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/status", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/status", nil) require.NoError(t, err, "FAIL: Could not create request") - client := NewHTTPClient(30 * time.Second) + client := e2e.NewHTTPClient(30 * time.Second) resp, err := client.Do(req) require.NoError(t, err, "FAIL: Request failed") defer resp.Body.Close() @@ -295,9 +297,9 @@ func TestAuth_DeploymentsWithoutAuth(t *testing.T) { defer cancel() // Request deployments endpoint without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/deployments/list", + URL: e2e.GetGatewayURL() + "/v1/deployments/list", SkipAuth: true, } @@ -315,9 +317,9 @@ func TestAuth_SQLiteWithoutAuth(t *testing.T) { defer cancel() // Request SQLite endpoint without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/db/sqlite/list", + URL: e2e.GetGatewayURL() + "/v1/db/sqlite/list", SkipAuth: true, } diff --git a/e2e/cache_http_test.go b/e2e/shared/cache_http_test.go similarity index 79% rename from e2e/cache_http_test.go rename to e2e/shared/cache_http_test.go index 6f4a3ed..4e33f9e 100644 --- a/e2e/cache_http_test.go +++ b/e2e/shared/cache_http_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "context" @@ -8,17 +8,19 @@ import ( "net/http" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) func TestCache_Health(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/cache/health", + URL: e2e.GetGatewayURL() + "/v1/cache/health", } body, status, err := req.Do(ctx) @@ -31,7 +33,7 @@ func TestCache_Health(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) } @@ -45,19 +47,19 @@ func TestCache_Health(t *testing.T) { } func TestCache_PutGet(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "test-key" value := "test-value" // Put value - 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, @@ -75,9 +77,9 @@ func TestCache_PutGet(t *testing.T) { } // Get value - 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, @@ -94,7 +96,7 @@ func TestCache_PutGet(t *testing.T) { } var getResp map[string]interface{} - if err := DecodeJSON(body, &getResp); err != nil { + if err := e2e.DecodeJSON(body, &getResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -104,12 +106,12 @@ func TestCache_PutGet(t *testing.T) { } func TestCache_PutGetJSON(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "json-key" jsonValue := map[string]interface{}{ "name": "John", @@ -118,9 +120,9 @@ func TestCache_PutGetJSON(t *testing.T) { } // Put JSON value - 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, @@ -138,9 +140,9 @@ func TestCache_PutGetJSON(t *testing.T) { } // Get JSON value - 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, @@ -157,7 +159,7 @@ func TestCache_PutGetJSON(t *testing.T) { } var getResp map[string]interface{} - if err := DecodeJSON(body, &getResp); err != nil { + if err := e2e.DecodeJSON(body, &getResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -171,19 +173,19 @@ func TestCache_PutGetJSON(t *testing.T) { } func TestCache_Delete(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "delete-key" value := "delete-value" // Put value - 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, @@ -197,9 +199,9 @@ func TestCache_Delete(t *testing.T) { } // Delete value - 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, @@ -216,9 +218,9 @@ func TestCache_Delete(t *testing.T) { } // Verify deletion - 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, @@ -233,19 +235,19 @@ func TestCache_Delete(t *testing.T) { } func TestCache_TTL(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() key := "ttl-key" value := "ttl-value" // Put value with 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, @@ -264,9 +266,9 @@ func TestCache_TTL(t *testing.T) { } // Verify value exists - 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, @@ -279,7 +281,7 @@ func TestCache_TTL(t *testing.T) { } // Wait for TTL expiry (2 seconds + buffer) - Delay(2500) + e2e.Delay(2500) // Verify value is expired _, status, err = getReq.Do(ctx) @@ -289,19 +291,19 @@ func TestCache_TTL(t *testing.T) { } func TestCache_Scan(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() // Put multiple keys keys := []string{"user-1", "user-2", "session-1", "session-2"} for _, key := range keys { - 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, @@ -316,9 +318,9 @@ func TestCache_Scan(t *testing.T) { } // Scan all keys - 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, }, @@ -334,7 +336,7 @@ func TestCache_Scan(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) } @@ -345,19 +347,19 @@ func TestCache_Scan(t *testing.T) { } func TestCache_ScanWithRegex(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() // Put keys with different patterns keys := []string{"user-1", "user-2", "session-1", "session-2"} for _, key := range keys { - 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, @@ -372,9 +374,9 @@ func TestCache_ScanWithRegex(t *testing.T) { } // Scan with regex pattern - 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, "pattern": "^user-", @@ -391,7 +393,7 @@ func TestCache_ScanWithRegex(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) } @@ -402,19 +404,19 @@ func TestCache_ScanWithRegex(t *testing.T) { } func TestCache_MultiGet(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() keys := []string{"key-1", "key-2", "key-3"} // Put values for i, key := range keys { - 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, @@ -429,9 +431,9 @@ func TestCache_MultiGet(t *testing.T) { } // Multi-get - multiGetReq := &HTTPRequest{ + multiGetReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/mget", + URL: e2e.GetGatewayURL() + "/v1/cache/mget", Body: map[string]interface{}{ "dmap": dmap, "keys": keys, @@ -448,7 +450,7 @@ func TestCache_MultiGet(t *testing.T) { } var mgetResp map[string]interface{} - if err := DecodeJSON(body, &mgetResp); err != nil { + if err := e2e.DecodeJSON(body, &mgetResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -459,14 +461,14 @@ func TestCache_MultiGet(t *testing.T) { } func TestCache_MissingDMap(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - getReq := &HTTPRequest{ + getReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/cache/get", + URL: e2e.GetGatewayURL() + "/v1/cache/get", Body: map[string]interface{}{ "dmap": "", "key": "any-key", @@ -484,16 +486,16 @@ func TestCache_MissingDMap(t *testing.T) { } func TestCache_MissingKey(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - dmap := GenerateDMapName() + dmap := e2e.GenerateDMapName() - 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": "non-existent-key", diff --git a/e2e/network_http_test.go b/e2e/shared/network_http_test.go similarity index 80% rename from e2e/network_http_test.go rename to e2e/shared/network_http_test.go index 0f91f4e..ad1b390 100644 --- a/e2e/network_http_test.go +++ b/e2e/shared/network_http_test.go @@ -1,23 +1,25 @@ //go:build e2e -package e2e +package shared_test import ( "context" "net/http" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) func TestNetwork_Health(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/health", + URL: e2e.GetGatewayURL() + "/v1/health", SkipAuth: true, } @@ -31,7 +33,7 @@ func TestNetwork_Health(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) } @@ -41,14 +43,14 @@ func TestNetwork_Health(t *testing.T) { } func TestNetwork_Status(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - 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) @@ -61,7 +63,7 @@ func TestNetwork_Status(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) } @@ -75,14 +77,14 @@ func TestNetwork_Status(t *testing.T) { } func TestNetwork_Peers(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - 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) @@ -95,7 +97,7 @@ func TestNetwork_Peers(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) } @@ -105,14 +107,14 @@ func TestNetwork_Peers(t *testing.T) { } func TestNetwork_ProxyAnonSuccess(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/proxy/anon", + URL: e2e.GetGatewayURL() + "/v1/proxy/anon", Body: map[string]interface{}{ "url": "https://httpbin.org/get", "method": "GET", @@ -130,7 +132,7 @@ func TestNetwork_ProxyAnonSuccess(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) } @@ -144,14 +146,14 @@ func TestNetwork_ProxyAnonSuccess(t *testing.T) { } func TestNetwork_ProxyAnonBadURL(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/proxy/anon", + URL: e2e.GetGatewayURL() + "/v1/proxy/anon", Body: map[string]interface{}{ "url": "http://localhost:1/nonexistent", "method": "GET", @@ -165,14 +167,14 @@ func TestNetwork_ProxyAnonBadURL(t *testing.T) { } func TestNetwork_ProxyAnonPostRequest(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/proxy/anon", + URL: e2e.GetGatewayURL() + "/v1/proxy/anon", Body: map[string]interface{}{ "url": "https://httpbin.org/post", "method": "POST", @@ -191,7 +193,7 @@ func TestNetwork_ProxyAnonPostRequest(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) } @@ -206,9 +208,9 @@ func TestNetwork_Unauthorized(t *testing.T) { defer cancel() // Create request without auth - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/network/status", + URL: e2e.GetGatewayURL() + "/v1/network/status", SkipAuth: true, } diff --git a/e2e/pubsub_client_test.go b/e2e/shared/pubsub_client_test.go similarity index 84% rename from e2e/pubsub_client_test.go rename to e2e/shared/pubsub_client_test.go index 90fd517..b73fc2f 100644 --- a/e2e/pubsub_client_test.go +++ b/e2e/shared/pubsub_client_test.go @@ -1,40 +1,42 @@ //go:build e2e -package e2e +package shared_test import ( "fmt" "sync" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) // TestPubSub_SubscribePublish tests basic pub/sub functionality via WebSocket func TestPubSub_SubscribePublish(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() message := "test-message-from-publisher" // Create subscriber first - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish message if err := publisher.Publish([]byte(message)); err != nil { @@ -54,37 +56,37 @@ func TestPubSub_SubscribePublish(t *testing.T) { // TestPubSub_MultipleSubscribers tests that multiple subscribers receive the same message func TestPubSub_MultipleSubscribers(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() message1 := "message-1" message2 := "message-2" // Create two subscribers - sub1, err := NewWSPubSubClient(t, topic) + sub1, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber1: %v", err) } defer sub1.Close() - sub2, err := NewWSPubSubClient(t, topic) + sub2, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber2: %v", err) } defer sub2.Close() // Give subscribers time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish first message if err := publisher.Publish([]byte(message1)); err != nil { @@ -133,30 +135,30 @@ func TestPubSub_MultipleSubscribers(t *testing.T) { // TestPubSub_Deduplication tests that multiple identical messages are all received func TestPubSub_Deduplication(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() message := "duplicate-test-message" // Create subscriber - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish the same message multiple times for i := 0; i < 3; i++ { @@ -164,7 +166,7 @@ func TestPubSub_Deduplication(t *testing.T) { t.Fatalf("publish %d failed: %v", i, err) } // Small delay between publishes - Delay(50) + e2e.Delay(50) } // Receive messages - should get all (no dedup filter) @@ -185,30 +187,30 @@ func TestPubSub_Deduplication(t *testing.T) { // TestPubSub_ConcurrentPublish tests concurrent message publishing func TestPubSub_ConcurrentPublish(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() numMessages := 10 // Create subscriber - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish multiple messages concurrently var wg sync.WaitGroup @@ -241,45 +243,45 @@ func TestPubSub_ConcurrentPublish(t *testing.T) { // TestPubSub_TopicIsolation tests that messages are isolated to their topics func TestPubSub_TopicIsolation(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic1 := GenerateTopic() - topic2 := GenerateTopic() + topic1 := e2e.GenerateTopic() + topic2 := e2e.GenerateTopic() msg1 := "message-on-topic1" msg2 := "message-on-topic2" // Create subscriber for topic1 - sub1, err := NewWSPubSubClient(t, topic1) + sub1, err := e2e.NewWSPubSubClient(t, topic1) if err != nil { t.Fatalf("failed to create subscriber1: %v", err) } defer sub1.Close() // Create subscriber for topic2 - sub2, err := NewWSPubSubClient(t, topic2) + sub2, err := e2e.NewWSPubSubClient(t, topic2) if err != nil { t.Fatalf("failed to create subscriber2: %v", err) } defer sub2.Close() // Give subscribers time to register - Delay(200) + e2e.Delay(200) // Create publishers - pub1, err := NewWSPubSubClient(t, topic1) + pub1, err := e2e.NewWSPubSubClient(t, topic1) if err != nil { t.Fatalf("failed to create publisher1: %v", err) } defer pub1.Close() - pub2, err := NewWSPubSubClient(t, topic2) + pub2, err := e2e.NewWSPubSubClient(t, topic2) if err != nil { t.Fatalf("failed to create publisher2: %v", err) } defer pub2.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish to topic2 first if err := pub2.Publish([]byte(msg2)); err != nil { @@ -312,29 +314,29 @@ func TestPubSub_TopicIsolation(t *testing.T) { // TestPubSub_EmptyMessage tests sending and receiving empty messages func TestPubSub_EmptyMessage(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() // Create subscriber - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish empty message if err := publisher.Publish([]byte("")); err != nil { @@ -354,9 +356,9 @@ func TestPubSub_EmptyMessage(t *testing.T) { // TestPubSub_LargeMessage tests sending and receiving large messages func TestPubSub_LargeMessage(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() // Create a large message (100KB) largeMessage := make([]byte, 100*1024) @@ -365,24 +367,24 @@ func TestPubSub_LargeMessage(t *testing.T) { } // Create subscriber - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish large message if err := publisher.Publish(largeMessage); err != nil { @@ -409,30 +411,30 @@ func TestPubSub_LargeMessage(t *testing.T) { // TestPubSub_RapidPublish tests rapid message publishing func TestPubSub_RapidPublish(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() numMessages := 50 // Create subscriber - subscriber, err := NewWSPubSubClient(t, topic) + subscriber, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create subscriber: %v", err) } defer subscriber.Close() // Give subscriber time to register - Delay(200) + e2e.Delay(200) // Create publisher - publisher, err := NewWSPubSubClient(t, topic) + publisher, err := e2e.NewWSPubSubClient(t, topic) if err != nil { t.Fatalf("failed to create publisher: %v", err) } defer publisher.Close() // Give connections time to stabilize - Delay(200) + e2e.Delay(200) // Publish messages rapidly for i := 0; i < numMessages; i++ { diff --git a/e2e/pubsub_presence_test.go b/e2e/shared/pubsub_presence_test.go similarity index 86% rename from e2e/pubsub_presence_test.go rename to e2e/shared/pubsub_presence_test.go index 8c0ddc1..b4de5fe 100644 --- a/e2e/pubsub_presence_test.go +++ b/e2e/shared/pubsub_presence_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "context" @@ -9,17 +9,19 @@ import ( "net/http" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) func TestPubSub_Presence(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) - topic := GenerateTopic() + topic := e2e.GenerateTopic() memberID := "user123" memberMeta := map[string]interface{}{"name": "Alice"} // 1. Subscribe with presence - client1, err := NewWSPubSubPresenceClient(t, topic, memberID, memberMeta) + client1, err := e2e.NewWSPubSubPresenceClient(t, topic, memberID, memberMeta) if err != nil { t.Fatalf("failed to create presence client: %v", err) } @@ -48,9 +50,9 @@ func TestPubSub_Presence(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: fmt.Sprintf("%s/v1/pubsub/presence?topic=%s", GetGatewayURL(), topic), + URL: fmt.Sprintf("%s/v1/pubsub/presence?topic=%s", e2e.GetGatewayURL(), topic), } body, status, err := req.Do(ctx) @@ -63,7 +65,7 @@ func TestPubSub_Presence(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) } @@ -83,7 +85,7 @@ func TestPubSub_Presence(t *testing.T) { // 3. Subscribe second member memberID2 := "user456" - client2, err := NewWSPubSubPresenceClient(t, topic, memberID2, nil) + client2, err := e2e.NewWSPubSubPresenceClient(t, topic, memberID2, nil) if err != nil { t.Fatalf("failed to create second presence client: %v", err) } @@ -119,4 +121,3 @@ func TestPubSub_Presence(t *testing.T) { t.Fatalf("expected presence.leave for %s, got %v for %v", memberID2, event["type"], event["member_id"]) } } - diff --git a/e2e/rqlite_http_test.go b/e2e/shared/rqlite_http_test.go similarity index 78% rename from e2e/rqlite_http_test.go rename to e2e/shared/rqlite_http_test.go index a25f605..0a1cfe8 100644 --- a/e2e/rqlite_http_test.go +++ b/e2e/shared/rqlite_http_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "context" @@ -8,21 +8,23 @@ import ( "net/http" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) func TestRQLite_CreateTable(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*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()) @@ -33,9 +35,9 @@ func TestRQLite_CreateTable(t *testing.T) { table, ) - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/create-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/create-table", Body: map[string]interface{}{ "schema": schema, }, @@ -52,18 +54,18 @@ func TestRQLite_CreateTable(t *testing.T) { } func TestRQLite_InsertQuery(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*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()) @@ -75,9 +77,9 @@ func TestRQLite_InsertQuery(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, }, @@ -89,9 +91,9 @@ func TestRQLite_InsertQuery(t *testing.T) { } // Insert rows - 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(name) VALUES ('alice')", table), @@ -106,9 +108,9 @@ func TestRQLite_InsertQuery(t *testing.T) { } // Query 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 name FROM %s ORDER BY id", table), }, @@ -124,7 +126,7 @@ func TestRQLite_InsertQuery(t *testing.T) { } var queryResp map[string]interface{} - if err := DecodeJSON(body, &queryResp); err != nil { + if err := e2e.DecodeJSON(body, &queryResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -134,21 +136,21 @@ func TestRQLite_InsertQuery(t *testing.T) { } func TestRQLite_DropTable(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - table := GenerateTableName() + table := e2e.GenerateTableName() schema := fmt.Sprintf( "CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, note TEXT)", table, ) // 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, }, @@ -160,9 +162,9 @@ func TestRQLite_DropTable(t *testing.T) { } // Drop table - 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, }, @@ -178,9 +180,9 @@ func TestRQLite_DropTable(t *testing.T) { } // Verify table doesn't exist via schema - schemaReq := &HTTPRequest{ + schemaReq := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/rqlite/schema", + URL: e2e.GetGatewayURL() + "/v1/rqlite/schema", } body, status, err := schemaReq.Do(ctx) @@ -190,7 +192,7 @@ func TestRQLite_DropTable(t *testing.T) { } var schemaResp map[string]interface{} - if err := DecodeJSON(body, &schemaResp); err != nil { + if err := e2e.DecodeJSON(body, &schemaResp); err != nil { t.Logf("warning: failed to decode schema response: %v", err) return } @@ -206,14 +208,14 @@ func TestRQLite_DropTable(t *testing.T) { } func TestRQLite_Schema(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - 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) @@ -226,7 +228,7 @@ func TestRQLite_Schema(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) } @@ -236,14 +238,14 @@ func TestRQLite_Schema(t *testing.T) { } func TestRQLite_MalformedSQL(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - req := &HTTPRequest{ + req := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/query", + URL: e2e.GetGatewayURL() + "/v1/rqlite/query", Body: map[string]interface{}{ "sql": "SELECT * FROM nonexistent_table WHERE invalid syntax", }, @@ -261,18 +263,18 @@ func TestRQLite_MalformedSQL(t *testing.T) { } func TestRQLite_LargeTransaction(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 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()) @@ -284,9 +286,9 @@ func TestRQLite_LargeTransaction(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, }, @@ -303,9 +305,9 @@ func TestRQLite_LargeTransaction(t *testing.T) { statements = append(statements, fmt.Sprintf("INSERT INTO %s(value) VALUES (%d)", table, i)) } - txReq := &HTTPRequest{ + txReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": statements, }, @@ -317,9 +319,9 @@ func TestRQLite_LargeTransaction(t *testing.T) { } // Verify all rows were inserted - 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), }, @@ -331,7 +333,7 @@ func TestRQLite_LargeTransaction(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) } @@ -345,35 +347,35 @@ func TestRQLite_LargeTransaction(t *testing.T) { } func TestRQLite_ForeignKeyMigration(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - orgsTable := GenerateTableName() - usersTable := GenerateTableName() + orgsTable := e2e.GenerateTableName() + usersTable := e2e.GenerateTableName() // Cleanup tables after test defer func() { - dropUsersReq := &HTTPRequest{ + dropUsersReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": usersTable}, } dropUsersReq.Do(context.Background()) - dropOrgsReq := &HTTPRequest{ + dropOrgsReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/drop-table", + URL: e2e.GetGatewayURL() + "/v1/rqlite/drop-table", Body: map[string]interface{}{"table": orgsTable}, } dropOrgsReq.Do(context.Background()) }() // Create base tables - createOrgsReq := &HTTPRequest{ + createOrgsReq := &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, name TEXT)", @@ -387,9 +389,9 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { t.Fatalf("create orgs table failed: status %d, err %v", status, err) } - createUsersReq := &HTTPRequest{ + createUsersReq := &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, name TEXT, org_id INTEGER, age TEXT)", @@ -404,9 +406,9 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { } // Seed data - seedReq := &HTTPRequest{ + seedReq := &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(id,name) VALUES (1,'org')", orgsTable), @@ -421,9 +423,9 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { } // Migrate: change age type and add FK - migrationReq := &HTTPRequest{ + migrationReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/rqlite/transaction", + URL: e2e.GetGatewayURL() + "/v1/rqlite/transaction", Body: map[string]interface{}{ "statements": []string{ fmt.Sprintf( @@ -446,9 +448,9 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { } // Verify data is intact - 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 name, org_id, age FROM %s", usersTable), }, @@ -460,7 +462,7 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { } var queryResp map[string]interface{} - if err := DecodeJSON(body, &queryResp); err != nil { + if err := e2e.DecodeJSON(body, &queryResp); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -470,14 +472,14 @@ func TestRQLite_ForeignKeyMigration(t *testing.T) { } func TestRQLite_DropNonexistentTable(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - 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": "nonexistent_table_xyz_" + fmt.Sprintf("%d", time.Now().UnixNano()), }, diff --git a/e2e/serverless_test.go b/e2e/shared/serverless_test.go similarity index 77% rename from e2e/serverless_test.go rename to e2e/shared/serverless_test.go index 81111d6..89177cc 100644 --- a/e2e/serverless_test.go +++ b/e2e/shared/serverless_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "bytes" @@ -11,10 +11,12 @@ import ( "os" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) func TestServerless_DeployAndInvoke(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -53,14 +55,14 @@ func TestServerless_DeployAndInvoke(t *testing.T) { part.Write(wasmBytes) writer.Close() - deployReq, _ := http.NewRequestWithContext(ctx, "POST", GetGatewayURL()+"/v1/functions", &buf) + deployReq, _ := http.NewRequestWithContext(ctx, "POST", e2e.GetGatewayURL()+"/v1/functions", &buf) deployReq.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { deployReq.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(1 * time.Minute) + client := e2e.NewHTTPClient(1 * time.Minute) resp, err := client.Do(deployReq) if err != nil { t.Fatalf("deploy request failed: %v", err) @@ -74,10 +76,10 @@ func TestServerless_DeployAndInvoke(t *testing.T) { // 2. Invoke function invokePayload := []byte(`{"name": "E2E Tester"}`) - invokeReq, _ := http.NewRequestWithContext(ctx, "POST", GetGatewayURL()+"/v1/functions/"+funcName+"/invoke?namespace="+namespace, bytes.NewReader(invokePayload)) + invokeReq, _ := http.NewRequestWithContext(ctx, "POST", e2e.GetGatewayURL()+"/v1/functions/"+funcName+"/invoke?namespace="+namespace, bytes.NewReader(invokePayload)) invokeReq.Header.Set("Content-Type", "application/json") - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { invokeReq.Header.Set("Authorization", "Bearer "+apiKey) } @@ -99,8 +101,8 @@ func TestServerless_DeployAndInvoke(t *testing.T) { } // 3. List functions - listReq, _ := http.NewRequestWithContext(ctx, "GET", GetGatewayURL()+"/v1/functions?namespace="+namespace, nil) - if apiKey := GetAPIKey(); apiKey != "" { + listReq, _ := http.NewRequestWithContext(ctx, "GET", e2e.GetGatewayURL()+"/v1/functions?namespace="+namespace, nil) + if apiKey := e2e.GetAPIKey(); apiKey != "" { listReq.Header.Set("Authorization", "Bearer "+apiKey) } resp, err = client.Do(listReq) @@ -113,8 +115,8 @@ func TestServerless_DeployAndInvoke(t *testing.T) { } // 4. Delete function - deleteReq, _ := http.NewRequestWithContext(ctx, "DELETE", GetGatewayURL()+"/v1/functions/"+funcName+"?namespace="+namespace, nil) - if apiKey := GetAPIKey(); apiKey != "" { + deleteReq, _ := http.NewRequestWithContext(ctx, "DELETE", e2e.GetGatewayURL()+"/v1/functions/"+funcName+"?namespace="+namespace, nil) + if apiKey := e2e.GetAPIKey(); apiKey != "" { deleteReq.Header.Set("Authorization", "Bearer "+apiKey) } resp, err = client.Do(deleteReq) diff --git a/e2e/storage_http_test.go b/e2e/shared/storage_http_test.go similarity index 80% rename from e2e/storage_http_test.go rename to e2e/shared/storage_http_test.go index b8a3436..d61b075 100644 --- a/e2e/storage_http_test.go +++ b/e2e/shared/storage_http_test.go @@ -1,6 +1,6 @@ //go:build e2e -package e2e +package shared_test import ( "bytes" @@ -10,6 +10,8 @@ import ( "net/http" "testing" "time" + + e2e "github.com/DeBrosOfficial/network/e2e" ) // uploadFile is a helper to upload a file to storage @@ -34,7 +36,7 @@ func uploadFile(t *testing.T, ctx context.Context, content []byte, filename stri } // Create request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } @@ -42,13 +44,13 @@ func uploadFile(t *testing.T, ctx context.Context, content []byte, filename stri req.Header.Set("Content-Type", writer.FormDataContentType()) // Add auth headers - if jwt := GetJWT(); jwt != "" { + if jwt := e2e.GetJWT(); jwt != "" { req.Header.Set("Authorization", "Bearer "+jwt) - } else if apiKey := GetAPIKey(); apiKey != "" { + } else if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload request failed: %v", err) @@ -60,28 +62,20 @@ func uploadFile(t *testing.T, ctx context.Context, content []byte, filename stri t.Fatalf("upload failed with status %d: %s", resp.StatusCode, string(body)) } - result, err := DecodeJSONFromReader(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { + t.Fatalf("failed to read upload response: %v", err) + } + var result map[string]interface{} + if err := e2e.DecodeJSON(body, &result); err != nil { t.Fatalf("failed to decode upload response: %v", err) } return result["cid"].(string) } -// DecodeJSON is a helper to decode JSON from io.ReadCloser -func DecodeJSONFromReader(rc io.ReadCloser) (map[string]interface{}, error) { - defer rc.Close() - body, err := io.ReadAll(rc) - if err != nil { - return nil, err - } - var result map[string]interface{} - err = DecodeJSON(body, &result) - return result, err -} - func TestStorage_UploadText(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -107,18 +101,18 @@ func TestStorage_UploadText(t *testing.T) { } // Create request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload request failed: %v", err) @@ -132,7 +126,7 @@ func TestStorage_UploadText(t *testing.T) { var result map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &result); err != nil { + if err := e2e.DecodeJSON(body, &result); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -150,7 +144,7 @@ func TestStorage_UploadText(t *testing.T) { } func TestStorage_UploadBinary(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -177,18 +171,18 @@ func TestStorage_UploadBinary(t *testing.T) { } // Create request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload request failed: %v", err) @@ -202,7 +196,7 @@ func TestStorage_UploadBinary(t *testing.T) { var result map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &result); err != nil { + if err := e2e.DecodeJSON(body, &result); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -212,7 +206,7 @@ func TestStorage_UploadBinary(t *testing.T) { } func TestStorage_UploadLarge(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -239,18 +233,18 @@ func TestStorage_UploadLarge(t *testing.T) { } // Create request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload request failed: %v", err) @@ -264,7 +258,7 @@ func TestStorage_UploadLarge(t *testing.T) { var result map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &result); err != nil { + if err := e2e.DecodeJSON(body, &result); err != nil { t.Fatalf("failed to decode response: %v", err) } @@ -274,7 +268,7 @@ func TestStorage_UploadLarge(t *testing.T) { } func TestStorage_PinUnpin(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -299,18 +293,18 @@ func TestStorage_PinUnpin(t *testing.T) { } // Create upload request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload failed: %v", err) @@ -319,7 +313,7 @@ func TestStorage_PinUnpin(t *testing.T) { var uploadResult map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &uploadResult); err != nil { + if err := e2e.DecodeJSON(body, &uploadResult); err != nil { t.Fatalf("failed to decode upload response: %v", err) } @@ -333,9 +327,9 @@ func TestStorage_PinUnpin(t *testing.T) { } // Pin the file - pinReq := &HTTPRequest{ + pinReq := &e2e.HTTPRequest{ Method: http.MethodPost, - URL: GetGatewayURL() + "/v1/storage/pin", + URL: e2e.GetGatewayURL() + "/v1/storage/pin", Body: map[string]interface{}{ "cid": cid, "name": "pinned-file", @@ -352,7 +346,7 @@ func TestStorage_PinUnpin(t *testing.T) { } var pinResult map[string]interface{} - if err := DecodeJSON(body2, &pinResult); err != nil { + if err := e2e.DecodeJSON(body2, &pinResult); err != nil { t.Fatalf("failed to decode pin response: %v", err) } @@ -361,9 +355,9 @@ func TestStorage_PinUnpin(t *testing.T) { } // Unpin the file - unpinReq := &HTTPRequest{ + unpinReq := &e2e.HTTPRequest{ Method: http.MethodDelete, - URL: GetGatewayURL() + "/v1/storage/unpin/" + cid, + URL: e2e.GetGatewayURL() + "/v1/storage/unpin/" + cid, } body3, status, err := unpinReq.Do(ctx) @@ -377,7 +371,7 @@ func TestStorage_PinUnpin(t *testing.T) { } func TestStorage_Status(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -402,18 +396,18 @@ func TestStorage_Status(t *testing.T) { } // Create upload request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload failed: %v", err) @@ -422,16 +416,16 @@ func TestStorage_Status(t *testing.T) { var uploadResult map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &uploadResult); err != nil { + if err := e2e.DecodeJSON(body, &uploadResult); err != nil { t.Fatalf("failed to decode upload response: %v", err) } cid := uploadResult["cid"].(string) // Get status - statusReq := &HTTPRequest{ + statusReq := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/storage/status/" + cid, + URL: e2e.GetGatewayURL() + "/v1/storage/status/" + cid, } statusBody, status, err := statusReq.Do(ctx) @@ -444,7 +438,7 @@ func TestStorage_Status(t *testing.T) { } var statusResult map[string]interface{} - if err := DecodeJSON(statusBody, &statusResult); err != nil { + if err := e2e.DecodeJSON(statusBody, &statusResult); err != nil { t.Fatalf("failed to decode status response: %v", err) } @@ -454,14 +448,14 @@ func TestStorage_Status(t *testing.T) { } func TestStorage_InvalidCID(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - statusReq := &HTTPRequest{ + statusReq := &e2e.HTTPRequest{ Method: http.MethodGet, - URL: GetGatewayURL() + "/v1/storage/status/QmInvalidCID123456789", + URL: e2e.GetGatewayURL() + "/v1/storage/status/QmInvalidCID123456789", } _, status, err := statusReq.Do(ctx) @@ -475,7 +469,7 @@ func TestStorage_InvalidCID(t *testing.T) { } func TestStorage_GetByteRange(t *testing.T) { - SkipIfMissingGateway(t) + e2e.SkipIfMissingGateway(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -500,18 +494,18 @@ func TestStorage_GetByteRange(t *testing.T) { } // Create upload request - req, err := http.NewRequestWithContext(ctx, http.MethodPost, GetGatewayURL()+"/v1/storage/upload", &buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, e2e.GetGatewayURL()+"/v1/storage/upload", &buf) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } - client := NewHTTPClient(5 * time.Minute) + client := e2e.NewHTTPClient(5 * time.Minute) resp, err := client.Do(req) if err != nil { t.Fatalf("upload failed: %v", err) @@ -520,19 +514,19 @@ func TestStorage_GetByteRange(t *testing.T) { var uploadResult map[string]interface{} body, _ := io.ReadAll(resp.Body) - if err := DecodeJSON(body, &uploadResult); err != nil { + if err := e2e.DecodeJSON(body, &uploadResult); err != nil { t.Fatalf("failed to decode upload response: %v", err) } cid := uploadResult["cid"].(string) // Get full content - getReq, err := http.NewRequestWithContext(ctx, http.MethodGet, GetGatewayURL()+"/v1/storage/get/"+cid, nil) + getReq, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/storage/get/"+cid, nil) if err != nil { t.Fatalf("failed to create get request: %v", err) } - if apiKey := GetAPIKey(); apiKey != "" { + if apiKey := e2e.GetAPIKey(); apiKey != "" { getReq.Header.Set("Authorization", "Bearer "+apiKey) }