Implement creator studio and upload updates
This commit is contained in:
208
tests/Unit/Moderation/ContentModerationServiceTest.php
Normal file
208
tests/Unit/Moderation/ContentModerationServiceTest.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\ModerationDomainStatus;
|
||||
use App\Enums\ModerationRuleType;
|
||||
use App\Enums\ModerationContentType;
|
||||
use App\Enums\ModerationSeverity;
|
||||
use App\Enums\ModerationStatus;
|
||||
use App\Models\ContentModerationDomain;
|
||||
use App\Models\ContentModerationFinding;
|
||||
use App\Models\ContentModerationRule;
|
||||
use App\Models\User;
|
||||
use App\Services\Moderation\ContentModerationService;
|
||||
use App\Services\Moderation\ContentModerationSourceService;
|
||||
use App\Services\Moderation\Rules\LinkPresenceRule;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
it('extracts explicit and www urls from content', function (): void {
|
||||
$rule = app(LinkPresenceRule::class);
|
||||
|
||||
$urls = $rule->extractUrls('Visit https://spam.example.com and www.short.test/offer now.');
|
||||
|
||||
expect($urls)->toContain('https://spam.example.com')
|
||||
->and($urls)->toContain('www.short.test/offer');
|
||||
});
|
||||
|
||||
it('detects suspicious keywords and domains with a queued status', function (): void {
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'Buy followers today at https://promo.pornsite.com right now',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 1]
|
||||
);
|
||||
|
||||
expect($result->score)->toBeGreaterThanOrEqual(110)
|
||||
->and(in_array($result->severity, [ModerationSeverity::High, ModerationSeverity::Critical], true))->toBeTrue()
|
||||
->and($result->matchedDomains)->toContain('promo.pornsite.com')
|
||||
->and($result->matchedKeywords)->toContain('buy followers');
|
||||
});
|
||||
|
||||
it('detects unicode obfuscation patterns', function (): void {
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'раypal giveaway click here',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 1]
|
||||
);
|
||||
|
||||
expect($result->score)->toBeGreaterThan(0)
|
||||
->and(collect($result->reasons)->implode(' '))->toContain('Unicode');
|
||||
});
|
||||
|
||||
it('produces stable hashes for semantically identical whitespace variants', function (): void {
|
||||
$service = app(ContentModerationService::class);
|
||||
|
||||
$first = $service->analyze("Visit my site\n\nnow", ['content_type' => 'artwork_comment', 'content_id' => 1]);
|
||||
$second = $service->analyze(' visit my site now ', ['content_type' => 'artwork_comment', 'content_id' => 2]);
|
||||
|
||||
expect($first->contentHash)->toBe($second->contentHash);
|
||||
});
|
||||
|
||||
it('detects keyword stuffing patterns', function (): void {
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'seo seo seo seo seo seo seo seo seo seo seo seo seo service service service service service cheap cheap cheap traffic traffic traffic',
|
||||
['content_type' => 'artwork_description', 'content_id' => 1]
|
||||
);
|
||||
|
||||
expect($result->score)->toBeGreaterThan(0)
|
||||
->and($result->matchedKeywords)->toContain('seo');
|
||||
});
|
||||
|
||||
it('maps score thresholds to the expected severities', function (): void {
|
||||
expect(ModerationSeverity::fromScore(0))->toBe(ModerationSeverity::Low)
|
||||
->and(ModerationSeverity::fromScore(30))->toBe(ModerationSeverity::Medium)
|
||||
->and(ModerationSeverity::fromScore(60))->toBe(ModerationSeverity::High)
|
||||
->and(ModerationSeverity::fromScore(90))->toBe(ModerationSeverity::Critical);
|
||||
});
|
||||
|
||||
it('applies db managed moderation rules alongside config rules', function (): void {
|
||||
ContentModerationRule::query()->create([
|
||||
'type' => ModerationRuleType::SuspiciousKeyword,
|
||||
'value' => 'rare promo blast',
|
||||
'enabled' => true,
|
||||
'weight' => 22,
|
||||
]);
|
||||
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'This rare promo blast just dropped.',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 1]
|
||||
);
|
||||
|
||||
expect($result->matchedKeywords)->toContain('rare promo blast')
|
||||
->and($result->score)->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('uses domain reputation to escalate blocked domains and auto hide recommendations', function (): void {
|
||||
ContentModerationDomain::query()->create([
|
||||
'domain' => 'campaign.spam.test',
|
||||
'status' => ModerationDomainStatus::Blocked,
|
||||
]);
|
||||
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'Buy followers now at https://campaign.spam.test and claim your giveaway',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 1]
|
||||
);
|
||||
|
||||
expect($result->matchedDomains)->toContain('campaign.spam.test')
|
||||
->and($result->autoHideRecommended)->toBeTrue()
|
||||
->and($result->score)->toBeGreaterThanOrEqual(95);
|
||||
});
|
||||
|
||||
it('applies user risk modifiers conservatively', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
ContentModerationFinding::query()->create([
|
||||
'content_type' => 'artwork_comment',
|
||||
'content_id' => 11,
|
||||
'user_id' => $user->id,
|
||||
'status' => ModerationStatus::ConfirmedSpam->value,
|
||||
'severity' => 'high',
|
||||
'score' => 85,
|
||||
'content_hash' => hash('sha256', 'a'),
|
||||
'scanner_version' => '2.0',
|
||||
'content_snapshot' => 'spam one',
|
||||
]);
|
||||
|
||||
ContentModerationFinding::query()->create([
|
||||
'content_type' => 'artwork_comment',
|
||||
'content_id' => 12,
|
||||
'user_id' => $user->id,
|
||||
'status' => ModerationStatus::ConfirmedSpam->value,
|
||||
'severity' => 'critical',
|
||||
'score' => 120,
|
||||
'content_hash' => hash('sha256', 'b'),
|
||||
'scanner_version' => '2.0',
|
||||
'content_snapshot' => 'spam two',
|
||||
]);
|
||||
|
||||
$base = app(ContentModerationService::class)->analyze(
|
||||
'Visit https://safe.example.test for more info',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 1]
|
||||
);
|
||||
|
||||
$risky = app(ContentModerationService::class)->analyze(
|
||||
'Visit https://safe.example.test for more info',
|
||||
['content_type' => 'artwork_comment', 'content_id' => 2, 'user_id' => $user->id]
|
||||
);
|
||||
|
||||
expect($risky->score)->toBeGreaterThan($base->score)
|
||||
->and($risky->userRiskScore)->toBeGreaterThan(0)
|
||||
->and($risky->ruleHits)->toHaveKey('user_risk_modifier');
|
||||
});
|
||||
|
||||
it('applies strict seo policy and v3 assistive fields for link-heavy profile content', function (): void {
|
||||
$result = app(ContentModerationService::class)->analyze(
|
||||
'Visit https://promo.cluster-test.com now for a limited time offer and boost your traffic',
|
||||
[
|
||||
'content_type' => ModerationContentType::UserProfileLink->value,
|
||||
'content_id' => 77,
|
||||
'content_target_type' => 'user_social_link',
|
||||
'content_target_id' => 77,
|
||||
'is_publicly_exposed' => true,
|
||||
]
|
||||
);
|
||||
|
||||
expect($result->policyName)->toBe('strict_seo_protection')
|
||||
->and($result->status)->toBe(ModerationStatus::Pending)
|
||||
->and($result->priorityScore)->toBeGreaterThan($result->score)
|
||||
->and(in_array($result->reviewBucket, ['urgent', 'high'], true))->toBeTrue()
|
||||
->and($result->aiProvider)->toBe('heuristic_assist')
|
||||
->and($result->aiLabel)->not->toBeNull()
|
||||
->and($result->contentTargetType)->toBe('user_social_link')
|
||||
->and($result->contentTargetId)->toBe(77)
|
||||
->and($result->scoreBreakdown)->not->toBeEmpty();
|
||||
});
|
||||
|
||||
it('builds v3 moderation source context for profile links and card text', function (): void {
|
||||
$service = app(ContentModerationSourceService::class);
|
||||
|
||||
$profileLinkContext = $service->buildContext(ModerationContentType::UserProfileLink, (object) [
|
||||
'id' => 14,
|
||||
'user_id' => 5,
|
||||
'url' => 'https://promo.example.test',
|
||||
]);
|
||||
|
||||
$cardTextContext = $service->buildContext(ModerationContentType::CardText, (object) [
|
||||
'id' => 9,
|
||||
'user_id' => 3,
|
||||
'quote_text' => 'Promo headline',
|
||||
'description' => 'Additional landing page copy',
|
||||
'quote_author' => 'Campaign Bot',
|
||||
'quote_source' => 'promo.example.test',
|
||||
'visibility' => 'public',
|
||||
]);
|
||||
|
||||
expect($profileLinkContext['content_type'])->toBe(ModerationContentType::UserProfileLink->value)
|
||||
->and($profileLinkContext['content_target_type'])->toBe('user_social_link')
|
||||
->and($profileLinkContext['content_target_id'])->toBe(14)
|
||||
->and($profileLinkContext['user_id'])->toBe(5)
|
||||
->and($profileLinkContext['is_publicly_exposed'])->toBeTrue()
|
||||
->and($cardTextContext['content_type'])->toBe(ModerationContentType::CardText->value)
|
||||
->and($cardTextContext['content_target_type'])->toBe('nova_card')
|
||||
->and($cardTextContext['content_target_id'])->toBe(9)
|
||||
->and($cardTextContext['user_id'])->toBe(3)
|
||||
->and($cardTextContext['is_publicly_exposed'])->toBeTrue()
|
||||
->and($cardTextContext['content_snapshot'])->toContain('Promo headline')
|
||||
->and($cardTextContext['content_snapshot'])->toContain('promo.example.test');
|
||||
});
|
||||
Reference in New Issue
Block a user