To gitea and beyond, let's go(-yco)

This commit is contained in:
2025-11-10 19:12:09 +01:00
parent 8f6133392d
commit 71a031342b
245 changed files with 83994 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
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)
}
})
}