create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); ArtworkComment::factory()->create([ 'artwork_id' => $artwork->id, 'user_id' => $artwork->user_id, 'content' => 'Visit my site at https://promo.pornsite.com now', 'raw_content' => 'Visit my site at https://promo.pornsite.com now', ]); $code = Artisan::call('skinbase:scan-content-moderation'); expect($code)->toBe(0) ->and(ContentModerationFinding::query()->count())->toBe(2) ->and(ContentModerationFinding::query()->where('content_type', ModerationContentType::ArtworkComment)->exists())->toBeTrue() ->and(ContentModerationFinding::query()->where('content_type', ModerationContentType::ArtworkDescription)->exists())->toBeTrue(); }); it('does not create duplicate findings for unchanged content', function (): void { $artwork = Artwork::factory()->create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); Artisan::call('skinbase:scan-content-moderation', ['--only' => 'descriptions']); Artisan::call('skinbase:scan-content-moderation', ['--only' => 'descriptions']); expect(ContentModerationFinding::query()->where('content_type', ModerationContentType::ArtworkDescription)->count())->toBe(1) ->and(ContentModerationFinding::query()->first()?->content_id)->toBe($artwork->id); }); it('supports dry runs without persisting findings', function (): void { Artwork::factory()->create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); $code = Artisan::call('skinbase:scan-content-moderation', ['--dry-run' => true]); expect($code)->toBe(0) ->and(ContentModerationFinding::query()->count())->toBe(0); }); it('logs a command summary after scanning', function (): void { Event::fake([MessageLogged::class]); Artwork::factory()->create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); $code = Artisan::call('skinbase:scan-content-moderation', ['--only' => 'descriptions']); expect($code)->toBe(0); Event::assertDispatched(MessageLogged::class, function (MessageLogged $event): bool { return $event->level === 'info' && $event->message === 'Content moderation scan complete.' && ($event->context['targets'] ?? []) === ['artwork_description'] && ($event->context['counts']['scanned'] ?? 0) === 1 && ($event->context['counts']['flagged'] ?? 0) === 1; }); }); it('shows target progress and verbose finding details while scanning', function (): void { Artwork::factory()->create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); $code = Artisan::call('skinbase:scan-content-moderation', [ '--only' => 'descriptions', '--verbose' => true, ]); $output = Artisan::output(); expect($code)->toBe(0) ->and($output)->toContain('Starting content moderation scan...') ->and($output)->toContain('Scanning Artwork Description entries...') ->and($output)->toContain('[artwork_description #') ->and($output)->toContain('flagged') ->and($output)->toContain('Finished Artwork Description: scanned=1, flagged=1'); }); it('auto hides critical comment spam while keeping the finding', function (): void { $artwork = Artwork::factory()->create(); $comment = ArtworkComment::factory()->create([ 'artwork_id' => $artwork->id, 'user_id' => $artwork->user_id, 'content' => 'Buy followers now at https://promo.pornsite.com and claim your crypto giveaway', 'raw_content' => 'Buy followers now at https://promo.pornsite.com and claim your crypto giveaway', 'is_approved' => true, ]); $code = Artisan::call('skinbase:scan-content-moderation', ['--only' => 'comments']); $finding = ContentModerationFinding::query()->where('content_type', ModerationContentType::ArtworkComment)->first(); expect($code)->toBe(0) ->and($finding)->not->toBeNull() ->and($finding?->is_auto_hidden)->toBeTrue() ->and($finding?->action_taken)->toBe('auto_hide_comment') ->and($comment->fresh()->is_approved)->toBeFalse(); }); it('rescans existing findings with the latest rules', function (): void { $artwork = Artwork::factory()->create([ 'description' => 'Buy followers at https://promo.pornsite.com and win a crypto giveaway now', ]); $finding = ContentModerationFinding::query()->create([ 'content_type' => ModerationContentType::ArtworkDescription->value, 'content_id' => $artwork->id, 'artwork_id' => $artwork->id, 'user_id' => $artwork->user_id, 'status' => 'pending', 'severity' => 'high', 'score' => 90, 'content_hash' => hash('sha256', 'old-hash'), 'scanner_version' => '1.0', 'content_snapshot' => 'old snapshot', ]); $code = Artisan::call('skinbase:rescan-content-moderation', ['--only' => 'descriptions']); $scannerVersion = (string) config('content_moderation.scanner_version'); expect($code)->toBe(0) ->and(ContentModerationFinding::query()->where('content_type', ModerationContentType::ArtworkDescription)->where('content_id', $artwork->id)->where('scanner_version', $scannerVersion)->exists())->toBeTrue() ->and(ContentModerationActionLog::query()->where('action_type', 'rescan')->exists())->toBeTrue(); });