Помилки авторизації рідко ловлять «звичайні» тести: розробник перевіряє, що власник може виконати дію, а те, що чужий користувач не може, - не перевіряється. Тому авторизацію тестують окремо й системно.
1. Матриця доступу. Для кожного ресурсу - таблиця «роль × дія → очікуваний результат»:
| гість | власник | інший користувач | модератор | адмін іншої організації | |
|---|---|---|---|---|---|
| переглянути | 404 | 200 | 404 | 200 | 404 |
| змінити | 401 | 200 | 403 | 403 | 404 |
| видалити | 401 | 200 | 403 | 200 | 404 |
Матриця - і документація, і основа тестів.
2. Датасети в Pest - один тест на всю матрицю:
dataset('post access', [
'guest' => [fn () => null, 'update', 401],
'owner' => [fn () => Post::first()->author, 'update', 200],
'stranger' => [fn () => User::factory()->create(), 'update', 403],
]);
it('enforces access to posts', function (Closure $actor, string $action, int $status) {
$post = Post::factory()->create();
$user = $actor();
$response = ($user ? $this->actingAs($user) : $this)
->putJson("/api/posts/{$post->id}", ['title' => 'Новий заголовок']);
$response->assertStatus($status);
if ($status !== 200) {
expect($post->fresh()->title)->not->toBe('Новий заголовок'); // стан не змінився
}
})->with('post access');
Перевіряти не лише статус, а й відсутність змін у базі: буває, що відповідь 403, а дія вже виконана.
3. Тест «кожен маршрут захищено». Пройтися по Route::getRoutes() і переконатися, що кожен маршрут має middleware auth чи явно позначений як публічний. Новий маршрут без авторизації ламає тест одразу:
it('protects every route', function () {
$public = ['login', 'register', 'password.request', 'home'];
collect(Route::getRoutes())
->reject(fn ($route) => in_array($route->getName(), $public, true))
->each(fn ($route) => expect($route->gatherMiddleware())->toContain('auth'));
});
4. Тести політик напряму - швидкі модульні тести для складних правил ($user->can('update', $post)), окремо від HTTP.
5. Багатоорендність: для кожної моделі - тест «орендар A не бачить даних орендаря B» на всіх шляхах: список, деталі, пошук, експорт, фільтри.
6. Ручне й автоматизоване тестування на продакшен-подібному середовищі: два облікові записи в браузері (чи розширення на кшталт Autorize для Burp), що повторюють запити одного користувача від імені іншого.
Процес: новий ендпойнт без тестів на заборону не проходить рев'ю коду - це найдешевший спосіб не допустити IDOR.