api/tests/Feature/ModerationReportsTest.php

136 lines
4.7 KiB
PHP

<?php
use App\Enums\ReportReason;
use App\Enums\UserRole;
use App\Models\MealPosts;
use App\Models\ModerationCase;
use App\Models\Report;
use App\Models\User;
use App\Notifications\ModerationThresholdReached;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Laravel\Sanctum\Sanctum;
uses(RefreshDatabase::class);
it('creates a report and a moderation case for a meal post', function () {
$reporter = User::factory()->create();
$mealPost = MealPosts::factory()->create();
Sanctum::actingAs($reporter);
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", [
'reason' => ReportReason::SPAM->value,
'details' => 'Même image publiée en boucle.',
])
->assertCreated()
->assertJsonPath('message', __('api.reports.created'))
->assertJsonPath('data.reason', ReportReason::SPAM->value)
->assertJsonPath('data.reportableType', 'meal_post')
->assertJsonPath('data.reportableId', $mealPost->id);
$this->assertDatabaseHas('reports', [
'reporter_id' => $reporter->id,
'reportable_type' => MealPosts::class,
'reportable_id' => $mealPost->id,
'reason' => ReportReason::SPAM->value,
'details' => 'Même image publiée en boucle.',
]);
$this->assertDatabaseHas('moderation_cases', [
'caseable_type' => MealPosts::class,
'caseable_id' => $mealPost->id,
'reports_count' => 1,
'threshold' => 3,
]);
});
it('prevents duplicate reports and reports on own content', function () {
$owner = User::factory()->create();
$mealPost = MealPosts::factory()->for($owner, 'user')->create();
$reporter = User::factory()->create();
Sanctum::actingAs($reporter);
$payload = ['reason' => ReportReason::OTHER->value];
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", $payload)
->assertCreated();
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", $payload)
->assertConflict()
->assertJsonPath('message', __('api.reports.already_exists'));
expect(Report::query()->count())->toBe(1);
Sanctum::actingAs($owner);
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", $payload)
->assertUnprocessable()
->assertJsonPath('message', __('api.reports.cannot_report_own_meal'));
});
it('reports users and prevents reporting own profile', function () {
$target = User::factory()->create();
$reporter = User::factory()->create();
Sanctum::actingAs($reporter);
$this->postJson("/api/users/{$target->id}/reports", [
'reason' => ReportReason::HARASSMENT->value,
])
->assertCreated()
->assertJsonPath('data.reportableType', 'user')
->assertJsonPath('data.reportableId', $target->id);
$this->assertDatabaseHas('moderation_cases', [
'caseable_type' => User::class,
'caseable_id' => $target->id,
'threshold' => 5,
]);
Sanctum::actingAs($target);
$this->postJson("/api/users/{$target->id}/reports", [
'reason' => ReportReason::OTHER->value,
])
->assertUnprocessable()
->assertJsonPath('message', __('api.reports.cannot_report_self'));
});
it('notifies moderators once when a report threshold is reached', function () {
Notification::fake();
$admin = User::factory()->create(['role' => UserRole::ADMIN]);
$moderator = User::factory()->create(['role' => UserRole::MODERATOR]);
$regularUser = User::factory()->create();
$mealPost = MealPosts::factory()->create();
foreach (User::factory()->count(3)->create() as $reporter) {
Sanctum::actingAs($reporter);
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", [
'reason' => ReportReason::INAPPROPRIATE->value,
])->assertCreated();
}
$moderationCase = ModerationCase::query()->where('caseable_id', $mealPost->id)->firstOrFail();
expect($moderationCase->reports_count)->toBe(3)
->and($moderationCase->threshold_notified_at)->not->toBeNull();
Notification::assertSentTo($admin, ModerationThresholdReached::class);
Notification::assertSentTo($moderator, ModerationThresholdReached::class);
Notification::assertNotSentTo($regularUser, ModerationThresholdReached::class);
Notification::assertSentTimes(ModerationThresholdReached::class, 2);
expect((new ModerationThresholdReached($moderationCase))->via($admin))->toBe(['database']);
Sanctum::actingAs(User::factory()->create());
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", [
'reason' => ReportReason::MISLEADING->value,
])->assertCreated();
Notification::assertSentTimes(ModerationThresholdReached::class, 2);
});