Files
Pham-Tan-Thien 2045c856e9 feat: complete Smartgift AI Laravel unit testing toolkit
- Add 10 core testing guides (setup, AAA principles, PHPUnit/Pest, mocking, anti-patterns, PHP 8.4+, debugging, data factories, advanced edge cases)
- Add 13 component testing guides covering all Laravel layers
- Add standard templates for PHPUnit 10/11 and Pest v2/v3 including ArchitectureTest
- Add Docker smart test runner, dependency installer, and CI/CD templates (GitHub Actions, GitLab CI)
- Add 4 Claude Code skills (laravel-test-setup, laravel-unit-test with --all mode, laravel-test-review, laravel-mock-helper)
- Add single unified installer script (sg-install-unit-testing.sh) with global support, CLI runners, and 1-click updater (sg-test-update)
- Add comprehensive documentation (README.md, USAGE-GUIDE.md)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 16:41:37 +07:00

2.9 KiB

Component Guide: Policies & Authorization Gates

Policies kiểm soát quyền hạn truy cập của người dùng đối với từng hành động trên Model (view, create, update, delete).


1. Bản Chất Kiểm Thử Policy

  • Policy methods là các hàm thuần túy nhận User và Model (hoặc chỉ User) và trả về bool (hoặc Response::allow() / Response::deny()).
  • Ưu điểm lớn: Policy có thể được kiểm thử đơn vị hoàn toàn 100% trong bộ nhớ (In-Memory) mà không cần kết nối database hay framework gate pipeline.

2. Mã Mẫu Chuẩn: Policy Unit Test

Policy Nguồn: App\Policies\ArticlePolicy

namespace App\Policies;

use App\Models\Article;
use App\Models\User;

class ArticlePolicy
{
    public function update(User $user, Article $article): bool
    {
        // Admin được sửa tất cả bài viết
        if ($user->is_admin) {
            return true;
        }

        // Tác giả chỉ được sửa bài của mình khi bài chưa publish
        return $user->id === $article->user_id && !$article->is_published;
    }

    public function delete(User $user, Article $article): bool
    {
        // Chỉ admin hoặc tác giả mới được xóa
        return $user->is_admin || $user->id === $article->user_id;
    }
}

Test Chuẩn: PHPUnit 10+

namespace Tests\Unit\Policies;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
use App\Policies\ArticlePolicy;
use App\Models\Article;
use App\Models\User;

class ArticlePolicyTest extends TestCase
{
    private ArticlePolicy $policy;

    protected function setUp(): void
    {
        parent::setUp();
        $this->policy = new ArticlePolicy();
    }

    #[Test]
    public function admin_can_update_any_article_even_if_published(): void
    {
        $admin = new User(['id' => 99, 'is_admin' => true]);
        $article = new Article(['user_id' => 1, 'is_published' => true]);

        $this->assertTrue($this->policy->update($admin, $article));
    }

    #[Test]
    public function author_can_update_own_article_if_not_published(): void
    {
        $author = new User(['id' => 10, 'is_admin' => false]);
        $article = new Article(['user_id' => 10, 'is_published' => false]);

        $this->assertTrue($this->policy->update($author, $article));
    }

    #[Test]
    public function author_cannot_update_own_article_once_published(): void
    {
        $author = new User(['id' => 10, 'is_admin' => false]);
        $article = new Article(['user_id' => 10, 'is_published' => true]);

        $this->assertFalse($this->policy->update($author, $article));
    }

    #[Test]
    public function non_author_cannot_update_others_article(): void
    {
        $otherUser = new User(['id' => 20, 'is_admin' => false]);
        $article = new Article(['user_id' => 10, 'is_published' => false]);

        $this->assertFalse($this->policy->update($otherUser, $article));
    }
}