Changeset View
Changeset View
Standalone View
Standalone View
src/tests/Browser/UsersTest.php
Show First 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | class UsersTest extends TestCaseDusk | ||||
} | } | ||||
/** | /** | ||||
* Test user account editing page (not profile page) | * Test user account editing page (not profile page) | ||||
*/ | */ | ||||
public function testInfo(): void | public function testInfo(): void | ||||
{ | { | ||||
$this->browse(function (Browser $browser) { | $this->browse(function (Browser $browser) { | ||||
$user = User::where('email', 'john@kolab.org')->first(); | $user = $this->getTestUser('john@kolab.org'); | ||||
$jack = $this->getTestUser('jack@kolab.org'); | |||||
$user->verificationcodes()->delete(); | |||||
$jack->verificationcodes()->delete(); | |||||
vanmeeuwen: Can we `$john` `$user`? | |||||
// Test that the page requires authentication | // Test that the page requires authentication | ||||
$browser->visit('/user/' . $user->id) | $browser->visit('/user/' . $user->id) | ||||
->on(new Home()) | ->on(new Home()) | ||||
->submitLogon('john@kolab.org', 'simple123', false) | ->submitLogon('john@kolab.org', 'simple123', false) | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->assertSeeIn('#user-info .card-title', 'User account') | ->assertSeeIn('#user-info .card-title', 'User account') | ||||
->with('@general', function (Browser $browser) { | ->with('@general', function (Browser $browser) { | ||||
Show All 12 Lines | public function testInfo(): void | ||||
->assertDisabled('div.row:nth-child(5) input[type=text]') | ->assertDisabled('div.row:nth-child(5) input[type=text]') | ||||
->assertSeeIn('div.row:nth-child(6) label', 'Email Aliases') | ->assertSeeIn('div.row:nth-child(6) label', 'Email Aliases') | ||||
->assertVisible('div.row:nth-child(6) .list-input') | ->assertVisible('div.row:nth-child(6) .list-input') | ||||
->with(new ListInput('#aliases'), function (Browser $browser) { | ->with(new ListInput('#aliases'), function (Browser $browser) { | ||||
$browser->assertListInputValue(['john.doe@kolab.org']) | $browser->assertListInputValue(['john.doe@kolab.org']) | ||||
->assertValue('@input', ''); | ->assertValue('@input', ''); | ||||
}) | }) | ||||
->assertSeeIn('div.row:nth-child(7) label', 'Password') | ->assertSeeIn('div.row:nth-child(7) label', 'Password') | ||||
->assertValue('div.row:nth-child(7) input[type=password]', '') | ->assertValue('div.row:nth-child(7) input#password', '') | ||||
->assertSeeIn('div.row:nth-child(8) label', 'Confirm Password') | ->assertValue('div.row:nth-child(7) input#password_confirmation', '') | ||||
->assertValue('div.row:nth-child(8) input[type=password]', '') | ->assertAttribute('#password', 'placeholder', 'Password') | ||||
->assertAttribute('#password_confirmation', 'placeholder', 'Confirm Password') | |||||
->assertMissing('div.row:nth-child(7) .btn-group') | |||||
->assertMissing('div.row:nth-child(7) #password-link') | |||||
->assertSeeIn('button[type=submit]', 'Submit') | ->assertSeeIn('button[type=submit]', 'Submit') | ||||
// Clear some fields and submit | // Clear some fields and submit | ||||
->vueClear('#first_name') | ->vueClear('#first_name') | ||||
->vueClear('#last_name') | ->vueClear('#last_name') | ||||
->click('button[type=submit]'); | ->click('button[type=submit]'); | ||||
}) | }) | ||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') | ->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') | ||||
->on(new UserList()) | ->on(new UserList()) | ||||
->click('@table tr:nth-child(3) a') | ->click('@table tr:nth-child(3) a') | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->assertSeeIn('#user-info .card-title', 'User account') | ->assertSeeIn('#user-info .card-title', 'User account') | ||||
->with('@general', function (Browser $browser) { | ->with('@general', function (Browser $browser) { | ||||
// Test error handling (password) | // Test error handling (password) | ||||
$browser->type('#password', 'aaaaaa') | $browser->type('#password', 'aaaaaa') | ||||
->vueClear('#password_confirmation') | ->vueClear('#password_confirmation') | ||||
->click('button[type=submit]') | ->click('button[type=submit]') | ||||
->waitFor('#password + .invalid-feedback') | ->waitFor('#password_confirmation + .invalid-feedback') | ||||
->assertSeeIn('#password + .invalid-feedback', 'The password confirmation does not match.') | ->assertSeeIn( | ||||
'#password_confirmation + .invalid-feedback', | |||||
'The password confirmation does not match.' | |||||
) | |||||
->assertFocused('#password') | ->assertFocused('#password') | ||||
->assertToast(Toast::TYPE_ERROR, 'Form validation error'); | ->assertToast(Toast::TYPE_ERROR, 'Form validation error'); | ||||
// TODO: Test password change | // TODO: Test password change | ||||
// Test form error handling (aliases) | // Test form error handling (aliases) | ||||
$browser->vueClear('#password') | $browser->vueClear('#password') | ||||
->vueClear('#password_confirmation') | ->vueClear('#password_confirmation') | ||||
Show All 14 Lines | public function testInfo(): void | ||||
}) | }) | ||||
->click('button[type=submit]') | ->click('button[type=submit]') | ||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.'); | ->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.'); | ||||
}) | }) | ||||
->on(new UserList()) | ->on(new UserList()) | ||||
->click('@table tr:nth-child(3) a') | ->click('@table tr:nth-child(3) a') | ||||
->on(new UserInfo()); | ->on(new UserInfo()); | ||||
$john = User::where('email', 'john@kolab.org')->first(); | $alias = $user->aliases()->where('alias', 'john.test@kolab.org')->first(); | ||||
$alias = UserAlias::where('user_id', $john->id)->where('alias', 'john.test@kolab.org')->first(); | |||||
$this->assertTrue(!empty($alias)); | $this->assertTrue(!empty($alias)); | ||||
// Test subscriptions | // Test subscriptions | ||||
$browser->with('@general', function (Browser $browser) { | $browser->with('@general', function (Browser $browser) { | ||||
$browser->assertSeeIn('div.row:nth-child(9) label', 'Subscriptions') | $browser->assertSeeIn('div.row:nth-child(8) label', 'Subscriptions') | ||||
->assertVisible('@skus.row:nth-child(9)') | ->assertVisible('@skus.row:nth-child(8)') | ||||
->with('@skus', function ($browser) { | ->with('@skus', function ($browser) { | ||||
$browser->assertElementsCount('tbody tr', 6) | $browser->assertElementsCount('tbody tr', 6) | ||||
// Mailbox SKU | // Mailbox SKU | ||||
->assertSeeIn('tbody tr:nth-child(1) td.name', 'User Mailbox') | ->assertSeeIn('tbody tr:nth-child(1) td.name', 'User Mailbox') | ||||
->assertSeeIn('tbody tr:nth-child(1) td.price', '5,00 CHF/month') | ->assertSeeIn('tbody tr:nth-child(1) td.price', '5,00 CHF/month') | ||||
->assertChecked('tbody tr:nth-child(1) td.selection input') | ->assertChecked('tbody tr:nth-child(1) td.selection input') | ||||
->assertDisabled('tbody tr:nth-child(1) td.selection input') | ->assertDisabled('tbody tr:nth-child(1) td.selection input') | ||||
->assertTip( | ->assertTip( | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | public function testInfo(): void | ||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.'); | ->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.'); | ||||
}) | }) | ||||
->on(new UserList()) | ->on(new UserList()) | ||||
->click('@table tr:nth-child(3) a') | ->click('@table tr:nth-child(3) a') | ||||
->on(new UserInfo()); | ->on(new UserInfo()); | ||||
$expected = ['activesync', 'groupware', 'mailbox', | $expected = ['activesync', 'groupware', 'mailbox', | ||||
'storage', 'storage', 'storage', 'storage', 'storage', 'storage']; | 'storage', 'storage', 'storage', 'storage', 'storage', 'storage']; | ||||
$this->assertEntitlements($john, $expected); | $this->assertEntitlements($user->fresh(), $expected); | ||||
// Test subscriptions interaction | // Test subscriptions interaction | ||||
$browser->with('@general', function (Browser $browser) { | $browser->with('@general', function (Browser $browser) { | ||||
$browser->with('@skus', function ($browser) { | $browser->with('@skus', function ($browser) { | ||||
// Uncheck 'groupware', expect activesync unchecked | // Uncheck 'groupware', expect activesync unchecked | ||||
$browser->click('#sku-input-groupware') | $browser->click('#sku-input-groupware') | ||||
->assertNotChecked('#sku-input-groupware') | ->assertNotChecked('#sku-input-groupware') | ||||
->assertNotChecked('#sku-input-activesync') | ->assertNotChecked('#sku-input-activesync') | ||||
Show All 15 Lines | public function testInfo(): void | ||||
->assertNotChecked('#sku-input-activesync') | ->assertNotChecked('#sku-input-activesync') | ||||
->assertReadonly('#sku-input-activesync') | ->assertReadonly('#sku-input-activesync') | ||||
// Uncheck '2FA' | // Uncheck '2FA' | ||||
->click('#sku-input-2fa') | ->click('#sku-input-2fa') | ||||
->assertNotChecked('#sku-input-2fa') | ->assertNotChecked('#sku-input-2fa') | ||||
->assertNotReadonly('#sku-input-activesync'); | ->assertNotReadonly('#sku-input-activesync'); | ||||
}); | }); | ||||
}); | }); | ||||
// Test password reset link delete and create | |||||
$code = new \App\VerificationCode(['mode' => 'password-reset']); | |||||
$jack->verificationcodes()->save($code); | |||||
$browser->visit('/user/' . $jack->id) | |||||
->on(new UserInfo()) | |||||
->with('@general', function (Browser $browser) use ($jack, $user, $code) { | |||||
// Test displaying an existing password reset link | |||||
$link = Browser::$baseUrl . '/password-reset/' . $code->code . '-' . $code->short_code; | |||||
$browser->assertSeeIn('div.row:nth-child(7) label', 'Password') | |||||
->assertMissing('#password') | |||||
->assertMissing('#password_confirmation') | |||||
->assertMissing('#pass-mode-link:checked') | |||||
->assertMissing('#pass-mode-input:checked') | |||||
->assertSeeIn('#password-link code', $link) | |||||
->assertVisible('#password-link button.text-danger') | |||||
->assertVisible('#password-link button:not(.text-danger)') | |||||
->assertAttribute('#password-link button:not(.text-danger)', 'title', 'Copy') | |||||
->assertAttribute('#password-link button.text-danger', 'title', 'Delete'); | |||||
// Test deleting an existing password reset link | |||||
$browser->click('#password-link button.text-danger') | |||||
->assertToast(Toast::TYPE_SUCCESS, 'Password reset code deleted successfully.') | |||||
->assertMissing('#password-link') | |||||
->assertMissing('#pass-mode-link:checked') | |||||
->assertMissing('#pass-mode-input:checked') | |||||
->assertMissing('#password'); | |||||
$this->assertSame(0, $jack->verificationcodes()->count()); | |||||
// Test creating a password reset link | |||||
$link = preg_replace('|/[a-z0-9A-Z-]+$|', '', $link) . '/'; | |||||
$browser->click('#pass-mode-link + label') | |||||
->assertMissing('#password') | |||||
->assertMissing('#password_confirmation') | |||||
->waitFor('#password-link code') | |||||
->assertSeeIn('#password-link code', $link); | |||||
// Test copy to clipboard | |||||
/* TODO: Figure out how to give permission to do this operation | |||||
$code = $user->verificationcodes()->first(); | |||||
$link .= $code->code . '-' . $code->short_code; | |||||
$browser->assertMissing('#password-link button.text-danger') | |||||
->click('#password-link button:not(.text-danger)') | |||||
->keys('#organization', ['{left_control}', 'v']) | |||||
->assertAttribute('#organization', 'value', $link) | |||||
->vueClear('#organization'); | |||||
*/ | |||||
// Finally submit the form | |||||
$browser->click('button[type=submit]') | |||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.'); | |||||
$this->assertSame(1, $jack->verificationcodes()->where('active', true)->count()); | |||||
$this->assertSame(0, $user->verificationcodes()->count()); | |||||
}); | |||||
}); | }); | ||||
} | } | ||||
/** | /** | ||||
* Test user settings tab | * Test user settings tab | ||||
* | * | ||||
* @depends testInfo | * @depends testInfo | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | public function testNewUser(): void | ||||
->assertEnabled('div.row:nth-child(4) input[type=text]') | ->assertEnabled('div.row:nth-child(4) input[type=text]') | ||||
->assertSeeIn('div.row:nth-child(5) label', 'Email Aliases') | ->assertSeeIn('div.row:nth-child(5) label', 'Email Aliases') | ||||
->assertVisible('div.row:nth-child(5) .list-input') | ->assertVisible('div.row:nth-child(5) .list-input') | ||||
->with(new ListInput('#aliases'), function (Browser $browser) { | ->with(new ListInput('#aliases'), function (Browser $browser) { | ||||
$browser->assertListInputValue([]) | $browser->assertListInputValue([]) | ||||
->assertValue('@input', ''); | ->assertValue('@input', ''); | ||||
}) | }) | ||||
->assertSeeIn('div.row:nth-child(6) label', 'Password') | ->assertSeeIn('div.row:nth-child(6) label', 'Password') | ||||
->assertValue('div.row:nth-child(6) input[type=password]', '') | ->assertValue('div.row:nth-child(6) input#password', '') | ||||
->assertSeeIn('div.row:nth-child(7) label', 'Confirm Password') | ->assertValue('div.row:nth-child(6) input#password_confirmation', '') | ||||
->assertValue('div.row:nth-child(7) input[type=password]', '') | ->assertAttribute('#password', 'placeholder', 'Password') | ||||
->assertSeeIn('div.row:nth-child(8) label', 'Package') | ->assertAttribute('#password_confirmation', 'placeholder', 'Confirm Password') | ||||
->assertSeeIn('div.row:nth-child(6) .btn-group input:first-child + label', 'Enter password') | |||||
->assertSeeIn('div.row:nth-child(6) .btn-group input:not(:first-child) + label', 'Set via link') | |||||
->assertChecked('div.row:nth-child(6) .btn-group input:first-child') | |||||
->assertMissing('div.row:nth-child(6) #password-link') | |||||
->assertSeeIn('div.row:nth-child(7) label', 'Package') | |||||
// assert packages list widget, select "Lite Account" | // assert packages list widget, select "Lite Account" | ||||
->with('@packages', function ($browser) { | ->with('@packages', function ($browser) { | ||||
$browser->assertElementsCount('tbody tr', 2) | $browser->assertElementsCount('tbody tr', 2) | ||||
->assertSeeIn('tbody tr:nth-child(1)', 'Groupware Account') | ->assertSeeIn('tbody tr:nth-child(1)', 'Groupware Account') | ||||
->assertSeeIn('tbody tr:nth-child(2)', 'Lite Account') | ->assertSeeIn('tbody tr:nth-child(2)', 'Lite Account') | ||||
->assertSeeIn('tbody tr:nth-child(1) .price', '9,90 CHF/month') | ->assertSeeIn('tbody tr:nth-child(1) .price', '9,90 CHF/month') | ||||
->assertSeeIn('tbody tr:nth-child(2) .price', '5,00 CHF/month') | ->assertSeeIn('tbody tr:nth-child(2) .price', '5,00 CHF/month') | ||||
->assertChecked('tbody tr:nth-child(1) input') | ->assertChecked('tbody tr:nth-child(1) input') | ||||
->click('tbody tr:nth-child(2) input') | ->click('tbody tr:nth-child(2) input') | ||||
->assertNotChecked('tbody tr:nth-child(1) input') | ->assertNotChecked('tbody tr:nth-child(1) input') | ||||
->assertChecked('tbody tr:nth-child(2) input'); | ->assertChecked('tbody tr:nth-child(2) input'); | ||||
}) | }) | ||||
->assertMissing('@packages table + .hint') | ->assertMissing('@packages table + .hint') | ||||
->assertSeeIn('button[type=submit]', 'Submit'); | ->assertSeeIn('button[type=submit]', 'Submit'); | ||||
// Test browser-side required fields and error handling | // Test browser-side required fields and error handling | ||||
$browser->click('button[type=submit]') | $browser->click('button[type=submit]') | ||||
->assertFocused('#email') | ->assertFocused('#email') | ||||
->type('#email', 'invalid email') | ->type('#email', 'invalid email') | ||||
->click('button[type=submit]') | |||||
->assertFocused('#password') | |||||
->type('#password', 'simple123') | ->type('#password', 'simple123') | ||||
->click('button[type=submit]') | |||||
->assertFocused('#password_confirmation') | |||||
->type('#password_confirmation', 'simple') | ->type('#password_confirmation', 'simple') | ||||
->click('button[type=submit]') | ->click('button[type=submit]') | ||||
->assertToast(Toast::TYPE_ERROR, 'Form validation error') | ->assertToast(Toast::TYPE_ERROR, 'Form validation error') | ||||
->assertSeeIn('#email + .invalid-feedback', 'The specified email is invalid.') | ->assertSeeIn('#email + .invalid-feedback', 'The specified email is invalid.') | ||||
->assertSeeIn('#password + .invalid-feedback', 'The password confirmation does not match.'); | ->assertSeeIn( | ||||
'#password_confirmation + .invalid-feedback', | |||||
'The password confirmation does not match.' | |||||
); | |||||
}); | }); | ||||
// Test form error handling (aliases) | // Test form error handling (aliases) | ||||
$browser->with('@general', function (Browser $browser) { | $browser->with('@general', function (Browser $browser) { | ||||
$browser->type('#email', 'julia.roberts@kolab.org') | $browser->type('#email', 'julia.roberts@kolab.org') | ||||
->type('#password_confirmation', 'simple123') | ->type('#password_confirmation', 'simple123') | ||||
->with(new ListInput('#aliases'), function (Browser $browser) { | ->with(new ListInput('#aliases'), function (Browser $browser) { | ||||
$browser->addListEntry('invalid address'); | $browser->addListEntry('invalid address'); | ||||
▲ Show 20 Lines • Show All 415 Lines • Show Last 20 Lines |
Can we $john $user?