169 lines
5.8 KiB
PHP
169 lines
5.8 KiB
PHP
<?php
|
|
|
|
use App\Enums\ReportReason;
|
|
use App\Enums\UserRole;
|
|
use App\Models\MealPosts;
|
|
use App\Models\ModerationCase;
|
|
use App\Models\PostReviews;
|
|
use App\Models\Report;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
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('reports post reviews and prevents reporting own review', function () {
|
|
$reviewAuthor = User::factory()->create();
|
|
$reporter = User::factory()->create();
|
|
$review = PostReviews::query()->create([
|
|
'meal_post_id' => MealPosts::factory()->create()->id,
|
|
'user_id' => $reviewAuthor->id,
|
|
'rating' => 2,
|
|
'comment' => 'Commentaire problématique.',
|
|
]);
|
|
|
|
Sanctum::actingAs($reporter);
|
|
|
|
$this->postJson("/api/post-reviews/{$review->id}/reports", [
|
|
'reason' => ReportReason::INAPPROPRIATE->value,
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.reportableType', 'post_review')
|
|
->assertJsonPath('data.reportableId', $review->id);
|
|
|
|
$this->assertDatabaseHas('moderation_cases', [
|
|
'caseable_type' => PostReviews::class,
|
|
'caseable_id' => $review->id,
|
|
'threshold' => 3,
|
|
]);
|
|
|
|
Sanctum::actingAs($reviewAuthor);
|
|
|
|
$this->postJson("/api/post-reviews/{$review->id}/reports", [
|
|
'reason' => ReportReason::OTHER->value,
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonPath('message', __('api.reports.cannot_report_own_review'));
|
|
});
|
|
|
|
it('notifies moderators once when a report threshold is reached', function () {
|
|
$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();
|
|
|
|
expect($admin->notifications)->toHaveCount(1)
|
|
->and($admin->notifications->first()->data['format'])->toBe('filament')
|
|
->and($admin->notifications->first()->data['title'])->toBe(__('api.notifications.moderation_threshold_title'))
|
|
->and($moderator->notifications)->toHaveCount(1)
|
|
->and($moderator->notifications->first()->data['format'])->toBe('filament')
|
|
->and($regularUser->notifications)->toHaveCount(0);
|
|
|
|
Sanctum::actingAs(User::factory()->create());
|
|
|
|
$this->postJson("/api/meal-posts/{$mealPost->id}/reports", [
|
|
'reason' => ReportReason::MISLEADING->value,
|
|
])->assertCreated();
|
|
|
|
expect($admin->fresh()->notifications)->toHaveCount(1)
|
|
->and($moderator->fresh()->notifications)->toHaveCount(1);
|
|
});
|