feat: add reciprocal user blocking
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\FollowStatus;
|
||||
use App\Models\MealPosts;
|
||||
use App\Models\PostReviews;
|
||||
use App\Models\User;
|
||||
use App\Models\UserBlock;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('blocks a user idempotently, removes follows, lists the block, and unblocks', function () {
|
||||
$blocker = User::factory()->create();
|
||||
$blocked = User::factory()->create();
|
||||
|
||||
$blocker->following()->attach($blocked, ['status' => FollowStatus::Accepted]);
|
||||
$blocked->following()->attach($blocker, ['status' => FollowStatus::Accepted]);
|
||||
|
||||
Sanctum::actingAs($blocker);
|
||||
|
||||
$this->postJson("/api/users/{$blocked->id}/block")
|
||||
->assertOk()
|
||||
->assertJsonPath('message', __('api.blocks.blocked'));
|
||||
|
||||
$this->postJson("/api/users/{$blocked->id}/block")->assertOk();
|
||||
|
||||
expect(UserBlock::query()->count())->toBe(1)
|
||||
->and($blocker->following()->count())->toBe(0)
|
||||
->and($blocker->followers()->count())->toBe(0);
|
||||
|
||||
$this->getJson('/api/me/blocked-users')
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data')
|
||||
->assertJsonPath('data.0.id', $blocked->id);
|
||||
|
||||
$this->deleteJson("/api/users/{$blocked->id}/block")->assertNoContent();
|
||||
|
||||
expect(UserBlock::query()->count())->toBe(0);
|
||||
});
|
||||
|
||||
it('does not allow a user to block themselves', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$this->postJson("/api/users/{$user->id}/block")
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('message', __('api.blocks.cannot_block_self'));
|
||||
});
|
||||
|
||||
it('requires authentication to manage blocks', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->getJson('/api/me/blocked-users')->assertUnauthorized();
|
||||
$this->postJson("/api/users/{$user->id}/block")->assertUnauthorized();
|
||||
$this->deleteJson("/api/users/{$user->id}/block")->assertUnauthorized();
|
||||
});
|
||||
|
||||
it('hides blocked users and prevents reciprocal content interactions', function () {
|
||||
$viewer = User::factory()->create();
|
||||
$blocked = User::factory()->create();
|
||||
$neutral = User::factory()->create();
|
||||
$blockedMeal = MealPosts::factory()->for($blocked, 'user')->create();
|
||||
$neutralMeal = MealPosts::factory()->for($neutral, 'user')->create();
|
||||
$blockedReview = PostReviews::query()->create([
|
||||
'meal_post_id' => $neutralMeal->id,
|
||||
'user_id' => $blocked->id,
|
||||
'rating' => 4,
|
||||
'comment' => 'Avis masqué.',
|
||||
]);
|
||||
|
||||
UserBlock::factory()->create([
|
||||
'blocker_id' => $viewer->id,
|
||||
'blocked_id' => $blocked->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($viewer);
|
||||
|
||||
$this->getJson('/api/meal-posts')
|
||||
->assertOk()
|
||||
->assertJsonFragment(['id' => $neutralMeal->id])
|
||||
->assertJsonMissing(['id' => $blockedMeal->id]);
|
||||
|
||||
$this->getJson("/api/users/{$blocked->id}")->assertNotFound();
|
||||
$this->getJson("/api/meal-posts/{$blockedMeal->id}")->assertNotFound();
|
||||
$this->postJson("/api/users/{$blocked->id}/follow")->assertNotFound();
|
||||
$this->postJson("/api/meal-posts/{$blockedMeal->id}/like")->assertNotFound();
|
||||
$this->postJson("/api/meal-posts/{$blockedMeal->id}/reviews", [
|
||||
'rating' => 5,
|
||||
'comment' => 'Interaction interdite.',
|
||||
])->assertNotFound();
|
||||
|
||||
$this->getJson("/api/meal-posts/{$neutralMeal->id}/reviews")
|
||||
->assertOk()
|
||||
->assertJsonMissing(['id' => $blockedReview->id]);
|
||||
});
|
||||
|
||||
it('applies reciprocal exclusion when the other user initiated the block', function () {
|
||||
$viewer = User::factory()->create();
|
||||
$blocker = User::factory()->create();
|
||||
$meal = MealPosts::factory()->for($blocker, 'user')->create();
|
||||
|
||||
UserBlock::factory()->create([
|
||||
'blocker_id' => $blocker->id,
|
||||
'blocked_id' => $viewer->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($viewer);
|
||||
|
||||
$this->getJson('/api/meal-posts')
|
||||
->assertOk()
|
||||
->assertJsonMissing(['id' => $meal->id]);
|
||||
$this->getJson("/api/users/{$blocker->id}")->assertNotFound();
|
||||
$this->getJson("/api/meal-posts/{$meal->id}")->assertNotFound();
|
||||
$this->postJson("/api/users/{$blocker->id}/follow")->assertNotFound();
|
||||
});
|
||||
|
||||
it('keeps content hidden until both directional blocks are removed', function () {
|
||||
$firstUser = User::factory()->create();
|
||||
$secondUser = User::factory()->create();
|
||||
$meal = MealPosts::factory()->for($secondUser, 'user')->create();
|
||||
|
||||
UserBlock::factory()->create([
|
||||
'blocker_id' => $firstUser->id,
|
||||
'blocked_id' => $secondUser->id,
|
||||
]);
|
||||
UserBlock::factory()->create([
|
||||
'blocker_id' => $secondUser->id,
|
||||
'blocked_id' => $firstUser->id,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($firstUser);
|
||||
|
||||
$this->deleteJson("/api/users/{$secondUser->id}/block")->assertNoContent();
|
||||
$this->getJson("/api/meal-posts/{$meal->id}")->assertNotFound();
|
||||
|
||||
UserBlock::query()
|
||||
->where('blocker_id', $secondUser->id)
|
||||
->where('blocked_id', $firstUser->id)
|
||||
->delete();
|
||||
|
||||
$this->getJson("/api/meal-posts/{$meal->id}")->assertOk();
|
||||
});
|
||||
Reference in New Issue
Block a user