package e2e import ( "bytes" "compress/gzip" "encoding/json" "fmt" "net/http" "sync" "sync/atomic" "testing" "time" "goyco/internal/testutils" ) func TestE2E_Performance(t *testing.T) { ctx := setupTestContext(t) t.Run("response_times", func(t *testing.T) { createdUser := ctx.createUserWithCleanup(t, "perfuser", "StrongPass123!") authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password) endpoints := []struct { name string req func() (*http.Request, error) }{ { name: "health", req: func() (*http.Request, error) { return http.NewRequest("GET", ctx.baseURL+"/health", nil) }, }, { name: "posts_list", req: func() (*http.Request, error) { req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil) if err == nil { testutils.WithStandardHeaders(req) } return req, err }, }, { name: "profile", req: func() (*http.Request, error) { req, err := http.NewRequest("GET", ctx.baseURL+"/api/auth/me", nil) if err == nil { req.Header.Set("Authorization", "Bearer "+authClient.Token) testutils.WithStandardHeaders(req) } return req, err }, }, } for _, endpoint := range endpoints { t.Run(endpoint.name, func(t *testing.T) { var totalTime time.Duration iterations := 10 for i := 0; i < iterations; i++ { req, err := endpoint.req() if err != nil { t.Fatalf("Failed to create request: %v", err) } start := time.Now() resp, err := ctx.client.Do(req) duration := time.Since(start) if err != nil { t.Fatalf("Request failed: %v", err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", resp.StatusCode) } totalTime += duration } avgTime := totalTime / time.Duration(iterations) if avgTime > 500*time.Millisecond { t.Errorf("Average response time %v exceeds 500ms", avgTime) } }) } }) t.Run("concurrent_requests", func(t *testing.T) { ctx.createUserWithCleanup(t, "concurrentperf", "StrongPass123!") concurrency := 20 requestsPerGoroutine := 5 var successCount int64 var errorCount int64 var wg sync.WaitGroup for i := 0; i < concurrency; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < requestsPerGoroutine; j++ { req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil) if err != nil { atomic.AddInt64(&errorCount, 1) continue } testutils.WithStandardHeaders(req) resp, err := ctx.client.Do(req) if err != nil { atomic.AddInt64(&errorCount, 1) continue } resp.Body.Close() if resp.StatusCode == http.StatusOK { atomic.AddInt64(&successCount, 1) } else { atomic.AddInt64(&errorCount, 1) } } }() } wg.Wait() totalRequests := int64(concurrency * requestsPerGoroutine) if successCount < totalRequests*8/10 { t.Errorf("Expected at least 80%% success rate, got %d/%d successful", successCount, totalRequests) } }) t.Run("database_query_performance", func(t *testing.T) { createdUser := ctx.createUserWithCleanup(t, "dbperf", "StrongPass123!") authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password) for i := 0; i < 10; i++ { authClient.CreatePost(t, fmt.Sprintf("Post %d", i), fmt.Sprintf("https://example.com/%d", i), "Content") } start := time.Now() postsResp := authClient.GetPosts(t) duration := time.Since(start) if len(postsResp.Data.Posts) < 10 { t.Errorf("Expected at least 10 posts, got %d", len(postsResp.Data.Posts)) } if duration > 1*time.Second { t.Errorf("Posts query took %v, expected under 1s", duration) } }) t.Run("memory_usage", func(t *testing.T) { createdUser := ctx.createUserWithCleanup(t, "memuser", "StrongPass123!") authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password) initialPosts := 50 for i := 0; i < initialPosts; i++ { authClient.CreatePost(t, fmt.Sprintf("Memory Test Post %d", i), fmt.Sprintf("https://example.com/mem%d", i), "Content") } req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts?limit=100", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } testutils.WithStandardHeaders(req) resp, err := ctx.client.Do(req) if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200, got %d", resp.StatusCode) } var postsResp testutils.PostsListResponse reader := resp.Body if resp.Header.Get("Content-Encoding") == "gzip" { gzReader, err := gzip.NewReader(resp.Body) if err != nil { t.Fatalf("Failed to create gzip reader: %v", err) } defer gzReader.Close() reader = gzReader } if err := json.NewDecoder(reader).Decode(&postsResp); err != nil { t.Fatalf("Failed to decode response: %v", err) } if len(postsResp.Data.Posts) < initialPosts { t.Errorf("Expected at least %d posts, got %d", initialPosts, len(postsResp.Data.Posts)) } }) } func TestE2E_LoadTest(t *testing.T) { ctx := setupTestContext(t) t.Run("sustained_load", func(t *testing.T) { ctx.createUserWithCleanup(t, "loaduser", "StrongPass123!") duration := 5 * time.Second requestsPerSecond := 10 ticker := time.NewTicker(time.Second / time.Duration(requestsPerSecond)) defer ticker.Stop() var successCount int64 var errorCount int64 done := make(chan bool) go func() { time.Sleep(duration) done <- true }() for { select { case <-done: totalRequests := successCount + errorCount if totalRequests == 0 { t.Error("No requests were made") return } successRate := float64(successCount) / float64(totalRequests) if successRate < 0.9 { t.Errorf("Success rate %.2f%% below 90%% threshold", successRate*100) } return case <-ticker.C: go func() { req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil) if err != nil { atomic.AddInt64(&errorCount, 1) return } testutils.WithStandardHeaders(req) resp, err := ctx.client.Do(req) if err != nil { atomic.AddInt64(&errorCount, 1) return } resp.Body.Close() if resp.StatusCode == http.StatusOK { atomic.AddInt64(&successCount, 1) } else { atomic.AddInt64(&errorCount, 1) } }() } } }) } func TestE2E_ConcurrentWrites(t *testing.T) { ctx := setupTestContext(t) t.Run("concurrent_post_creation", func(t *testing.T) { users := ctx.createMultipleUsersWithCleanup(t, 5, "writeuser", "StrongPass123!") var wg sync.WaitGroup var successCount int64 var errorCount int64 for _, user := range users { u := user wg.Add(1) go func() { defer wg.Done() authClient, err := ctx.loginUserSafe(t, u.Username, u.Password) if err != nil { atomic.AddInt64(&errorCount, 1) return } for i := 0; i < 5; i++ { post, err := authClient.CreatePostSafe( fmt.Sprintf("Concurrent Post %d", i), fmt.Sprintf("https://example.com/concurrent%d-%d", u.ID, i), "Content", ) if err == nil && post != nil { atomic.AddInt64(&successCount, 1) } else { atomic.AddInt64(&errorCount, 1) } } }() } wg.Wait() expectedPosts := int64(len(users) * 5) if successCount < expectedPosts*7/10 { t.Errorf("Expected at least 70%% success rate, got %d/%d successful (errors: %d)", successCount, expectedPosts, errorCount) } }) } func TestE2E_ResponseSize(t *testing.T) { ctx := setupTestContext(t) t.Run("large_response", func(t *testing.T) { createdUser := ctx.createUserWithCleanup(t, "sizetest", "StrongPass123!") authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password) for i := 0; i < 100; i++ { authClient.CreatePost(t, fmt.Sprintf("Post %d", i), fmt.Sprintf("https://example.com/%d", i), "Content") } req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } testutils.WithStandardHeaders(req) resp, err := ctx.client.Do(req) if err != nil { t.Fatalf("Request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", resp.StatusCode) } var buf bytes.Buffer buf.ReadFrom(resp.Body) responseSize := buf.Len() if responseSize > 10*1024*1024 { t.Errorf("Response size %d bytes exceeds 10MB limit", responseSize) } }) } func TestE2E_Throughput(t *testing.T) { ctx := setupTestContext(t) t.Run("requests_per_second", func(t *testing.T) { ctx.createUserWithCleanup(t, "throughput", "StrongPass123!") duration := 3 * time.Second start := time.Now() var requestCount int64 for time.Since(start) < duration { req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil) if err != nil { continue } testutils.WithStandardHeaders(req) resp, err := ctx.client.Do(req) if err == nil { resp.Body.Close() atomic.AddInt64(&requestCount, 1) } } elapsed := time.Since(start) rps := float64(requestCount) / elapsed.Seconds() if rps < 10 { t.Errorf("Throughput %.2f req/s below 10 req/s threshold", rps) } }) }