diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0cbe762 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + package-tests: + + runs-on: ubuntu-latest + + steps: + - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e + with: + php-version: '8.1' + - uses: actions/checkout@v3 + - name: Install Dependencies + run: composer update && composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist && composer dump-autoload + - name: Execute tests (Unit and Feature tests) via PEST + run: vendor/bin/pest diff --git a/.gitignore b/.gitignore index fbc2c3f..aacfb3f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ composer.lock node_modules/ vendor/ +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json index 17dcb14..d972dd0 100644 --- a/composer.json +++ b/composer.json @@ -25,14 +25,16 @@ "require-dev": { "fakerphp/faker": "^1.9.1", "laravel/pint": "^1.0", - "mockery/mockery": "^1.4.4", - "nunomaduro/collision": "^6.4", - "pestphp/pest": "^1.22", - "pestphp/pest-plugin-laravel": "^1.4", - "spatie/laravel-ignition": "^1.0", - "phpunit/phpunit": "^9.6", - "pestphp/pest-plugin-mock": "^1.0", - "orchestra/testbench": "^7.22" + "mockery/mockery": "^1.5", + "nunomaduro/collision": "^7.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-laravel": "^2.0", + "spatie/laravel-ignition": "^2.0", + "phpunit/phpunit": "^10.0", + "pestphp/pest-plugin-mock": "^2.0", + "orchestra/testbench": "^8.0", + "spatie/laravel-ray": "^1.32", + "nunomaduro/larastan": "^2.0" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..b11c7eb --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,18 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon + +parameters: + + paths: + - src/ + + # Level 9 is the highest level + level: 5 + +# ignoreErrors: +# - '#PHPDoc tag @var#' +# +# excludePaths: +# - ./*/*/FileToBeExcluded.php +# +# checkMissingIterableValueType: false \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..fb5ed85 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + ./tests + + + + + + + + + + ./functions + ./src + + + diff --git a/phpunit.xml.bak b/phpunit.xml.bak new file mode 100644 index 0000000..ae550e1 --- /dev/null +++ b/phpunit.xml.bak @@ -0,0 +1,22 @@ + + + + + ./tests + + + + + ./functions + ./src + + + + + + + diff --git a/src/Authenticator.php b/src/Authenticator.php index 5f2e5a3..4b2b072 100644 --- a/src/Authenticator.php +++ b/src/Authenticator.php @@ -64,9 +64,11 @@ class Authenticator * * @param string $name The name of the auth */ - public function __construct(private readonly string $name) + public function __construct(private readonly string $name, Collection $parameters = null) { - $this->parameters = Collection::make([ + $parameters = $parameters ?? new Collection(); + + $this->parameters = $parameters->merge([ 'enabled' => true, 'registration_enabled' => true, 'forgot_password_enabled' => true, diff --git a/src/Registrators/Registrator.php b/src/Builder.php similarity index 67% rename from src/Registrators/Registrator.php rename to src/Builder.php index 5fec40c..5fdee3b 100644 --- a/src/Registrators/Registrator.php +++ b/src/Builder.php @@ -1,10 +1,13 @@ registrators) - ->map(function ($registrator, $category) use ($authenticator, $name) { - return new $registrator($authenticator, $category); - })->map(function (RegistratorInterface $registrator) { - $registrator->boot(); + ->map(function ($registrator) use ($authenticator) { + + /** + * Build the registrator. + * + * @var AbstractRegistrator $registrator + */ + $registrator = $this->app->make($registrator); + $registrator->withAuthName($authenticator->name()); + + /** + * Merge all parameters from the registrator into the authenticator + */ + $authenticator->merge( + $registrator->collectAndMergeParameters() + ); + + return $registrator; + })->map(function (AbstractRegistrator $registrator) use ($authenticator) { + + /** + * Boot the registrator + */ + $registrator->boot($authenticator->parameters); }); /** diff --git a/src/Contracts/AbstractRegistrator.php b/src/Contracts/AbstractRegistrator.php new file mode 100644 index 0000000..bda1b09 --- /dev/null +++ b/src/Contracts/AbstractRegistrator.php @@ -0,0 +1,52 @@ +authName = $authName; + + return $this; + } + + /** + * This function will collect and merge all parameters inside the provided Authenticator + * @see Authenticator::merge() + */ + public abstract function collectAndMergeParameters(): Collection; + + /** + * Set of actions to be performed when booting the auth. + * @param Collection $allParameters All the parameters gathered by the authentication system + */ + public abstract function boot(Collection $allParameters): void; +} diff --git a/src/Contracts/RegistratorInterface.php b/src/Contracts/RegistratorInterface.php deleted file mode 100644 index 19b6b07..0000000 --- a/src/Contracts/RegistratorInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - $this->faker->name, + 'email' => $this->faker->unique()->safeEmail, + 'email_verified_at' => now(), + 'password' => bcrypt('password'), + ]; + } +} \ No newline at end of file diff --git a/src/Http/Middleware/InjectIntoApplication.php b/src/Http/Middleware/InjectIntoApplication.php index 457b367..4cf49a3 100644 --- a/src/Http/Middleware/InjectIntoApplication.php +++ b/src/Http/Middleware/InjectIntoApplication.php @@ -3,8 +3,8 @@ namespace Illegal\InsideAuth\Http\Middleware; use Closure; +use Illegal\InsideAuth\Builder; use Illegal\InsideAuth\InsideAuth; -use Illegal\InsideAuth\Registrators\Registrator; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; @@ -12,7 +12,7 @@ * This class will inject the current authenticator into: * - The request * - The current property of the Registrator / InsideAuth facade - * @see Registrator::current() + * @see Builder::current() * @see InsideAuth::current() */ class InjectIntoApplication diff --git a/src/InsideAuth.php b/src/InsideAuth.php index 9b0b651..ec209d3 100644 --- a/src/InsideAuth.php +++ b/src/InsideAuth.php @@ -2,16 +2,15 @@ namespace Illegal\InsideAuth; -use Illegal\InsideAuth\Registrators\Registrator; use Illuminate\Support\Facades\Facade; /** - * @mixin Registrator + * @mixin Builder */ class InsideAuth extends Facade { public static function getFacadeAccessor(): string { - return Registrator::class; + return Builder::class; } } diff --git a/src/Models/User.php b/src/Models/User.php index 256ada7..8f7a644 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -3,6 +3,7 @@ namespace Illegal\InsideAuth\Models; use Illegal\InsideAuth\Events\UserDeleted; +use Illegal\InsideAuth\Factories\UserFactory; use Illegal\InsideAuth\InsideAuth; use Illegal\LaravelUtils\Contracts\HasPrefix; use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; @@ -125,4 +126,12 @@ public function sendEmailVerificationNotification(): void $this->notify($notification); } + + /** + * Create a new factory instance for the model. + */ + protected static function newFactory(): UserFactory + { + return UserFactory::new(); + } } diff --git a/src/Providers/ServiceProvider.php b/src/Providers/ServiceProvider.php index d6a593d..ef78372 100644 --- a/src/Providers/ServiceProvider.php +++ b/src/Providers/ServiceProvider.php @@ -2,8 +2,11 @@ namespace Illegal\InsideAuth\Providers; +use Illegal\InsideAuth\Builder; use Illegal\InsideAuth\Passwords\PasswordBrokerManager; -use Illegal\InsideAuth\Registrators\Registrator; +use Illegal\InsideAuth\Registrators\MiddlewareRegistrator; +use Illegal\InsideAuth\Registrators\RouteRegistrator; +use Illegal\InsideAuth\Registrators\SecurityRegistrator; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; @@ -15,6 +18,7 @@ class ServiceProvider extends IlluminateServiceProvider public function register(): void { $this->registerSingletons(); + $this->bindServices(); } /** @@ -43,8 +47,26 @@ private function registerSingletons(): void /** * The Registrator class, used by the InsideAuth facade */ - $this->app->singleton(Registrator::class, function (Application $app) { - return new Registrator(); + $this->app->singleton(Builder::class, function (Application $app) { + return new Builder($app); + }); + } + + /** + * Bind the services to the DI + */ + private function bindServices(): void + { + $this->app->bind(MiddlewareRegistrator::class, function(Application $app) { + return new MiddlewareRegistrator($app->make('config'), $app->make('router')); + }); + + $this->app->bind(RouteRegistrator::class, function(Application $app) { + return new RouteRegistrator($app->make('config'), $app->make('router')); + }); + + $this->app->bind(SecurityRegistrator::class, function(Application $app) { + return new SecurityRegistrator($app->make('config'), $app->make('router'), config('inside_auth.db.prefix')); }); } diff --git a/src/Registrators/MiddlewareRegistrator.php b/src/Registrators/MiddlewareRegistrator.php index 08dd4c6..0b69f78 100644 --- a/src/Registrators/MiddlewareRegistrator.php +++ b/src/Registrators/MiddlewareRegistrator.php @@ -2,8 +2,7 @@ namespace Illegal\InsideAuth\Registrators; -use Illegal\InsideAuth\Authenticator; -use Illegal\InsideAuth\Contracts\RegistratorInterface; +use Illegal\InsideAuth\Contracts\AbstractRegistrator; use Illegal\InsideAuth\Http\Middleware\Authenticate; use Illegal\InsideAuth\Http\Middleware\EnsureAuthIsEnabled; use Illegal\InsideAuth\Http\Middleware\EnsureEmailIsVerified; @@ -15,7 +14,6 @@ use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Route; use Illuminate\View\Middleware\ShareErrorsFromSession; /** @@ -29,51 +27,56 @@ * @property string $inject * @property string $redirect_authenticated */ -class MiddlewareRegistrator implements RegistratorInterface +class MiddlewareRegistrator extends AbstractRegistrator { /** * The parameters collection, this will be merged inside the Authenticator, using the prefix */ private Collection $parameters; + /** + * @inheritdoc + */ + protected string $prefix = 'middleware'; + /** * @inheritDoc */ - public function __construct(private readonly Authenticator $authenticator, string $prefix = 'middleware') + public function __get($key) + { + return $this->parameters->get($key); + } + + /** + * @inheritDoc + */ + public function collectAndMergeParameters(): Collection { $this->parameters = collect([ - 'verified' => $this->authenticator->name() . '-verified', - 'guest' => $this->authenticator->name() . '-guest', - 'logged_in' => $this->authenticator->name() . '-logged', - 'web' => $this->authenticator->name() . '-web', - 'authenticated' => $this->authenticator->name() . '-authenticated', - 'ensure_verified' => $this->authenticator->name() . '-ensure-email-is-verified', - 'ensure_enabled' => $this->authenticator->name() . '-ensure-auth-is-enabled', - 'inject' => $this->authenticator->name() . '-inject', - 'redirect_authenticated' => $this->authenticator->name() . '-redirect-if-authenticated', + 'verified' => $this->authName . '-verified', + 'guest' => $this->authName . '-guest', + 'logged_in' => $this->authName . '-logged', + 'web' => $this->authName . '-web', + 'authenticated' => $this->authName . '-authenticated', + 'ensure_verified' => $this->authName . '-ensure-email-is-verified', + 'ensure_enabled' => $this->authName . '-ensure-auth-is-enabled', + 'inject' => $this->authName . '-inject', + 'redirect_authenticated' => $this->authName . '-redirect-if-authenticated', ]); - $this->authenticator->merge($this->parameters->except([ + return $this->parameters->except([ 'authenticated', 'ensure_verified', 'ensure_enabled', 'inject', 'redirect_authenticated' - ])->mapWithKeys(fn($value, $key) => [$prefix . '_' . $key => $value])); - } - - /** - * @inheritDoc - */ - public function __get($key) - { - return $this->parameters->get($key); + ])->mapWithKeys(fn($value, $key) => [$this->prefix . '_' . $key => $value]); } /** * @inheritDoc */ - public function boot(): void + public function boot(Collection $allParameters): void { /** * Aliases for the base middlewares @@ -82,16 +85,16 @@ public function boot(): void * redirect_authenticated: Redirect the user if he is already authenticated * inject: Inject the authenticator into the request */ - Route::aliasMiddleware($this->authenticated, Authenticate::class); - Route::aliasMiddleware($this->ensure_verified, EnsureEmailIsVerified::class); - Route::aliasMiddleware($this->ensure_enabled, EnsureAuthIsEnabled::class); - Route::aliasMiddleware($this->redirect_authenticated, RedirectIfAuthenticated::class); - Route::aliasMiddleware($this->inject, InjectIntoApplication::class); + $this->router->aliasMiddleware($this->authenticated, Authenticate::class); + $this->router->aliasMiddleware($this->ensure_verified, EnsureEmailIsVerified::class); + $this->router->aliasMiddleware($this->ensure_enabled, EnsureAuthIsEnabled::class); + $this->router->aliasMiddleware($this->redirect_authenticated, RedirectIfAuthenticated::class); + $this->router->aliasMiddleware($this->inject, InjectIntoApplication::class); /** * The web middleware, to be used on all web routes */ - Route::middlewareGroup($this->web, [ + $this->router->middlewareGroup($this->web, [ EncryptCookies::class, // From Laravel AddQueuedCookiesToResponse::class, // From Laravel StartSession::class, // From Laravel @@ -103,8 +106,8 @@ public function boot(): void /** * The guest middleware. Authenticated users will be redirected. */ - Route::middlewareGroup($this->guest, [ - $this->inject . ':' . $this->authenticator->name(), + $this->router->middlewareGroup($this->guest, [ + $this->inject . ':' . $this->authName, $this->ensure_enabled, $this->redirect_authenticated ]); @@ -113,17 +116,17 @@ public function boot(): void * The logged in middleware, we just check that the user is logged in. * It's not necessary that the user is also verified. */ - Route::middlewareGroup($this->logged_in, [ - $this->inject . ':' . $this->authenticator->name(), + $this->router->middlewareGroup($this->logged_in, [ + $this->inject . ':' . $this->authName, $this->ensure_enabled, - $this->authenticated . ':' . $this->authenticator->route_login . ',' . $this->authenticator->security_guard + $this->authenticated . ':' . $allParameters->get('route_login') . ',' . $allParameters->get('security_guard') ]); /** * The main middleware group for the application. * Checks that the user is logged in and that is verified. */ - Route::middlewareGroup($this->verified, [ + $this->router->middlewareGroup($this->verified, [ $this->logged_in, $this->ensure_verified ]); diff --git a/src/Registrators/RouteRegistrator.php b/src/Registrators/RouteRegistrator.php index 5d20b07..40eb8b1 100644 --- a/src/Registrators/RouteRegistrator.php +++ b/src/Registrators/RouteRegistrator.php @@ -2,8 +2,7 @@ namespace Illegal\InsideAuth\Registrators; -use Illegal\InsideAuth\Authenticator; -use Illegal\InsideAuth\Contracts\RegistratorInterface; +use Illegal\InsideAuth\Contracts\AbstractRegistrator; use Illegal\InsideAuth\Http\Controllers\AuthenticatedSessionController; use Illegal\InsideAuth\Http\Controllers\ConfirmablePasswordController; use Illegal\InsideAuth\Http\Controllers\EmailVerificationNotificationController; @@ -19,7 +18,6 @@ use Illegal\InsideAuth\Http\Middleware\EnsureRegistrationIsEnabled; use Illegal\InsideAuth\Http\Middleware\EnsureUserProfileIsEnabled; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Route; /** * @property string $login @@ -38,57 +36,62 @@ * @property string $profile_update * @property string $profile_destroy */ -class RouteRegistrator implements RegistratorInterface +class RouteRegistrator extends AbstractRegistrator { /** * The parameters collection, this will be merged inside the Authenticator, using the prefix */ private Collection $parameters; + /** + * @inheritdoc + */ + protected string $prefix = 'route'; + /** * @inheritDoc */ - public function __construct(private readonly Authenticator $authenticator, string $prefix = 'route') + public function __get(string $key): string { - $this->parameters = collect([ - 'login' => $this->authenticator->name() . '.auth.login', - 'register' => $this->authenticator->name() . '.auth.register', - 'password_request' => $this->authenticator->name() . '.auth.password.request', - 'password_email' => $this->authenticator->name() . '.auth.password.email', - 'password_reset' => $this->authenticator->name() . '.auth.password.reset', - 'password_store' => $this->authenticator->name() . '.auth.password.store', - 'logout' => $this->authenticator->name() . '.auth.logout', - 'verification_notice' => $this->authenticator->name() . '.auth.verification.notice', - 'verification_verify' => $this->authenticator->name() . '.auth.verification.verify', - 'verification_send' => $this->authenticator->name() . '.auth.verification.send', - 'password_confirm' => $this->authenticator->name() . '.auth.password.confirm', - 'password_update' => $this->authenticator->name() . '.auth.password.update', - 'profile_edit' => $this->authenticator->name() . '.auth.profile.edit', - 'profile_update' => $this->authenticator->name() . '.auth.profile.update', - 'profile_destroy' => $this->authenticator->name() . '.auth.profile.destroy' - ]); - - $this->authenticator->merge($this->parameters->mapWithKeys(fn($value, $key) => [$prefix . '_' . $key => $value])); + return $this->parameters->get($key); } /** * @inheritDoc */ - public function __get(string $key): string + public function collectAndMergeParameters(): Collection { - return $this->parameters->get($key); + $this->parameters = collect([ + 'login' => $this->authName . '.auth.login', + 'register' => $this->authName . '.auth.register', + 'password_request' => $this->authName . '.auth.password.request', + 'password_email' => $this->authName . '.auth.password.email', + 'password_reset' => $this->authName . '.auth.password.reset', + 'password_store' => $this->authName . '.auth.password.store', + 'logout' => $this->authName . '.auth.logout', + 'verification_notice' => $this->authName . '.auth.verification.notice', + 'verification_verify' => $this->authName . '.auth.verification.verify', + 'verification_send' => $this->authName . '.auth.verification.send', + 'password_confirm' => $this->authName . '.auth.password.confirm', + 'password_update' => $this->authName . '.auth.password.update', + 'profile_edit' => $this->authName . '.auth.profile.edit', + 'profile_update' => $this->authName . '.auth.profile.update', + 'profile_destroy' => $this->authName . '.auth.profile.destroy' + ]); + + return $this->parameters->mapWithKeys(fn($value, $key) => [$this->prefix . '_' . $key => $value]); } /** * @inheritDoc */ - public function boot(): void + public function boot(Collection $allParameters): void { - Route::prefix($this->authenticator->name())->middleware($this->authenticator->middleware_web)->group(function () { + $this->router->middleware($allParameters->get('middleware_web'))->prefix($this->authName)->group(function () use ($allParameters) { /** * Routes that are accessible to guests users */ - Route::middleware($this->authenticator->middleware_guest)->group(function () { + $this->router->middleware($allParameters->get('middleware_guest'))->group(function () { $this->registerLoginRoutes(); $this->registerRegisterRoutes(); $this->registerForgotPasswordRoutes(); @@ -97,7 +100,7 @@ public function boot(): void /** * Routes that are accessible to logged in users, verified or not */ - Route::middleware($this->authenticator->middleware_logged_in)->group(function () { + $this->router->middleware($allParameters->get('middleware_logged_in'))->group(function () { $this->registerLogoutRoutes(); $this->registerEmailVerificationRoutes(); }); @@ -105,7 +108,7 @@ public function boot(): void /** * Routes that are accessible to logged in and verified users */ - Route::middleware($this->authenticator->middleware_verified)->group(function () { + $this->router->middleware($allParameters->get('middleware_verified'))->group(function () { $this->registerProfileRoutes(); }); }); @@ -116,8 +119,8 @@ public function boot(): void */ private function registerLoginRoutes(): void { - Route::get('login', [AuthenticatedSessionController::class, 'create'])->name($this->login); - Route::post('login', [AuthenticatedSessionController::class, 'store']); + $this->router->get('login', [AuthenticatedSessionController::class, 'create'])->name($this->login); + $this->router->post('login', [AuthenticatedSessionController::class, 'store']); } /** @@ -125,9 +128,9 @@ private function registerLoginRoutes(): void */ private function registerRegisterRoutes(): void { - Route::middleware(EnsureRegistrationIsEnabled::class)->group(function () { - Route::get('register', [RegisteredUserController::class, 'create'])->name($this->register); - Route::post('register', [RegisteredUserController::class, 'store']); + $this->router->middleware(EnsureRegistrationIsEnabled::class)->group(function () { + $this->router->get('register', [RegisteredUserController::class, 'create'])->name($this->register); + $this->router->post('register', [RegisteredUserController::class, 'store']); }); } @@ -136,12 +139,12 @@ private function registerRegisterRoutes(): void */ private function registerForgotPasswordRoutes(): void { - Route::middleware(EnsureForgotPasswordIsEnabled::class)->group(function () { - Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])->name($this->password_request); - Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])->name($this->password_email); + $this->router->middleware(EnsureForgotPasswordIsEnabled::class)->group(function () { + $this->router->get('forgot-password', [PasswordResetLinkController::class, 'create'])->name($this->password_request); + $this->router->post('forgot-password', [PasswordResetLinkController::class, 'store'])->name($this->password_email); - Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])->name($this->password_reset); - Route::post('reset-password', [NewPasswordController::class, 'store'])->name($this->password_store); + $this->router->get('reset-password/{token}', [NewPasswordController::class, 'create'])->name($this->password_reset); + $this->router->post('reset-password', [NewPasswordController::class, 'store'])->name($this->password_store); }); } @@ -150,7 +153,7 @@ private function registerForgotPasswordRoutes(): void */ private function registerLogoutRoutes(): void { - Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name($this->logout); + $this->router->post('logout', [AuthenticatedSessionController::class, 'destroy'])->name($this->logout); } /** @@ -158,20 +161,20 @@ private function registerLogoutRoutes(): void */ public function registerEmailVerificationRoutes(): void { - Route::middleware(EnsureEmailVerificationIsEnabled::class)->group(function () { - Route::get('verify-email', EmailVerificationPromptController::class)->name($this->verification_notice); - Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) + $this->router->middleware(EnsureEmailVerificationIsEnabled::class)->group(function () { + $this->router->get('verify-email', EmailVerificationPromptController::class)->name($this->verification_notice); + $this->router->get('verify-email/{id}/{hash}', VerifyEmailController::class) ->middleware(['signed', 'throttle:6,1']) ->name($this->verification_verify); - Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) + $this->router->post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) ->middleware('throttle:6,1') ->name($this->verification_send); - Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])->name($this->password_confirm); - Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); + $this->router->get('confirm-password', [ConfirmablePasswordController::class, 'show'])->name($this->password_confirm); + $this->router->post('confirm-password', [ConfirmablePasswordController::class, 'store']); - Route::put('password', [PasswordController::class, 'update'])->name($this->password_update); + $this->router->put('password', [PasswordController::class, 'update'])->name($this->password_update); }); } @@ -180,10 +183,10 @@ public function registerEmailVerificationRoutes(): void */ public function registerProfileRoutes(): void { - Route::middleware(EnsureUserProfileIsEnabled::class)->group(function () { - Route::get('/profile', [ProfileController::class, 'edit'])->name($this->profile_edit); - Route::patch('/profile', [ProfileController::class, 'update'])->name($this->profile_update); - Route::delete('/profile', [ProfileController::class, 'destroy'])->name($this->profile_destroy); + $this->router->middleware(EnsureUserProfileIsEnabled::class)->group(function () { + $this->router->get('/profile', [ProfileController::class, 'edit'])->name($this->profile_edit); + $this->router->patch('/profile', [ProfileController::class, 'update'])->name($this->profile_update); + $this->router->delete('/profile', [ProfileController::class, 'destroy'])->name($this->profile_destroy); }); } } diff --git a/src/Registrators/SecurityRegistrator.php b/src/Registrators/SecurityRegistrator.php index 5c996da..3a81ff1 100644 --- a/src/Registrators/SecurityRegistrator.php +++ b/src/Registrators/SecurityRegistrator.php @@ -2,18 +2,18 @@ namespace Illegal\InsideAuth\Registrators; -use Illegal\InsideAuth\Authenticator; -use Illegal\InsideAuth\Contracts\RegistratorInterface; +use Illegal\InsideAuth\Contracts\AbstractRegistrator; use Illegal\InsideAuth\Models\User; +use Illuminate\Config\Repository; +use Illuminate\Routing\Router; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Config; /** * @property string $guard * @property string $provider * @property string $password_broker */ -class SecurityRegistrator implements RegistratorInterface +class SecurityRegistrator extends AbstractRegistrator { /** * The parameters collection, this will be merged inside the Authenticator, using the prefix @@ -21,17 +21,19 @@ class SecurityRegistrator implements RegistratorInterface private Collection $parameters; /** - * @inheritDoc + * @inheritdoc */ - public function __construct(private readonly Authenticator $authenticator, string $prefix = 'security') - { - $this->parameters = collect([ - 'guard' => $this->authenticator->name(), - 'provider' => $this->authenticator->name(), - 'password_broker' => $this->authenticator->name(), - ]); + protected string $prefix = 'security'; - $this->authenticator->merge($this->parameters->mapWithKeys(fn($value, $key) => [$prefix . '_' . $key => $value])); + /** + * The database prefix to use for the tables + */ + protected string $dbPrefix; + + public function __construct(Repository $config, Router $router, string $dbPrefix = "") + { + $this->dbPrefix = $dbPrefix; + parent::__construct($config, $router); } /** @@ -45,12 +47,26 @@ public function __get($key) /** * @inheritDoc */ - public function boot(): void + public function collectAndMergeParameters(): Collection + { + $this->parameters = collect([ + 'guard' => $this->authName, + 'provider' => $this->authName, + 'password_broker' => $this->authName, + ]); + + return $this->parameters->mapWithKeys(fn($value, $key) => [$this->prefix . '_' . $key => $value]); + } + + /** + * @inheritDoc + */ + public function boot(Collection $allParameters): void { /** * Configure the guard */ - Config::set('auth.guards.' . $this->guard, [ + $this->config->set('auth.guards.' . $this->guard, [ 'driver' => 'session', 'provider' => $this->provider ]); @@ -58,7 +74,7 @@ public function boot(): void /** * Configure the provider */ - Config::set('auth.providers.' . $this->provider, [ + $this->config->set('auth.providers.' . $this->provider, [ 'driver' => 'eloquent', 'model' => User::class ]); @@ -66,9 +82,9 @@ public function boot(): void /** * Configure the password broker */ - Config::set('auth.passwords.' . $this->password_broker, [ + $this->config->set('auth.passwords.' . $this->password_broker, [ 'provider' => $this->provider, - 'table' => config('inside_auth.db.prefix') . 'password_resets', + 'table' => $this->dbPrefix . 'password_resets', 'expire' => 60, 'throttle' => 60, ]); diff --git a/tests/Feature/LoggedIn/AllEnabledTest.php b/tests/Feature/LoggedIn/AllEnabledTest.php new file mode 100644 index 0000000..1ffb63c --- /dev/null +++ b/tests/Feature/LoggedIn/AllEnabledTest.php @@ -0,0 +1,52 @@ +bootAuth(); + $this->auth()->withDashboard('dashboard'); + } +); + +/** + * All routes are available + */ +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Redirects the authenticated user to the dashboard + */ +it('redirects login to dashboard', fn() => $this->redirects->loginToDashboard()); +it('redirects register to dashboard', fn() => $this->redirects->registerToDashboard()); +it('redirects verification notice to dashboard', fn() => $this->redirects->verificatioNoticeToDashboard()); +it('redirects forgot password to dashboard', fn() => $this->redirects->forgotPasswordToDashboard()); +it('redirects reset password to dashboard', fn() => $this->redirects->resetPasswordToDashboard()); + +/** + * Exposes the correct functionalities + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes dashboard', fn() => $this->exposes->dashboard()); +it('exposes error on verification verify', fn() => $this->exposes->errorPageOnVerificationVerify()); +it('exposes profile', fn() => $this->exposes->profile()); diff --git a/tests/Feature/LoggedIn/DisabledUserProfileTest.php b/tests/Feature/LoggedIn/DisabledUserProfileTest.php new file mode 100644 index 0000000..24076a8 --- /dev/null +++ b/tests/Feature/LoggedIn/DisabledUserProfileTest.php @@ -0,0 +1,57 @@ +bootAuth(); + $this->auth()->withDashboard('dashboard'); + $this->auth()->withoutUserProfile(); + } +); + +/** + * All routes are available + */ +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Redirects the authenticated user to the dashboard + */ +it('redirects login to dashboard', fn() => $this->redirects->loginToDashboard()); +it('redirects register to dashboard', fn() => $this->redirects->registerToDashboard()); +it('redirects verification notice to dashboard', fn() => $this->redirects->verificatioNoticeToDashboard()); +it('redirects forgot password to dashboard', fn() => $this->redirects->forgotPasswordToDashboard()); +it('redirects reset password to dashboard', fn() => $this->redirects->resetPasswordToDashboard()); + +/** + * Exposes the correct functionalities + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes dashboard', fn() => $this->exposes->dashboard()); +it('exposes error on verification verify', fn() => $this->exposes->errorPageOnVerificationVerify()); + +/** + * Hides profile without user profile enabled + */ +it('hides profile without user profile', fn() => $this->hides->profile()); diff --git a/tests/Feature/LoggedOut/AllEnabledTest.php b/tests/Feature/LoggedOut/AllEnabledTest.php new file mode 100644 index 0000000..046966b --- /dev/null +++ b/tests/Feature/LoggedOut/AllEnabledTest.php @@ -0,0 +1,56 @@ +bootAuth(); + } +); + +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Check that the login, register, forgot password and reset password are available + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes login', fn() => $this->exposes->login()); +it('exposes register', fn() => $this->exposes->register()); +it('exposes forgot password', fn() => $this->exposes->forgotPassword()); +it('exposes reset password', fn() => $this->exposes->resetPassword()); + +/** + * Has the correct links in the various views + */ +it('has register in login', fn() => $this->has->registerInLogin()); +it('has password request in login', fn() => $this->has->passwordRequestInLogin()); +it('has login in register', fn() => $this->has->loginInRegister()); + +/** + * Correcly redirects to login protected routes + */ +it('redirects dashboard to login', fn() => $this->redirects->dashboardToLogin()); +it('redirects verification notice to login', fn() => $this->redirects->verificatioNoticeToLogin()); +it('redirects verification verify to login', fn() => $this->redirects->verificationVerifyToLogin()); +it('redirects profile to login', fn() => $this->redirects->profileToLogin()); diff --git a/tests/Feature/LoggedOut/DisabledForgotPasswordTest.php b/tests/Feature/LoggedOut/DisabledForgotPasswordTest.php new file mode 100644 index 0000000..7e8a5b2 --- /dev/null +++ b/tests/Feature/LoggedOut/DisabledForgotPasswordTest.php @@ -0,0 +1,66 @@ +bootAuth(); + $this->auth()->withoutForgotPassword(); + } +); + +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Check that the login, register are available + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes login', fn() => $this->exposes->login()); +it('exposes register', fn() => $this->exposes->register()); + +/** + * Hides forgot and reset password + */ +it('hides forgot password', fn() => $this->hides->forgotPassword()); +it('hides reset password', fn() => $this->hides->resetPassword()); + +/** + * Has the correct links in the various views + */ +it('has register in login', fn() => $this->has->registerInLogin()); +it('has login in register', fn() => $this->has->loginInRegister()); + +/** + * Hasn't the link to forgot password in the login + */ + +it('has not password request in login', fn() => $this->hasNot->passwordRequestInLogin()); + +/** + * Correcly redirects to login protected routes + */ +it('redirects dashboard to login', fn() => $this->redirects->dashboardToLogin()); +it('redirects verification notice to login', fn() => $this->redirects->verificatioNoticeToLogin()); +it('redirects verification verify to login', fn() => $this->redirects->verificationVerifyToLogin()); +it('redirects profile to login', fn() => $this->redirects->profileToLogin()); diff --git a/tests/Feature/LoggedOut/DisabledRegistrationTest.php b/tests/Feature/LoggedOut/DisabledRegistrationTest.php new file mode 100644 index 0000000..f011d20 --- /dev/null +++ b/tests/Feature/LoggedOut/DisabledRegistrationTest.php @@ -0,0 +1,64 @@ +bootAuth(); + $this->auth()->withoutRegistration(); + } +); + +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Check that the login, forgot password and reset password are available + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes login', fn() => $this->exposes->login()); +it('exposes forgot password', fn() => $this->exposes->forgotPassword()); +it('exposes reset password', fn() => $this->exposes->resetPassword()); + +/** + * Check that the register is not available + */ +it('hides register', fn() => $this->hides->register()); + +/** + * Has the correct links in the various views + */ +it('has password request in login', fn() => $this->has->passwordRequestInLogin()); + +/** + * Hasn't the correct links in the various views + */ +it('has not register in login', fn() => $this->hasNot->registerInLogin()); + +/** + * Correcly redirects to login protected routes + */ +it('redirects dashboard to login', fn() => $this->redirects->dashboardToLogin()); +it('redirects verification notice to login', fn() => $this->redirects->verificatioNoticeToLogin()); +it('redirects verification verify to login', fn() => $this->redirects->verificationVerifyToLogin()); +it('redirects profile to login', fn() => $this->redirects->profileToLogin()); diff --git a/tests/Feature/Unverified/AllEnabledTest.php b/tests/Feature/Unverified/AllEnabledTest.php new file mode 100644 index 0000000..75501c5 --- /dev/null +++ b/tests/Feature/Unverified/AllEnabledTest.php @@ -0,0 +1,52 @@ +bootAuth(); + $this->auth()->withDashboard('dashboard'); + } +); + +/** + * All routes are available + */ +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Redirects the authenticated user to the dashboard + */ +it('redirects login to dashboard', fn() => $this->redirects->loginToDashboard()); +it('redirects register to dashboard', fn() => $this->redirects->registerToDashboard()); +it('redirects forgot password to dashboard', fn() => $this->redirects->forgotPasswordToDashboard()); +it('redirects reset password to dashboard', fn() => $this->redirects->resetPasswordToDashboard()); +it('redirects profile to verification notice', fn() => $this->redirects->profileToVerificationNotice()); +it('redirects dashboard to verification notice', fn() => $this->redirects->dashboardToVerificationNotice()); + +/** + * Exposes the correct functionalities + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes verification notice', fn() => $this->exposes->verificatioNotice()); +it('exposes error on verification verify', fn() => $this->exposes->errorPageOnVerificationVerify()); diff --git a/tests/Feature/Unverified/DisabledVerificationTest.php b/tests/Feature/Unverified/DisabledVerificationTest.php new file mode 100644 index 0000000..a39e43c --- /dev/null +++ b/tests/Feature/Unverified/DisabledVerificationTest.php @@ -0,0 +1,58 @@ +bootAuth(); + $this->auth()->withDashboard('dashboard'); + $this->auth()->withoutEmailVerification(); + } +); + +/** + * All routes are available + */ +it('routes to dashboard', fn() => $this->routes->toDashboard()); +it('routes to homepage', fn() => $this->routes->toHomepage()); +it('routes to login', fn() => $this->routes->toLogin()); +it('routes to logout', fn() => $this->routes->toLogout()); +it('routes to register', fn() => $this->routes->toRegister()); +it('routes to password confirm', fn() => $this->routes->toPasswordConfirm()); +it('routes to password request', fn() => $this->routes->toPasswordRequest()); +it('routes to password email', fn() => $this->routes->toPasswordEmail()); +it('routes to password update', fn() => $this->routes->toPasswordUpdate()); +it('routes to password store', fn() => $this->routes->toPasswordStore()); +it('routes to password reset', fn() => $this->routes->toPasswordReset()); +it('routes to profile edit', fn() => $this->routes->toProfileEdit()); +it('routes to profile update', fn() => $this->routes->toProfileUpdate()); +it('routes to profile destroy', fn() => $this->routes->toProfileDestroy()); +it('routes to verification send', fn() => $this->routes->toVerificationSend()); +it('routes to verification notice', fn() => $this->routes->toVerificationNotice()); +it('routes to verification verify', fn() => $this->routes->toVerificationVerify()); + +/** + * Redirects the authenticated user to the dashboard + */ +it('redirects login to dashboard', fn() => $this->redirects->loginToDashboard()); +it('redirects register to dashboard', fn() => $this->redirects->registerToDashboard()); +it('redirects forgot password to dashboard', fn() => $this->redirects->forgotPasswordToDashboard()); +it('redirects reset password to dashboard', fn() => $this->redirects->resetPasswordToDashboard()); + +/** + * Exposes the correct functionalities + */ +it('exposes homepage', fn() => $this->exposes->homepage()); +it('exposes dashboard', fn() => $this->exposes->dashboard()); +it('exposes profile', fn() => $this->exposes->profile()); + +/** + * Hides the correct functionalities + */ +it('hides verification notice', fn() => $this->hides->verificationNotice()); +it('hides verification send', fn() => $this->hides->verificationSend()); +it('hides verification verify', fn() => $this->hides->verificationVerify()); diff --git a/tests/FeatureHelpers/Contracts/Helper.php b/tests/FeatureHelpers/Contracts/Helper.php new file mode 100644 index 0000000..44f311d --- /dev/null +++ b/tests/FeatureHelpers/Contracts/Helper.php @@ -0,0 +1,25 @@ +user ? test() : actingAs($this->user, $guard); + } +} \ No newline at end of file diff --git a/tests/FeatureHelpers/Exposes.php b/tests/FeatureHelpers/Exposes.php new file mode 100644 index 0000000..3686fe5 --- /dev/null +++ b/tests/FeatureHelpers/Exposes.php @@ -0,0 +1,128 @@ +testCase()->get(route('homepage')) + ->assertOk() + ->assertSee('This is a dummy home'); + } + + /** + * The dummy dashboard - defined in the test suite - should be available + */ + public function dashboard(): void + { + $this->testCase()->get(route('dashboard')) + ->assertOk() + ->assertSee('This is a dummy dashboard'); + } + + /** + * Login should be available + */ + public function login(): void + { + $this->testCase()->get(route($this->auth->route_login)) + ->assertOk() // Should be 200 + ->assertSee('name="email"', false) // Email field should be present + ->assertSee('name="password"', false) // Password field should be present + ->assertSee('type="submit"', false) // Login button should be present + ; + } + + /** + * Register should be available + */ + public function register(): void + { + $this->testCase()->get(route($this->auth->route_register)) + ->assertOk() // Should be 200 + ->assertSee('name="name"', false) // Name field should be present + ->assertSee('name="email"', false) // Email field should be present + ->assertSee('name="password"', false) // Password field should be present + ->assertSee('name="password_confirmation"', false) // Confirm Password field should be present + ->assertSee('type="submit"', false) // Register button should be present + ; + } + + /** + * Forgot password should be available + */ + public function forgotPassword(): void + { + $this->testCase()->get(route($this->auth->route_password_request)) + ->assertOk() // Should be 200 + ->assertSee('name="email"', false) // Email field should be present + ->assertSee('type="submit"', false) // Send Password Reset Link button should be present + ; + } + + /** + * Reset password should be available + */ + public function resetPassword(): void + { + $this->testCase()->get(route($this->auth->route_password_reset, ['token' => 'wrong'])) + ->assertOk() // Should be 200 + ->assertSee('name="email"', false) // Email field should be present + ->assertSee('name="password"', false) // Password field should be present + ->assertSee('name="password_confirmation"', false) // Confirm Password field should be present + ->assertSee('type="submit"', false) // Send Password Reset Link button should be present + ; + } + + /** + * Verification notice should be available + */ + public function verificatioNotice(): void + { + $this->testCase()->get(route($this->auth->route_verification_notice)) + ->assertOk() // Should be 200 + ; + } + + /** + * The Verification verify page should be available + */ + public function verificationVerify(): void + { + $this->testCase()->get(route($this->auth->route_verification_verify, ['id' => 1, 'hash' => 'wrong'])) + ->assertOk() // Should be 200 + ; + } + + /** + * The Verification verify page should return a 403 error + */ + public function errorPageOnVerificationVerify(): void + { + $this->testCase()->get(route($this->auth->route_verification_verify, ['id' => 1, 'hash' => 'wrong'])) + ->assertStatus(403); + ; + } + + /** + * The profile should be available + */ + public function profile(): void + { + $this->testCase()->get(route($this->auth->route_profile_edit)) + ->assertOk() // Should be 200 + ->assertSee('name="name"', false) // Name field should be present + ->assertSee('name="email"', false) // Email field should be present + ->assertSee(route($this->auth->route_profile_update), false) // Update profile form should be present + ->assertSee(route($this->auth->route_password_update), false) // Update password form should be present + ->assertSee(route($this->auth->route_profile_destroy), false) // Delete profile form should be present + ->assertSee(route($this->auth->route_logout), false) // Logout button should be present + ; + } +} diff --git a/tests/FeatureHelpers/Has.php b/tests/FeatureHelpers/Has.php new file mode 100644 index 0000000..d0ce36b --- /dev/null +++ b/tests/FeatureHelpers/Has.php @@ -0,0 +1,27 @@ +testCase()->get(route($this->auth->route_login)) + ->assertSee(route($this->auth->route_register)); + } + + public function passwordRequestInLogin(): void + { + $this->testCase()->get(route($this->auth->route_login)) + ->assertSee(route($this->auth->route_password_request)); + } + + public function loginInRegister(): void + { + $this->testCase()->get(route($this->auth->route_register)) + ->assertSee(route($this->auth->route_login)); + } +} \ No newline at end of file diff --git a/tests/FeatureHelpers/HasNot.php b/tests/FeatureHelpers/HasNot.php new file mode 100644 index 0000000..a44b686 --- /dev/null +++ b/tests/FeatureHelpers/HasNot.php @@ -0,0 +1,21 @@ +testCase()->get(route($this->auth->route_login)) + ->assertDontSee(route($this->auth->route_register)); + } + + public function passwordRequestInLogin(): void + { + $this->testCase()->get(route($this->auth->route_login)) + ->assertDontSee(route($this->auth->route_password_request)); + } +} \ No newline at end of file diff --git a/tests/FeatureHelpers/Hides.php b/tests/FeatureHelpers/Hides.php new file mode 100644 index 0000000..2e1c06a --- /dev/null +++ b/tests/FeatureHelpers/Hides.php @@ -0,0 +1,89 @@ +testCase()->get(route($this->auth->route_register)) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Forgot password should be available + */ + public function forgotPassword(): void + { + $this->testCase()->get(route($this->auth->route_password_request)) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Reset password should be available + */ + public function resetPassword(): void + { + $this->testCase()->get(route($this->auth->route_password_reset, ['token' => 'wrong'])) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Profile should not be available + */ + public function profile(): void + { + $this->testCase()->get(route($this->auth->route_profile_edit)) + ->assertNotFound() // Should be 404 + ; + } + + /** + * The dashboard - defined in the test suite - should not be available + */ + public function dashboard(): void + { + $this->testCase()->get(route('dashboard')) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Hides the verification notice route + */ + public function verificationNotice(): void + { + $this->testCase()->get(route($this->auth->route_verification_notice)) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Hides the verification send route + */ + public function verificationSend(): void + { + $this->testCase()->post(route($this->auth->route_verification_send)) + ->assertNotFound() // Should be 404 + ; + } + + /** + * Hides the verification verify route + */ + public function verificationVerify(): void + { + $this->testCase()->get(route($this->auth->route_verification_verify, ['id' => 1, 'hash' => 'wrong'])) + ->assertNotFound() // Should be 404 + ; + } +} \ No newline at end of file diff --git a/tests/FeatureHelpers/Providers/AuthServiceProvider.php b/tests/FeatureHelpers/Providers/AuthServiceProvider.php new file mode 100644 index 0000000..c1b8005 --- /dev/null +++ b/tests/FeatureHelpers/Providers/AuthServiceProvider.php @@ -0,0 +1,20 @@ +middleware_web) + ->get('/homepage', fn() => 'This is a dummy home') + ->name('homepage'); + + /** + * A dummy dashboard route, protected + */ + Route::middleware(InsideAuth::getAuthenticator('test')->middleware_verified) + ->get('/dashboard', fn() => 'This is a dummy dashboard') + ->name('dashboard'); + } +} diff --git a/tests/FeatureHelpers/Redirects.php b/tests/FeatureHelpers/Redirects.php new file mode 100644 index 0000000..d6a7df0 --- /dev/null +++ b/tests/FeatureHelpers/Redirects.php @@ -0,0 +1,111 @@ +testCase()->get(route('dashboard')) + ->assertRedirect(route($this->auth->route_login)); + } + + /** + * The Verification notice should redirect to login + */ + public function verificatioNoticeToLogin(): void + { + $this->testCase()->get(route($this->auth->route_verification_notice)) + ->assertRedirect(route($this->auth->route_login)); + } + + /** + * The Verification verify should redirect to login if id and hash are wrong + */ + public function verificationVerifyToLogin(): void + { + $this->testCase()->get(route($this->auth->route_verification_verify, ['id' => 1, 'hash' => 'wrong'])) + ->assertRedirect(route($this->auth->route_login)); + } + + /** + * All profile routes should redirect to login + */ + public function profileToLogin(): void + { + $this->testCase()->get(route($this->auth->route_profile_edit)) + ->assertRedirect(route($this->auth->route_login)); + $this->testCase()->get(route($this->auth->route_profile_update)) + ->assertRedirect(route($this->auth->route_login)); + $this->testCase()->get(route($this->auth->route_profile_destroy)) + ->assertRedirect(route($this->auth->route_login)); + } + /** + * The login should redirect to dashboard if logged in + */ + public function loginToDashboard(): void + { + $this->testCase()->get(route($this->auth->route_login)) + ->assertRedirect(route('dashboard')); + } + + /** + * The register should redirect to dashboard if logged in + */ + public function registerToDashboard(): void + { + $this->testCase()->get(route($this->auth->route_register)) + ->assertRedirect(route('dashboard')); + } + + /** + * The verification notice should redirect to dashboard if logged in + */ + public function verificatioNoticeToDashboard(): void + { + $this->testCase()->get(route($this->auth->route_verification_notice)) + ->assertRedirect(route('dashboard')); + } + + /** + * The verification verify should redirect to dashboard if logged in + */ + public function forgotPasswordToDashboard(): void + { + $this->testCase()->get(route($this->auth->route_password_request)) + ->assertRedirect(route('dashboard')); + } + + /** + * The reset password should redirect to dashboard if logged in + */ + public function resetPasswordToDashboard(): void + { + $this->testCase()->get(route($this->auth->route_password_reset, ['token' => 'wrong'])) + ->assertRedirect(route('dashboard')); + } + + /** + * The dashboard should redirect to verification notice if not verified + */ + public function dashboardToVerificationNotice(): void + { + $this->testCase()->get(route('dashboard')) + ->assertRedirect(route($this->auth->route_verification_notice)); + } + + /** + * The profile should redirect to verification notice if not verified + */ + public function profileToVerificationNotice(): void + { + $this->testCase()->get(route($this->auth->route_profile_edit)) + ->assertRedirect(route($this->auth->route_verification_notice)); + } +} \ No newline at end of file diff --git a/tests/FeatureHelpers/Routes.php b/tests/FeatureHelpers/Routes.php new file mode 100644 index 0000000..d6a56ff --- /dev/null +++ b/tests/FeatureHelpers/Routes.php @@ -0,0 +1,178 @@ +toBe('/homepage'); + } + + /** + * The route to the dummy dashboard is correct + */ + public function toDashboard(): void + { + expect( + route('dashboard', [], false) + )->toBe('/dashboard'); + } + + /** + * The route to login is correct + */ + public function toLogin(): void + { + expect( + route($this->auth->route_login, [], false) + )->toBe('/' . $this->auth->name() . '/login'); + } + + /** + * The route to logout is correct + */ + public function toLogout(): void + { + expect( + route($this->auth->route_logout, [], false) + )->toBe('/' . $this->auth->name() . '/logout'); + } + + /** + * The route to register is correct + */ + public function toRegister(): void + { + expect( + route($this->auth->route_register, [], false) + )->toBe('/' . $this->auth->name() . '/register'); + } + + /** + * The route to the password confirm is correct + */ + public function toPasswordConfirm(): void + { + expect( + route($this->auth->route_password_confirm, [], false) + )->toBe('/' . $this->auth->name() . '/confirm-password'); + } + + /** + * The route to the password request is correct + */ + public function toPasswordRequest(): void + { + expect( + route($this->auth->route_password_request, [], false) + )->toBe('/' . $this->auth->name() . '/forgot-password'); + } + + /** + * The route to the password email is correct + */ + public function toPasswordEmail(): void + { + expect( + route($this->auth->route_password_email, [], false) + )->toBe('/' . $this->auth->name() . '/forgot-password'); + } + + /** + * The route to the password update is correct + */ + public function toPasswordUpdate(): void + { + expect( + route($this->auth->route_password_update, [], false) + )->toBe('/' . $this->auth->name() . '/password'); + } + + /** + * The route to the password store is correct + */ + public function toPasswordStore(): void + { + expect( + route($this->auth->route_password_store, [], false) + )->toBe('/' . $this->auth->name() . '/reset-password'); + } + + /** + * The route to the password reset is correct + */ + public function toPasswordReset(): void + { + expect( + route($this->auth->route_password_reset, ['token' => 'test'], false) + )->toBe('/' . $this->auth->name() . '/reset-password/test'); + } + + /** + * The route to the profile edit is correct + */ + public function toProfileEdit(): void + { + expect( + route($this->auth->route_profile_edit, [], false) + )->toBe('/' . $this->auth->name() . '/profile'); + } + + /** + * The route to the profile update is correct + */ + public function toProfileUpdate(): void + { + expect( + route($this->auth->route_profile_update, [], false) + )->toBe('/' . $this->auth->name() . '/profile'); + } + + /** + * The route to the profile destroy is correct + */ + public function toProfileDestroy(): void + { + expect( + route($this->auth->route_profile_destroy, [], false) + )->toBe('/' . $this->auth->name() . '/profile'); + } + + /** + * The route to the verification send is correct + */ + public function toVerificationSend(): void + { + expect( + route($this->auth->route_verification_send, [], false) + )->toBe('/' . $this->auth->name() . '/email/verification-notification'); + } + + /** + * The route to the verification notice is correct + */ + public function toVerificationNotice(): void + { + expect( + route($this->auth->route_verification_notice, [], false) + )->toBe('/' . $this->auth->name() . '/verify-email'); + } + + /** + * The route to the verification verify is correct + */ + public function toVerificationVerify(): void + { + expect( + route($this->auth->route_verification_verify, ['id' => 1, 'hash' => 'test'], false) + )->toBe('/' . $this->auth->name() . '/verify-email/1/test'); + } +} diff --git a/tests/LoggedInFeatureTestCase.php b/tests/LoggedInFeatureTestCase.php new file mode 100644 index 0000000..073b801 --- /dev/null +++ b/tests/LoggedInFeatureTestCase.php @@ -0,0 +1,35 @@ +create(); + + $authenticator = InsideAuth::getAuthenticator(AuthServiceProvider::AUTH_NAME); + $this->exposes = new Exposes($authenticator, $user); + $this->redirects = new Redirects($authenticator, $user); + $this->routes = new Routes($authenticator, $user); + $this->has = new Has($authenticator, $user); + $this->hasNot = new HasNot($authenticator, $user); + $this->hides = new Hides($authenticator, $user); + } +} \ No newline at end of file diff --git a/tests/LoggedOutFeatureTestCase.php b/tests/LoggedOutFeatureTestCase.php new file mode 100644 index 0000000..a3e53e7 --- /dev/null +++ b/tests/LoggedOutFeatureTestCase.php @@ -0,0 +1,88 @@ +exposes = new Exposes($authenticator); + $this->redirects = new Redirects($authenticator); + $this->routes = new Routes($authenticator); + $this->has = new Has($authenticator); + $this->hasNot = new HasNot($authenticator); + $this->hides = new Hides($authenticator); + } + + /** + * Returns the Authenticator + */ + protected function auth(): Authenticator + { + return InsideAuth::getAuthenticator(AuthServiceProvider::AUTH_NAME); + } + + /** + * Returns the package providers needed for the tests + */ + protected function getPackageProviders($app): array + { + return [ + ServiceProvider::class, + AuthServiceProvider::class, + RouteServiceProvider::class + ]; + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function getEnvironmentSetUp($app) + { + $config = $app->get('config'); + $config->set('logging.default', 'errorlog'); + $config->set('database.default', 'testbench'); + $config->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } +} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..e212b47 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,48 @@ +in('Feature/LoggedOut'); +uses(LoggedInFeatureTestCase::class)->in('Feature/LoggedIn'); +uses(UnverifiedFeatureTestCase::class)->in('Feature/Unverified'); +uses(TestCase::class)->in('Unit'); + +/* +|-------------------------------------------------------------------------- +| 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. +| +*/ diff --git a/tests/Unit/AuthenticatorTest.php b/tests/Unit/AuthenticatorTest.php new file mode 100644 index 0000000..33ceb0c --- /dev/null +++ b/tests/Unit/AuthenticatorTest.php @@ -0,0 +1,142 @@ +authenticator = new Authenticator('auth'); +}); + +test('The authenticator has the correct class', function () { + expect($this->authenticator)->toBeInstanceOf(Authenticator::class); +}); + +test('Parameters are a Collection', function () { + expect($this->authenticator->parameters)->toBeInstanceOf(Collection::class); +}); + +test('Expect the correct number of parameters', function () { + expect($this->authenticator->parameters->count())->toBe(14); +}); + +test('The authenticator defaults are correct', function () { + expect($this->authenticator->enabled)->toBeTrue() + ->and($this->authenticator->registration_enabled)->toBeTrue() + ->and($this->authenticator->forgot_password_enabled)->toBeTrue() + ->and($this->authenticator->email_verification_enabled)->toBeTrue() + ->and($this->authenticator->user_profile_enabled)->toBeTrue() + ->and($this->authenticator->dashboard)->toBeNull() + ->and($this->authenticator->homepage)->toBeNull() + ->and($this->authenticator->template_confirm_password)->toBe('inside_auth::auth.confirm-password') + ->and($this->authenticator->template_forgot_password)->toBe('inside_auth::auth.forgot-password') + ->and($this->authenticator->template_login)->toBe('inside_auth::auth.login') + ->and($this->authenticator->template_register)->toBe('inside_auth::auth.register') + ->and($this->authenticator->template_reset_password)->toBe('inside_auth::auth.reset-password') + ->and($this->authenticator->template_verify_email)->toBe('inside_auth::auth.verify-email') + ->and($this->authenticator->template_profile_edit)->toBe('inside_auth::profile.edit'); +}); + +test('The name of the authenticator is correct', function () { + expect($this->authenticator->name())->toBe('auth'); +}); + +test('The authenticator can be disabled', function () { + expect() + ->and($this->authenticator->enabled(false))->toBe($this->authenticator) + ->and($this->authenticator->enabled)->toBeFalse() + ->and($this->authenticator->enabled())->toBe($this->authenticator) + ->and($this->authenticator->enabled)->toBeTrue(); +}); + +test('The registration can be disabled', function () { + expect() + ->and($this->authenticator->withoutRegistration())->toBe($this->authenticator) + ->and($this->authenticator->registration_enabled)->toBeFalse() + ->and($this->authenticator->withoutRegistration(false))->toBe($this->authenticator) + ->and($this->authenticator->registration_enabled)->toBeTrue(); +}); + +test('The forgot password can be disabled', function () { + expect() + ->and($this->authenticator->withoutForgotPassword())->toBe($this->authenticator) + ->and($this->authenticator->forgot_password_enabled)->toBeFalse() + ->and($this->authenticator->withoutForgotPassword(false))->toBe($this->authenticator) + ->and($this->authenticator->forgot_password_enabled)->toBeTrue(); +}); + +test('The email verification can be disabled', function () { + expect() + ->and($this->authenticator->withoutEmailVerification())->toBe($this->authenticator) + ->and($this->authenticator->email_verification_enabled)->toBeFalse() + ->and($this->authenticator->withoutEmailVerification(false))->toBe($this->authenticator) + ->and($this->authenticator->email_verification_enabled)->toBeTrue(); +}); + +test('The user profile can be disabled', function () { + expect() + ->and($this->authenticator->withoutUserProfile())->toBe($this->authenticator) + ->and($this->authenticator->user_profile_enabled)->toBeFalse() + ->and($this->authenticator->withoutUserProfile(false))->toBe($this->authenticator) + ->and($this->authenticator->user_profile_enabled)->toBeTrue(); +}); + +test('The dashboard can be set', function () { + expect() + ->and($this->authenticator->withDashboard('dashboard'))->toBe($this->authenticator) + ->and($this->authenticator->dashboard)->toBe('dashboard'); +}); + +test('The homepage can be set', function() { + expect() + ->and($this->authenticator->withHomepage('homepage'))->toBe($this->authenticator) + ->and($this->authenticator->homepage)->toBe('homepage'); +}); + +test('The confirm password template can be set', function () { + expect() + ->and($this->authenticator->withConfirmPasswordTemplate('confirm-password'))->toBe($this->authenticator) + ->and($this->authenticator->template_confirm_password)->toBe('confirm-password'); +}); + +test('The forgot password template can be set', function () { + expect() + ->and($this->authenticator->withForgotPasswordTemplate('forgot-password'))->toBe($this->authenticator) + ->and($this->authenticator->template_forgot_password)->toBe('forgot-password'); +}); + +test('The login template can be set', function () { + expect() + ->and($this->authenticator->withLoginTemplate('login'))->toBe($this->authenticator) + ->and($this->authenticator->template_login)->toBe('login'); +}); + +test('The register template can be set', function () { + expect() + ->and($this->authenticator->withRegisterTemplate('register'))->toBe($this->authenticator) + ->and($this->authenticator->template_register)->toBe('register'); +}); + +test('The reset password template can be set', function () { + expect() + ->and($this->authenticator->withResetPasswordTemplate('reset-password'))->toBe($this->authenticator) + ->and($this->authenticator->template_reset_password)->toBe('reset-password'); +}); + +test('The verify email template can be set', function () { + expect() + ->and($this->authenticator->withVerifyEmailTemplate('verify-email'))->toBe($this->authenticator) + ->and($this->authenticator->template_verify_email)->toBe('verify-email'); +}); + +test('The profile edit template can be set', function () { + expect() + ->and($this->authenticator->withProfileEditTemplate('profile-edit'))->toBe($this->authenticator) + ->and($this->authenticator->template_profile_edit)->toBe('profile-edit'); +}); + +test('Array properties can be merged', function () { + expect() + ->and($this->authenticator->merge(['test' => 'dummy']))->toBe($this->authenticator) + ->and($this->authenticator->test)->toBe('dummy'); +}); diff --git a/tests/Unit/Registrators/MiddlewareRegistratorTest.php b/tests/Unit/Registrators/MiddlewareRegistratorTest.php new file mode 100644 index 0000000..37346d9 --- /dev/null +++ b/tests/Unit/Registrators/MiddlewareRegistratorTest.php @@ -0,0 +1,96 @@ +withAuthName($authName); + + $parameters = $middlewareRegistrator->collectAndMergeParameters(); + + expect($parameters)->toBeInstanceOf(Collection::class) + ->and($parameters->toArray())->toMatchArray([ + 'middleware_verified' => $authName . '-verified', + 'middleware_guest' => $authName . '-guest', + 'middleware_logged_in' => $authName . '-logged', + 'middleware_web' => $authName . '-web' + ]); + + $router->shouldReceive('aliasMiddleware')->withArgs(function ($alias, $class) use ($authName) { + + expect($alias)->toBeIn([ + $authName . '-authenticated', + $authName . '-ensure-email-is-verified', + $authName . '-ensure-auth-is-enabled', + $authName . '-redirect-if-authenticated', + $authName . '-inject' + ])->and($class) + ->when($alias === $authName . "-authenticated", fn(Pest\Expectation $class) => $class->toBe(Authenticate::class)) + ->when($alias === $authName . "-ensure-email-is-verified", fn(Pest\Expectation $class) => $class->toBe(EnsureEmailIsVerified::class)) + ->when($alias === $authName . "-ensure-auth-is-enabled", fn(Pest\Expectation $class) => $class->toBe(EnsureAuthIsEnabled::class)) + ->when($alias === $authName . "-redirect-if-authenticated", fn(Pest\Expectation $class) => $class->toBe(RedirectIfAuthenticated::class)) + ->when($alias === $authName . "-inject", fn(Pest\Expectation $class) => $class->toBe(InjectIntoApplication::class));; + + return true; + })->times(5); + + $router->shouldReceive('middlewareGroup')->withArgs([ + $authName . '-web', + [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class + ] + ]); + + $router->shouldReceive('middlewareGroup')->withArgs([ + $authName . '-guest', + [ + $authName . '-inject:' . $authName, + $authName . '-ensure-auth-is-enabled', + $authName . '-redirect-if-authenticated' + ] + ]); + + $router->shouldReceive('middlewareGroup')->withArgs([ + $authName . '-logged', + [ + $authName . '-inject:' . $authName, + $authName . '-ensure-auth-is-enabled', + $authName . '-authenticated:login,guard' + ] + ]); + + $router->shouldReceive('middlewareGroup')->withArgs([ + $authName . '-verified', + [ + $authName . '-logged', + $authName . '-ensure-email-is-verified' + ] + ]); + + $middlewareRegistrator->boot(collect([ + 'route_login' => 'login', + 'security_guard' => 'guard' + ])); +})->with(['auth', 'test_auth', 'test-auth']); \ No newline at end of file diff --git a/tests/Unit/Registrators/RouteRegistratorTest.php b/tests/Unit/Registrators/RouteRegistratorTest.php new file mode 100644 index 0000000..54e0f13 --- /dev/null +++ b/tests/Unit/Registrators/RouteRegistratorTest.php @@ -0,0 +1,200 @@ +shouldAllowMockingProtectedMethods(); + + $routeRegistrar = Mockery::mock(RouteRegistrar::class); + + $routeRegistrator = ( new RouteRegistrator($config, $router) ) + ->withAuthName($authName); + + $parameters = $routeRegistrator->collectAndMergeParameters(); + + expect($parameters)->toBeInstanceOf(Collection::class) + ->and($parameters->toArray())->toMatchArray([ + 'route_login' => $authName . '.auth.login', + 'route_register' => $authName . '.auth.register', + 'route_password_request' => $authName . '.auth.password.request', + 'route_password_email' => $authName . '.auth.password.email', + 'route_password_reset' => $authName . '.auth.password.reset', + 'route_password_store' => $authName . '.auth.password.store', + 'route_logout' => $authName . '.auth.logout', + 'route_verification_notice' => $authName . '.auth.verification.notice', + 'route_verification_verify' => $authName . '.auth.verification.verify', + 'route_verification_send' => $authName . '.auth.verification.send', + 'route_password_confirm' => $authName . '.auth.password.confirm', + 'route_password_update' => $authName . '.auth.password.update', + 'route_profile_edit' => $authName . '.auth.profile.edit', + 'route_profile_update' => $authName . '.auth.profile.update', + 'route_profile_destroy' => $authName . '.auth.profile.destroy' + ]); + + $router->shouldReceive('getLastGroupPrefix')->andReturn($routeRegistrar); + $routeRegistrar->shouldReceive('prefix')->andReturn($routeRegistrar); + $routeRegistrar->shouldReceive('group')->with(Mockery::on(function ($callable) { + /** + * Call the internal implementation + */ + if (is_callable($callable)) { + $callable(); + return true; + } + return false; + }))->andReturn($routeRegistrar); + $routeRegistrar->shouldReceive('name')->andReturn($routeRegistrar); + + $routeRegistrar->shouldReceive('middleware')->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + 'test_middleware_web' + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + 'test_middleware_guest' + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + 'test_middleware_logged_in' + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + 'test_middleware_verified' + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + EnsureRegistrationIsEnabled::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + EnsureForgotPasswordIsEnabled::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + EnsureEmailVerificationIsEnabled::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('middleware')->withArgs([ + EnsureUserProfileIsEnabled::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'login', + [AuthenticatedSessionController::class, 'create'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'login', + [AuthenticatedSessionController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'register', + [RegisteredUserController::class, 'create'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'register', + [RegisteredUserController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'forgot-password', + [PasswordResetLinkController::class, 'create'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'forgot-password', + [PasswordResetLinkController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'reset-password/{token}', + [NewPasswordController::class, 'create'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'reset-password', + [NewPasswordController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'logout', + [AuthenticatedSessionController::class, 'destroy'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'verify-email', + EmailVerificationPromptController::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'verify-email/{id}/{hash}', + VerifyEmailController::class + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'email/verification-notification', + [EmailVerificationNotificationController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + 'confirm-password', + [ConfirmablePasswordController::class, 'show'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('post')->withArgs([ + 'confirm-password', + [ConfirmablePasswordController::class, 'store'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('put')->withArgs([ + 'password', + [PasswordController::class, 'update'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('get')->withArgs([ + '/profile', + [ProfileController::class, 'edit'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('patch')->withArgs([ + '/profile', + [ProfileController::class, 'update'] + ])->once()->andReturn($routeRegistrar); + + $router->shouldReceive('delete')->withArgs([ + '/profile', + [ProfileController::class, 'destroy'] + ])->once()->andReturn($routeRegistrar); + + $routeRegistrator->boot(collect([ + 'middleware_web' => 'test_middleware_web', + 'middleware_guest' => 'test_middleware_guest', + 'middleware_logged_in' => 'test_middleware_logged_in', + 'middleware_verified' => 'test_middleware_verified', + ])); + +})->with(['auth', 'test_auth', 'test-auth']); diff --git a/tests/Unit/Registrators/SecurityRegistratorTest.php b/tests/Unit/Registrators/SecurityRegistratorTest.php new file mode 100644 index 0000000..682c655 --- /dev/null +++ b/tests/Unit/Registrators/SecurityRegistratorTest.php @@ -0,0 +1,52 @@ +withAuthName($authName); + + $parameters = $securityRegistrator->collectAndMergeParameters(); + + expect($parameters)->toBeInstanceOf(Collection::class) + ->and($parameters->toArray())->toMatchArray([ + 'security_guard' => $authName, + 'security_provider' => $authName, + 'security_password_broker' => $authName + ]); + + $config->shouldReceive('set')->withArgs([ + 'auth.guards.' . $authName, + [ + 'driver' => 'session', + 'provider' => $authName + ] + ]); + + $config->shouldReceive('set')->withArgs([ + 'auth.providers.' . $authName, + [ + 'driver' => 'eloquent', + 'model' => User::class + ] + ]); + + $config->shouldReceive('set')->withArgs([ + 'auth.passwords.' . $authName, + [ + 'provider' => $authName, + 'table' => $authName . 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ] + ]); + + $securityRegistrator->boot(collect([])); +})->with(['auth', 'test_auth', 'test-auth']); diff --git a/tests/UnverifiedFeatureTestCase.php b/tests/UnverifiedFeatureTestCase.php new file mode 100644 index 0000000..37bd98b --- /dev/null +++ b/tests/UnverifiedFeatureTestCase.php @@ -0,0 +1,37 @@ +create([ + 'email_verified_at' => null, + ]); + + $authenticator = InsideAuth::getAuthenticator(AuthServiceProvider::AUTH_NAME); + $this->exposes = new Exposes($authenticator, $user); + $this->redirects = new Redirects($authenticator, $user); + $this->routes = new Routes($authenticator, $user); + $this->has = new Has($authenticator, $user); + $this->hasNot = new HasNot($authenticator, $user); + $this->hides = new Hides($authenticator, $user); + } + +} \ No newline at end of file