- 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
509 lines
14 KiB
Go
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(®isterResult)
|
|
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))
|
|
}
|