247 lines
6.6 KiB
Go
247 lines
6.6 KiB
Go
package e2e
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"goyco/internal/testutils"
|
|
)
|
|
|
|
func TestE2E_CompleteUserJourney(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("complete_user_journey", func(t *testing.T) {
|
|
_, authClient := ctx.createUserAndLogin(t, "testuser", "StrongPass123!")
|
|
|
|
createdPost := authClient.CreatePost(t, "Test Post", "https://example.com/test", "This is a test post content")
|
|
|
|
voteResp := authClient.VoteOnPost(t, createdPost.ID, "up")
|
|
if !voteResp.Success {
|
|
t.Errorf("Expected vote to be successful, got failure: %s", voteResp.Message)
|
|
}
|
|
|
|
postsResp := authClient.GetPosts(t)
|
|
assertPostInList(t, postsResp, createdPost)
|
|
|
|
searchResp := authClient.SearchPosts(t, "test")
|
|
assertPostInList(t, searchResp, createdPost)
|
|
|
|
authClient.Logout(t)
|
|
})
|
|
}
|
|
|
|
func TestE2E_ErrorHandlingWorkflows(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("unauthenticated_user_workflow", func(t *testing.T) {
|
|
request, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", bytes.NewReader([]byte(`{"title":"Test","url":"https://example.com"}`)))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(request)
|
|
|
|
resp, err := ctx.client.Do(request)
|
|
if err != nil {
|
|
t.Fatalf("Failed to make request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected 401 for unauthenticated post creation, got %d", resp.StatusCode)
|
|
}
|
|
|
|
request, err = testutils.NewRequestBuilder("GET", ctx.baseURL+"/api/auth/me").Build()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
|
|
resp, err = ctx.client.Do(request)
|
|
if err != nil {
|
|
t.Fatalf("Failed to make request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected 401 for unauthenticated profile access, got %d", resp.StatusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid_registration_workflow", func(t *testing.T) {
|
|
invalidData := []struct {
|
|
name string
|
|
body []byte
|
|
}{
|
|
{
|
|
name: "empty_username",
|
|
body: []byte(`{"username":"","email":"test@example.com","password":"ValidPass123!"}`),
|
|
},
|
|
{
|
|
name: "invalid_email",
|
|
body: []byte(`{"username":"testuser","email":"invalid-email","password":"ValidPass123!"}`),
|
|
},
|
|
{
|
|
name: "weak_password",
|
|
body: []byte(`{"username":"testuser","email":"test@example.com","password":"123"}`),
|
|
},
|
|
{
|
|
name: "malformed_json",
|
|
body: []byte(`{"username": "test", "password": }`),
|
|
},
|
|
}
|
|
|
|
for _, test := range invalidData {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
request, err := testutils.NewRequestBuilder("POST", ctx.baseURL+"/api/auth/register").
|
|
WithBody(bytes.NewReader(test.body)).
|
|
Build()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
|
|
resp, err := ctx.client.Do(request)
|
|
if err != nil {
|
|
t.Fatalf("Failed to make request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
|
|
t.Errorf("Expected invalid registration to fail, got success status %d", resp.StatusCode)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_ConcurrentUserWorkflows(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("concurrent_user_workflows", func(t *testing.T) {
|
|
users := ctx.createMultipleUsersWithCleanup(t, 3, "concurrent", "StrongPass123!")
|
|
|
|
type result struct {
|
|
userID uint
|
|
err error
|
|
}
|
|
|
|
results := make(chan result, len(users))
|
|
var wg sync.WaitGroup
|
|
done := make(chan struct{})
|
|
|
|
for _, user := range users {
|
|
u := user
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
var err error
|
|
authClient, loginErr := ctx.loginUserSafe(t, u.Username, u.Password)
|
|
if loginErr != nil || authClient == nil || authClient.Token == "" {
|
|
err = fmt.Errorf("User %s failed to login", u.Username)
|
|
} else {
|
|
postURL := fmt.Sprintf("https://example.com/concurrent/%d", u.ID)
|
|
post, postErr := authClient.CreatePostSafe("Concurrent Post", postURL, "Content")
|
|
if postErr != nil || post == nil || post.ID == 0 {
|
|
err = fmt.Errorf("User %s failed to create post: %v", u.Username, postErr)
|
|
} else {
|
|
voteResp, voteErr := authClient.VoteOnPostSafe(post.ID, "up")
|
|
if voteErr != nil || voteResp == nil || !voteResp.Success {
|
|
err = fmt.Errorf("User %s failed to vote: %v", u.Username, voteErr)
|
|
}
|
|
}
|
|
}
|
|
select {
|
|
case results <- result{userID: u.ID, err: err}:
|
|
case <-done:
|
|
}
|
|
}()
|
|
}
|
|
|
|
go func() {
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
timeout := time.After(10 * time.Second)
|
|
successCount := 0
|
|
receivedCount := 0
|
|
|
|
for {
|
|
select {
|
|
case res, ok := <-results:
|
|
if !ok {
|
|
return
|
|
}
|
|
receivedCount++
|
|
if res.err != nil {
|
|
t.Errorf("Concurrent operation error for user %d: %v", res.userID, res.err)
|
|
} else {
|
|
successCount++
|
|
}
|
|
if receivedCount >= len(users) {
|
|
return
|
|
}
|
|
case <-timeout:
|
|
close(done)
|
|
t.Errorf("Timeout waiting for concurrent operations to complete")
|
|
return
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_SystemMonitoringWorkflows(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("system_monitoring_workflows", func(t *testing.T) {
|
|
t.Run("health_endpoint", func(t *testing.T) {
|
|
health := getHealth(t, ctx.client, ctx.baseURL)
|
|
if !health.Success {
|
|
t.Errorf("Expected health check to succeed, got failure: %s", health.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("metrics_endpoint", func(t *testing.T) {
|
|
metrics := getMetrics(t, ctx.client, ctx.baseURL)
|
|
if metrics == nil {
|
|
t.Errorf("Expected metrics to be returned")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestE2E_AccountDeletion(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("account_deletion_flow", func(t *testing.T) {
|
|
_, authClient := ctx.createUserAndLogin(t, "testuser", "StrongPass123!")
|
|
|
|
_ = authClient.CreatePost(t, "Test Post", "https://example.com/test", "Test content")
|
|
|
|
statusCode, deletionResp := ctx.requestAccountDeletionExpectStatus(t, authClient.Token, http.StatusOK)
|
|
if statusCode == http.StatusTooManyRequests {
|
|
statusCode = retryOnRateLimit(t, 3, func() int {
|
|
code, _ := ctx.requestAccountDeletionExpectStatus(t, authClient.Token, http.StatusOK)
|
|
return code
|
|
})
|
|
if statusCode == http.StatusTooManyRequests {
|
|
t.Skip("Skipping account deletion flow test: rate limited after retries")
|
|
return
|
|
}
|
|
}
|
|
|
|
if deletionResp == nil {
|
|
t.Fatalf("Expected account deletion response, got nil")
|
|
}
|
|
if !deletionResp.Success {
|
|
t.Errorf("Expected account deletion request to be successful, got %v", deletionResp.Success)
|
|
}
|
|
if deletionResp.Message == "" {
|
|
t.Errorf("Expected deletion message to be present, got empty string")
|
|
}
|
|
})
|
|
}
|