diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6537ca46 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..b8358813 --- /dev/null +++ b/.env.example @@ -0,0 +1,51 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=root +DB_PASSWORD= + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DRIVER=local +QUEUE_CONNECTION=sync +SESSION_DRIVER=database +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mailhog +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS=null +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..967315dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.css linguist-vendored +*.scss linguist-vendored +*.js linguist-vendored +CHANGELOG.md export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..598ed0bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/public/css/ +/public/images/ +/public/js/ + +# Project exclude paths +/public/dev/node_modules/ \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000..80fb6751 --- /dev/null +++ b/.htaccess @@ -0,0 +1,4 @@ + + RewriteEngine On + RewriteRule ^(.*)$ public/$1 [L] + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 00000000..a50b93c7 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..b5bdd69a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/mrs-lara.iml b/.idea/mrs-lara.iml new file mode 100644 index 00000000..274271fe --- /dev/null +++ b/.idea/mrs-lara.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 00000000..f6159c8b --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/webServers.xml b/.idea/webServers.xml new file mode 100644 index 00000000..b48d741d --- /dev/null +++ b/.idea/webServers.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..3ef5edeb --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if ($errors->any()) +

Ошибка аутентификации

+ @endif +@endsection + + + diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100644 index 00000000..d6a0b771 --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.main') + +@push('styles') + +@endpush + +@section('title') + Регистрация +@endsection + +@section('content') +
+ +
+ @csrf + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+ +
+ @if ($errors->any()) +

Ошибка регистрации

+ + @endif + +@endsection + + + diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php new file mode 100644 index 00000000..36a9f65a --- /dev/null +++ b/resources/views/auth/reset-password.blade.php @@ -0,0 +1,36 @@ + + + + + + + + +
+ @csrf + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + {{ __('Reset Password') }} + +
+
+
+
diff --git a/resources/views/auth/verify-email.blade.php b/resources/views/auth/verify-email.blade.php new file mode 100644 index 00000000..7196c0e3 --- /dev/null +++ b/resources/views/auth/verify-email.blade.php @@ -0,0 +1,32 @@ +@extends('layouts.main') + +@section('title') + Подтверждение E-mail +@endsection + +@push('styles') + +@endpush + +@section('content') +
+
+

Для завершения регистрации необходимо подтвердить E-mail.

+

Для этого перейдите по ссылке в писме которое мы вам отправили...

+ + @if (session('status') == 'verification-link-sent') +

Повторное письмо отправлено

+ @else +
+ @csrf + +
+ @endif +
+
+@endsection + + + diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php new file mode 100644 index 00000000..b924ac63 --- /dev/null +++ b/resources/views/dashboard.blade.php @@ -0,0 +1,14 @@ +@extends('layouts.main') + +@section('styles') + +@endsection + +@section('content') + +

Вфырищфкв

+ +@endsection + + + diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php new file mode 100644 index 00000000..08ace355 --- /dev/null +++ b/resources/views/home.blade.php @@ -0,0 +1,15 @@ +@extends('layouts.main') + +@push('styles') + +@endpush + +@section('title') + Главная +@endsection + +@section('content') +
+

Hello!

+
+@endsection diff --git a/resources/views/layouts/footer.blade.php b/resources/views/layouts/footer.blade.php new file mode 100644 index 00000000..e3acd4e3 --- /dev/null +++ b/resources/views/layouts/footer.blade.php @@ -0,0 +1,3 @@ +
+ footer +
diff --git a/resources/views/layouts/header.blade.php b/resources/views/layouts/header.blade.php new file mode 100644 index 00000000..4ea41d83 --- /dev/null +++ b/resources/views/layouts/header.blade.php @@ -0,0 +1,17 @@ +
+
+ + @auth + + @include('layouts.menu.dashboard-menu') + + @else +
+ @include('layouts.menu.auth-menu') +
+ @endauth +
+
diff --git a/resources/views/layouts/main.blade.php b/resources/views/layouts/main.blade.php new file mode 100644 index 00000000..e27c0ccd --- /dev/null +++ b/resources/views/layouts/main.blade.php @@ -0,0 +1,36 @@ + + + + + + + + + + + + + @stack('styles') + + @yield('title') + + + +
+ @include('layouts.header') +
+ +
+ @yield('content') +
+ +
+ @include('layouts.footer') +
+ + + +@stack('scripts') + + diff --git a/resources/views/layouts/menu/auth-menu.blade.php b/resources/views/layouts/menu/auth-menu.blade.php new file mode 100644 index 00000000..0325cf13 --- /dev/null +++ b/resources/views/layouts/menu/auth-menu.blade.php @@ -0,0 +1,12 @@ +@if(Route::currentRouteName() != 'login') + + Вход + +@endif + +@if(Route::currentRouteName() != 'register') + + + Регистрация + +@endif diff --git a/resources/views/layouts/menu/dashboard-menu.blade.php b/resources/views/layouts/menu/dashboard-menu.blade.php new file mode 100644 index 00000000..2b5d870b --- /dev/null +++ b/resources/views/layouts/menu/dashboard-menu.blade.php @@ -0,0 +1,32 @@ +
+ Dashboard + + +
+
+ +
+ +
+
+ + + + diff --git a/resources/views/profile/delete-user-form.blade.php b/resources/views/profile/delete-user-form.blade.php new file mode 100644 index 00000000..280819c0 --- /dev/null +++ b/resources/views/profile/delete-user-form.blade.php @@ -0,0 +1,52 @@ + + + {{ __('Delete Account') }} + + + + {{ __('Permanently delete your account.') }} + + + +
+ {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }} +
+ +
+ + {{ __('Delete Account') }} + +
+ + + + + {{ __('Delete Account') }} + + + + {{ __('Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} + +
+ + + +
+
+ + + + {{ __('Cancel') }} + + + + {{ __('Delete Account') }} + + +
+
+
diff --git a/resources/views/profile/logout-other-browser-sessions-form.blade.php b/resources/views/profile/logout-other-browser-sessions-form.blade.php new file mode 100644 index 00000000..8754f79e --- /dev/null +++ b/resources/views/profile/logout-other-browser-sessions-form.blade.php @@ -0,0 +1,97 @@ + + + {{ __('Browser Sessions') }} + + + + {{ __('Manage and log out your active sessions on other browsers and devices.') }} + + + +
+ {{ __('If necessary, you may log out of all of your other browser sessions across all of your devices. Some of your recent sessions are listed below; however, this list may not be exhaustive. If you feel your account has been compromised, you should also update your password.') }} +
+ + @if (count($this->sessions) > 0) +
+ + @foreach ($this->sessions as $session) +
+
+ @if ($session->agent->isDesktop()) + + + + @else + + + + @endif +
+ +
+
+ {{ $session->agent->platform() }} - {{ $session->agent->browser() }} +
+ +
+
+ {{ $session->ip_address }}, + + @if ($session->is_current_device) + {{ __('This device') }} + @else + {{ __('Last active') }} {{ $session->last_active }} + @endif +
+
+
+
+ @endforeach +
+ @endif + +
+ + {{ __('Log Out Other Browser Sessions') }} + + + + {{ __('Done.') }} + +
+ + + + + {{ __('Log Out Other Browser Sessions') }} + + + + {{ __('Please enter your password to confirm you would like to log out of your other browser sessions across all of your devices.') }} + +
+ + + +
+
+ + + + {{ __('Cancel') }} + + + + {{ __('Log Out Other Browser Sessions') }} + + +
+
+
diff --git a/resources/views/profile/show.blade.php b/resources/views/profile/show.blade.php new file mode 100644 index 00000000..72253fed --- /dev/null +++ b/resources/views/profile/show.blade.php @@ -0,0 +1,59 @@ +@extends('layouts.main') + +@section('title') + Профиль пользователя +@endsection + +@push('styles') + +@endpush + +@section('content') +
+

+ {{ __('Profile') }} +

+ + +
+ @if (Laravel\Fortify\Features::canUpdateProfileInformation()) + @livewire('profile.update-profile-information-form') + + + @endif + + @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords())) +
+ @livewire('profile.update-password-form') +
+ + + @endif + +
+ @livewire('profile.logout-other-browser-sessions-form') +
+ + @if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures()) + + +
+ @livewire('profile.delete-user-form') +
+ @endif +
+
+ + + +@endsection + + + + + + + + + + diff --git a/resources/views/profile/update-password-form.blade.php b/resources/views/profile/update-password-form.blade.php new file mode 100644 index 00000000..b5360056 --- /dev/null +++ b/resources/views/profile/update-password-form.blade.php @@ -0,0 +1,39 @@ + + + {{ __('Update Password') }} + + + + {{ __('Ensure your account is using a long, random password to stay secure.') }} + + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + + {{ __('Saved.') }} + + + + {{ __('Save') }} + + +
diff --git a/resources/views/profile/update-profile-information-form.blade.php b/resources/views/profile/update-profile-information-form.blade.php new file mode 100644 index 00000000..d01f1022 --- /dev/null +++ b/resources/views/profile/update-profile-information-form.blade.php @@ -0,0 +1,79 @@ + + + {{ __('Profile Information') }} + + + + {{ __('Update your account\'s profile information and email address.') }} + + + + + @if (Laravel\Jetstream\Jetstream::managesProfilePhotos()) +
+ + + + + + +
+ {{ $this->user->name }} +
+ + +
+ + +
+ + + {{ __('Select A New Photo') }} + + + @if ($this->user->profile_photo_path) + + {{ __('Remove Photo') }} + + @endif + + +
+ @endif + + +
+ + + +
+ + +
+ + + +
+
+ + + + {{ __('Saved.') }} + + + + {{ __('Save') }} + + +
diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 00000000..eb6fa48c --- /dev/null +++ b/routes/api.php @@ -0,0 +1,19 @@ +get('/user', function (Request $request) { + return $request->user(); +}); diff --git a/routes/channels.php b/routes/channels.php new file mode 100644 index 00000000..5d451e1f --- /dev/null +++ b/routes/channels.php @@ -0,0 +1,18 @@ +id === (int) $id; +}); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 00000000..e05f4c9a --- /dev/null +++ b/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 00000000..d3487702 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,22 @@ +get('/dashboard', function () { + return view('dashboard'); +})->name('dashboard'); diff --git a/server.php b/server.php new file mode 100644 index 00000000..5fb6379e --- /dev/null +++ b/server.php @@ -0,0 +1,21 @@ + + */ + +$uri = urldecode( + parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) +); + +// This file allows us to emulate Apache's "mod_rewrite" functionality from the +// built-in PHP web server. This provides a convenient way to test a Laravel +// application without having installed a "real" web server software here. +if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { + return false; +} + +require_once __DIR__.'/public/index.php'; diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 00000000..8f4803c0 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,3 @@ +* +!public/ +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 00000000..05c4471f --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 00000000..01e4a6cd --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php new file mode 100644 index 00000000..547152f6 --- /dev/null +++ b/tests/CreatesApplication.php @@ -0,0 +1,22 @@ +make(Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/tests/Feature/ApiTokenPermissionsTest.php b/tests/Feature/ApiTokenPermissionsTest.php new file mode 100644 index 00000000..829b4225 --- /dev/null +++ b/tests/Feature/ApiTokenPermissionsTest.php @@ -0,0 +1,49 @@ +markTestSkipped('API support is not enabled.'); + } + + if (Features::hasTeamFeatures()) { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + Livewire::test(ApiTokenManager::class) + ->set(['managingPermissionsFor' => $token]) + ->set(['updateApiTokenForm' => [ + 'permissions' => [ + 'delete', + 'missing-permission', + ], + ]]) + ->call('updateApiToken'); + + $this->assertTrue($user->fresh()->tokens->first()->can('delete')); + $this->assertFalse($user->fresh()->tokens->first()->can('read')); + $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); + } +} diff --git a/tests/Feature/AuthenticationTest.php b/tests/Feature/AuthenticationTest.php new file mode 100644 index 00000000..2dbceac2 --- /dev/null +++ b/tests/Feature/AuthenticationTest.php @@ -0,0 +1,45 @@ +get('/login'); + + $response->assertStatus(200); + } + + public function test_users_can_authenticate_using_the_login_screen() + { + $user = User::factory()->create(); + + $response = $this->post('/login', [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(RouteServiceProvider::HOME); + } + + public function test_users_can_not_authenticate_with_invalid_password() + { + $user = User::factory()->create(); + + $this->post('/login', [ + 'email' => $user->email, + 'password' => 'wrong-password', + ]); + + $this->assertGuest(); + } +} diff --git a/tests/Feature/BrowserSessionsTest.php b/tests/Feature/BrowserSessionsTest.php new file mode 100644 index 00000000..d848789b --- /dev/null +++ b/tests/Feature/BrowserSessionsTest.php @@ -0,0 +1,23 @@ +actingAs($user = User::factory()->create()); + + Livewire::test(LogoutOtherBrowserSessionsForm::class) + ->set('password', 'password') + ->call('logoutOtherBrowserSessions'); + } +} diff --git a/tests/Feature/CreateApiTokenTest.php b/tests/Feature/CreateApiTokenTest.php new file mode 100644 index 00000000..e0834fa3 --- /dev/null +++ b/tests/Feature/CreateApiTokenTest.php @@ -0,0 +1,43 @@ +markTestSkipped('API support is not enabled.'); + } + + if (Features::hasTeamFeatures()) { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + Livewire::test(ApiTokenManager::class) + ->set(['createApiTokenForm' => [ + 'name' => 'Test Token', + 'permissions' => [ + 'read', + 'update', + ], + ]]) + ->call('createApiToken'); + + $this->assertCount(1, $user->fresh()->tokens); + $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); + $this->assertTrue($user->fresh()->tokens->first()->can('read')); + $this->assertFalse($user->fresh()->tokens->first()->can('delete')); + } +} diff --git a/tests/Feature/DeleteAccountTest.php b/tests/Feature/DeleteAccountTest.php new file mode 100644 index 00000000..7267957f --- /dev/null +++ b/tests/Feature/DeleteAccountTest.php @@ -0,0 +1,46 @@ +markTestSkipped('Account deletion is not enabled.'); + } + + $this->actingAs($user = User::factory()->create()); + + $component = Livewire::test(DeleteUserForm::class) + ->set('password', 'password') + ->call('deleteUser'); + + $this->assertNull($user->fresh()); + } + + public function test_correct_password_must_be_provided_before_account_can_be_deleted() + { + if (! Features::hasAccountDeletionFeatures()) { + return $this->markTestSkipped('Account deletion is not enabled.'); + } + + $this->actingAs($user = User::factory()->create()); + + Livewire::test(DeleteUserForm::class) + ->set('password', 'wrong-password') + ->call('deleteUser') + ->assertHasErrors(['password']); + + $this->assertNotNull($user->fresh()); + } +} diff --git a/tests/Feature/DeleteApiTokenTest.php b/tests/Feature/DeleteApiTokenTest.php new file mode 100644 index 00000000..a8844c04 --- /dev/null +++ b/tests/Feature/DeleteApiTokenTest.php @@ -0,0 +1,41 @@ +markTestSkipped('API support is not enabled.'); + } + + if (Features::hasTeamFeatures()) { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + Livewire::test(ApiTokenManager::class) + ->set(['apiTokenIdBeingDeleted' => $token->id]) + ->call('deleteApiToken'); + + $this->assertCount(0, $user->fresh()->tokens); + } +} diff --git a/tests/Feature/EmailVerificationTest.php b/tests/Feature/EmailVerificationTest.php new file mode 100644 index 00000000..bed27270 --- /dev/null +++ b/tests/Feature/EmailVerificationTest.php @@ -0,0 +1,80 @@ +markTestSkipped('Email verification not enabled.'); + } + + $user = User::factory()->withPersonalTeam()->create([ + 'email_verified_at' => null, + ]); + + $response = $this->actingAs($user)->get('/email/verify'); + + $response->assertStatus(200); + } + + public function test_email_can_be_verified() + { + if (! Features::enabled(Features::emailVerification())) { + return $this->markTestSkipped('Email verification not enabled.'); + } + + Event::fake(); + + $user = User::factory()->create([ + 'email_verified_at' => null, + ]); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + + $this->assertTrue($user->fresh()->hasVerifiedEmail()); + $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1'); + } + + public function test_email_can_not_verified_with_invalid_hash() + { + if (! Features::enabled(Features::emailVerification())) { + return $this->markTestSkipped('Email verification not enabled.'); + } + + $user = User::factory()->create([ + 'email_verified_at' => null, + ]); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + $this->assertFalse($user->fresh()->hasVerifiedEmail()); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 00000000..4ae02bc5 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,21 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/PasswordConfirmationTest.php b/tests/Feature/PasswordConfirmationTest.php new file mode 100644 index 00000000..6bb50c59 --- /dev/null +++ b/tests/Feature/PasswordConfirmationTest.php @@ -0,0 +1,47 @@ +withPersonalTeam()->create() + : User::factory()->create(); + + $response = $this->actingAs($user)->get('/user/confirm-password'); + + $response->assertStatus(200); + } + + public function test_password_can_be_confirmed() + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/user/confirm-password', [ + 'password' => 'password', + ]); + + $response->assertRedirect(); + $response->assertSessionHasNoErrors(); + } + + public function test_password_is_not_confirmed_with_invalid_password() + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/user/confirm-password', [ + 'password' => 'wrong-password', + ]); + + $response->assertSessionHasErrors(); + } +} diff --git a/tests/Feature/PasswordResetTest.php b/tests/Feature/PasswordResetTest.php new file mode 100644 index 00000000..d09261cc --- /dev/null +++ b/tests/Feature/PasswordResetTest.php @@ -0,0 +1,94 @@ +markTestSkipped('Password updates are not enabled.'); + } + + $response = $this->get('/forgot-password'); + + $response->assertStatus(200); + } + + public function test_reset_password_link_can_be_requested() + { + if (! Features::enabled(Features::updatePasswords())) { + return $this->markTestSkipped('Password updates are not enabled.'); + } + + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class); + } + + public function test_reset_password_screen_can_be_rendered() + { + if (! Features::enabled(Features::updatePasswords())) { + return $this->markTestSkipped('Password updates are not enabled.'); + } + + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); + + $response->assertStatus(200); + + return true; + }); + } + + public function test_password_can_be_reset_with_valid_token() + { + if (! Features::enabled(Features::updatePasswords())) { + return $this->markTestSkipped('Password updates are not enabled.'); + } + + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $response = $this->post('/reset-password', [ + 'token' => $notification->token, + 'email' => $user->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $response->assertSessionHasNoErrors(); + + return true; + }); + } +} diff --git a/tests/Feature/ProfileInformationTest.php b/tests/Feature/ProfileInformationTest.php new file mode 100644 index 00000000..5487825b --- /dev/null +++ b/tests/Feature/ProfileInformationTest.php @@ -0,0 +1,36 @@ +actingAs($user = User::factory()->create()); + + $component = Livewire::test(UpdateProfileInformationForm::class); + + $this->assertEquals($user->name, $component->state['name']); + $this->assertEquals($user->email, $component->state['email']); + } + + public function test_profile_information_can_be_updated() + { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdateProfileInformationForm::class) + ->set('state', ['name' => 'Test Name', 'email' => 'test@example.com']) + ->call('updateProfileInformation'); + + $this->assertEquals('Test Name', $user->fresh()->name); + $this->assertEquals('test@example.com', $user->fresh()->email); + } +} diff --git a/tests/Feature/RegistrationTest.php b/tests/Feature/RegistrationTest.php new file mode 100644 index 00000000..aae144fc --- /dev/null +++ b/tests/Feature/RegistrationTest.php @@ -0,0 +1,54 @@ +markTestSkipped('Registration support is not enabled.'); + } + + $response = $this->get('/register'); + + $response->assertStatus(200); + } + + public function test_registration_screen_cannot_be_rendered_if_support_is_disabled() + { + if (Features::enabled(Features::registration())) { + return $this->markTestSkipped('Registration support is enabled.'); + } + + $response = $this->get('/register'); + + $response->assertStatus(404); + } + + public function test_new_users_can_register() + { + if (! Features::enabled(Features::registration())) { + return $this->markTestSkipped('Registration support is not enabled.'); + } + + $response = $this->post('/register', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => 'password', + 'password_confirmation' => 'password', + 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(RouteServiceProvider::HOME); + } +} diff --git a/tests/Feature/TwoFactorAuthenticationSettingsTest.php b/tests/Feature/TwoFactorAuthenticationSettingsTest.php new file mode 100644 index 00000000..60dc0dbc --- /dev/null +++ b/tests/Feature/TwoFactorAuthenticationSettingsTest.php @@ -0,0 +1,63 @@ +actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication'); + + $user = $user->fresh(); + + $this->assertNotNull($user->two_factor_secret); + $this->assertCount(8, $user->recoveryCodes()); + } + + public function test_recovery_codes_can_be_regenerated() + { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $component = Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication') + ->call('regenerateRecoveryCodes'); + + $user = $user->fresh(); + + $component->call('regenerateRecoveryCodes'); + + $this->assertCount(8, $user->recoveryCodes()); + $this->assertCount(8, array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes())); + } + + public function test_two_factor_authentication_can_be_disabled() + { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $component = Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication'); + + $this->assertNotNull($user->fresh()->two_factor_secret); + + $component->call('disableTwoFactorAuthentication'); + + $this->assertNull($user->fresh()->two_factor_secret); + } +} diff --git a/tests/Feature/UpdatePasswordTest.php b/tests/Feature/UpdatePasswordTest.php new file mode 100644 index 00000000..a4588c08 --- /dev/null +++ b/tests/Feature/UpdatePasswordTest.php @@ -0,0 +1,62 @@ +actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]) + ->call('updatePassword'); + + $this->assertTrue(Hash::check('new-password', $user->fresh()->password)); + } + + public function test_current_password_must_be_correct() + { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'wrong-password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]) + ->call('updatePassword') + ->assertHasErrors(['current_password']); + + $this->assertTrue(Hash::check('password', $user->fresh()->password)); + } + + public function test_new_passwords_must_match() + { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'wrong-password', + ]) + ->call('updatePassword') + ->assertHasErrors(['password']); + + $this->assertTrue(Hash::check('password', $user->fresh()->password)); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 00000000..2932d4a6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +}