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,298 @@
package fuzz
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"unicode/utf8"
"github.com/go-chi/chi/v5"
"goyco/internal/handlers"
"goyco/internal/middleware"
"goyco/internal/repositories"
"goyco/internal/services"
"goyco/internal/testutils"
)
func FuzzIntegrationHandlers(f *testing.F) {
f.Add("testuser")
f.Add("test@example.com")
f.Add("password123")
f.Add("")
f.Add("<script>alert('xss')</script>")
f.Fuzz(func(t *testing.T, input string) {
if len(input) > 500 {
input = input[:500]
}
if !isValidUTF8(input) {
return
}
db := testutils.NewTestDB(t)
defer func() {
sqlDB, _ := db.DB()
sqlDB.Close()
}()
userRepo := repositories.NewUserRepository(db)
postRepo := repositories.NewPostRepository(db)
voteRepo := repositories.NewVoteRepository(db)
deletionRepo := repositories.NewAccountDeletionRepository(db)
refreshTokenRepo := repositories.NewRefreshTokenRepository(db)
emailSender := &testutils.MockEmailSender{}
authService, err := services.NewAuthFacadeForTest(testutils.AppTestConfig, userRepo, postRepo, deletionRepo, refreshTokenRepo, emailSender)
if err != nil {
t.Fatalf("Failed to create auth service: %v", err)
}
voteService := services.NewVoteService(voteRepo, postRepo, db)
titleFetcher := &testutils.MockTitleFetcher{}
authHandler := handlers.NewAuthHandler(authService, userRepo)
postHandler := handlers.NewPostHandler(postRepo, titleFetcher, voteService)
apiHandler := handlers.NewAPIHandler(testutils.AppTestConfig, postRepo, userRepo, voteService)
router := chi.NewRouter()
router.Use(middleware.Logging(false))
router.Use(middleware.SecurityHeadersMiddleware())
router.Use(middleware.GeneralRateLimitMiddleware())
router.Route("/api", func(r chi.Router) {
r.Post("/auth/register", authHandler.Register)
r.Post("/auth/login", authHandler.Login)
r.Get("/posts/search", postHandler.SearchPosts)
r.Get("/posts", postHandler.GetPosts)
r.Group(func(protected chi.Router) {
protected.Use(middleware.NewAuth(authService))
protected.Get("/auth/me", authHandler.Me)
protected.Post("/posts", postHandler.CreatePost)
})
})
router.Get("/health", apiHandler.GetHealth)
t.Run("register_endpoint", func(t *testing.T) {
username := input[:min(len(input), 50)]
email := input[:min(len(input), 50)] + "@example.com"
password := input[:min(len(input), 128)]
if len(password) < 8 {
password = password + "12345678"
}
registerBody := fmt.Sprintf(`{"username":"%s","email":"%s","password":"%s"}`,
escapeJSON(username), escapeJSON(email), escapeJSON(password))
req, _ := http.NewRequest("POST", "/api/auth/register", bytes.NewBufferString(registerBody))
req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
if resp.Code == 0 {
t.Fatal("Handler should return a status code")
}
if resp.Code != http.StatusCreated && resp.Code != http.StatusBadRequest {
t.Logf("Unexpected status code %d for register (expected 201 or 400)", resp.Code)
}
var result map[string]any
if err := json.Unmarshal(resp.Body.Bytes(), &result); err != nil {
t.Fatalf("Response should be valid JSON: %v", err)
}
})
t.Run("search_endpoint", func(t *testing.T) {
query := input[:min(len(input), 200)]
escapedQuery := url.QueryEscape(query)
req, _ := http.NewRequest("GET", "/api/posts/search?q="+escapedQuery, nil)
req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
if resp.Code == 0 {
t.Fatal("Handler should return a status code")
}
if resp.Code != http.StatusOK {
t.Logf("Unexpected status code %d for search (expected 200)", resp.Code)
}
var result map[string]any
if err := json.Unmarshal(resp.Body.Bytes(), &result); err != nil {
t.Fatalf("Response should be valid JSON: %v", err)
}
})
})
}
func FuzzIntegrationServices(f *testing.F) {
f.Add("testuser")
f.Add("test@example.com")
f.Add("password123")
f.Add("")
f.Add("a")
f.Add(strings.Repeat("x", 100))
f.Fuzz(func(t *testing.T, input string) {
if len(input) > 200 {
input = input[:200]
}
if !utf8.ValidString(input) {
return
}
db := testutils.NewTestDB(t)
defer func() {
sqlDB, _ := db.DB()
sqlDB.Close()
}()
userRepo := repositories.NewUserRepository(db)
postRepo := repositories.NewPostRepository(db)
deletionRepo := repositories.NewAccountDeletionRepository(db)
refreshTokenRepo := repositories.NewRefreshTokenRepository(db)
emailSender := &testutils.MockEmailSender{}
authService, err := services.NewAuthFacadeForTest(testutils.AppTestConfig, userRepo, postRepo, deletionRepo, refreshTokenRepo, emailSender)
if err != nil {
t.Fatalf("Failed to create auth service: %v", err)
}
usernameLen := len(input)
if usernameLen > 50 {
usernameLen = 50
}
username := input[:usernameLen]
email := input[:usernameLen] + "@example.com"
passwordLen := len(input)
if passwordLen > 128 {
passwordLen = 128
}
password := input[:passwordLen]
if len(password) < 8 {
password = password + "12345678"
}
result, err := authService.Register(username, email, password)
if err != nil {
if strings.Contains(err.Error(), "panic") || strings.Contains(err.Error(), "nil pointer") {
t.Fatalf("Registration should not panic: %v", err)
}
} else {
if result.User == nil {
t.Fatal("Registration result should contain a user")
}
if result.User.Username != username {
t.Fatalf("Expected username %q, got %q", username, result.User.Username)
}
if !strings.EqualFold(result.User.Email, email) {
t.Fatalf("Expected email %q, got %q", email, result.User.Email)
}
}
if err == nil {
loginResult, loginErr := authService.Login(username, password)
if loginErr == nil {
if loginResult.User == nil {
t.Fatal("Login result should contain a user")
}
if loginResult.User.Username != username {
t.Fatalf("Expected username %q, got %q", username, loginResult.User.Username)
}
if loginResult.AccessToken == "" {
t.Fatal("Login result should contain an access token")
}
}
}
})
}
func FuzzIntegrationRepositories(f *testing.F) {
helper := NewFuzzTestHelper()
helper.RunIntegrationFuzzTest(f, func(t *testing.T, fuzzedData string) {
searchQuery := fuzzedData
if len(searchQuery) > 100 {
searchQuery = searchQuery[:100]
}
sanitizer := repositories.NewSearchSanitizer()
sanitizedQuery, err := sanitizer.SanitizeSearchQuery(searchQuery)
if err == nil {
if !utf8.ValidString(sanitizedQuery) {
t.Fatal("String contains invalid UTF-8")
}
validationErr := sanitizer.ValidateSearchQuery(sanitizedQuery)
_ = validationErr
}
username := fuzzedData
email := fuzzedData + "@example.com"
if len(username) > 50 {
username = username[:50]
}
if len(email) > 100 {
email = email[:100]
}
if !utf8.ValidString(username) {
t.Fatal("String contains invalid UTF-8")
}
if !utf8.ValidString(email) {
t.Fatal("String contains invalid UTF-8")
}
postTitle := fuzzedData
postContent := fuzzedData
if len(postTitle) > 200 {
postTitle = postTitle[:200]
}
if len(postContent) > 1000 {
postContent = postContent[:1000]
}
if !utf8.ValidString(postTitle) {
t.Fatal("String contains invalid UTF-8")
}
if !utf8.ValidString(postContent) {
t.Fatal("String contains invalid UTF-8")
}
})
}
func isValidUTF8(s string) bool {
for _, r := range s {
if r == utf8.RuneError {
return false
}
}
return true
}
func escapeJSON(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, "\"", "\\\"")
s = strings.ReplaceAll(s, "\n", "\\n")
s = strings.ReplaceAll(s, "\r", "\\r")
s = strings.ReplaceAll(s, "\t", "\\t")
return s
}