sojorn/go-backend/internal/testing/integration_test_suite.go
Patrick Britton 56a9dd032f feat: Add enhanced video moderation with frame extraction and implement placeholder UI methods
- Add VideoProcessor service to PostHandler for frame-based video moderation
- Implement multi-frame extraction and Azure OpenAI Vision analysis for video content
- Enhance VideoStitchingService with filters, speed control, and text overlays
- Add image upload dialogs for group avatar and banner in GroupCreationModal
- Implement navigation placeholders for mentions, hashtags, and URLs in sojornRichText
2026-02-17 13:32:58 -06:00

509 lines
14 KiB
Go

package testing
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// IntegrationTestSuite provides comprehensive testing for the Sojorn platform
type IntegrationTestSuite struct {
suite.Suite
db *pgxpool.Pool
router *gin.Engine
server *httptest.Server
testUser *TestUser
testGroup *TestGroup
testPost *TestPost
cleanup []func()
}
// TestUser represents a test user
type TestUser struct {
ID string `json:"id"`
Email string `json:"email"`
Handle string `json:"handle"`
Token string `json:"token"`
Password string `json:"password"`
}
// TestGroup represents a test group
type TestGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
IsPrivate bool `json:"is_private"`
}
// TestPost represents a test post
type TestPost struct {
ID string `json:"id"`
Body string `json:"body"`
AuthorID string `json:"author_id"`
ImageURL string `json:"image_url,omitempty"`
VideoURL string `json:"video_url,omitempty"`
Visibility string `json:"visibility"`
}
// TestConfig holds test configuration
type TestConfig struct {
DatabaseURL string
BaseURL string
TestTimeout time.Duration
}
// SetupSuite initializes the test suite
func (suite *IntegrationTestSuite) SetupSuite() {
config := suite.getTestConfig()
// Initialize database
db, err := pgxpool.New(context.Background(), config.DatabaseURL)
require.NoError(suite.T(), err)
suite.db = db
// Initialize router
suite.router = gin.New()
suite.setupRoutes()
// Start test server
suite.server = httptest.NewServer(suite.router)
// Create test data
suite.createTestData()
}
// TearDownSuite cleans up after tests
func (suite *IntegrationTestSuite) TearDownSuite() {
// Run cleanup functions
for _, cleanup := range suite.cleanup {
cleanup()
}
// Close database connection
if suite.db != nil {
suite.db.Close()
}
// Close test server
if suite.server != nil {
suite.server.Close()
}
}
// getTestConfig loads test configuration
func (suite *IntegrationTestSuite) getTestConfig() TestConfig {
return TestConfig{
DatabaseURL: os.Getenv("TEST_DATABASE_URL"),
BaseURL: "http://localhost:8080",
TestTimeout: 30 * time.Second,
}
}
// setupRoutes configures test routes
func (suite *IntegrationTestSuite) setupRoutes() {
// This would include all your API routes
// For now, we'll add basic health check
suite.router.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "healthy"})
})
// Add auth routes
suite.router.POST("/auth/register", suite.handleRegister)
suite.router.POST("/auth/login", suite.handleLogin)
// Add post routes
suite.router.GET("/posts", suite.handleGetPosts)
suite.router.POST("/posts", suite.handleCreatePost)
// Add group routes
suite.router.GET("/groups", suite.handleGetGroups)
suite.router.POST("/groups", suite.handleCreateGroup)
}
// createTestData sets up test data
func (suite *IntegrationTestSuite) createTestData() {
// Create test user
suite.testUser = &TestUser{
Email: "test@example.com",
Handle: "testuser",
Password: "testpassword123",
}
userResp := suite.makeRequest("POST", "/auth/register", suite.testUser)
require.Equal(suite.T(), 200, userResp.StatusCode)
var userResult struct {
User TestUser `json:"user"`
Token string `json:"token"`
}
json.NewDecoder(userResp.Body).Decode(&userResult)
suite.testUser = &userResult.User
suite.testUser.Token = userResult.Token
// Create test group
suite.testGroup = &TestGroup{
Name: "Test Group",
Description: "A group for testing",
Category: "general",
IsPrivate: false,
}
groupResp := suite.makeAuthenticatedRequest("POST", "/groups", suite.testGroup)
require.Equal(suite.T(), 200, groupResp.StatusCode)
json.NewDecoder(groupResp.Body).Decode(&suite.testGroup)
// Create test post
suite.testPost = &TestPost{
Body: "This is a test post",
AuthorID: suite.testUser.ID,
Visibility: "public",
}
postResp := suite.makeAuthenticatedRequest("POST", "/posts", suite.testPost)
require.Equal(suite.T(), 200, postResp.StatusCode)
json.NewDecoder(postResp.Body).Decode(&suite.testPost)
}
// makeRequest makes an HTTP request
func (suite *IntegrationTestSuite) makeRequest(method, path string, body interface{}) *http.Response {
var reqBody *bytes.Buffer
if body != nil {
jsonBody, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(jsonBody)
} else {
reqBody = bytes.NewBuffer(nil)
}
req, _ := http.NewRequest(method, suite.server.URL+path, reqBody)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
resp, _ := client.Do(req)
return resp
}
// makeAuthenticatedRequest makes an authenticated HTTP request
func (suite *IntegrationTestSuite) makeAuthenticatedRequest(method, path string, body interface{}) *http.Response {
var reqBody *bytes.Buffer
if body != nil {
jsonBody, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(jsonBody)
} else {
reqBody = bytes.NewBuffer(nil)
}
req, _ := http.NewRequest(method, suite.server.URL+path, reqBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+suite.testUser.Token)
client := &http.Client{Timeout: 10 * time.Second}
resp, _ := client.Do(req)
return resp
}
// Test Authentication Flow
func (suite *IntegrationTestSuite) TestAuthenticationFlow() {
// Test user registration
newUser := TestUser{
Email: "newuser@example.com",
Handle: "newuser",
Password: "newpassword123",
}
resp := suite.makeRequest("POST", "/auth/register", newUser)
assert.Equal(suite.T(), 200, resp.StatusCode)
var registerResult struct {
User TestUser `json:"user"`
Token string `json:"token"`
}
json.NewDecoder(resp.Body).Decode(&registerResult)
assert.NotEmpty(suite.T(), registerResult.Token)
// Test user login
loginReq := map[string]string{
"email": newUser.Email,
"password": newUser.Password,
}
resp = suite.makeRequest("POST", "/auth/login", loginReq)
assert.Equal(suite.T(), 200, resp.StatusCode)
var loginResult struct {
User TestUser `json:"user"`
Token string `json:"token"`
}
json.NewDecoder(resp.Body).Decode(&loginResult)
assert.NotEmpty(suite.T(), loginResult.Token)
}
// Test Post Creation and Retrieval
func (suite *IntegrationTestSuite) TestPostOperations() {
// Test creating a post
newPost := TestPost{
Body: "This is a new test post",
AuthorID: suite.testUser.ID,
Visibility: "public",
}
resp := suite.makeAuthenticatedRequest("POST", "/posts", newPost)
assert.Equal(suite.T(), 200, resp.StatusCode)
var createdPost TestPost
json.NewDecoder(resp.Body).Decode(&createdPost)
assert.NotEmpty(suite.T(), createdPost.ID)
// Test retrieving posts
resp = suite.makeAuthenticatedRequest("GET", "/posts", nil)
assert.Equal(suite.T(), 200, resp.StatusCode)
var posts []TestPost
json.NewDecoder(resp.Body).Decode(&posts)
assert.Greater(suite.T(), len(posts), 0)
}
// Test Group Operations
func (suite *IntegrationTestSuite) TestGroupOperations() {
// Test creating a group
newGroup := TestGroup{
Name: "New Test Group",
Description: "Another test group",
Category: "hobby",
IsPrivate: false,
}
resp := suite.makeAuthenticatedRequest("POST", "/groups", newGroup)
assert.Equal(suite.T(), 200, resp.StatusCode)
var createdGroup TestGroup
json.NewDecoder(resp.Body).Decode(&createdGroup)
assert.NotEmpty(suite.T(), createdGroup.ID)
// Test retrieving groups
resp = suite.makeAuthenticatedRequest("GET", "/groups", nil)
assert.Equal(suite.T(), 200, resp.StatusCode)
var groups []TestGroup
json.NewDecoder(resp.Body).Decode(&groups)
assert.Greater(suite.T(), len(groups), 0)
}
// Test Feed Algorithm
func (suite *IntegrationTestSuite) TestFeedAlgorithm() {
// Create multiple posts with different engagement
posts := []TestPost{
{Body: "Popular post 1", AuthorID: suite.testUser.ID, Visibility: "public"},
{Body: "Popular post 2", AuthorID: suite.testUser.ID, Visibility: "public"},
{Body: "Regular post", AuthorID: suite.testUser.ID, Visibility: "public"},
}
for _, post := range posts {
resp := suite.makeAuthenticatedRequest("POST", "/posts", post)
assert.Equal(suite.T(), 200, resp.StatusCode)
}
// Test algorithmic feed
resp := suite.makeAuthenticatedRequest("GET", "/feed?algorithm=true", nil)
assert.Equal(suite.T(), 200, resp.StatusCode)
var feedPosts []TestPost
json.NewDecoder(resp.Body).Decode(&feedPosts)
assert.Greater(suite.T(), len(feedPosts), 0)
}
// Test E2EE Chat
func (suite *IntegrationTestSuite) TestE2EEChat() {
// Test device registration
deviceData := map[string]interface{}{
"name": "Test Device",
"type": "mobile",
"public_key": "test-public-key",
}
resp := suite.makeAuthenticatedRequest("POST", "/e2ee/register-device", deviceData)
assert.Equal(suite.T(), 200, resp.StatusCode)
// Test message encryption
messageData := map[string]interface{}{
"recipient_id": suite.testUser.ID,
"message": "Encrypted test message",
"encrypted": true,
}
resp = suite.makeAuthenticatedRequest("POST", "/e2ee/send-message", messageData)
assert.Equal(suite.T(), 200, resp.StatusCode)
}
// Test AI Moderation
func (suite *IntegrationTestSuite) TestAIModeration() {
// Test content moderation
contentData := map[string]interface{}{
"content": "This is safe content",
"type": "text",
}
resp := suite.makeAuthenticatedRequest("POST", "/moderation/analyze", contentData)
assert.Equal(suite.T(), 200, resp.StatusCode)
var moderationResult struct {
IsSafe bool `json:"is_safe"`
Score float64 `json:"score"`
}
json.NewDecoder(resp.Body).Decode(&moderationResult)
assert.True(suite.T(), moderationResult.IsSafe)
}
// Test Video Processing
func (suite *IntegrationTestSuite) TestVideoProcessing() {
// Test video upload and processing
videoData := map[string]interface{}{
"title": "Test Video",
"description": "A test video for processing",
"duration": 30,
}
resp := suite.makeAuthenticatedRequest("POST", "/videos/upload", videoData)
assert.Equal(suite.T(), 200, resp.StatusCode)
// Test video processing status
resp = suite.makeAuthenticatedRequest("GET", "/videos/processing-status", nil)
assert.Equal(suite.T(), 200, resp.StatusCode)
}
// Test Performance
func (suite *IntegrationTestSuite) TestPerformance() {
// Test API response times
start := time.Now()
resp := suite.makeAuthenticatedRequest("GET", "/posts", nil)
duration := time.Since(start)
assert.Equal(suite.T(), 200, resp.StatusCode)
assert.Less(suite.T(), duration, 1*time.Second, "API response time should be under 1 second")
// Test concurrent requests
concurrentRequests := 10
done := make(chan bool, concurrentRequests)
for i := 0; i < concurrentRequests; i++ {
go func() {
resp := suite.makeAuthenticatedRequest("GET", "/posts", nil)
assert.Equal(suite.T(), 200, resp.StatusCode)
done <- true
}()
}
// Wait for all requests to complete
for i := 0; i < concurrentRequests; i++ {
<-done
}
}
// Test Security
func (suite *IntegrationTestSuite) TestSecurity() {
// Test unauthorized access
resp := suite.makeRequest("GET", "/posts", nil)
assert.Equal(suite.T(), 401, resp.StatusCode)
// Test invalid token
resp = suite.makeRequestWithAuth("GET", "/posts", nil, "invalid-token")
assert.Equal(suite.T(), 401, resp.StatusCode)
// Test SQL injection protection
maliciousInput := "'; DROP TABLE users; --"
resp = suite.makeAuthenticatedRequest("GET", "/posts?search="+maliciousInput, nil)
assert.Equal(suite.T(), 200, resp.StatusCode) // Should not crash
}
// makeRequestWithAuth makes a request with custom auth token
func (suite *IntegrationTestSuite) makeRequestWithAuth(method, path string, body interface{}, token string) *http.Response {
var reqBody *bytes.Buffer
if body != nil {
jsonBody, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(jsonBody)
} else {
reqBody = bytes.NewBuffer(nil)
}
req, _ := http.NewRequest(method, suite.server.URL+path, reqBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, _ := client.Do(req)
return resp
}
// Mock handlers for testing
func (suite *IntegrationTestSuite) handleRegister(c *gin.Context) {
var user TestUser
c.ShouldBindJSON(&user)
user.ID = "test-user-id"
c.JSON(200, gin.H{"user": user, "token": "test-token"})
}
func (suite *IntegrationTestSuite) handleLogin(c *gin.Context) {
var loginReq map[string]string
c.ShouldBindJSON(&loginReq)
user := TestUser{
ID: "test-user-id",
Email: loginReq["email"],
Handle: "testuser",
}
c.JSON(200, gin.H{"user": user, "token": "test-token"})
}
func (suite *IntegrationTestSuite) handleGetPosts(c *gin.Context) {
posts := []TestPost{
{ID: "1", Body: "Test post 1", AuthorID: "test-user-id"},
{ID: "2", Body: "Test post 2", AuthorID: "test-user-id"},
}
c.JSON(200, posts)
}
func (suite *IntegrationTestSuite) handleCreatePost(c *gin.Context) {
var post TestPost
c.ShouldBindJSON(&post)
post.ID = "new-post-id"
c.JSON(200, post)
}
func (suite *IntegrationTestSuite) handleGetGroups(c *gin.Context) {
groups := []TestGroup{
{ID: "1", Name: "Test Group 1", Category: "general"},
{ID: "2", Name: "Test Group 2", Category: "hobby"},
}
c.JSON(200, groups)
}
func (suite *IntegrationTestSuite) handleCreateGroup(c *gin.Context) {
var group TestGroup
c.ShouldBindJSON(&group)
group.ID = "new-group-id"
c.JSON(200, group)
}
// RunIntegrationTests runs the complete integration test suite
func RunIntegrationTests(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}