From 8e7ccad922b898c2d7a1704cf0ef8cf017365ef1 Mon Sep 17 00:00:00 2001 From: Carlo Beltrame Date: Thu, 19 Dec 2019 15:42:08 +0100 Subject: [PATCH 1/4] Preserve form data when the user's session is expired --- app/Exceptions/Handler.php | 17 ++++++++++ app/Http/Kernel.php | 1 + app/Http/Middleware/KeepOldInputInFlash.php | 32 +++++++++++++++++++ .../BlockAndRequirementsInputWrapper.vue | 13 ++++++++ resources/js/Components/MultiSelect.vue | 11 ++++--- resources/lang/de/t.php | 3 ++ resources/lang/fr/t.php | 3 ++ .../components/form/checkboxInput.blade.php | 2 +- .../views/components/form/dateInput.blade.php | 2 +- .../form/multiSelectInput.blade.php | 5 ++- .../form/radioButtonInput.blade.php | 2 +- .../views/components/form/textInput.blade.php | 2 +- .../components/form/textareaInput.blade.php | 2 +- resources/views/observation/new.blade.php | 6 ++-- routes/web.php | 10 +++--- 15 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 app/Http/Middleware/KeepOldInputInFlash.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 043cad6b..59afdc9c 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,7 +2,9 @@ namespace App\Exceptions; +use App\Http\Middleware\KeepOldInputInFlash; use Exception; +use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler @@ -46,6 +48,21 @@ public function report(Exception $exception) */ public function render($request, Exception $exception) { + if ($exception instanceof AuthenticationException && $request->method() != 'GET') { + $this->preserveSubmittedFormData($request); + } return parent::render($request, $exception); } + + /** + * The user submitted a form but wasn't authenticated. Probably the session expired. + * Preserve the form data so it can be restored once the user logs back in. + * + * @param \Illuminate\Http\Request $request + */ + protected function preserveSubmittedFormData($request) { + $request->flashExcept($this->dontFlash); + session()->flash('alert-warning', __('t.errors.session_expired_try_again')); + session()->flash(KeepOldInputInFlash::KEEP_OLD_INPUT_IN_FLASH, true); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 190f58b4..e1af34c9 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -63,6 +63,7 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'courseNotArchived' => CourseMustNotBeArchived::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'keepOldInputInFlash' => \App\Http\Middleware\KeepOldInputInFlash::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, diff --git a/app/Http/Middleware/KeepOldInputInFlash.php b/app/Http/Middleware/KeepOldInputInFlash.php new file mode 100644 index 00000000..5c7e0b93 --- /dev/null +++ b/app/Http/Middleware/KeepOldInputInFlash.php @@ -0,0 +1,32 @@ +has(self::KEEP_OLD_INPUT_IN_FLASH)) { + session()->keep('_old_input'); + session()->keep(self::KEEP_OLD_INPUT_IN_FLASH); + } + return $next($request); + } +} diff --git a/resources/js/Components/BlockAndRequirementsInputWrapper.vue b/resources/js/Components/BlockAndRequirementsInputWrapper.vue index 93e140c3..3994dde7 100644 --- a/resources/js/Components/BlockAndRequirementsInputWrapper.vue +++ b/resources/js/Components/BlockAndRequirementsInputWrapper.vue @@ -8,13 +8,26 @@ export default { name: 'BlockAndRequirementsInputWrapper', + props: { + hasOldValues: { + type: Boolean, + default: false + } + }, data: function () { return { + // If we have old input, ignore the first input event from the block selection component, + // so we don't overwrite the old value of the requirements selection component initially. + ignoreNextBlockUpdateEvent: this.hasOldValues, requirementsValue: '' } }, methods: { onBlockUpdate (blockObject) { + if (this.ignoreNextBlockUpdateEvent) { + this.ignoreNextBlockUpdateEvent = false + return + } this.requirementsValue = blockObject.data } } diff --git a/resources/js/Components/MultiSelect.vue b/resources/js/Components/MultiSelect.vue index 326b7a2c..2e1f59c2 100644 --- a/resources/js/Components/MultiSelect.vue +++ b/resources/js/Components/MultiSelect.vue @@ -24,6 +24,7 @@ export default { name: String, multiple: Boolean, noOptions: String, + oldValue: String, value: String, options: Array, submitOnInput: String, @@ -34,7 +35,7 @@ export default { }, data: function() { return { - currentValue: this.initialValue() + currentValue: this.selectedOptions(this.oldValue !== undefined ? this.oldValue : this.value) } }, computed: { @@ -50,11 +51,11 @@ export default { } }, methods: { - initialValue() { + selectedOptions(value) { if (this.multiple) { - return this.value ? this.options.filter(el => this.value.split(',').includes(el.value)) : [] + return value ? this.options.filter(el => value.split(',').includes(el.value)) : [] } else { - return this.value ? this.options.find(el => el.value === this.value) : [] + return value ? this.options.find(el => el.value === value) : [] } }, onInput(val, id) { @@ -72,7 +73,7 @@ export default { }, watch: { value (newValue, oldValue) { - this.currentValue = this.initialValue() + this.currentValue = this.selectedOptions(this.value) } }, mounted () { diff --git a/resources/lang/de/t.php b/resources/lang/de/t.php index c73f63df..008767ca 100644 --- a/resources/lang/de/t.php +++ b/resources/lang/de/t.php @@ -1,5 +1,8 @@ array( + "session_expired_try_again" => "Ups, du bist inzwischen nicht mehr eingeloggt. Bitte logge dich nochmals ein, deine Eingaben werden dann wiederhergestellt.", + ), "footer" => array( "slogan" => "Qualix. was gaffsch?", ), diff --git a/resources/lang/fr/t.php b/resources/lang/fr/t.php index c4104d9c..87ffb2fb 100644 --- a/resources/lang/fr/t.php +++ b/resources/lang/fr/t.php @@ -1,5 +1,8 @@ array( + "session_expired_try_again" => "Oups, tu n'es plus inscrit. Merci de t'inscrire encore une fois, tes données saisies seront restaurées.", + ), "footer" => array( "slogan" => "Qualix. quoi reluques-tu?", ), diff --git a/resources/views/components/form/checkboxInput.blade.php b/resources/views/components/form/checkboxInput.blade.php index 170c135f..cba81399 100644 --- a/resources/views/components/form/checkboxInput.blade.php +++ b/resources/views/components/form/checkboxInput.blade.php @@ -9,7 +9,7 @@ name="{{ $name }}" class="custom-control-input{{ $errors->has($name) ? ' is-invalid' : '' }}" value="1" - {{ ((isset($value) && $value) || (!isset($value) && old($name))) ? 'checked' : '' }}> + {{ (old($name) ?? $value ?? false) ? 'checked' : '' }}> diff --git a/resources/views/components/form/dateInput.blade.php b/resources/views/components/form/dateInput.blade.php index 5d83a769..bdafcbd6 100644 --- a/resources/views/components/form/dateInput.blade.php +++ b/resources/views/components/form/dateInput.blade.php @@ -7,7 +7,7 @@ id="{{ $name }}" name="{{ $name }}" class="form-control{{ $errors->has($name) ? ' is-invalid' : '' }}" - value="{{ isset($value) ? $value->format('d.m.Y') : old($name) }}" + value="{{ old($name) ?? $value->format('d.m.Y') ?? '' }}" {{ isset($required) && $required ? 'required' : '' }} {{ isset($autofocus) && $autofocus ? 'autofocus v-focus' : '' }} :config="{ format: 'DD.MM.YYYY', useCurrent: false, locale: '{{App::getLocale()}}' }"> diff --git a/resources/views/components/form/multiSelectInput.blade.php b/resources/views/components/form/multiSelectInput.blade.php index 82e99f72..dabaf7dc 100644 --- a/resources/views/components/form/multiSelectInput.blade.php +++ b/resources/views/components/form/multiSelectInput.blade.php @@ -6,10 +6,13 @@ id="{{ $name }}" name="{{ $name }}" class="form-control-multiselect {{ $errors->has($name) ? ' is-invalid' : '' }}" + @if (old($name) !== null) + old-value="{{ old($name) }}" + @endif @if (isset($valueBind) && $valueBind) :value="{{ $valueBind }}" @else - value="{{ isset($value) ? $value : old($name) }}" + value="{{ $value ?? '' }}" @endif {{ isset($required) && $required ? 'required' : '' }} :allow-empty="true" diff --git a/resources/views/components/form/radioButtonInput.blade.php b/resources/views/components/form/radioButtonInput.blade.php index 6a61be32..0d8c6b9c 100644 --- a/resources/views/components/form/radioButtonInput.blade.php +++ b/resources/views/components/form/radioButtonInput.blade.php @@ -5,7 +5,7 @@
@foreach($options as $optionValue => $option)
- +
@endforeach diff --git a/resources/views/components/form/textInput.blade.php b/resources/views/components/form/textInput.blade.php index 48cb5d6b..e314015f 100644 --- a/resources/views/components/form/textInput.blade.php +++ b/resources/views/components/form/textInput.blade.php @@ -7,7 +7,7 @@ id="{{ $name }}" name="{{ $name }}" class="form-control{{ $errors->has($name) ? ' is-invalid' : '' }}" - value="{{ isset($value) ? $value : old($name) }}" + value="{{ old($name) ?? $value ?? '' }}" {{ isset($required) && $required ? 'required' : '' }} {{ isset($autofocus) && $autofocus ? 'autofocus v-focus' : '' }}> diff --git a/resources/views/components/form/textareaInput.blade.php b/resources/views/components/form/textareaInput.blade.php index a7db83cb..ea43ef2f 100644 --- a/resources/views/components/form/textareaInput.blade.php +++ b/resources/views/components/form/textareaInput.blade.php @@ -7,7 +7,7 @@ name="{{ $name }}" class="form-control{{ $errors->has($name) ? ' is-invalid' : '' }}" {{ isset($required) && $required ? 'required' : '' }} - {{ isset($autofocus) && $autofocus ? 'autofocus v-focus' : '' }}>{{ isset($value) ? $value : old($name) }} + {{ isset($autofocus) && $autofocus ? 'autofocus v-focus' : '' }}>{{ old($name) ?? $value ?? '' }} @if ($errors->has($name)) diff --git a/resources/views/observation/new.blade.php b/resources/views/observation/new.blade.php index a61fa387..e47809e5 100644 --- a/resources/views/observation/new.blade.php +++ b/resources/views/observation/new.blade.php @@ -10,7 +10,7 @@ 'name' => 'participant_ids', 'label' => __('t.models.observation.participant'), 'required' => true, - 'value' => $participant_id ?? '', + 'value' => $participant_id, 'options' => $course->participants->all(), 'valueFn' => function(\App\Models\Participant $participant) { return $participant->id; }, 'displayFn' => function(\App\Models\Participant $participant) { return $participant->scout_name; }, @@ -20,13 +20,13 @@ @component('components.form.textareaInput', ['name' => 'content', 'label' => __('t.models.observation.content'), 'required' => true, 'autofocus' => ($participant_id !== null)])@endcomponent - + @component('components.form.multiSelectInput', [ 'name' => 'block_id', 'label' => __('t.models.observation.block'), 'required' => true, - 'value' => $block_id ?? '', + 'value' => $block_id, 'options' => $course->blocks->all(), 'valueFn' => function(\App\Models\Block $block) { return $block->id; }, 'displayFn' => function(\App\Models\Block $block) { return $block->blockname_and_number; }, diff --git a/routes/web.php b/routes/web.php index 9f057a17..2849ce0b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -83,7 +83,9 @@ Route::post('/newcourse', 'CourseController@store')->name('admin.newcourse.store'); }); -Auth::routes(['verify' => true]); -Route::get('login/hitobito', 'Auth\LoginController@redirectToHitobitoOAuth')->name('login.hitobito'); -Route::get('login/hitobito/callback', 'Auth\LoginController@handleHitobitoOAuthCallback')->name('login.hitobito.callback'); -Route::get('locale/{locale}', 'LocalizationController@select')->name('locale.select'); +Route::middleware('keepOldInputInFlash')->group(function () { + Auth::routes(['verify' => true]); + Route::get('login/hitobito', 'Auth\LoginController@redirectToHitobitoOAuth')->name('login.hitobito'); + Route::get('login/hitobito/callback', 'Auth\LoginController@handleHitobitoOAuthCallback')->name('login.hitobito.callback'); + Route::get('locale/{locale}', 'LocalizationController@select')->name('locale.select'); +}); From 9e3b0f59ca19b2b319a002285357c65b9d2d8c72 Mon Sep 17 00:00:00 2001 From: Carlo Beltrame Date: Fri, 20 Dec 2019 12:22:40 +0100 Subject: [PATCH 2/4] Save expired form data in persistent session instead of flash, and avoid issues with _old_input from the login forms --- app/Exceptions/Handler.php | 5 ++- app/Http/Kernel.php | 2 +- app/Http/Middleware/KeepOldInputInFlash.php | 32 ------------------- .../RestoreFormDataFromExpiredSession.php | 31 ++++++++++++++++++ resources/lang/de/t.php | 1 + resources/lang/fr/t.php | 1 + routes/web.php | 12 +++---- 7 files changed, 41 insertions(+), 43 deletions(-) delete mode 100644 app/Http/Middleware/KeepOldInputInFlash.php create mode 100644 app/Http/Middleware/RestoreFormDataFromExpiredSession.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 59afdc9c..fdc8ef05 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,7 +2,7 @@ namespace App\Exceptions; -use App\Http\Middleware\KeepOldInputInFlash; +use App\Http\Middleware\RestoreFormDataFromExpiredSession; use Exception; use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -61,8 +61,7 @@ public function render($request, Exception $exception) * @param \Illuminate\Http\Request $request */ protected function preserveSubmittedFormData($request) { - $request->flashExcept($this->dontFlash); + session()->put(RestoreFormDataFromExpiredSession::KEY, $request->except($this->dontFlash)); session()->flash('alert-warning', __('t.errors.session_expired_try_again')); - session()->flash(KeepOldInputInFlash::KEEP_OLD_INPUT_IN_FLASH, true); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e1af34c9..700970bd 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -63,7 +63,7 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'courseNotArchived' => CourseMustNotBeArchived::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'keepOldInputInFlash' => \App\Http\Middleware\KeepOldInputInFlash::class, + 'restoreFormData' => \App\Http\Middleware\RestoreFormDataFromExpiredSession::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, diff --git a/app/Http/Middleware/KeepOldInputInFlash.php b/app/Http/Middleware/KeepOldInputInFlash.php deleted file mode 100644 index 5c7e0b93..00000000 --- a/app/Http/Middleware/KeepOldInputInFlash.php +++ /dev/null @@ -1,32 +0,0 @@ -has(self::KEEP_OLD_INPUT_IN_FLASH)) { - session()->keep('_old_input'); - session()->keep(self::KEEP_OLD_INPUT_IN_FLASH); - } - return $next($request); - } -} diff --git a/app/Http/Middleware/RestoreFormDataFromExpiredSession.php b/app/Http/Middleware/RestoreFormDataFromExpiredSession.php new file mode 100644 index 00000000..988991ca --- /dev/null +++ b/app/Http/Middleware/RestoreFormDataFromExpiredSession.php @@ -0,0 +1,31 @@ +has(self::KEY)) { + session()->now('_old_input', session(self::KEY)); + session()->forget(self::KEY); + session()->flash('alert-warning', __('t.errors.form_data_restored_please_submit_again')); + } + return $next($request); + } +} diff --git a/resources/lang/de/t.php b/resources/lang/de/t.php index 008767ca..7fdb4371 100644 --- a/resources/lang/de/t.php +++ b/resources/lang/de/t.php @@ -1,6 +1,7 @@ array( + "form_data_restored_please_submit_again" => "Deine eingegebenen Daten wurden wiederhergestellt. Speichern nicht vergessen!", "session_expired_try_again" => "Ups, du bist inzwischen nicht mehr eingeloggt. Bitte logge dich nochmals ein, deine Eingaben werden dann wiederhergestellt.", ), "footer" => array( diff --git a/resources/lang/fr/t.php b/resources/lang/fr/t.php index 87ffb2fb..a0671f4b 100644 --- a/resources/lang/fr/t.php +++ b/resources/lang/fr/t.php @@ -1,6 +1,7 @@ array( + "form_data_restored_please_submit_again" => "Tes données saisies ont étés restaurées. N'oublie pas a sauvegarder!", "session_expired_try_again" => "Oups, tu n'es plus inscrit. Merci de t'inscrire encore une fois, tes données saisies seront restaurées.", ), "footer" => array( diff --git a/routes/web.php b/routes/web.php index 2849ce0b..24c46007 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; -Route::middleware(['auth', 'verified'])->group(function () { +Route::middleware(['auth', 'verified', 'restoreFormData'])->group(function () { Route::get('/', 'CourseController@noCourse')->name('home'); Route::get('/course', 'CourseController@noCourse'); @@ -83,9 +83,7 @@ Route::post('/newcourse', 'CourseController@store')->name('admin.newcourse.store'); }); -Route::middleware('keepOldInputInFlash')->group(function () { - Auth::routes(['verify' => true]); - Route::get('login/hitobito', 'Auth\LoginController@redirectToHitobitoOAuth')->name('login.hitobito'); - Route::get('login/hitobito/callback', 'Auth\LoginController@handleHitobitoOAuthCallback')->name('login.hitobito.callback'); - Route::get('locale/{locale}', 'LocalizationController@select')->name('locale.select'); -}); +Auth::routes(['verify' => true]); +Route::get('login/hitobito', 'Auth\LoginController@redirectToHitobitoOAuth')->name('login.hitobito'); +Route::get('login/hitobito/callback', 'Auth\LoginController@handleHitobitoOAuthCallback')->name('login.hitobito.callback'); +Route::get('locale/{locale}', 'LocalizationController@select')->name('locale.select'); From 8aa578abe75e40d415faf7c3e69e544c87ec449d Mon Sep 17 00:00:00 2001 From: Carlo Beltrame Date: Fri, 20 Dec 2019 12:23:25 +0100 Subject: [PATCH 3/4] Mark all required auth related form fields with asterisks --- resources/views/auth/login.blade.php | 4 ++-- resources/views/auth/passwords/email.blade.php | 2 +- resources/views/auth/register.blade.php | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index ad8c9bf5..509c569e 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -22,7 +22,7 @@ class="btn btn-hitobito form-control{{ $errors->has('hitobito') ? ' is-invalid'
@csrf -
+
@@ -38,7 +38,7 @@ class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email
-
+
diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php index 1bc3b73d..eeb19faa 100644 --- a/resources/views/auth/passwords/email.blade.php +++ b/resources/views/auth/passwords/email.blade.php @@ -14,7 +14,7 @@ @csrf -
+
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 4526e4e8..61e6cd6a 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -2,7 +2,7 @@ @section('content') @component('components.card', ['header' => __('Register')]) -
+
@csrf -
+
@@ -36,7 +36,7 @@ class="btn btn-hitobito form-control{{ $errors->has('hitobito') ? ' is-invalid'
-
+
@@ -52,7 +52,7 @@ class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email
-
+
@@ -68,7 +68,7 @@ class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="pa
-
+
From 0672de11dbddec2b4f35ac53ca2a0f50e7154aa1 Mon Sep 17 00:00:00 2001 From: Carlo Beltrame Date: Tue, 24 Dec 2019 15:27:44 +0100 Subject: [PATCH 4/4] Add tests for restoring form data when session expired --- .../RestoreFormDataFromExpiredSession.php | 2 +- .../RestoreFormDataWhenSessionExpiredTest.php | 131 ++++++++++++++++++ tests/Feature/Auth/HitobitoOAuthTest.php | 4 +- tests/TestCase.php | 2 +- 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 tests/Feature/App/RestoreFormDataWhenSessionExpiredTest.php diff --git a/app/Http/Middleware/RestoreFormDataFromExpiredSession.php b/app/Http/Middleware/RestoreFormDataFromExpiredSession.php index 988991ca..410dbd1f 100644 --- a/app/Http/Middleware/RestoreFormDataFromExpiredSession.php +++ b/app/Http/Middleware/RestoreFormDataFromExpiredSession.php @@ -24,7 +24,7 @@ public function handle($request, Closure $next) if (session()->has(self::KEY)) { session()->now('_old_input', session(self::KEY)); session()->forget(self::KEY); - session()->flash('alert-warning', __('t.errors.form_data_restored_please_submit_again')); + session()->now('alert-warning', __('t.errors.form_data_restored_please_submit_again')); } return $next($request); } diff --git a/tests/Feature/App/RestoreFormDataWhenSessionExpiredTest.php b/tests/Feature/App/RestoreFormDataWhenSessionExpiredTest.php new file mode 100644 index 00000000..fd8f66a5 --- /dev/null +++ b/tests/Feature/App/RestoreFormDataWhenSessionExpiredTest.php @@ -0,0 +1,131 @@ +createObservation('hat gut mitgemacht', 1, [], [], $this->blockId); + + $this->payload = ['participant_ids' => '' . $this->participantId, 'content' => 'this text will be restored', 'impression' => '1', 'block_id' => '' . $this->blockId, 'requirement_ids' => '', 'category_ids' => '']; + } + + public function test_shouldRestoreSubmittedFormData_whenLoggingBackInNormally() { + $this->checkRestorationOfFormData(function () { + // the user logs back in + return $this->post('/login', ['email' => $this->email, 'password' => '87654321'], ['referer' => '/login']); + }); + } + + public function test_shouldRestoreSubmittedFormData_whenLoginFailsOnce() { + $this->checkRestorationOfFormData(function () { + // the user at first fails to log back in + $response = $this->post('/login', ['email' => $this->email, 'password' => 'wrong-password'], ['referer' => '/login']); + $response->assertStatus(302); + $response->assertRedirect('/login'); + + // then the user manages to log back in + return $this->post('/login', ['email' => $this->email, 'password' => '87654321'], ['referer' => '/login']); + }); + } + + public function test_shouldRestoreSubmittedFormData_whenLoggingInAsADifferentUser_inSameCourse() { + $otherUser = $this->createUser(['name' => 'Lindo', 'password' => bcrypt('12345678'), 'email' => 'another@user.com'], false); + $otherUser->courses()->attach($this->courseId); + + $this->checkRestorationOfFormData(function () { + // log in as a different user + return $this->post('/login', ['email' => 'another@user.com', 'password' => '12345678'], ['referer' => '/login']); + }); + } + + public function test_shouldNotRestoreSubmittedFormData_whenLoggingInAsADifferentUser_whoIsNotPartOfTheCourse() { + $this->createUser(['name' => 'Lindo', 'password' => bcrypt('12345678'), 'email' => 'another@user.com'], false); + + $this->checkRestorationOfFormData(function () { + // log in as a different user + return $this->post('/login', ['email' => 'another@user.com', 'password' => '12345678'], ['referer' => '/login']); + }, false); + } + + public function test_shouldRestoreSubmittedFormData_whenLoggingInViaHitobito() { + $otherUser = factory(HitobitoUser::class)->create(['hitobito_id' => 123, 'name' => 'Cosinus', 'email' => 'cosinus@hitobito.com']); + HitobitoOAuthTest::mockHitobitoResponses(123, 'cosinus@hitobito.com', 'Cosinus'); + $otherUser->courses()->attach($this->courseId); + + $this->checkRestorationOfFormData(function () { + $state = HitobitoOAuthTest::extractRedirectQueryParams($this->get('/login/hitobito'))['state']; + return $this->get('/login/hitobito/callback?code=1234&state=' . $state); + }); + } + + public function test_shouldRestoreSubmittedFormData_whenChangingLanguageOnLoginScreen() { + $this->checkRestorationOfFormData(function () { + // the user switches the language + $this->get('/locale/fr'); + + // the user logs back in + return $this->post('/login', ['email' => $this->email, 'password' => '87654321'], ['referer' => '/login']); + }, true, 'Tes données saisies ont étés restaurées. N'oublie pas a sauvegarder!'); + } + + public function checkRestorationOfFormData(Closure $logBackIn, bool $shouldRestore = true, $restoredFlashMessage = 'Deine eingegebenen Daten wurden wiederhergestellt. Speichern nicht vergessen!') { + // given + $this->get('/course/' . $this->courseId . '/observation/new'); + + // simulate the user session expiring + auth()->logout(); + + // when + // simulate the user clicking the stale submit button + $response = $this->post('/course/' . $this->courseId . '/observation/new', $this->payload); + $response->assertStatus(302); + $response->assertRedirect('/login'); + + // then + // check that the flash message is displayed + /** @var TestResponse $response */ + $response = $response->followRedirects(); + $response->assertSeeText('Ups, du bist inzwischen nicht mehr eingeloggt. Bitte logge dich nochmals ein, deine Eingaben werden dann wiederhergestellt.'); + + // when + $response = $logBackIn(); + + // then + $response->assertStatus(302); + $response->assertRedirect('/course/' . $this->courseId . '/observation/new'); + + /** @var TestResponse $response */ + $response = $response->followRedirects(); + + // check that restoration works as intended + if ($shouldRestore) { + $response->assertSeeText($restoredFlashMessage); + $response->assertSee('this text will be restored'); + } else { + $response->assertDontSeeText($restoredFlashMessage); + $response->assertDontSee('this text will be restored'); + } + + // when + // Refresh the page + $response = $this->get('/course/' . $this->courseId . '/observation/new'); + + // then + // data should not be restored a second time + $response->assertDontSeeText($restoredFlashMessage); + $response->assertDontSee('this text will be restored'); + } + +} diff --git a/tests/Feature/Auth/HitobitoOAuthTest.php b/tests/Feature/Auth/HitobitoOAuthTest.php index e432e2f9..0d3ebe3b 100644 --- a/tests/Feature/Auth/HitobitoOAuthTest.php +++ b/tests/Feature/Auth/HitobitoOAuthTest.php @@ -29,13 +29,13 @@ public function test_loginWithMiData_shouldRedirectToCorrectOAuthUrl() { $location); } - protected function extractRedirectQueryParams($response) { + public static function extractRedirectQueryParams($response) { $response->assertRedirect(); parse_str( parse_url( $response->headers->get('Location'), PHP_URL_QUERY), $result ); return $result; } - protected function mockHitobitoResponses($hitobitoId, $email, $nickname) { + public static function mockHitobitoResponses($hitobitoId, $email, $nickname) { $hitobitoMock = new MockHandler([ // Respond to the authorization_token request new Response(200, [], '{"access_token": "abcd"}'), diff --git a/tests/TestCase.php b/tests/TestCase.php index 72c0d954..413f02aa 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -27,7 +27,7 @@ public function setUp(): void { parent::setUp(); - $this->createUser(['name' => 'Bari'], true); + $this->createUser(['name' => 'Bari', 'password' => bcrypt('87654321'), 'email' => 'bari@example.com'], true); Session::start();