Changeset View
Changeset View
Standalone View
Standalone View
src/tests/Browser/UsersTest.php
Show All 13 Lines | |||||
use Tests\Browser\Components\QuotaInput; | use Tests\Browser\Components\QuotaInput; | ||||
use Tests\Browser\Components\Toast; | use Tests\Browser\Components\Toast; | ||||
use Tests\Browser\Pages\Dashboard; | use Tests\Browser\Pages\Dashboard; | ||||
use Tests\Browser\Pages\Home; | use Tests\Browser\Pages\Home; | ||||
use Tests\Browser\Pages\UserInfo; | use Tests\Browser\Pages\UserInfo; | ||||
use Tests\Browser\Pages\UserList; | use Tests\Browser\Pages\UserList; | ||||
use Tests\Browser\Pages\Wallet as WalletPage; | use Tests\Browser\Pages\Wallet as WalletPage; | ||||
use Tests\TestCaseDusk; | use Tests\TestCaseDusk; | ||||
use Illuminate\Foundation\Testing\DatabaseMigrations; | |||||
class UsersTest extends TestCaseDusk | class UsersTest extends TestCaseDusk | ||||
{ | { | ||||
private $profile = [ | private $profile = [ | ||||
'first_name' => 'John', | 'first_name' => 'John', | ||||
'last_name' => 'Doe', | 'last_name' => 'Doe', | ||||
'organization' => 'Kolab Developers', | 'organization' => 'Kolab Developers', | ||||
'limit_geo' => null, | 'limit_geo' => null, | ||||
'currency' => 'USD', | |||||
'country' => 'US', | |||||
'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", | |||||
'external_email' => 'john.doe.external@gmail.com', | |||||
'phone' => '+1 509-248-1111', | |||||
]; | ]; | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
public function setUp(): void | public function setUp(): void | ||||
{ | { | ||||
parent::setUp(); | parent::setUp(); | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | public function tearDown(): void | ||||
$wallet->save(); | $wallet->save(); | ||||
$this->clearBetaEntitlements(); | $this->clearBetaEntitlements(); | ||||
parent::tearDown(); | parent::tearDown(); | ||||
} | } | ||||
/** | /** | ||||
* Test user account editing page (not profile page) | * Test user page - General tab | ||||
*/ | */ | ||||
public function testInfo(): void | public function testUserGeneralTab(): void | ||||
{ | { | ||||
$this->browse(function (Browser $browser) { | $this->browse(function (Browser $browser) { | ||||
$john = $this->getTestUser('john@kolab.org'); | $john = $this->getTestUser('john@kolab.org'); | ||||
$jack = $this->getTestUser('jack@kolab.org'); | $jack = $this->getTestUser('jack@kolab.org'); | ||||
$john->verificationcodes()->delete(); | $john->verificationcodes()->delete(); | ||||
$jack->verificationcodes()->delete(); | $jack->verificationcodes()->delete(); | ||||
$john->setSetting('password_policy', 'min:10,upper,digit'); | $john->setSetting('password_policy', 'min:10,upper,digit'); | ||||
// Test that the page requires authentication | // Test that the page requires authentication | ||||
$browser->visit('/user/' . $john->id) | $browser->visit('/user/' . $john->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') | ||||
->assertSeeIn('@nav #tab-general', 'General') | |||||
->with('@general', function (Browser $browser) { | ->with('@general', function (Browser $browser) { | ||||
// Assert form content | // Assert the General tab content | ||||
$browser->assertSeeIn('div.row:nth-child(1) label', 'Status') | $browser->assertSeeIn('div.row:nth-child(1) label', 'Status') | ||||
->assertSeeIn('div.row:nth-child(1) #status', 'Active') | ->assertSeeIn('div.row:nth-child(1) #status', 'Active') | ||||
->assertFocused('div.row:nth-child(2) input') | ->assertSeeIn('div.row:nth-child(2) label', 'Email') | ||||
->assertSeeIn('div.row:nth-child(2) label', 'First Name') | ->assertValue('div.row:nth-child(2) input[type=text]', 'john@kolab.org') | ||||
->assertValue('div.row:nth-child(2) input[type=text]', $this->profile['first_name']) | ->assertDisabled('div.row:nth-child(2) input[type=text]') | ||||
->assertSeeIn('div.row:nth-child(3) label', 'Last Name') | ->assertSeeIn('div.row:nth-child(3) label', 'Email Aliases') | ||||
->assertValue('div.row:nth-child(3) input[type=text]', $this->profile['last_name']) | ->assertVisible('div.row:nth-child(3) .list-input') | ||||
->assertSeeIn('div.row:nth-child(4) label', 'Organization') | |||||
->assertValue('div.row:nth-child(4) input[type=text]', $this->profile['organization']) | |||||
->assertSeeIn('div.row:nth-child(5) label', 'Email') | |||||
->assertValue('div.row:nth-child(5) input[type=text]', 'john@kolab.org') | |||||
->assertDisabled('div.row:nth-child(5) input[type=text]') | |||||
->assertSeeIn('div.row:nth-child(6) label', 'Email Aliases') | |||||
->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(4) label', 'Password') | ||||
->assertValue('div.row:nth-child(7) input#password', '') | ->assertValue('div.row:nth-child(4) input#password', '') | ||||
->assertValue('div.row:nth-child(7) input#password_confirmation', '') | ->assertValue('div.row:nth-child(4) input#password_confirmation', '') | ||||
->assertAttribute('#password', 'placeholder', 'Password') | ->assertAttribute('#password', 'placeholder', 'Password') | ||||
->assertAttribute('#password_confirmation', 'placeholder', 'Confirm Password') | ->assertAttribute('#password_confirmation', 'placeholder', 'Confirm Password') | ||||
->assertMissing('div.row:nth-child(7) .btn-group') | ->assertMissing('div.row:nth-child(4) .btn-group') | ||||
->assertMissing('div.row:nth-child(7) #password-link') | ->assertMissing('div.row:nth-child(4) #password-link'); | ||||
->assertSeeIn('button[type=submit]', 'Submit') | |||||
// Clear some fields and submit | |||||
->vueClear('#first_name') | |||||
->vueClear('#last_name') | |||||
->click('button[type=submit]'); | |||||
}) | |||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') | |||||
->on(new UserList()) | |||||
->click('@table tr:nth-child(3) a') | |||||
->on(new UserInfo()) | |||||
->assertSeeIn('#user-info .card-title', 'User account') | |||||
->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') | ||||
->whenAvailable('#password_policy', function (Browser $browser) { | ->whenAvailable('#password_policy', function (Browser $browser) { | ||||
$browser->assertElementsCount('li', 3) | $browser->assertElementsCount('li', 3) | ||||
->assertMissing('li:nth-child(1) svg.text-success') | ->assertMissing('li:nth-child(1) svg.text-success') | ||||
->assertSeeIn('li:nth-child(1) small', "Minimum password length: 10 characters") | ->assertSeeIn('li:nth-child(1) small', "Minimum password length: 10 characters") | ||||
->waitFor('li:nth-child(2) svg.text-success') | ->waitFor('li:nth-child(2) svg.text-success') | ||||
Show All 15 Lines | public function testUserGeneralTab(): void | ||||
// Test form error handling (aliases) | // Test form error handling (aliases) | ||||
$browser->vueClear('#password') | $browser->vueClear('#password') | ||||
->vueClear('#password_confirmation') | ->vueClear('#password_confirmation') | ||||
->with(new ListInput('#aliases'), function (Browser $browser) { | ->with(new ListInput('#aliases'), function (Browser $browser) { | ||||
$browser->addListEntry('invalid address'); | $browser->addListEntry('invalid address'); | ||||
}) | }) | ||||
->scrollTo('button[type=submit]')->pause(500) | ->scrollTo('button[type=submit]')->pause(500) | ||||
->click('button[type=submit]') | ->click('button[type=submit]') | ||||
->assertToast(Toast::TYPE_ERROR, 'Form validation error'); | ->assertToast(Toast::TYPE_ERROR, 'Form validation error') | ||||
->with(new ListInput('#aliases'), function (Browser $browser) { | |||||
$browser->with(new ListInput('#aliases'), function (Browser $browser) { | $browser->assertFormError(2, 'The specified alias is invalid.', false) | ||||
$browser->assertFormError(2, 'The specified alias is invalid.', false); | |||||
}); | |||||
// Test adding aliases | // Test adding aliases | ||||
$browser->with(new ListInput('#aliases'), function (Browser $browser) { | ->removeListEntry(2) | ||||
$browser->removeListEntry(2) | |||||
->addListEntry('john.test@kolab.org'); | ->addListEntry('john.test@kolab.org'); | ||||
}) | }) | ||||
->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()); | ||||
$alias = $john->aliases()->where('alias', 'john.test@kolab.org')->first(); | $alias = $john->aliases()->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(8) label', 'Subscriptions') | $browser->assertSeeIn('div.row:nth-child(5) label', 'Subscriptions') | ||||
->assertVisible('@skus.row:nth-child(8)') | ->assertVisible('@skus.row:nth-child(5)') | ||||
->with('@skus', function ($browser) { | ->with('@skus', function ($browser) { | ||||
$browser->assertElementsCount('tbody tr', 5) | $browser->assertElementsCount('tbody tr', 5) | ||||
// 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 84 Lines • ▼ Show 20 Lines | public function testUserGeneralTab(): void | ||||
$code = new \App\VerificationCode(['mode' => 'password-reset']); | $code = new \App\VerificationCode(['mode' => 'password-reset']); | ||||
$jack->verificationcodes()->save($code); | $jack->verificationcodes()->save($code); | ||||
$browser->visit('/user/' . $jack->id) | $browser->visit('/user/' . $jack->id) | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->with('@general', function (Browser $browser) use ($jack, $john, $code) { | ->with('@general', function (Browser $browser) use ($jack, $john, $code) { | ||||
// Test displaying an existing password reset link | // Test displaying an existing password reset link | ||||
$link = Browser::$baseUrl . '/password-reset/' . $code->short_code . '-' . $code->code; | $link = Browser::$baseUrl . '/password-reset/' . $code->short_code . '-' . $code->code; | ||||
$browser->assertSeeIn('div.row:nth-child(7) label', 'Password') | $browser->assertSeeIn('div.row:nth-child(4) label', 'Password') | ||||
->assertMissing('#password') | ->assertMissing('#password') | ||||
->assertMissing('#password_confirmation') | ->assertMissing('#password_confirmation') | ||||
->assertMissing('#pass-mode-link:checked') | ->assertMissing('#pass-mode-link:checked') | ||||
->assertMissing('#pass-mode-input:checked') | ->assertMissing('#pass-mode-input:checked') | ||||
->assertSeeIn('#password-link code', $link) | ->assertSeeIn('#password-link code', $link) | ||||
->assertVisible('#password-link button.text-danger') | ->assertVisible('#password-link button.text-danger') | ||||
->assertVisible('#password-link button:not(.text-danger)') | ->assertVisible('#password-link button:not(.text-danger)') | ||||
->assertAttribute('#password-link button:not(.text-danger)', 'title', 'Copy') | ->assertAttribute('#password-link button:not(.text-danger)', 'title', 'Copy') | ||||
Show All 38 Lines | public function testUserGeneralTab(): void | ||||
$this->assertSame(1, $jack->verificationcodes()->where('active', true)->count()); | $this->assertSame(1, $jack->verificationcodes()->where('active', true)->count()); | ||||
$this->assertSame(0, $john->verificationcodes()->count()); | $this->assertSame(0, $john->verificationcodes()->count()); | ||||
}); | }); | ||||
}); | }); | ||||
} | } | ||||
/** | /** | ||||
* Test user settings tab | * Test user page - General tab | ||||
* | |||||
* @depends testUserGeneralTab | |||||
*/ | |||||
public function testUserPersonalTab(): void | |||||
{ | |||||
$this->browse(function (Browser $browser) { | |||||
$john = $this->getTestUser('john@kolab.org'); | |||||
$jack = $this->getTestUser('jack@kolab.org'); | |||||
$jack->setSetting('organization', null); | |||||
// Test the account controller | |||||
$browser->visit('/user/' . $john->id) | |||||
->on(new UserInfo()) | |||||
->assertSeeIn('@nav #tab-personal', 'Personal information') | |||||
->click('#tab-personal') | |||||
->with('@personal', function (Browser $browser) { | |||||
$browser->assertSeeIn('div.row:nth-child(1) label', 'First Name') | |||||
->assertValue('div.row:nth-child(1) input[type=text]', $this->profile['first_name']) | |||||
->assertSeeIn('div.row:nth-child(2) label', 'Last Name') | |||||
->assertValue('div.row:nth-child(2) input[type=text]', $this->profile['last_name']) | |||||
->assertSeeIn('div.row:nth-child(3) label', 'Organization') | |||||
->assertValue('div.row:nth-child(3) input[type=text]', $this->profile['organization']) | |||||
->assertSeeIn('div.row:nth-child(4) label', 'Phone') | |||||
->assertValue('div.row:nth-child(4) input[type=text]', $this->profile['phone']) | |||||
->assertSeeIn('div.row:nth-child(5) label', 'External Email') | |||||
->assertValue('div.row:nth-child(5) input[type=text]', $this->profile['external_email']) | |||||
->assertSeeIn('div.row:nth-child(6) label', 'Address') | |||||
->assertValue('div.row:nth-child(6) textarea', $this->profile['billing_address']) | |||||
->assertSeeIn('div.row:nth-child(7) label', 'Country') | |||||
->assertValue('div.row:nth-child(7) select', $this->profile['country']) | |||||
// Set some fields and submit | |||||
->type('#first_name', 'Arnie') | |||||
->vueClear('#last_name') | |||||
->click('button[type=submit]'); | |||||
}) | |||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') | |||||
->on(new UserList()); | |||||
$this->assertSame('Arnie', $john->getSetting('first_name')); | |||||
$this->assertSame(null, $john->getSetting('last_name')); | |||||
// Test the non-controller user | |||||
$browser->visit('/user/' . $jack->id) | |||||
->on(new UserInfo()) | |||||
->click('#tab-personal') | |||||
->with('@personal', function (Browser $browser) { | |||||
$browser->assertSeeIn('div.row:nth-child(1) label', 'First Name') | |||||
->assertValue('div.row:nth-child(1) input[type=text]', 'Jack') | |||||
->assertSeeIn('div.row:nth-child(2) label', 'Last Name') | |||||
->assertValue('div.row:nth-child(2) input[type=text]', 'Daniels') | |||||
->assertSeeIn('div.row:nth-child(3) label', 'Organization') | |||||
->assertValue('div.row:nth-child(3) input[type=text]', '') | |||||
// Set some fields and submit | |||||
->type('#organization', 'Test') | |||||
->click('button[type=submit]'); | |||||
}) | |||||
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') | |||||
->on(new UserList()); | |||||
$this->assertSame('Test', $jack->getSetting('organization')); | |||||
}); | |||||
} | |||||
/** | |||||
* Test user page - Settings tab | |||||
* | * | ||||
* @depends testInfo | * @depends testUserPersonalTab | ||||
*/ | */ | ||||
public function testUserSettings(): void | public function testUserSettingsTab(): void | ||||
{ | { | ||||
$john = $this->getTestUser('john@kolab.org'); | $john = $this->getTestUser('john@kolab.org'); | ||||
$john->setSetting('greylist_enabled', null); | $john->setSetting('greylist_enabled', null); | ||||
$john->setSetting('guam_enabled', null); | $john->setSetting('guam_enabled', null); | ||||
$john->setSetting('limit_geo', null); | $john->setSetting('limit_geo', null); | ||||
$this->browse(function (Browser $browser) use ($john) { | $this->browse(function (Browser $browser) use ($john) { | ||||
$browser->visit('/user/' . $john->id) | $browser->visit('/user/' . $john->id) | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->assertElementsCount('@nav a', 2) | |||||
->assertSeeIn('@nav #tab-general', 'General') | |||||
->assertSeeIn('@nav #tab-settings', 'Settings') | ->assertSeeIn('@nav #tab-settings', 'Settings') | ||||
->click('@nav #tab-settings') | ->click('@nav #tab-settings') | ||||
->with('#settings form', function (Browser $browser) { | ->with('@settings', function (Browser $browser) { | ||||
$browser->assertSeeIn('div.row:nth-child(1) label', 'Greylisting') | $browser->assertSeeIn('div.row:nth-child(1) label', 'Greylisting') | ||||
->assertMissing('div.row:nth-child(2)') // guam and geo-lockin settings are hidden | ->assertMissing('div.row:nth-child(2)') // guam and geo-lockin settings are hidden | ||||
->click('div.row:nth-child(1) input[type=checkbox]:checked') | ->click('div.row:nth-child(1) input[type=checkbox]:checked') | ||||
->click('button[type=submit]') | ->click('button[type=submit]') | ||||
->assertToast(Toast::TYPE_SUCCESS, 'User settings updated successfully.'); | ->assertToast(Toast::TYPE_SUCCESS, 'User settings updated successfully.'); | ||||
}); | }); | ||||
}); | }); | ||||
$this->assertSame('false', $john->getSetting('greylist_enabled')); | $this->assertSame('false', $john->getSetting('greylist_enabled')); | ||||
$this->addBetaEntitlement($john); | $this->addBetaEntitlement($john); | ||||
$this->browse(function (Browser $browser) use ($john) { | $this->browse(function (Browser $browser) use ($john) { | ||||
$browser->refresh() | $browser->refresh() | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->click('@nav #tab-settings') | ->click('@nav #tab-settings') | ||||
->with('#settings form', function (Browser $browser) use ($john) { | ->with('@settings', function (Browser $browser) use ($john) { | ||||
$browser->assertSeeIn('div.row:nth-child(1) label', 'Greylisting') | $browser->assertSeeIn('div.row:nth-child(1) label', 'Greylisting') | ||||
->assertSeeIn('div.row:nth-child(2) label', 'IMAP proxy') | ->assertSeeIn('div.row:nth-child(2) label', 'IMAP proxy') | ||||
->assertNotChecked('div.row:nth-child(2) input') | ->assertNotChecked('div.row:nth-child(2) input') | ||||
->assertSeeIn('div.row:nth-child(3) label', 'Geo-lockin') | ->assertSeeIn('div.row:nth-child(3) label', 'Geo-lockin') | ||||
->with(new CountrySelect('#limit_geo'), function ($browser) { | ->with(new CountrySelect('#limit_geo'), function ($browser) { | ||||
$browser->assertCountries([]) | $browser->assertCountries([]) | ||||
->setCountries(['DE', 'PL']) | ->setCountries(['DE', 'PL']) | ||||
->assertCountries(['DE', 'PL']); | ->assertCountries(['DE', 'PL']); | ||||
Show All 17 Lines | public function testUserSettingsTab(): void | ||||
$this->assertSame(null, $john->getSetting('limit_geo')); | $this->assertSame(null, $john->getSetting('limit_geo')); | ||||
$this->assertSame(null, $john->getSetting('guam_enabled')); | $this->assertSame(null, $john->getSetting('guam_enabled')); | ||||
}); | }); | ||||
}); | }); | ||||
} | } | ||||
/** | /** | ||||
* Test user adding page | * Test user adding page | ||||
* | |||||
* @depends testInfo | |||||
*/ | */ | ||||
public function testNewUser(): void | public function testNewUser(): void | ||||
{ | { | ||||
$john = $this->getTestUser('john@kolab.org'); | $john = $this->getTestUser('john@kolab.org'); | ||||
$john->setSetting('password_policy', null); | $john->setSetting('password_policy', null); | ||||
$this->browse(function (Browser $browser) { | $this->browse(function (Browser $browser) { | ||||
$browser->visit(new UserList()) | $browser->visit(new UserList()) | ||||
->assertSeeIn('button.user-new', 'Create user') | ->assertSeeIn('button.user-new', 'Create user') | ||||
->click('button.user-new') | ->click('button.user-new') | ||||
->on(new UserInfo()) | ->on(new UserInfo()) | ||||
->assertSeeIn('#user-info .card-title', 'New user account') | ->assertSeeIn('#user-info .card-title', 'New user account') | ||||
->assertMissing('@nav #tab-settings') | |||||
->assertMissing('@nav #tab-personal') | |||||
->with('@general', function (Browser $browser) { | ->with('@general', function (Browser $browser) { | ||||
// Assert form content | // Assert form content | ||||
$browser->assertFocused('div.row:nth-child(1) input') | $browser->assertFocused('div.row:nth-child(1) input') | ||||
->assertSeeIn('div.row:nth-child(1) label', 'First Name') | ->assertSeeIn('div.row:nth-child(1) label', 'First Name') | ||||
->assertValue('div.row:nth-child(1) input[type=text]', '') | ->assertValue('div.row:nth-child(1) input[type=text]', '') | ||||
->assertSeeIn('div.row:nth-child(2) label', 'Last Name') | ->assertSeeIn('div.row:nth-child(2) label', 'Last Name') | ||||
->assertValue('div.row:nth-child(2) input[type=text]', '') | ->assertValue('div.row:nth-child(2) input[type=text]', '') | ||||
->assertSeeIn('div.row:nth-child(3) label', 'Organization') | ->assertSeeIn('div.row:nth-child(3) label', 'Organization') | ||||
▲ Show 20 Lines • Show All 363 Lines • Show Last 20 Lines |