sojorn/go-backend/internal/services/moderation_service_test.go
2026-02-15 00:33:24 -06:00

297 lines
7.2 KiB
Go

package services
import (
"context"
"testing"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockModerationService tests the AI moderation functionality
func TestModerationService_AnalyzeContent(t *testing.T) {
// Test with mock service (no API keys for testing)
pool := &pgxpool.Pool{} // Mock pool
service := NewModerationService(pool, "", "")
ctx := context.Background()
tests := []struct {
name string
content string
mediaURLs []string
wantReason string
wantHate float64
wantGreed float64
wantDelusion float64
}{
{
name: "Clean content",
content: "Hello world, how are you today?",
mediaURLs: []string{},
wantReason: "",
wantHate: 0.0,
wantGreed: 0.0,
wantDelusion: 0.0,
},
{
name: "Hate content",
content: "I hate everyone and want to attack them",
mediaURLs: []string{},
wantReason: "hate",
wantHate: 0.0, // Will be 0 without OpenAI API
wantGreed: 0.0,
wantDelusion: 0.0,
},
{
name: "Greed content",
content: "Get rich quick with crypto investment guaranteed returns",
mediaURLs: []string{},
wantReason: "greed",
wantHate: 0.0,
wantGreed: 0.7, // Keyword-based detection
wantDelusion: 0.0,
},
{
name: "Delusion content",
content: "Fake news conspiracy theories about truth",
mediaURLs: []string{},
wantReason: "delusion",
wantHate: 0.0,
wantGreed: 0.0,
wantDelusion: 0.0, // Will be 0 without OpenAI API
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
score, reason, err := service.AnalyzeContent(ctx, tt.content, tt.mediaURLs)
assert.NoError(t, err)
assert.Equal(t, tt.wantReason, reason)
assert.Equal(t, tt.wantHate, score.Hate)
assert.Equal(t, tt.wantGreed, score.Greed)
assert.Equal(t, tt.wantDelusion, score.Delusion)
})
}
}
func TestModerationService_KeywordDetection(t *testing.T) {
pool := &pgxpool.Pool{} // Mock pool
service := NewModerationService(pool, "", "")
ctx := context.Background()
// Test keyword-based greed detection
score, reason, err := service.AnalyzeContent(ctx, "Buy now get rich quick crypto scam", []string{})
assert.NoError(t, err)
assert.Equal(t, "greed", reason)
assert.Greater(t, score.Greed, 0.5)
}
func TestModerationService_ImageURLDetection(t *testing.T) {
// Test the isImageURL helper function
tests := []struct {
url string
expected bool
}{
{"https://example.com/image.jpg", true},
{"https://example.com/image.jpeg", true},
{"https://example.com/image.png", true},
{"https://example.com/image.gif", true},
{"https://example.com/image.webp", true},
{"https://example.com/video.mp4", false},
{"https://example.com/document.pdf", false},
{"https://example.com/", false},
}
for _, tt := range tests {
t.Run(tt.url, func(t *testing.T) {
result := isImageURL(tt.url)
assert.Equal(t, tt.expected, result)
})
}
}
func TestModerationService_VisionScoreConversion(t *testing.T) {
pool := &pgxpool.Pool{} // Mock pool
service := NewModerationService(pool, "", "")
tests := []struct {
name string
safeSearch GoogleVisionSafeSearch
expectedHate float64
expectedDelusion float64
}{
{
name: "Clean image",
safeSearch: GoogleVisionSafeSearch{
Adult: "UNLIKELY",
Violence: "UNLIKELY",
Racy: "UNLIKELY",
},
expectedHate: 0.3,
expectedDelusion: 0.3,
},
{
name: "Violent image",
safeSearch: GoogleVisionSafeSearch{
Adult: "UNLIKELY",
Violence: "VERY_LIKELY",
Racy: "UNLIKELY",
},
expectedHate: 0.9,
expectedDelusion: 0.3,
},
{
name: "Adult content",
safeSearch: GoogleVisionSafeSearch{
Adult: "VERY_LIKELY",
Violence: "UNLIKELY",
Racy: "UNLIKELY",
},
expectedHate: 0.9,
expectedDelusion: 0.3,
},
{
name: "Racy content",
safeSearch: GoogleVisionSafeSearch{
Adult: "UNLIKELY",
Violence: "UNLIKELY",
Racy: "VERY_LIKELY",
},
expectedHate: 0.3,
expectedDelusion: 0.9,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
score := service.convertVisionScore(tt.safeSearch)
assert.Equal(t, tt.expectedHate, score.Hate)
assert.Equal(t, tt.expectedDelusion, score.Delusion)
})
}
}
func TestThreePoisonsScore_Max(t *testing.T) {
tests := []struct {
name string
values []float64
expected float64
}{
{
name: "Single value",
values: []float64{0.5},
expected: 0.5,
},
{
name: "Multiple values",
values: []float64{0.1, 0.7, 0.3},
expected: 0.7,
},
{
name: "All zeros",
values: []float64{0.0, 0.0, 0.0},
expected: 0.0,
},
{
name: "Empty slice",
values: []float64{},
expected: 0.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := max(tt.values...)
assert.Equal(t, tt.expected, result)
})
}
}
// Integration test example (requires actual database and API keys)
func TestModerationService_Integration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
// This test requires:
// 1. A real database connection
// 2. OpenAI and Google Vision API keys
// 3. Proper test environment setup
t.Skip("Integration test requires database and API keys setup")
// Example structure for integration test:
/*
ctx := context.Background()
// Setup test database
pool := setupTestDB(t)
defer cleanupTestDB(t, pool)
// Setup service with real API keys
service := NewModerationService(pool, "test-openai-key", "test-google-key")
// Test actual content analysis
score, reason, err := service.AnalyzeContent(ctx, "Test content", []string{})
assert.NoError(t, err)
assert.NotNil(t, score)
// Test database operations
postID := uuid.New()
err = service.FlagPost(ctx, postID, score, reason)
assert.NoError(t, err)
// Verify flag was created
flags, err := service.GetPendingFlags(ctx, 10, 0)
assert.NoError(t, err)
assert.Len(t, flags, 1)
*/
}
// Benchmark tests
func BenchmarkModerationService_AnalyzeContent(b *testing.B) {
pool := &pgxpool.Pool{} // Mock pool
service := NewModerationService(pool, "", "")
ctx := context.Background()
content := "This is a test post with some content to analyze"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = service.AnalyzeContent(ctx, content, []string{})
}
}
func BenchmarkModerationService_KeywordDetection(b *testing.B) {
pool := &pgxpool.Pool{} // Mock pool
service := NewModerationService(pool, "", "")
ctx := context.Background()
content := "Buy crypto get rich quick investment scam"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = service.AnalyzeContent(ctx, content, []string{})
}
}
// Helper function to setup test database (for integration tests)
func setupTestDB(t *testing.T) *pgxpool.Pool {
// This would setup a test database connection
// Implementation depends on your test environment
t.Helper()
return nil
}
// Helper function to cleanup test database (for integration tests)
func cleanupTestDB(t *testing.T, pool *pgxpool.Pool) {
// This would cleanup the test database
// Implementation depends on your test environment
t.Helper()
}