create(); $mealPost = MealPosts::factory()->create(); Sanctum::actingAs($user); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 5, 'comment' => 'Très bon repas.', ]) ->assertCreated() ->assertJsonPath('data.rating', 5) ->assertJsonPath('data.comment', 'Très bon repas.') ->assertJsonPath('data.meal_post_id', $mealPost->id) ->assertJsonPath('data.user_id', $user->id); $this->assertDatabaseHas('post_reviews', [ 'meal_post_id' => $mealPost->id, 'user_id' => $user->id, 'rating' => 5, 'comment' => 'Très bon repas.', ]); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 4, ]) ->assertConflict() ->assertJsonPath('message', __('api.post_reviews.already_exists')); expect(PostReviews::query()->count())->toBe(1); }); it('sends a push notification to the meal owner when another user comments on their meal', function () { Http::fake([ 'https://exp.host/*' => Http::response([ 'data' => [ ['status' => 'ok', 'id' => 'ticket-one'], ], ]), ]); $owner = User::factory()->create(); $owner->deviceTokens()->create([ 'expo_push_token' => 'ExponentPushToken[owner]', 'platform' => 'ios', ]); $reviewer = User::factory()->create(['name' => 'Alex']); $mealPost = MealPosts::factory() ->for($owner, 'user') ->create(['title' => 'Recovery bowl']); Sanctum::actingAs($reviewer); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 5, 'comment' => 'Très bon repas.', ])->assertCreated(); Http::assertSent(fn (Request $request) => $request->url() === 'https://exp.host/--/api/v2/push/send' && $request->data() === [ [ 'to' => 'ExponentPushToken[owner]', 'title' => 'New comment', 'body' => 'Alex commented on your meal "Recovery bowl".', 'sound' => 'default', 'channelId' => 'default', 'data' => [ 'url' => "bowly://meals/{$mealPost->id}", ], ], ]); }); it('does not send a push notification for rating-only reviews', function () { Http::fake(); $owner = User::factory()->create(); $owner->deviceTokens()->create([ 'expo_push_token' => 'ExponentPushToken[owner]', 'platform' => 'ios', ]); $reviewer = User::factory()->create(); $mealPost = MealPosts::factory()->for($owner, 'user')->create(); Sanctum::actingAs($reviewer); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 5, ])->assertCreated(); Http::assertNothingSent(); }); it('does not notify the owner when they comment on their own meal', function () { Http::fake(); $owner = User::factory()->create(); $owner->deviceTokens()->create([ 'expo_push_token' => 'ExponentPushToken[owner]', 'platform' => 'ios', ]); $mealPost = MealPosts::factory()->for($owner, 'user')->create(); Sanctum::actingAs($owner); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 5, 'comment' => 'Note personnelle.', ])->assertCreated(); Http::assertNothingSent(); }); it('lists reviews for a single meal post', function () { $user = User::factory()->create(); $mealPost = MealPosts::factory()->create(); $otherMealPost = MealPosts::factory()->create(); $firstReview = PostReviews::query()->create([ 'meal_post_id' => $mealPost->id, 'user_id' => User::factory()->create()->id, 'rating' => 4, 'comment' => 'Solide.', ]); $secondReview = PostReviews::query()->create([ 'meal_post_id' => $mealPost->id, 'user_id' => User::factory()->create()->id, 'rating' => 5, 'comment' => null, ]); $otherReview = PostReviews::query()->create([ 'meal_post_id' => $otherMealPost->id, 'user_id' => User::factory()->create()->id, 'rating' => 1, 'comment' => 'Pas celui-ci.', ]); Sanctum::actingAs($user); $this->getJson("/api/meal-posts/{$mealPost->id}/reviews") ->assertOk() ->assertJsonCount(2, 'data') ->assertJsonFragment(['id' => $firstReview->id]) ->assertJsonFragment(['id' => $secondReview->id]) ->assertJsonMissing(['id' => $otherReview->id]); }); it('shows the authenticated users review for a meal post', function () { $user = User::factory()->create(); $mealPost = MealPosts::factory()->create(); $review = PostReviews::query()->create([ 'meal_post_id' => $mealPost->id, 'user_id' => $user->id, 'rating' => 4, 'comment' => 'Mon avis.', ]); PostReviews::query()->create([ 'meal_post_id' => $mealPost->id, 'user_id' => User::factory()->create()->id, 'rating' => 5, 'comment' => 'Un autre avis.', ]); Sanctum::actingAs($user); $this->getJson("/api/meal-posts/{$mealPost->id}/my-review") ->assertOk() ->assertJsonPath('data.id', $review->id) ->assertJsonPath('data.user_id', $user->id); }); it('returns null when the authenticated user has not reviewed a meal post', function () { $user = User::factory()->create(); $mealPost = MealPosts::factory()->create(); Sanctum::actingAs($user); $this->getJson("/api/meal-posts/{$mealPost->id}/my-review") ->assertOk() ->assertJsonPath('data', null); }); it('lets the review author update and delete their review', function () { $user = User::factory()->create(); $review = PostReviews::query()->create([ 'meal_post_id' => MealPosts::factory()->create()->id, 'user_id' => $user->id, 'rating' => 3, 'comment' => 'Initial.', ]); Sanctum::actingAs($user); $this->patchJson("/api/post-reviews/{$review->id}", [ 'rating' => 4, 'comment' => null, ]) ->assertOk() ->assertJsonPath('data.rating', 4) ->assertJsonPath('data.comment', null); $this->assertDatabaseHas('post_reviews', [ 'id' => $review->id, 'rating' => 4, 'comment' => null, ]); $this->deleteJson("/api/post-reviews/{$review->id}") ->assertNoContent(); $this->assertDatabaseMissing('post_reviews', [ 'id' => $review->id, ]); }); it('does not let another user mutate a review', function () { $review = PostReviews::query()->create([ 'meal_post_id' => MealPosts::factory()->create()->id, 'user_id' => User::factory()->create()->id, 'rating' => 3, 'comment' => 'Initial.', ]); Sanctum::actingAs(User::factory()->create()); $this->patchJson("/api/post-reviews/{$review->id}", [ 'rating' => 4, ])->assertNotFound(); $this->deleteJson("/api/post-reviews/{$review->id}") ->assertNotFound(); $this->assertDatabaseHas('post_reviews', [ 'id' => $review->id, 'rating' => 3, ]); }); it('requires authentication to create a review', function () { $mealPost = MealPosts::factory()->create(); $this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [ 'rating' => 5, ])->assertUnauthorized(); });