From a34d937bdec5c3cb2c743cd3f1bc445a1f4179ba Mon Sep 17 00:00:00 2001 From: Giulio Troccoli-Allard Date: Mon, 7 Oct 2024 08:27:46 +0100 Subject: [PATCH] Added roles and permissions --- .github/workflows/master.yml | 16 + .github/workflows/test.yml | 266 ++++ app/Events/ClubCreated.php | 15 + app/Events/CompetitionCreated.php | 15 + app/Events/DivisionCreated.php | 15 + app/Events/SeasonCreated.php | 15 + app/Events/TeamCreated.php | 15 + app/Helpers/PermissionsHelper.php | 132 ++ app/Helpers/RolesHelper.php | 128 ++ .../Permissions/CreateClubPermissions.php | 22 + .../CreateCompetitionPermissions.php | 22 + .../Permissions/CreateDivisionPermissions.php | 25 + app/Jobs/Permissions/CreatePermissions.php | 22 + .../Permissions/CreateSeasonPermissions.php | 22 + .../Permissions/CreateTeamPermissions.php | 21 + app/Jobs/Roles/CreateClubSecretaryRole.php | 16 + app/Jobs/Roles/CreateCompetitionAdminRole.php | 16 + app/Jobs/Roles/CreateDivisionAdminRole.php | 16 + app/Jobs/Roles/CreateRole.php | 18 + app/Jobs/Roles/CreateSeasonAdminRole.php | 16 + app/Jobs/Roles/CreateTeamSecretaryRole.php | 16 + app/Jobs/Roles/DeleteRole.php | 29 + app/Listeners/SetUpClubSecretary.php | 28 + app/Listeners/SetUpCompetitionAdmin.php | 38 + app/Listeners/SetUpDivisionAdmin.php | 55 + app/Listeners/SetUpSeasonAdmin.php | 28 + app/Listeners/SetUpTeamSecretary.php | 35 + app/Models/Club.php | 5 + app/Models/Competition.php | 5 + app/Models/Division.php | 5 + app/Models/Season.php | 5 + app/Models/Team.php | 5 + app/Models/User.php | 2 + composer.json | 5 +- composer.lock | 1311 ++++++++++++++--- ..._10_02_081916_create_permission_tables.php | 142 ++ phpunit.xml | 6 +- tests/Feature/Auth/AuthenticationTest.php | 105 +- tests/Feature/Auth/EmailVerificationTest.php | 72 +- .../Feature/Auth/PasswordConfirmationTest.php | 66 +- tests/Feature/Auth/PasswordResetTest.php | 105 +- tests/Feature/Auth/PasswordUpdateTest.php | 61 +- tests/Feature/Auth/RegistrationTest.php | 43 +- tests/Feature/ExampleTest.php | 19 - tests/Feature/ProfileTest.php | 132 +- .../Helpers/PermissionsHelperTest.php | 125 ++ tests/Integration/Helpers/RolesHelperTest.php | 204 +++ tests/Pest.php | 45 + tests/TestCase.php | 28 +- tests/Unit/CreatePermissionsJobsTest.php | 99 ++ tests/Unit/CreateRolesJobsTest.php | 77 + tests/Unit/ExampleTest.php | 16 - 52 files changed, 3201 insertions(+), 549 deletions(-) create mode 100644 .github/workflows/master.yml create mode 100644 .github/workflows/test.yml create mode 100644 app/Events/ClubCreated.php create mode 100644 app/Events/CompetitionCreated.php create mode 100644 app/Events/DivisionCreated.php create mode 100644 app/Events/SeasonCreated.php create mode 100644 app/Events/TeamCreated.php create mode 100644 app/Helpers/PermissionsHelper.php create mode 100644 app/Helpers/RolesHelper.php create mode 100644 app/Jobs/Permissions/CreateClubPermissions.php create mode 100644 app/Jobs/Permissions/CreateCompetitionPermissions.php create mode 100644 app/Jobs/Permissions/CreateDivisionPermissions.php create mode 100644 app/Jobs/Permissions/CreatePermissions.php create mode 100644 app/Jobs/Permissions/CreateSeasonPermissions.php create mode 100644 app/Jobs/Permissions/CreateTeamPermissions.php create mode 100644 app/Jobs/Roles/CreateClubSecretaryRole.php create mode 100644 app/Jobs/Roles/CreateCompetitionAdminRole.php create mode 100644 app/Jobs/Roles/CreateDivisionAdminRole.php create mode 100644 app/Jobs/Roles/CreateRole.php create mode 100644 app/Jobs/Roles/CreateSeasonAdminRole.php create mode 100644 app/Jobs/Roles/CreateTeamSecretaryRole.php create mode 100644 app/Jobs/Roles/DeleteRole.php create mode 100644 app/Listeners/SetUpClubSecretary.php create mode 100644 app/Listeners/SetUpCompetitionAdmin.php create mode 100644 app/Listeners/SetUpDivisionAdmin.php create mode 100644 app/Listeners/SetUpSeasonAdmin.php create mode 100644 app/Listeners/SetUpTeamSecretary.php create mode 100644 database/migrations/2024_10_02_081916_create_permission_tables.php delete mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Integration/Helpers/PermissionsHelperTest.php create mode 100644 tests/Integration/Helpers/RolesHelperTest.php create mode 100644 tests/Pest.php create mode 100644 tests/Unit/CreatePermissionsJobsTest.php create mode 100644 tests/Unit/CreateRolesJobsTest.php delete mode 100644 tests/Unit/ExampleTest.php diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 00000000..1f80366b --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,16 @@ +name: Matrix + +on: + push + +jobs: + master-matrix: + strategy: + matrix: + os: ['ubuntu-latest', 'macos-latest'] + php: ['8.3'] + uses: ./.github/workflows/test.yml + with: + os: ${{ matrix.os }} + php: ${{ matrix.php }} + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..545528ac --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,266 @@ +name: Testing + +on: + workflow_call: + inputs: + os: + required: true + type: string + php: + required: true + type: string + +env: + node-modules-cache-name: cache-node-modules + composer-packages-cache-name: cache-composer-packages + build-artifacts: build-artifacts + +jobs: + debug: + runs-on: ${{ inputs.os }} + steps: + - name: Setup PHP ${{ inputs.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php }} + - run: php --version + - run: node --version + - run: npm --version + build: + runs-on: ${{ inputs.os }} + steps: + - uses: actions/checkout@v4 + - name: Setup PHP ${{ inputs.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php }} + - name: Setup Environment + run: cp .env.ci .env + - name: Cache node modules + id: cache-node-modules + uses: actions/cache@v4 + with: + path: ./node_modules + key: ${{ runner.os }}-build-${{ env.node-modules-cache-name }}-${{ hashFiles('**/package-lock.json') }} + - name: Install node dependencise + run: npm install + - name: Cache composer packages + id: cache-composer-packages + uses: actions/cache@v4 + with: + path: ./vendor + key: ${{ runner.os }}-build-${{ env.composer-packages-cache-name }}-${{ hashFiles('**/composer.lock') }} + - name: Install composer dependencies + run: composer install -n --ignore-platform-reqs --no-progress --no-suggest + - name: Build artifacts + run: npm run prod + - name: Tar artifacts + run: /bin/tar -cz -f ~/${{ env.build-artifacts }}.tgz -C ./public css fonts js mix-manifest.json + - name: Store build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.build-artifacts }} + path: ~/${{ env.build-artifacts }}.tgz + unit-tests: + env: + job-name: unit-tests + runs-on: ${{ inputs.os }} + needs: [build] + steps: + - uses: actions/checkout@v4 + - name: Setup PHP ${{ inputs.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php }} + - name: Setup Environment + run: | + cp .env.ci .env + touch ./storage/logs/laravel.log + touch ./database/database.sqlite + - name: Restore node modules cache + uses: actions/cache@v4 + with: + path: ./node_modules + key: ${{ runner.os }}-build-${{ env.node-modules-cache-name }}-${{ hashFiles('**/package-lock.json') }} + - name: Restore composer packages cache + uses: actions/cache@v4 + with: + path: ./vendor + key: ${{ runner.os }}-build-${{ env.composer-packages-cache-name }}-${{ hashFiles('**/composer.lock') }} + - name: Migrate Database + run: php artisan migrate --force + - name: Setup Laravel Passport + run: php artisan passport:install + - name: Directory Permissions + run: chmod -R 777 storage bootstrap/cache + - name: Dump Autoloader + run: composer dump-autoload + - name: Execute Unit tests via PHPUnit + run: vendor/bin/phpunit tests/Unit + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: ${{ env.job-name }}-log + path: ./storage/logs/*.log + integration-tests: + env: + job-name: integration-tests + runs-on: ${{ inputs.os }} + needs: [build] + steps: + - uses: actions/checkout@v4 + - name: Setup PHP ${{ inputs.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php }} + - name: Setup Environment + run: | + cp .env.ci .env + touch ./storage/logs/laravel.log + touch ./database/database.sqlite + - name: Restore node modules cache + uses: actions/cache@v4 + with: + path: ./node_modules + key: ${{ runner.os }}-build-${{ env.node-modules-cache-name }}-${{ hashFiles('**/package-lock.json') }} + - name: Restore composer packages cache + uses: actions/cache@v4 + with: + path: ./vendor + key: ${{ runner.os }}-build-${{ env.composer-packages-cache-name }}-${{ hashFiles('**/composer.lock') }} + - name: Migrate Database + run: php artisan migrate --force + - name: Setup Laravel Passport + run: php artisan passport:install + - name: Directory Permissions + run: chmod -R 777 storage bootstrap/cache + - name: Dump Autoloader + run: composer dump-autoload + - name: Run Laravel Server + run: php artisan serve & + - name: Execute Integration tests via PHPUnit + run: vendor/bin/phpunit tests/Integration + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: ${{ env.job-name }}-log + path: ./storage/logs/*.log + feature-tests: + env: + job-name: feature-tests + runs-on: ${{ inputs.os }} + needs: [build] + steps: + - uses: actions/checkout@v4 + - name: Setup PHP ${{ inputs.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php }} + - name: Setup Environment + run: | + cp .env.ci .env + touch ./storage/logs/laravel.log + touch ./database/database.sqlite + - name: Restore node modules cache + uses: actions/cache@v4 + with: + path: ./node_modules + key: ${{ runner.os }}-build-${{ env.node-modules-cache-name }}-${{ hashFiles('**/package-lock.json') }} + - name: Restore composer packages cache + uses: actions/cache@v4 + with: + path: ./vendor + key: ${{ runner.os }}-build-${{ env.composer-packages-cache-name }}-${{ hashFiles('**/composer.lock') }} + - name: Restore building artifacts + uses: actions/download-artifact@v2 + with: + name: ${{ env.build-artifacts }} + - run: tar -xz -f ./${{ env.build-artifacts }}.tgz -C ./public + - name: Migrate Database + run: php artisan migrate --force + - name: Setup Laravel Passport + run: php artisan passport:install + - name: Directory Permissions + run: chmod -R 777 storage bootstrap/cache + - name: Dump Autoloader + run: composer dump-autoload + - name: Run Laravel Server + run: php artisan serve & + - name: Execute Feature tests via PHPUnit + run: vendor/bin/phpunit tests/Feature + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: ${{ env.job-name }}-log + path: ./storage/logs/*.log +# dusk-tests: +# env: +# job-name: dusk-tests +# runs-on: ${{ inputs.os }} +# needs: [build] +# steps: +# - uses: actions/checkout@v4 +# - name: Setup PHP ${{ inputs.php }} +# uses: shivammathur/setup-php@v2 +# with: +# php-version: ${{ inputs.php }} +# - name: Setup Environment +# run: | +# cp .env.ci .env +# touch ./storage/logs/laravel.log +# touch ./database/database.sqlite +# - name: Restore node modules cache +# uses: actions/cache@v4 +# with: +# path: ./node_modules +# key: ${{ runner.os }}-build-${{ env.node-modules-cache-name }}-${{ hashFiles('**/package-lock.json') }} +# - name: Restore composer packages cache +# uses: actions/cache@v4 +# with: +# path: ./vendor +# key: ${{ runner.os }}-build-${{ env.composer-packages-cache-name }}-${{ hashFiles('**/composer.lock') }} +# - name: Restore building artifacts +# uses: actions/download-artifact@v2 +# with: +# name: ${{ env.build-artifacts }} +# - run: tar -xz -f ./${{ env.build-artifacts }}.tgz -C ./public +# - name: Migrate Database +# run: php artisan migrate --force +# - name: Setup Laravel Passport +# run: php artisan passport:install +# - name: Directory Permissions +# run: chmod -R 777 storage bootstrap/cache +# - name: Dump Autoloader +# run: composer dump-autoload +# - name: Update Chrome Driver +# run: | +# CHROME_VERSION="$(google-chrome --version)" +# CHROMEDRIVER_RELEASE="$(echo $CHROME_VERSION | sed 's/^Google Chrome //')" +# CHROMEDRIVER_RELEASE=${CHROMEDRIVER_RELEASE%%.*} +# php artisan dusk:chrome-driver $CHROMEDRIVER_RELEASE +# - name: Start Chrome Driver +# run: ./vendor/laravel/dusk/bin/chromedriver-linux & +# - name: Run Laravel Server +# run: php artisan serve & +# - name: Run Laravel Dusk Tests +# id: laravel-dusk +# continue-on-error: true +# run: php artisan dusk +# - name: Upload logs +# uses: actions/upload-artifact@v4 +# if: ${{ steps.laravel-dusk.outcome == 'failure'}} +# with: +# name: ${{ env.job-name }}-log +# path: ./storage/logs/*.log +# - name: Upload screenshots +# uses: actions/upload-artifact@v4 +# if: ${{ steps.laravel-dusk.outcome == 'failure' }} +# with: +# name: ${{ env.job-name }}-screenshots +# path: ./tests/Browser/screenshots +# - name: Sanity check +# if: ${{ steps.laravel-dusk.outcome == 'failure'}} +# run: exit 2 diff --git a/app/Events/ClubCreated.php b/app/Events/ClubCreated.php new file mode 100644 index 00000000..b99aefb2 --- /dev/null +++ b/app/Events/ClubCreated.php @@ -0,0 +1,15 @@ +getKey()}"; + } + + final public static function editSeason(Season $season): string + { + return "edit-season-{$season->getKey()}"; + } + + final public static function deleteSeason(Season $season): string + { + return "delete-season-{$season->getKey()}"; + } + + final public static function addCompetition(Season $season): string + { + return "add-competition-in-season-{$season->getKey()}"; + } + + final public static function viewCompetition(Competition $competition): string + { + return "view-competition-{$competition->getKey()}"; + } + + final public static function editCompetition(Competition $competition): string + { + return "edit-competition-{$competition->getKey()}"; + } + + final public static function deleteCompetition(Competition $competition): string + { + return "delete-competition-{$competition->getKey()}"; + } + + final public static function addDivision(Competition $competition): string + { + return "add-division-in-competition-{$competition->getKey()}"; + } + + final public static function viewDivision(Division $division): string + { + return "view-division-{$division->getKey()}"; + } + + final public static function editDivision(Division $division): string + { + return "edit-division-{$division->getKey()}"; + } + + final public static function deleteDivision(Division $division): string + { + return "delete-division-{$division->getKey()}"; + } + + final public static function viewFixtures(Division $division): string + { + return "view-fixtures-in-division-{$division->getKey()}"; + } + + final public static function addFixtures(Division $division): string + { + return "add-fixtures-in-division-{$division->getKey()}"; + } + + final public static function editFixtures(Division $division): string + { + return "edit-fixtures-in-division-{$division->getKey()}"; + } + + final public static function deleteFixtures(Division $division): string + { + return "delete-fixtures-in-division-{$division->getKey()}"; + } + + final public static function addClub(): string + { + return 'add-club'; + } + + final public static function viewClub(Club $club): string + { + return "view-club-{$club->getKey()}"; + } + + final public static function editClub(Club $club): string + { + return "edit-club-{$club->getKey()}"; + } + + final public static function deleteClub(Club $club): string + { + return "delete-club-{$club->getKey()}"; + } + + final public static function addTeam(Club $club): string + { + return "add-team-in-club-{$club->getKey()}"; + } + + final public static function viewTeam(Team $team): string + { + return "view-team-{$team->getKey()}"; + } + + final public static function editTeam(Team $team): string + { + return "edit-team-{$team->getKey()}"; + } + + final public static function deleteTeam(Team $team): string + { + return "delete-team-{$team->getKey()}"; + } +} diff --git a/app/Helpers/RolesHelper.php b/app/Helpers/RolesHelper.php new file mode 100644 index 00000000..112995a7 --- /dev/null +++ b/app/Helpers/RolesHelper.php @@ -0,0 +1,128 @@ +getKey()); + } + + final public static function isSeasonAdmin(Role $role): bool + { + return (bool) preg_match(self::buildPattern(self::SEASON_ADMIN_TEMPLATE), $role->name); + } + + final public static function findSeason(Role $role): ?Season + { + if (preg_match(self::buildPattern(self::SEASON_ADMIN_TEMPLATE), $role->name, $matches)) { + return Season::find($matches[1]); + } + + return null; + } + + final public static function competitionAdmin(Competition $competition): string + { + return sprintf(self::COMPETITION_ADMIN_TEMPLATE, $competition->getKey()); + } + + final public static function isCompetitionAdmin(Role $role): bool + { + return (bool) preg_match(self::buildPattern(self::COMPETITION_ADMIN_TEMPLATE), $role->name); + } + + final public static function findCompetition(Role $role): ?Competition + { + if (preg_match(self::buildPattern(self::COMPETITION_ADMIN_TEMPLATE), $role->name, $matches)) { + return Competition::find($matches[1]); + } + + return null; + } + + final public static function divisionAdmin(Division $competition): string + { + return sprintf(self::DIVISION_ADMIN_TEMPLATE, $competition->getKey()); + } + + final public static function isDivisionAdmin(Role $role): bool + { + return (bool) preg_match(self::buildPattern(self::DIVISION_ADMIN_TEMPLATE), $role->name); + } + + final public static function findDivision(Role $role): ?Division + { + if (preg_match(self::buildPattern(self::DIVISION_ADMIN_TEMPLATE), $role->name, $matches)) { + return Division::find($matches[1]); + } + + return null; + } + + final public static function clubSecretary(Club $club): string + { + return sprintf(self::CLUB_SECRETARY_TEMPLATE, $club->getKey()); + } + + final public static function isClubSecretary(Role $role): bool + { + return (bool) preg_match(self::buildPattern(self::CLUB_SECRETARY_TEMPLATE), $role->name); + } + + final public static function findClub(Role $role): ?Club + { + if (preg_match(self::buildPattern(self::CLUB_SECRETARY_TEMPLATE), $role->name, $matches)) { + return Club::find($matches[1]); + } + + return null; + } + + final public static function teamSecretary(Team $team): string + { + return sprintf(self::TEAM_SECRETARY_TEMPLATE, $team->getKey()); + } + + final public static function isTeamSecretary(Role $role): bool + { + return (bool) preg_match(self::buildPattern(self::TEAM_SECRETARY_TEMPLATE), $role->name); + } + + final public static function findTeam(Role $role): ?Team + { + if (preg_match(self::buildPattern(self::TEAM_SECRETARY_TEMPLATE), $role->name, $matches)) { + return Team::find($matches[1]); + } + + return null; + } + + private static function buildPattern(string $template): string + { + return '/^'.Str::replaceFirst('%s', '([0-9a-f\-]+)', $template).'$/'; + } +} diff --git a/app/Jobs/Permissions/CreateClubPermissions.php b/app/Jobs/Permissions/CreateClubPermissions.php new file mode 100644 index 00000000..a41d920f --- /dev/null +++ b/app/Jobs/Permissions/CreateClubPermissions.php @@ -0,0 +1,22 @@ +club), + PermissionsHelper::editClub($this->club), + PermissionsHelper::deleteClub($this->club), + PermissionsHelper::addTeam($this->club), + ]); + } +} diff --git a/app/Jobs/Permissions/CreateCompetitionPermissions.php b/app/Jobs/Permissions/CreateCompetitionPermissions.php new file mode 100644 index 00000000..d1510a1e --- /dev/null +++ b/app/Jobs/Permissions/CreateCompetitionPermissions.php @@ -0,0 +1,22 @@ +competition), + PermissionsHelper::editCompetition($this->competition), + PermissionsHelper::deleteCompetition($this->competition), + PermissionsHelper::addDivision($this->competition), + ]); + } +} diff --git a/app/Jobs/Permissions/CreateDivisionPermissions.php b/app/Jobs/Permissions/CreateDivisionPermissions.php new file mode 100644 index 00000000..cc74a440 --- /dev/null +++ b/app/Jobs/Permissions/CreateDivisionPermissions.php @@ -0,0 +1,25 @@ +division), + PermissionsHelper::editDivision($this->division), + PermissionsHelper::deleteDivision($this->division), + PermissionsHelper::addFixtures($this->division), + PermissionsHelper::editFixtures($this->division), + PermissionsHelper::deleteFixtures($this->division), + PermissionsHelper::viewFixtures($this->division), + ]); + } +} diff --git a/app/Jobs/Permissions/CreatePermissions.php b/app/Jobs/Permissions/CreatePermissions.php new file mode 100644 index 00000000..506fb3bc --- /dev/null +++ b/app/Jobs/Permissions/CreatePermissions.php @@ -0,0 +1,22 @@ +getPermissions() + ->each(function (string $permission): void { + Permission::create(['name' => $permission]); + }); + } +} diff --git a/app/Jobs/Permissions/CreateSeasonPermissions.php b/app/Jobs/Permissions/CreateSeasonPermissions.php new file mode 100644 index 00000000..bc51789a --- /dev/null +++ b/app/Jobs/Permissions/CreateSeasonPermissions.php @@ -0,0 +1,22 @@ +season), + PermissionsHelper::editSeason($this->season), + PermissionsHelper::deleteSeason($this->season), + PermissionsHelper::addCompetition($this->season), + ]); + } +} diff --git a/app/Jobs/Permissions/CreateTeamPermissions.php b/app/Jobs/Permissions/CreateTeamPermissions.php new file mode 100644 index 00000000..05f098fb --- /dev/null +++ b/app/Jobs/Permissions/CreateTeamPermissions.php @@ -0,0 +1,21 @@ +team), + PermissionsHelper::editTeam($this->team), + PermissionsHelper::deleteTeam($this->team), + ]); + } +} diff --git a/app/Jobs/Roles/CreateClubSecretaryRole.php b/app/Jobs/Roles/CreateClubSecretaryRole.php new file mode 100644 index 00000000..f57cf580 --- /dev/null +++ b/app/Jobs/Roles/CreateClubSecretaryRole.php @@ -0,0 +1,16 @@ +club); + } +} diff --git a/app/Jobs/Roles/CreateCompetitionAdminRole.php b/app/Jobs/Roles/CreateCompetitionAdminRole.php new file mode 100644 index 00000000..d3e8c88b --- /dev/null +++ b/app/Jobs/Roles/CreateCompetitionAdminRole.php @@ -0,0 +1,16 @@ +competition); + } +} diff --git a/app/Jobs/Roles/CreateDivisionAdminRole.php b/app/Jobs/Roles/CreateDivisionAdminRole.php new file mode 100644 index 00000000..8b396bcc --- /dev/null +++ b/app/Jobs/Roles/CreateDivisionAdminRole.php @@ -0,0 +1,16 @@ +division); + } +} diff --git a/app/Jobs/Roles/CreateRole.php b/app/Jobs/Roles/CreateRole.php new file mode 100644 index 00000000..44570eda --- /dev/null +++ b/app/Jobs/Roles/CreateRole.php @@ -0,0 +1,18 @@ + $this->getRole()]); + } +} diff --git a/app/Jobs/Roles/CreateSeasonAdminRole.php b/app/Jobs/Roles/CreateSeasonAdminRole.php new file mode 100644 index 00000000..cafc5678 --- /dev/null +++ b/app/Jobs/Roles/CreateSeasonAdminRole.php @@ -0,0 +1,16 @@ +season); + } +} diff --git a/app/Jobs/Roles/CreateTeamSecretaryRole.php b/app/Jobs/Roles/CreateTeamSecretaryRole.php new file mode 100644 index 00000000..0b299937 --- /dev/null +++ b/app/Jobs/Roles/CreateTeamSecretaryRole.php @@ -0,0 +1,16 @@ +team); + } +} diff --git a/app/Jobs/Roles/DeleteRole.php b/app/Jobs/Roles/DeleteRole.php new file mode 100644 index 00000000..30d7968a --- /dev/null +++ b/app/Jobs/Roles/DeleteRole.php @@ -0,0 +1,29 @@ +roles->each(function (Role $role): void { + $role->users->each(function (User $user) use ($role): void { + $user->removeRole($role); + }); + $role->delete(); + }); + }); + } +} diff --git a/app/Listeners/SetUpClubSecretary.php b/app/Listeners/SetUpClubSecretary.php new file mode 100644 index 00000000..957f42fb --- /dev/null +++ b/app/Listeners/SetUpClubSecretary.php @@ -0,0 +1,28 @@ +club; + + CreateClubSecretaryRole::dispatchSync($club); + CreateClubPermissions::dispatchSync($club); + + $role = Role::findByName(RolesHelper::clubSecretary($club)); + $role->givePermissionTo([ + PermissionsHelper::viewClub($club), + PermissionsHelper::editClub($club), + PermissionsHelper::addTeam($club), + ]); + } +} diff --git a/app/Listeners/SetUpCompetitionAdmin.php b/app/Listeners/SetUpCompetitionAdmin.php new file mode 100644 index 00000000..560a2e2c --- /dev/null +++ b/app/Listeners/SetUpCompetitionAdmin.php @@ -0,0 +1,38 @@ +competition; + + CreateCompetitionAdminRole::dispatchSync($competition); + CreateCompetitionPermissions::dispatchSync($competition); + + $competitionAdminRole = Role::findByName(RolesHelper::competitionAdmin($competition)); + $competitionAdminRole->givePermissionTo([ + PermissionsHelper::viewCompetition($competition), + PermissionsHelper::editCompetition($competition), + PermissionsHelper::addDivision($competition), + PermissionsHelper::viewSeason($competition->season), + ]); + + /** @var Role $seasonAdminRole */ + $seasonAdminRole = Role::findByName(RolesHelper::seasonAdmin($competition->season)); + $seasonAdminRole->givePermissionTo([ + PermissionsHelper::viewCompetition($competition), + PermissionsHelper::editCompetition($competition), + PermissionsHelper::deleteCompetition($competition), + PermissionsHelper::addDivision($competition), + ]); + } +} diff --git a/app/Listeners/SetUpDivisionAdmin.php b/app/Listeners/SetUpDivisionAdmin.php new file mode 100644 index 00000000..39cdaa43 --- /dev/null +++ b/app/Listeners/SetUpDivisionAdmin.php @@ -0,0 +1,55 @@ +division; + + CreateDivisionAdminRole::dispatchSync($division); + CreateDivisionPermissions::dispatchSync($division); + + $divisionAdminRole = Role::findByName(RolesHelper::divisionAdmin($division)); + $divisionAdminRole->givePermissionTo([ + PermissionsHelper::viewDivision($division), + PermissionsHelper::editDivision($division), + PermissionsHelper::addFixtures($division), + PermissionsHelper::editFixtures($division), + PermissionsHelper::deleteFixtures($division), + PermissionsHelper::viewFixtures($division), + PermissionsHelper::viewCompetition($division->competition), + PermissionsHelper::viewSeason($division->competition->season), + ]); + + $competitionAdminRole = Role::findByName(RolesHelper::competitionAdmin($division->competition)); + $competitionAdminRole->givePermissionTo([ + PermissionsHelper::viewDivision($division), + PermissionsHelper::editDivision($division), + PermissionsHelper::deleteDivision($division), + PermissionsHelper::addFixtures($division), + PermissionsHelper::editFixtures($division), + PermissionsHelper::deleteFixtures($division), + PermissionsHelper::viewFixtures($division), + ]); + + $seasonAdminRole = Role::findByName(RolesHelper::seasonAdmin($division->competition->season)); + $seasonAdminRole->givePermissionTo([ + PermissionsHelper::viewDivision($division), + PermissionsHelper::editDivision($division), + PermissionsHelper::deleteDivision($division), + PermissionsHelper::addFixtures($division), + PermissionsHelper::editFixtures($division), + PermissionsHelper::deleteFixtures($division), + PermissionsHelper::viewFixtures($division), + ]); + } +} diff --git a/app/Listeners/SetUpSeasonAdmin.php b/app/Listeners/SetUpSeasonAdmin.php new file mode 100644 index 00000000..2f76af67 --- /dev/null +++ b/app/Listeners/SetUpSeasonAdmin.php @@ -0,0 +1,28 @@ +season; + + CreateSeasonAdminRole::dispatchSync($season); + CreateSeasonPermissions::dispatchSync($season); + + $role = Role::findByName(RolesHelper::seasonAdmin($season)); + $role->givePermissionTo([ + PermissionsHelper::viewSeason($season), + PermissionsHelper::editSeason($season), + PermissionsHelper::addCompetition($season), + ]); + } +} diff --git a/app/Listeners/SetUpTeamSecretary.php b/app/Listeners/SetUpTeamSecretary.php new file mode 100644 index 00000000..3545976b --- /dev/null +++ b/app/Listeners/SetUpTeamSecretary.php @@ -0,0 +1,35 @@ +team; + + CreateTeamSecretaryRole::dispatchSync($team); + CreateTeamPermissions::dispatchSync($team); + + $teamSecretaryRole = Role::findByName(RolesHelper::teamSecretary($team)); + $teamSecretaryRole->givePermissionTo([ + PermissionsHelper::viewTeam($team), + PermissionsHelper::editTeam($team), + PermissionsHelper::viewClub($team->club), + ]); + + $clubSecretaryRole = Role::findByName(RolesHelper::clubSecretary($team->club)); + $clubSecretaryRole->givePermissionTo([ + PermissionsHelper::viewTeam($team), + PermissionsHelper::editTeam($team), + PermissionsHelper::deleteTeam($team), + ]); + } +} diff --git a/app/Models/Club.php b/app/Models/Club.php index 1e88d906..a1060e93 100644 --- a/app/Models/Club.php +++ b/app/Models/Club.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\ClubCreated; use App\Models\Contracts\Selectable; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -26,6 +27,10 @@ class Club extends Model implements Selectable 'venue_id', ]; + protected $dispatchesEvents = [ + 'created' => ClubCreated::class, + ]; + public function teams(): HasMany { return $this->hasMany(Team::class); diff --git a/app/Models/Competition.php b/app/Models/Competition.php index 35da4b2c..8d8dcb78 100644 --- a/app/Models/Competition.php +++ b/app/Models/Competition.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\CompetitionCreated; use App\Models\Builders\CompetitionBuilder; use App\Models\Contracts\Selectable; use Illuminate\Database\Eloquent\Collection; @@ -30,6 +31,10 @@ class Competition extends Model implements Selectable 'name', ]; + protected $dispatchesEvents = [ + 'created' => CompetitionCreated::class, + ]; + public function newEloquentBuilder($query): CompetitionBuilder { return new CompetitionBuilder($query); diff --git a/app/Models/Division.php b/app/Models/Division.php index 8a2baf72..36ecec06 100644 --- a/app/Models/Division.php +++ b/app/Models/Division.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\DivisionCreated; use App\Models\Builders\DivisionBuilder; use App\Models\Contracts\Selectable; use Illuminate\Database\Eloquent\Collection; @@ -36,6 +37,10 @@ class Division extends Model implements Selectable 'display_order', ]; + protected $dispatchesEvents = [ + 'created' => DivisionCreated::class, + ]; + public function newEloquentBuilder($query): DivisionBuilder { return new DivisionBuilder($query); diff --git a/app/Models/Season.php b/app/Models/Season.php index 90005feb..71be186e 100644 --- a/app/Models/Season.php +++ b/app/Models/Season.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\SeasonCreated; use App\Models\Contracts\Selectable; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -24,6 +25,10 @@ class Season extends Model implements Selectable 'year', ]; + protected $dispatchesEvents = [ + 'created' => SeasonCreated::class, + ]; + protected static function booted(): void { static::saving(function (Season $season) { diff --git a/app/Models/Team.php b/app/Models/Team.php index 2bd2659d..a8715a75 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\TeamCreated; use App\Models\Contracts\Selectable; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -32,6 +33,10 @@ class Team extends Model implements Selectable 'venue_id', ]; + protected $dispatchesEvents = [ + 'created' => TeamCreated::class, + ]; + public function club(): BelongsTo { return $this->belongsTo(Club::class); diff --git a/app/Models/User.php b/app/Models/User.php index 47d53997..497befde 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,10 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { use HasFactory, + HasRoles, HasUuids, Notifiable; diff --git a/composer.json b/composer.json index d37de4f7..7c32d94c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "laravel/framework": "^11.9", "laravel/tinker": "^2.9", "livewire/livewire": "^3.4", - "livewire/volt": "^1.0" + "livewire/volt": "^1.0", + "spatie/laravel-permission": "^6.9" }, "require-dev": { "fakerphp/faker": "^1.23", @@ -35,7 +36,7 @@ "mnito/round-robin": "^2.2", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.0", - "phpunit/phpunit": "^11.0.1", + "pestphp/pest": "^3.3", "spatie/laravel-login-link": "^1.3" }, "autoload": { diff --git a/composer.lock b/composer.lock index 04123289..504b6607 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2cef2b68c1a138f540fc7956bd99601d", + "content-hash": "645ea67478b01f09d85b2788a801cb90", "packages": [ { "name": "blade-ui-kit/blade-heroicons", @@ -2471,16 +2471,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -2523,22 +2523,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.0.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" + "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a", + "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a", "shasum": "" }, "require": { @@ -2548,11 +2548,11 @@ }, "require-dev": { "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.0.0", - "laravel/pint": "^1.14.0", - "mockery/mockery": "^1.6.7", - "pestphp/pest": "^2.34.1", - "phpstan/phpstan": "^1.10.59", + "illuminate/console": "^11.1.1", + "laravel/pint": "^1.15.0", + "mockery/mockery": "^1.6.11", + "pestphp/pest": "^2.34.6", + "phpstan/phpstan": "^1.10.66", "phpstan/phpstan-strict-rules": "^1.5.2", "symfony/var-dumper": "^7.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" @@ -2597,7 +2597,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0" }, "funding": [ { @@ -2613,7 +2613,7 @@ "type": "github" } ], - "time": "2024-03-06T16:17:14+00:00" + "time": "2024-09-05T15:25:50+00:00" }, { "name": "phpoption/phpoption", @@ -3003,16 +3003,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -3047,9 +3047,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -3406,6 +3406,88 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.9.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "fe973a58b44380d0e8620107259b7bda22f70408" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/fe973a58b44380d0e8620107259b7bda22f70408", + "reference": "fe973a58b44380d0e8620107259b7bda22f70408", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.9.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-06-22T23:04:52+00:00" + }, { "name": "symfony/clock", "version": "v7.1.1", @@ -3482,16 +3564,16 @@ }, { "name": "symfony/console", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", "shasum": "" }, "require": { @@ -3555,7 +3637,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.2" + "source": "https://github.com/symfony/console/tree/v7.1.5" }, "funding": [ { @@ -3571,7 +3653,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/css-selector", @@ -3938,16 +4020,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", "shasum": "" }, "require": { @@ -3982,7 +4064,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.1" + "source": "https://github.com/symfony/finder/tree/v7.1.4" }, "funding": [ { @@ -3998,7 +4080,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/http-foundation", @@ -4357,20 +4439,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -4416,7 +4498,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -4432,24 +4514,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4494,7 +4576,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -4510,7 +4592,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -4598,20 +4680,20 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4659,7 +4741,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -4675,24 +4757,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -4739,7 +4821,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -4755,7 +4837,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php72", @@ -4832,20 +4914,20 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -4892,7 +4974,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -4908,7 +4990,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", @@ -5067,16 +5149,16 @@ }, { "name": "symfony/process", - "version": "v7.1.1", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + "reference": "5c03ee6369281177f07f7c68252a280beccba847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", + "reference": "5c03ee6369281177f07f7c68252a280beccba847", "shasum": "" }, "require": { @@ -5108,7 +5190,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.1" + "source": "https://github.com/symfony/process/tree/v7.1.5" }, "funding": [ { @@ -5124,7 +5206,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-19T21:48:23+00:00" }, { "name": "symfony/routing", @@ -5292,16 +5374,16 @@ }, { "name": "symfony/string", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -5359,7 +5441,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.2" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -5375,7 +5457,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:27:18+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/translation", @@ -5977,6 +6059,147 @@ } ], "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.5.7", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "4890b17f569efea5e034e519dc883da53a67448d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4890b17f569efea5e034e519dc883da53a67448d", + "reference": "4890b17f569efea5e034e519dc883da53a67448d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.2.0", + "jean85/pretty-package-versions": "^2.0.6", + "php": "~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^11.0.6", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-timer": "^7.0.1", + "phpunit/phpunit": "^11.4.0", + "sebastian/environment": "^7.2.0", + "symfony/console": "^6.4.11 || ^7.1.5", + "symfony/process": "^6.4.8 || ^7.1.5" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "infection/infection": "^0.29.7", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "squizlabs/php_codesniffer": "^3.10.3", + "symfony/filesystem": "^6.4.9 || ^7.1.5" + }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.5.7" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2024-10-07T06:27:54+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, { "name": "fakerphp/faker", "version": "v1.23.1", @@ -6040,28 +6263,89 @@ }, "time": "2024-01-02T13:46:09+00:00" }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, { "name": "filp/whoops", - "version": "2.15.4", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -6101,7 +6385,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -6109,7 +6393,7 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -6230,6 +6514,65 @@ ], "time": "2024-06-11T04:27:58+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + }, + "time": "2024-03-08T09:58:59+00:00" + }, { "name": "laravel/breeze", "version": "v2.1.2", @@ -6616,23 +6959,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.3.0", + "version": "v8.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229" + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b49f5b2891ce52726adfd162841c69d4e4c84229", - "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", "shasum": "" }, "require": { "filp/whoops": "^2.15.4", "nunomaduro/termwind": "^2.0.1", "php": "^8.2.0", - "symfony/console": "^7.1.2" + "symfony/console": "^7.1.3" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", @@ -6640,13 +6983,13 @@ }, "require-dev": { "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.16.0", - "laravel/pint": "^1.16.2", - "laravel/sail": "^1.30.2", + "laravel/framework": "^11.19.0", + "laravel/pint": "^1.17.1", + "laravel/sail": "^1.31.0", "laravel/sanctum": "^4.0.2", "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.2.1", - "pestphp/pest": "^2.34.9 || ^3.0.0", + "orchestra/testbench-core": "^9.2.3", + "pestphp/pest": "^2.35.0 || ^3.0.0", "sebastian/environment": "^6.1.0 || ^7.0.0" }, "type": "library", @@ -6709,43 +7052,366 @@ "type": "patreon" } ], - "time": "2024-07-16T22:41:01+00:00" + "time": "2024-08-03T15:32:23+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "pestphp/pest", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/pestphp/pest.git", + "reference": "0a7bff0d246b10040e12e4152215e12a599e742a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/pestphp/pest/zipball/0a7bff0d246b10040e12e4152215e12a599e742a", + "reference": "0a7bff0d246b10040e12e4152215e12a599e742a", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "brianium/paratest": "^7.5.6", + "nunomaduro/collision": "^8.4.0", + "nunomaduro/termwind": "^2.1.0", + "pestphp/pest-plugin": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.0.0", + "pestphp/pest-plugin-mutate": "^3.0.5", + "php": "^8.2.0", + "phpunit/phpunit": "^11.4.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "conflict": { + "phpunit/phpunit": ">11.4.0", + "sebastian/exporter": "<6.0.0", + "webmozart/assert": "<1.11.0" }, - "autoload": { - "classmap": [ - "src/" - ] + "require-dev": { + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.1.0", + "symfony/process": "^7.1.5" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-10-06T18:25:27+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.2" + }, + "conflict": { + "pestphp/pest": "<3.0.0" + }, + "require-dev": { + "composer/composer": "^2.7.9", + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-09-08T23:21:41+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/0a27e55a270cfe73d8cb70551b91002ee2cb64b0", + "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-09-08T23:23:55+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.2.0", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^3.0.8", + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-09-22T07:54:40+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ "BSD-3-Clause" ], "authors": [ @@ -6829,34 +7495,256 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.32.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + }, + "time": "2024-09-26T07:23:32+00:00" + }, { "name": "phpunit/php-code-coverage", - "version": "11.0.5", + "version": "11.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.0", + "nikic/php-parser": "^5.1.0", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0", - "phpunit/php-text-template": "^4.0", - "sebastian/code-unit-reverse-lookup": "^4.0", - "sebastian/complexity": "^4.0", - "sebastian/environment": "^7.0", - "sebastian/lines-of-code": "^3.0", - "sebastian/version": "^5.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^11.0" @@ -6868,7 +7756,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -6897,7 +7785,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6" }, "funding": [ { @@ -6905,20 +7793,20 @@ "type": "github" } ], - "time": "2024-07-03T05:05:37+00:00" + "time": "2024-08-22T04:37:56+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26", - "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { @@ -6958,7 +7846,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -6966,7 +7854,7 @@ "type": "github" } ], - "time": "2024-07-03T05:06:37+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", @@ -7154,16 +8042,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.2.8", + "version": "11.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39" + "reference": "89fe0c530133c08f7fff89d3d727154e4e504925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89fe0c530133c08f7fff89d3d727154e4e504925", + "reference": "89fe0c530133c08f7fff89d3d727154e4e504925", "shasum": "" }, "require": { @@ -7177,20 +8065,20 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.5", - "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-code-coverage": "^11.0.6", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.0.1", + "sebastian/comparator": "^6.1.0", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.1.3", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.0.1", + "sebastian/type": "^5.1.0", "sebastian/version": "^5.0.1" }, "suggest": { @@ -7202,7 +8090,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.2-dev" + "dev-main": "11.4-dev" } }, "autoload": { @@ -7234,7 +8122,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.0" }, "funding": [ { @@ -7250,7 +8138,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T14:56:37+00:00" + "time": "2024-10-05T08:39:03+00:00" }, { "name": "sebastian/cli-parser", @@ -7424,16 +8312,16 @@ }, { "name": "sebastian/comparator", - "version": "6.0.1", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "131942b86d3587291067a94f295498ab6ac79c20" + "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20", - "reference": "131942b86d3587291067a94f295498ab6ac79c20", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d", + "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d", "shasum": "" }, "require": { @@ -7444,12 +8332,12 @@ "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -7489,7 +8377,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0" }, "funding": [ { @@ -7497,7 +8385,7 @@ "type": "github" } ], - "time": "2024-07-03T04:48:07+00:00" + "time": "2024-09-11T15:42:56+00:00" }, { "name": "sebastian/complexity", @@ -8066,28 +8954,28 @@ }, { "name": "sebastian/type", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -8111,7 +8999,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" }, "funding": [ { @@ -8119,7 +9007,7 @@ "type": "github" } ], - "time": "2024-07-03T05:11:49+00:00" + "time": "2024-09-17T13:12:04+00:00" }, { "name": "sebastian/version", @@ -8302,16 +9190,16 @@ }, { "name": "symfony/yaml", - "version": "v7.1.1", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2" + "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4e561c316e135e053bd758bf3b3eb291d9919de4", + "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4", "shasum": "" }, "require": { @@ -8353,7 +9241,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.1.1" + "source": "https://github.com/symfony/yaml/tree/v7.1.5" }, "funding": [ { @@ -8369,7 +9257,66 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-17T12:49:58+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.4", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.4" + }, + "time": "2024-01-05T14:10:56+00:00" }, { "name": "theseer/tokenizer", diff --git a/database/migrations/2024_10_02_081916_create_permission_tables.php b/database/migrations/2024_10_02_081916_create_permission_tables.php new file mode 100644 index 00000000..f892c496 --- /dev/null +++ b/database/migrations/2024_10_02_081916_create_permission_tables.php @@ -0,0 +1,142 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + //$table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + // $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->uuid($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + // $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->uuid($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/phpunit.xml b/phpunit.xml index c09b5bcf..3078a2bb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,9 @@ tests/Feature + + tests/Integration + @@ -22,7 +25,8 @@ - + + diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php index bf777465..5759b632 100644 --- a/tests/Feature/Auth/AuthenticationTest.php +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -1,86 +1,75 @@ get('/login'); +test('login screen can be rendered', function () { + $response = $this->get('/login'); - $response - ->assertOk() - ->assertSeeVolt('pages.auth.login'); - } + $response + ->assertOk() + ->assertSeeVolt('pages.auth.login'); +}); - public function test_users_can_authenticate_using_the_login_screen(): void - { - $user = User::factory()->create(); +test('users can authenticate using the login screen', function () { + $user = User::factory()->create(); - $component = Volt::test('pages.auth.login') - ->set('form.email', $user->email) - ->set('form.password', 'password'); + $component = Volt::test('pages.auth.login') + ->set('form.email', $user->email) + ->set('form.password', 'password'); - $component->call('login'); + $component->call('login'); - $component - ->assertHasNoErrors() - ->assertRedirect(route('dashboard', absolute: false)); + $component + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); - $this->assertAuthenticated(); - } + $this->assertAuthenticated(); +}); - public function test_users_can_not_authenticate_with_invalid_password(): void - { - $user = User::factory()->create(); +test('users can not authenticate with invalid password', function () { + $user = User::factory()->create(); - $component = Volt::test('pages.auth.login') - ->set('form.email', $user->email) - ->set('form.password', 'wrong-password'); + $component = Volt::test('pages.auth.login') + ->set('form.email', $user->email) + ->set('form.password', 'wrong-password'); - $component->call('login'); + $component->call('login'); - $component - ->assertHasErrors() - ->assertNoRedirect(); + $component + ->assertHasErrors() + ->assertNoRedirect(); - $this->assertGuest(); - } + $this->assertGuest(); +}); - public function test_navigation_menu_can_be_rendered(): void - { - $user = User::factory()->create(); +test('navigation menu can be rendered', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $response = $this->get('/dashboard'); + $response = $this->get('/dashboard'); - $response - ->assertOk() - ->assertSeeVolt('layout.navigation'); - } + $response + ->assertOk() + ->assertSeeVolt('layout.navigation'); +}); - public function test_users_can_logout(): void - { - $user = User::factory()->create(); +test('users can logout', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('layout.navigation'); + $component = Volt::test('layout.navigation'); - $component->call('logout'); + $component->call('logout'); - $component - ->assertHasNoErrors() - ->assertRedirect('/'); + $component + ->assertHasNoErrors() + ->assertRedirect('/'); - $this->assertGuest(); - } -} + $this->assertGuest(); +}); diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php index 12369835..64e95da0 100644 --- a/tests/Feature/Auth/EmailVerificationTest.php +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -1,60 +1,50 @@ unverified()->create(); +test('email verification screen can be rendered', function () { + $user = User::factory()->unverified()->create(); - $response = $this->actingAs($user)->get('/verify-email'); + $response = $this->actingAs($user)->get('/verify-email'); - $response - ->assertSeeVolt('pages.auth.verify-email') - ->assertStatus(200); - } + $response + ->assertSeeVolt('pages.auth.verify-email') + ->assertStatus(200); +}); - public function test_email_can_be_verified(): void - { - $user = User::factory()->unverified()->create(); +test('email can be verified', function () { + $user = User::factory()->unverified()->create(); - Event::fake(); + Event::fake(); - $verificationUrl = URL::temporarySignedRoute( - 'verification.verify', - now()->addMinutes(60), - ['id' => $user->id, 'hash' => sha1($user->email)] - ); + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); - $response = $this->actingAs($user)->get($verificationUrl); + $response = $this->actingAs($user)->get($verificationUrl); - Event::assertDispatched(Verified::class); - $this->assertTrue($user->fresh()->hasVerifiedEmail()); - $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); - } + Event::assertDispatched(Verified::class); + expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); + $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); +}); - public function test_email_is_not_verified_with_invalid_hash(): void - { - $user = User::factory()->unverified()->create(); +test('email is not verified with invalid hash', function () { + $user = User::factory()->unverified()->create(); - $verificationUrl = URL::temporarySignedRoute( - 'verification.verify', - now()->addMinutes(60), - ['id' => $user->id, 'hash' => sha1('wrong-email')] - ); + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); - $this->actingAs($user)->get($verificationUrl); + $this->actingAs($user)->get($verificationUrl); - $this->assertFalse($user->fresh()->hasVerifiedEmail()); - } -} + expect($user->fresh()->hasVerifiedEmail())->toBeFalse(); +}); diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php index e91c6bb0..3ef6bdd8 100644 --- a/tests/Feature/Auth/PasswordConfirmationTest.php +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -1,56 +1,46 @@ create(); +test('confirm password screen can be rendered', function () { + $user = User::factory()->create(); - $response = $this->actingAs($user)->get('/confirm-password'); + $response = $this->actingAs($user)->get('/confirm-password'); - $response - ->assertSeeVolt('pages.auth.confirm-password') - ->assertStatus(200); - } + $response + ->assertSeeVolt('pages.auth.confirm-password') + ->assertStatus(200); +}); - public function test_password_can_be_confirmed(): void - { - $user = User::factory()->create(); +test('password can be confirmed', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('pages.auth.confirm-password') - ->set('password', 'password'); + $component = Volt::test('pages.auth.confirm-password') + ->set('password', 'password'); - $component->call('confirmPassword'); + $component->call('confirmPassword'); - $component - ->assertRedirect('/dashboard') - ->assertHasNoErrors(); - } + $component + ->assertRedirect('/dashboard') + ->assertHasNoErrors(); +}); - public function test_password_is_not_confirmed_with_invalid_password(): void - { - $user = User::factory()->create(); +test('password is not confirmed with invalid password', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('pages.auth.confirm-password') - ->set('password', 'wrong-password'); + $component = Volt::test('pages.auth.confirm-password') + ->set('password', 'wrong-password'); - $component->call('confirmPassword'); + $component->call('confirmPassword'); - $component - ->assertNoRedirect() - ->assertHasErrors('password'); - } -} + $component + ->assertNoRedirect() + ->assertHasErrors('password'); +}); diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php index f5bc7651..c166ab82 100644 --- a/tests/Feature/Auth/PasswordResetTest.php +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -1,84 +1,73 @@ get('/forgot-password'); +test('reset password link screen can be rendered', function () { + $response = $this->get('/forgot-password'); - $response - ->assertSeeVolt('pages.auth.forgot-password') - ->assertStatus(200); - } + $response + ->assertSeeVolt('pages.auth.forgot-password') + ->assertStatus(200); +}); - public function test_reset_password_link_can_be_requested(): void - { - Notification::fake(); +test('reset password link can be requested', function () { + Notification::fake(); - $user = User::factory()->create(); + $user = User::factory()->create(); - Volt::test('pages.auth.forgot-password') - ->set('email', $user->email) - ->call('sendPasswordResetLink'); + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); - Notification::assertSentTo($user, ResetPassword::class); - } + Notification::assertSentTo($user, ResetPassword::class); +}); - public function test_reset_password_screen_can_be_rendered(): void - { - Notification::fake(); +test('reset password screen can be rendered', function () { + Notification::fake(); - $user = User::factory()->create(); + $user = User::factory()->create(); - Volt::test('pages.auth.forgot-password') - ->set('email', $user->email) - ->call('sendPasswordResetLink'); + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); - Notification::assertSentTo($user, ResetPassword::class, function ($notification) { - $response = $this->get('/reset-password/'.$notification->token); + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); - $response - ->assertSeeVolt('pages.auth.reset-password') - ->assertStatus(200); + $response + ->assertSeeVolt('pages.auth.reset-password') + ->assertStatus(200); - return true; - }); - } + return true; + }); +}); - public function test_password_can_be_reset_with_valid_token(): void - { - Notification::fake(); +test('password can be reset with valid token', function () { + Notification::fake(); - $user = User::factory()->create(); + $user = User::factory()->create(); - Volt::test('pages.auth.forgot-password') - ->set('email', $user->email) - ->call('sendPasswordResetLink'); + Volt::test('pages.auth.forgot-password') + ->set('email', $user->email) + ->call('sendPasswordResetLink'); - Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { - $component = Volt::test('pages.auth.reset-password', ['token' => $notification->token]) - ->set('email', $user->email) - ->set('password', 'password') - ->set('password_confirmation', 'password'); + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $component = Volt::test('pages.auth.reset-password', ['token' => $notification->token]) + ->set('email', $user->email) + ->set('password', 'password') + ->set('password_confirmation', 'password'); - $component->call('resetPassword'); + $component->call('resetPassword'); - $component - ->assertRedirect('/login') - ->assertHasNoErrors(); + $component + ->assertRedirect('/login') + ->assertHasNoErrors(); - return true; - }); - } -} + return true; + }); +}); diff --git a/tests/Feature/Auth/PasswordUpdateTest.php b/tests/Feature/Auth/PasswordUpdateTest.php index d24c2958..a3d2446f 100644 --- a/tests/Feature/Auth/PasswordUpdateTest.php +++ b/tests/Feature/Auth/PasswordUpdateTest.php @@ -1,50 +1,41 @@ create(); +test('password can be updated', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.update-password-form') - ->set('current_password', 'password') - ->set('password', 'new-password') - ->set('password_confirmation', 'new-password') - ->call('updatePassword'); + $component = Volt::test('profile.update-password-form') + ->set('current_password', 'password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); - $component - ->assertHasNoErrors() - ->assertNoRedirect(); + $component + ->assertHasNoErrors() + ->assertNoRedirect(); - $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); - } + expect(Hash::check('new-password', $user->refresh()->password))->toBeTrue(); +}); - public function test_correct_password_must_be_provided_to_update_password(): void - { - $user = User::factory()->create(); +test('correct password must be provided to update password', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.update-password-form') - ->set('current_password', 'wrong-password') - ->set('password', 'new-password') - ->set('password_confirmation', 'new-password') - ->call('updatePassword'); + $component = Volt::test('profile.update-password-form') + ->set('current_password', 'wrong-password') + ->set('password', 'new-password') + ->set('password_confirmation', 'new-password') + ->call('updatePassword'); - $component - ->assertHasErrors(['current_password']) - ->assertNoRedirect(); - } -} + $component + ->assertHasErrors(['current_password']) + ->assertNoRedirect(); +}); diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php index 16100aa5..6cb22285 100644 --- a/tests/Feature/Auth/RegistrationTest.php +++ b/tests/Feature/Auth/RegistrationTest.php @@ -1,36 +1,27 @@ get('/register'); +test('registration screen can be rendered', function () { + $response = $this->get('/register'); - $response - ->assertOk() - ->assertSeeVolt('pages.auth.register'); - } + $response + ->assertOk() + ->assertSeeVolt('pages.auth.register'); +}); - public function test_new_users_can_register(): void - { - $component = Volt::test('pages.auth.register') - ->set('name', 'Test User') - ->set('email', 'test@example.com') - ->set('password', 'password') - ->set('password_confirmation', 'password'); +test('new users can register', function () { + $component = Volt::test('pages.auth.register') + ->set('name', 'Test User') + ->set('email', 'test@example.com') + ->set('password', 'password') + ->set('password_confirmation', 'password'); - $component->call('register'); + $component->call('register'); - $component->assertRedirect(route('dashboard', absolute: false)); + $component->assertRedirect(route('dashboard', absolute: false)); - $this->assertAuthenticated(); - } -} + $this->assertAuthenticated(); +}); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index 8364a84e..00000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php index 52fae1ba..05fcee6a 100644 --- a/tests/Feature/ProfileTest.php +++ b/tests/Feature/ProfileTest.php @@ -1,101 +1,89 @@ create(); +test('profile page is displayed', function () { + $user = User::factory()->create(); - $response = $this->actingAs($user)->get('/profile'); + $response = $this->actingAs($user)->get('/profile'); - $response - ->assertOk() - ->assertSeeVolt('profile.update-profile-information-form') - ->assertSeeVolt('profile.update-password-form') - ->assertSeeVolt('profile.delete-user-form'); - } + $response + ->assertOk() + ->assertSeeVolt('profile.update-profile-information-form') + ->assertSeeVolt('profile.update-password-form') + ->assertSeeVolt('profile.delete-user-form'); +}); - public function test_profile_information_can_be_updated(): void - { - $user = User::factory()->create(); +test('profile information can be updated', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.update-profile-information-form') - ->set('name', 'Test User') - ->set('email', 'test@example.com') - ->call('updateProfileInformation'); + $component = Volt::test('profile.update-profile-information-form') + ->set('name', 'Test User') + ->set('email', 'test@example.com') + ->call('updateProfileInformation'); - $component - ->assertHasNoErrors() - ->assertNoRedirect(); + $component + ->assertHasNoErrors() + ->assertNoRedirect(); - $user->refresh(); + $user->refresh(); - $this->assertSame('Test User', $user->name); - $this->assertSame('test@example.com', $user->email); - $this->assertNull($user->email_verified_at); - } + expect($user->name)->toBe('Test User'); + expect($user->email)->toBe('test@example.com'); + expect($user->email_verified_at)->toBeNull(); +}); - public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void - { - $user = User::factory()->create(); +test('email verification status is unchanged when the email address is unchanged', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.update-profile-information-form') - ->set('name', 'Test User') - ->set('email', $user->email) - ->call('updateProfileInformation'); + $component = Volt::test('profile.update-profile-information-form') + ->set('name', 'Test User') + ->set('email', $user->email) + ->call('updateProfileInformation'); - $component - ->assertHasNoErrors() - ->assertNoRedirect(); + $component + ->assertHasNoErrors() + ->assertNoRedirect(); - $this->assertNotNull($user->refresh()->email_verified_at); - } + expect($user->refresh()->email_verified_at)->not->toBeNull(); +}); - public function test_user_can_delete_their_account(): void - { - $user = User::factory()->create(); +test('user can delete their account', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.delete-user-form') - ->set('password', 'password') - ->call('deleteUser'); + $component = Volt::test('profile.delete-user-form') + ->set('password', 'password') + ->call('deleteUser'); - $component - ->assertHasNoErrors() - ->assertRedirect('/'); + $component + ->assertHasNoErrors() + ->assertRedirect('/'); - $this->assertGuest(); - $this->assertNull($user->fresh()); - } + $this->assertGuest(); + expect($user->fresh())->toBeNull(); +}); - public function test_correct_password_must_be_provided_to_delete_account(): void - { - $user = User::factory()->create(); +test('correct password must be provided to delete account', function () { + $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user); - $component = Volt::test('profile.delete-user-form') - ->set('password', 'wrong-password') - ->call('deleteUser'); + $component = Volt::test('profile.delete-user-form') + ->set('password', 'wrong-password') + ->call('deleteUser'); - $component - ->assertHasErrors('password') - ->assertNoRedirect(); + $component + ->assertHasErrors('password') + ->assertNoRedirect(); - $this->assertNotNull($user->fresh()); - } -} + expect($user->fresh())->not->toBeNull(); +}); diff --git a/tests/Integration/Helpers/PermissionsHelperTest.php b/tests/Integration/Helpers/PermissionsHelperTest.php new file mode 100644 index 00000000..8344de06 --- /dev/null +++ b/tests/Integration/Helpers/PermissionsHelperTest.php @@ -0,0 +1,125 @@ +toBe('add-season'); +}); +it('returns the permission to view a season', function () { + $season = Season::factory()->create(); + + expect(PermissionsHelper::viewSeason($season))->toBe("view-season-{$season->getKey()}"); +}); +it('returns the permission to edit a season', function () { + $season = Season::factory()->create(); + + expect(PermissionsHelper::editSeason($season))->toBe("edit-season-{$season->getKey()}"); +}); +it('returns the permission to delete a season', function () { + $season = Season::factory()->create(); + + expect(PermissionsHelper::deleteSeason($season))->toBe("delete-season-{$season->getKey()}"); +}); +it('returns the permission to add a competition', function () { + $season = Season::factory()->create(); + + expect(PermissionsHelper::addCompetition($season))->toBe("add-competition-in-season-{$season->getKey()}"); +}); +it('returns the permission to view a competition', function () { + $competition = Competition::factory()->create(); + + expect(PermissionsHelper::viewCompetition($competition))->toBe("view-competition-{$competition->getKey()}"); +}); +it('returns the permission to edit a competition', function () { + $competition = Competition::factory()->create(); + + expect(PermissionsHelper::editCompetition($competition))->toBe("edit-competition-{$competition->getKey()}"); +}); +it('returns the permission to delete a competition', function () { + $competition = Competition::factory()->create(); + + expect(PermissionsHelper::deleteCompetition($competition))->toBe("delete-competition-{$competition->getKey()}"); +}); +it('returns the permission to add a division', function () { + $competition = Competition::factory()->create(); + + expect(PermissionsHelper::addDivision($competition))->toBe("add-division-in-competition-{$competition->getKey()}"); +}); +it('returns the permission to view a division', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::viewDivision($division))->toBe("view-division-{$division->getKey()}"); +}); +it('returns the permission to edit a division', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::editDivision($division))->toBe("edit-division-{$division->getKey()}"); +}); +it('returns the permission to delete a division', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::deleteDivision($division))->toBe("delete-division-{$division->getKey()}"); +}); +it('returns the permission to view fixtures', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::viewFixtures($division))->toBe("view-fixtures-in-division-{$division->getKey()}"); +}); +it('returns the permission to add a fixture', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::addFixtures($division))->toBe("add-fixtures-in-division-{$division->getKey()}"); +}); +it('returns the permission to edit a fixture', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::editFixtures($division))->toBe("edit-fixtures-in-division-{$division->getKey()}"); +}); +it('returns the permission to delete a fixture', function () { + $division = Division::factory()->create(); + + expect(PermissionsHelper::deleteFixtures($division))->toBe("delete-fixtures-in-division-{$division->getKey()}"); +}); +it('returns the permission to add a club', function () { + expect(PermissionsHelper::addClub())->toBe('add-club'); +}); +it('returns the permission to view a club', function () { + $club = Club::factory()->create(); + + expect(PermissionsHelper::viewClub($club))->toBe("view-club-{$club->getKey()}"); +}); +it('returns the permission to edit a club', function () { + $club = Club::factory()->create(); + + expect(PermissionsHelper::editClub($club))->toBe("edit-club-{$club->getKey()}"); +}); +it('returns the permission to delete a club', function () { + $club = Club::factory()->create(); + + expect(PermissionsHelper::deleteClub($club))->toBe("delete-club-{$club->getKey()}"); +}); +it('returns the permission to add a team', function () { + $club = Club::factory()->create(); + + expect(PermissionsHelper::addTeam($club))->toBe("add-team-in-club-{$club->getKey()}"); +}); +it('returns the permission to view a team', function () { + $team = Team::factory()->create(); + + expect(PermissionsHelper::viewTeam($team))->toBe("view-team-{$team->getKey()}"); +}); +it('returns the permission to edit a team', function () { + $team = Team::factory()->create(); + + expect(PermissionsHelper::editTeam($team))->toBe("edit-team-{$team->getKey()}"); +}); +it('returns the permission to delete a team', function () { + $team = Team::factory()->create(); + + expect(PermissionsHelper::deleteTeam($team))->toBe("delete-team-{$team->getKey()}"); +}); diff --git a/tests/Integration/Helpers/RolesHelperTest.php b/tests/Integration/Helpers/RolesHelperTest.php new file mode 100644 index 00000000..adac31c2 --- /dev/null +++ b/tests/Integration/Helpers/RolesHelperTest.php @@ -0,0 +1,204 @@ +create(); + $seasonId = $season->getKey(); + + expect(RolesHelper::seasonAdmin($season))->toBe("Season $seasonId Administrator"); +}); + +it('can check if the role is season administrator', function () { + $seasonAdmin = Role::create(['name' => 'Season 1 Administrator']); + $siteAdmin = Role::create(['name' => 'Useless Role']); + + expect(RolesHelper::isSeasonAdmin($seasonAdmin))->toBeTrue() + ->and(RolesHelper::isSeasonAdmin($siteAdmin))->toBeFalse(); +}); + +it('gets the season from the season administrator role', function () { + /** @var Season $season */ + $season = Season::factory()->create(); + $seasonId = $season->getKey(); + + /** @var Role $role */ + $role = Role::findByName("Season $seasonId Administrator"); + + expect(RolesHelper::findSeason($role)->is($season))->toBeTrue(); +}); + +it('does not get the season if the season does not exist', function () { + $role = Role::create(['name' => 'Season 1 Administrator']); + + expect(RolesHelper::findSeason($role))->toBeNull(); +}); + +it('does not get the season if the role is not season administrator', function () { + $role = Role::create(['name' => 'Competition 1 Administrator']); + + expect(RolesHelper::findSeason($role))->toBeNull(); +}); + +it('returns the competition administrator role name', function () { + /** @var Competition $competition */ + $competition = Competition::factory()->create(); + $competitionId = $competition->getKey(); + + expect(RolesHelper::competitionAdmin($competition))->toBe("Competition $competitionId Administrator"); +}); + +it('can check if the role is competition administrator', function () { + $competitionAdmin = Role::create(['name' => 'Competition 1 Administrator']); + $siteAdmin = Role::create(['name' => 'Useless Role']); + + expect(RolesHelper::isCompetitionAdmin($competitionAdmin))->toBeTrue() + ->and(RolesHelper::isCompetitionAdmin($siteAdmin))->toBeFalse(); +}); + +it('gets the competition from the competition administrator role', function () { + /** @var Competition $competition */ + $competition = Competition::factory()->create(); + $competitionId = $competition->getKey(); + + /** @var Role $role */ + $role = Role::findByName("Competition $competitionId Administrator"); + + expect(RolesHelper::findCompetition($role)->is($competition))->toBeTrue(); +}); + +it('does not get the competition if the competition does not exist', function () { + $role = Role::create(['name' => 'Competition 1 Administrator']); + + expect(RolesHelper::findCompetition($role))->toBeNull(); +}); + +it('does not get the competition if the role is not competition administrator', function () { + $role = Role::create(['name' => 'Season 1 Administrator']); + + expect(RolesHelper::findCompetition($role))->toBeNull(); +}); + +it('returns the division administrator role name', function () { + /** @var Division $division */ + $division = Division::factory()->create(); + $divisionId = $division->getKey(); + + expect(RolesHelper::divisionAdmin($division))->toBe("Division $divisionId Administrator"); +}); + +it('can check if the role is division administrator', function () { + $divisionAdministrator = Role::create(['name' => 'Division 1 Administrator']); + $siteAdmin = Role::create(['name' => 'Useless Role']); + + expect(RolesHelper::isDivisionAdmin($divisionAdministrator))->toBeTrue() + ->and(RolesHelper::isDivisionAdmin($siteAdmin))->toBeFalse(); +}); + +it('gets the division from the division administrator role', function () { + /** @var Division $division */ + $division = Division::factory()->create(); + $divisionId = $division->getKey(); + + /** @var Role $role */ + $role = Role::findByName("Division $divisionId Administrator"); + + expect(RolesHelper::findDivision($role)->is($division))->toBeTrue(); +}); + +it('does not get the division if the division does not exist', function () { + $role = Role::create(['name' => 'Division 1 Administrator']); + + expect(RolesHelper::findDivision($role))->toBeNull(); +}); + +it('does not get the division if the role is not division administrator', function () { + $role = Role::create(['name' => 'Season 1 Administrator']); + + expect(RolesHelper::findDivision($role))->toBeNull(); +}); + +it('returns the club secretary role name', function () { + /** @var Club $club */ + $club = Club::factory()->create(); + $clubId = $club->getKey(); + + expect(RolesHelper::clubSecretary($club))->toBe("Club $clubId Secretary"); +}); + +it('can check if the role is club secretary', function () { + $clubSecretary = Role::create(['name' => 'Club 1 Secretary']); + $siteAdmin = Role::create(['name' => 'Useless Role']); + + expect(RolesHelper::isClubSecretary($clubSecretary))->toBeTrue() + ->and(RolesHelper::isClubSecretary($siteAdmin))->toBeFalse(); +}); + +it('gets the club from the club secretary role', function () { + /** @var Club $club */ + $club = Club::factory()->create(); + $clubId = $club->getKey(); + + /** @var Role $role */ + $role = Role::findByName("Club $clubId Secretary"); + + expect(RolesHelper::findClub($role)->is($club))->toBeTrue(); +}); + +it('does not get the club if the club does not exist', function () { + $role = Role::create(['name' => 'Club 1 Secretary']); + + expect(RolesHelper::findClub($role))->toBeNull(); +}); + +it('does not get the club if the role is not club secretary', function () { + $role = Role::create(['name' => 'Season 1 Administrator']); + + expect(RolesHelper::findClub($role))->toBeNull(); +}); + +it('returns the team secretary role name', function () { + /** @var Team $team */ + $team = Team::factory()->create(); + $teamId = $team->getKey(); + + expect(RolesHelper::teamSecretary($team))->toBe("Team $teamId Secretary"); +}); + +it('can check if the role is team secretary', function () { + $teamSecretary = Role::create(['name' => 'Team 1 Secretary']); + $siteAdmin = Role::create(['name' => 'Useless Role']); + + expect(RolesHelper::isTeamSecretary($teamSecretary))->toBeTrue() + ->and(RolesHelper::isTeamSecretary($siteAdmin))->toBeFalse(); +}); + +it('gets the team from the team secretary role', function () { + /** @var Team $team */ + $team = Team::factory()->create(); + $teamId = $team->getKey(); + + /** @var Role $role */ + $role = Role::findByName("Team $teamId Secretary"); + + expect(RolesHelper::findTeam($role)->is($team))->toBeTrue(); +}); + +it('does not get the team if the team does not exist', function () { + $role = Role::create(['name' => 'Team 1 Secretary']); + + expect(RolesHelper::findTeam($role))->toBeNull(); +}); + +it('does not get the team if the role is not team secretary', function () { + $role = Role::create(['name' => 'Season 1 Administrator']); + + expect(RolesHelper::findTeam($role))->toBeNull(); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 00000000..84cafcb8 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +extend(Tests\TestCase::class)->in('Unit', 'Integration', 'Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2f..6699f00d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,35 @@ namespace Tests; +use App\Helpers\RolesHelper; +use App\Models\User; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; +use Illuminate\Support\Collection; +use Spatie\Permission\Models\Role; abstract class TestCase extends BaseTestCase { - // + use RefreshDatabase; + + protected User $siteAdmin; + + protected function setUp(): void + { + parent::setUp(); + + Role::create(['name' => RolesHelper::SITE_ADMIN]); + + $this->siteAdmin = $this->userWithRole(RolesHelper::SITE_ADMIN); + } + + protected function keyArrayBy(array $items, string $key): array + { + return Collection::make($items)->keyBy($key)->toArray(); + } + + protected function userWithRole(string $role): User + { + return User::factory()->create()->assignRole($role); + } } diff --git a/tests/Unit/CreatePermissionsJobsTest.php b/tests/Unit/CreatePermissionsJobsTest.php new file mode 100644 index 00000000..d1e041d6 --- /dev/null +++ b/tests/Unit/CreatePermissionsJobsTest.php @@ -0,0 +1,99 @@ +numberBetween(1, 1_000); + $season = Mockery::mock(Season::class) + ->shouldReceive('getKey')->andReturn($seasonId) + ->getMock(); + + $sut = new CreateSeasonPermissions($season); + + $sut->handle(); + + $this->assertDatabaseCount('permissions', 4); + $this->assertDatabaseHas('permissions', ['name' => "view-season-$seasonId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-season-$seasonId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-season-$seasonId"]); + $this->assertDatabaseHas('permissions', ['name' => "add-competition-in-season-$seasonId"]); +}); + +it('creates the competition permissions', function () { + $competitionId = fake()->numberBetween(1, 1_000); + $competition = Mockery::mock(Competition::class) + ->shouldReceive('getKey')->andReturn($competitionId) + ->getMock(); + + $sut = new CreateCompetitionPermissions($competition); + + $sut->handle(); + + $this->assertDatabaseCount('permissions', 4); + $this->assertDatabaseHas('permissions', ['name' => "view-competition-$competitionId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-competition-$competitionId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-competition-$competitionId"]); + $this->assertDatabaseHas('permissions', ['name' => "add-division-in-competition-$competitionId"]); +}); + +it('creates the division permissions', function () { + $divisionId = fake()->numberBetween(1, 1_000); + $division = Mockery::mock(Division::class) + ->shouldReceive('getKey')->andReturn($divisionId) + ->getMock(); + + $sut = new CreateDivisionPermissions($division); + + $sut->handle(); + + $this->assertDatabaseCount('permissions', 7); + $this->assertDatabaseHas('permissions', ['name' => "view-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "add-fixtures-in-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-fixtures-in-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-fixtures-in-division-$divisionId"]); + $this->assertDatabaseHas('permissions', ['name' => "view-fixtures-in-division-$divisionId"]); +}); + +it('creates the club permissions', function () { + $clubId = fake()->numberBetween(1, 1_000); + $club = Mockery::mock(Club::class) + ->shouldReceive('getKey')->andReturn($clubId) + ->getMock(); + + $sut = new CreateClubPermissions($club); + + $sut->handle(); + + $this->assertDatabaseCount('permissions', 4); + $this->assertDatabaseHas('permissions', ['name' => "view-club-$clubId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-club-$clubId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-club-$clubId"]); + $this->assertDatabaseHas('permissions', ['name' => "add-team-in-club-$clubId"]); +}); + +it('creates the team permissions', function () { + $teamId = fake()->numberBetween(1, 1_000); + $team = Mockery::mock(Team::class) + ->shouldReceive('getKey')->andReturn($teamId) + ->getMock(); + + $sut = new CreateTeamPermissions($team); + + $sut->handle(); + + $this->assertDatabaseCount('permissions', 3); + $this->assertDatabaseHas('permissions', ['name' => "view-team-$teamId"]); + $this->assertDatabaseHas('permissions', ['name' => "edit-team-$teamId"]); + $this->assertDatabaseHas('permissions', ['name' => "delete-team-$teamId"]); +}); diff --git a/tests/Unit/CreateRolesJobsTest.php b/tests/Unit/CreateRolesJobsTest.php new file mode 100644 index 00000000..f860c3b7 --- /dev/null +++ b/tests/Unit/CreateRolesJobsTest.php @@ -0,0 +1,77 @@ +numberBetween(1, 1_000); + $season = Mockery::mock(Season::class) + ->shouldReceive('getKey')->andReturn($seasonId) + ->getMock(); + + $sut = new CreateSeasonAdminRole($season); + + $sut->handle(); + + $this->assertDatabaseHas('roles', ['name' => "Season $seasonId Administrator"]); +}); + +it('creates the competition admin role', function () { + $competitionId = fake()->numberBetween(1, 1_000); + $competition = Mockery::mock(Competition::class) + ->shouldReceive('getKey')->andReturn($competitionId) + ->getMock(); + + $sut = new CreateCompetitionAdminRole($competition); + + $sut->handle(); + + $this->assertDatabaseHas('roles', ['name' => "Competition $competitionId Administrator"]); +}); + +it('creates the division admin role', function () { + $divisionId = fake()->numberBetween(1, 1_000); + $division = Mockery::mock(Division::class) + ->shouldReceive('getKey')->andReturn($divisionId) + ->getMock(); + + $sut = new CreateDivisionAdminRole($division); + + $sut->handle(); + + $this->assertDatabaseHas('roles', ['name' => "Division $divisionId Administrator"]); +}); + +it('creates the club secretary role', function () { + $clubId = fake()->numberBetween(1, 1_000); + $club = Mockery::mock(Club::class) + ->shouldReceive('getKey')->andReturn($clubId) + ->getMock(); + + $sut = new CreateClubSecretaryRole($club); + + $sut->handle(); + + $this->assertDatabaseHas('roles', ['name' => "Club $clubId Secretary"]); +}); + +it('creates the team secretary role', function () { + $teamId = fake()->numberBetween(1, 1_000); + $team = Mockery::mock(Team::class) + ->shouldReceive('getKey')->andReturn($teamId) + ->getMock(); + + $sut = new CreateTeamSecretaryRole($team); + + $sut->handle(); + + $this->assertDatabaseHas('roles', ['name' => "Team $teamId Secretary"]); +}); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 5773b0ce..00000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertTrue(true); - } -}