Cleanup test framework; drop all the unused browser tests
This commit is contained in:
parent
a4359064ca
commit
341ff6e178
19 changed files with 18 additions and 712 deletions
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
@ -63,9 +63,9 @@ jobs:
|
||||||
run: php artisan test tests/Unit
|
run: php artisan test tests/Unit
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
TESTING_DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
- name: Execute Integration Tests
|
- name: Execute Integration Tests
|
||||||
run: php artisan test tests/Integration
|
run: php artisan test tests/Integration
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
TESTING_DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RequireTwoFactorAuthentication
|
||||||
// send them right through, nothing else needs to be checked.
|
// send them right through, nothing else needs to be checked.
|
||||||
//
|
//
|
||||||
// If the level is set as admin and the user is not an admin, pass them through as well.
|
// If the level is set as admin and the user is not an admin, pass them through as well.
|
||||||
if ($level === self::LEVEL_NONE || ($user->use_totp || $user->webauthnKeys()->count() > 0)) {
|
if ($level === self::LEVEL_NONE || ($user->use_totp || !empty($user->securityKeys))) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
} elseif ($level === self::LEVEL_ADMIN && !$user->root_admin) {
|
} elseif ($level === self::LEVEL_ADMIN && !$user->root_admin) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|
|
@ -22,9 +22,12 @@ $kernel->bootstrap();
|
||||||
|
|
||||||
$output = new ConsoleOutput();
|
$output = new ConsoleOutput();
|
||||||
|
|
||||||
if (config('database.default') !== 'testing') {
|
$connection = config('database.default');
|
||||||
|
$database = config("database.connections.$connection.database");
|
||||||
|
|
||||||
|
if ($database !== 'panel_test') {
|
||||||
$output->writeln(PHP_EOL . '<error>Cannot run test process against non-testing database.</error>');
|
$output->writeln(PHP_EOL . '<error>Cannot run test process against non-testing database.</error>');
|
||||||
$output->writeln(PHP_EOL . '<error>Environment is currently pointed at: "' . config('database.default') . '".</error>');
|
$output->writeln(PHP_EOL . "<error>Environment is currently pointed at: [$database]</error>");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,10 +37,10 @@ if (config('database.default') !== 'testing') {
|
||||||
*/
|
*/
|
||||||
if (!env('SKIP_MIGRATIONS')) {
|
if (!env('SKIP_MIGRATIONS')) {
|
||||||
$output->writeln(PHP_EOL . '<info>Refreshing database for Integration tests...</info>');
|
$output->writeln(PHP_EOL . '<info>Refreshing database for Integration tests...</info>');
|
||||||
$kernel->call('migrate:fresh', ['--database' => 'testing']);
|
$kernel->call('migrate:fresh');
|
||||||
|
|
||||||
$output->writeln('<info>Seeding database for Integration tests...</info>' . PHP_EOL);
|
$output->writeln('<info>Seeding database for Integration tests...</info>' . PHP_EOL);
|
||||||
$kernel->call('db:seed', ['--database' => 'testing']);
|
$kernel->call('db:seed');
|
||||||
} else {
|
} else {
|
||||||
$output->writeln(PHP_EOL . '<comment>Skipping database migrations...</comment>' . PHP_EOL);
|
$output->writeln(PHP_EOL . '<comment>Skipping database migrations...</comment>' . PHP_EOL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,36 +57,6 @@ return [
|
||||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true),
|
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true),
|
||||||
]) : [],
|
]) : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
|
||||||
| -------------------------------------------------------------------------
|
|
||||||
| Test Database Connection
|
|
||||||
| -------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This connection is used by the integration and HTTP tests for Pterodactyl
|
|
||||||
| development. Normal users of the Panel do not need to adjust any settings
|
|
||||||
| in here.
|
|
||||||
*/
|
|
||||||
'testing' => [
|
|
||||||
'driver' => 'mysql',
|
|
||||||
'host' => env('TESTING_DB_HOST', '127.0.0.1'),
|
|
||||||
'port' => env('TESTING_DB_PORT', '3306'),
|
|
||||||
'database' => env('TESTING_DB_DATABASE', 'panel_test'),
|
|
||||||
'username' => env('TESTING_DB_USERNAME', 'pterodactyl_test'),
|
|
||||||
'password' => env('TESTING_DB_PASSWORD', ''),
|
|
||||||
'charset' => 'utf8mb4',
|
|
||||||
'collation' => 'utf8mb4_unicode_ci',
|
|
||||||
'prefix' => '',
|
|
||||||
'strict' => env('TESTING_DB_STRICT_MODE', false),
|
|
||||||
'timezone' => env('TESTING_DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))),
|
|
||||||
'sslmode' => env('TESTING_DB_SSLMODE', 'prefer'),
|
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
||||||
PDO::MYSQL_ATTR_SSL_CERT => env('MYSQL_ATTR_SSL_CERT'),
|
|
||||||
PDO::MYSQL_ATTR_SSL_KEY => env('MYSQL_ATTR_SSL_KEY'),
|
|
||||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true),
|
|
||||||
]) : [],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
19
phpunit.xml
19
phpunit.xml
|
@ -1,17 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit
|
<phpunit
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
backupGlobals="false"
|
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
backupStaticAttributes="false"
|
|
||||||
bootstrap="bootstrap/tests.php"
|
bootstrap="bootstrap/tests.php"
|
||||||
colors="true"
|
colors="true"
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
printerClass="NunoMaduro\Collision\Adapters\Phpunit\Printer"
|
printerClass="NunoMaduro\Collision\Adapters\Phpunit\Printer"
|
||||||
processIsolation="false"
|
|
||||||
stopOnFailure="false"
|
|
||||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
|
||||||
>
|
>
|
||||||
<coverage processUncoveredFiles="true">
|
<coverage processUncoveredFiles="true">
|
||||||
<include>
|
<include>
|
||||||
|
@ -19,21 +12,17 @@
|
||||||
</include>
|
</include>
|
||||||
</coverage>
|
</coverage>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Browser">
|
|
||||||
<directory suffix="Test.php">./tests/Browser/Processes</directory>
|
|
||||||
</testsuite>
|
|
||||||
<testsuite name="Integration">
|
<testsuite name="Integration">
|
||||||
<directory suffix="Test.php">./tests/Integration</directory>
|
<directory>./tests/Integration</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Unit">
|
<testsuite name="Unit">
|
||||||
<directory suffix="Test.php">./tests/Unit</directory>
|
<directory>./tests/Unit</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<php>
|
<php>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
<env name="DB_CONNECTION" value="testing"/>
|
<env name="DB_DATABASE" value="panel_test"/>
|
||||||
<env name="TESTING_DB_USERNAME" value="root"/>
|
|
||||||
<env name="CACHE_DRIVER" value="array"/>
|
<env name="CACHE_DRIVER" value="array"/>
|
||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser;
|
|
||||||
|
|
||||||
use Laravel\Dusk\TestCase;
|
|
||||||
use BadMethodCallException;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Pterodactyl\Console\Kernel;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Pterodactyl\Tests\CreatesApplication;
|
|
||||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
|
||||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
|
||||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
|
||||||
|
|
||||||
abstract class BrowserTestCase extends TestCase
|
|
||||||
{
|
|
||||||
use CreatesApplication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default password to use for new accounts.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $userPassword = 'Password123';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a fresh database instance before each test class is initialized. This is different
|
|
||||||
* than the default DatabaseMigrations as it is only run when the class is setup. The trait
|
|
||||||
* provided by Laravel will run on EACH test function, slowing things down significantly.
|
|
||||||
*
|
|
||||||
* If you need to reset the DB between function runs just include the trait in that specific
|
|
||||||
* test. In most cases you probably wont need to do this, or can modify the test slightly to
|
|
||||||
* avoid the need to do so.
|
|
||||||
*/
|
|
||||||
public static function setUpBeforeClass(): void
|
|
||||||
{
|
|
||||||
parent::setUpBeforeClass();
|
|
||||||
|
|
||||||
$app = require __DIR__ . '/../../bootstrap/app.php';
|
|
||||||
|
|
||||||
/** @var \Pterodactyl\Console\Kernel $kernel */
|
|
||||||
$kernel = $app->make(Kernel::class);
|
|
||||||
|
|
||||||
$kernel->bootstrap();
|
|
||||||
$kernel->call('migrate:fresh');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup tests.
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
// Don't accidentally run the migrations aganist the non-testing database. Ask me
|
|
||||||
// how many times I've accidentally dropped my database...
|
|
||||||
if (env('DB_CONNECTION') !== 'testing') {
|
|
||||||
throw new BadMethodCallException('Cannot call browser tests using the non-testing database connection.');
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
// Gotta unset this to continue avoiding issues with the validation.
|
|
||||||
Model::unsetEventDispatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the RemoteWebDriver instance.
|
|
||||||
*
|
|
||||||
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
|
|
||||||
*/
|
|
||||||
protected function driver()
|
|
||||||
{
|
|
||||||
$options = (new ChromeOptions())->addArguments([
|
|
||||||
'--disable-gpu',
|
|
||||||
'--disable-infobars',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return RemoteWebDriver::create(
|
|
||||||
'http://host.pterodactyl.local:4444/wd/hub',
|
|
||||||
DesiredCapabilities::chrome()->setCapability(
|
|
||||||
ChromeOptions::CAPABILITY,
|
|
||||||
$options
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an instance of the browser to be used for tests.
|
|
||||||
*
|
|
||||||
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Tests\Browser\PterodactylBrowser
|
|
||||||
*/
|
|
||||||
protected function newBrowser($driver): PterodactylBrowser
|
|
||||||
{
|
|
||||||
return new PterodactylBrowser($driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tear down the test and delete all cookies from the browser instance to address
|
|
||||||
* instances where the test would be kicked over to the login page.
|
|
||||||
*/
|
|
||||||
protected function tearDown(): void
|
|
||||||
{
|
|
||||||
/** @var \Pterodactyl\Tests\Browser\PterodactylBrowser $browser */
|
|
||||||
foreach (static::$browsers as $browser) {
|
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a user model to authenticate aganist and use in the tests.
|
|
||||||
*/
|
|
||||||
protected function user(array $attributes = []): User
|
|
||||||
{
|
|
||||||
return User::factory()->create(array_merge([
|
|
||||||
'password' => Hash::make(static::$userPassword),
|
|
||||||
], $attributes));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Pages;
|
|
||||||
|
|
||||||
use Laravel\Dusk\Page;
|
|
||||||
|
|
||||||
abstract class BasePage extends Page
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function siteElements()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'@@success' => '.alert.success[role="alert"]',
|
|
||||||
'@@error' => '.alert.error[role="alert"]',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Pages\Dashboard;
|
|
||||||
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\BasePage;
|
|
||||||
|
|
||||||
class AccountPage extends BasePage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function url()
|
|
||||||
{
|
|
||||||
return '/account';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function elements()
|
|
||||||
{
|
|
||||||
return array_merge(parent::elements(), [
|
|
||||||
'@email' => '#update-email-container #grid-email',
|
|
||||||
'@password' => '#update-email-container #grid-password[type="password"]',
|
|
||||||
'@submit' => '#update-email-container button[type="submit"]',
|
|
||||||
|
|
||||||
'@current_password' => '#change-password-container #grid-password-current[type="password"]',
|
|
||||||
'@new_password' => '#change-password-container #grid-password-new[type="password"]',
|
|
||||||
'@confirm_password' => '#change-password-container #grid-password-new-confirm[type="password"]',
|
|
||||||
'@submit_password' => '#change-password-container button[type="submit"]',
|
|
||||||
|
|
||||||
'@2fa_button' => '#grid-open-two-factor-modal',
|
|
||||||
'@2fa_modal' => '.modal-mask #configure-two-factor',
|
|
||||||
'@2fa_token' => '#configure-two-factor #container-enable-two-factor #grid-two-factor-token[type="number"]',
|
|
||||||
'@2fa_token_disable' => '#configure-two-factor #container-disable-two-factor #grid-two-factor-token-disable',
|
|
||||||
'@2fa_enable' => '#configure-two-factor #container-enable-two-factor button[type="submit"]',
|
|
||||||
'@2fa_disable' => '#configure-two-factor #container-disable-two-factor button.btn-red[type="submit"]',
|
|
||||||
'@2fa_cancel' => '#configure-two-factor #container-disable-two-factor button.btn-secondary',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Pages;
|
|
||||||
|
|
||||||
class LoginPage extends BasePage
|
|
||||||
{
|
|
||||||
public function url(): string
|
|
||||||
{
|
|
||||||
return '/auth/login';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function elements()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'@email' => '#grid-email',
|
|
||||||
'@username' => '#grid-username',
|
|
||||||
'@password' => '#grid-password',
|
|
||||||
'@loginButton' => '#grid-login-button',
|
|
||||||
'@submitButton' => 'button.btn.btn-jumbo[type="submit"]',
|
|
||||||
'@forgotPassword' => 'a[href="/auth/password"][aria-label="Forgot password"]',
|
|
||||||
'@goToLogin' => 'a[href="/auth/login"][aria-label="Go to login"]',
|
|
||||||
'@alertSuccess' => 'div[role="alert"].success > span.message',
|
|
||||||
'@alertDanger' => 'div[role="alert"].danger > span.message',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Authentication;
|
|
||||||
|
|
||||||
use Pterodactyl\Tests\Browser\BrowserTestCase;
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\LoginPage;
|
|
||||||
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
|
||||||
|
|
||||||
class ForgotPasswordProcessTest extends BrowserTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that the password reset page works as expected and displays the expected
|
|
||||||
* success messages to the client when submitted.
|
|
||||||
*/
|
|
||||||
public function testResetPasswordWithInvalidAccount()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->visit(new LoginPage())
|
|
||||||
->assertSee(trans('auth.forgot_password.label'))
|
|
||||||
->click('@forgotPassword')
|
|
||||||
->waitForLocation('/auth/password')
|
|
||||||
->assertFocused('@email')
|
|
||||||
->assertSeeIn('.input-open > p.text-xs', trans('auth.forgot_password.label_help'))
|
|
||||||
->assertSeeIn('@submitButton', trans('auth.forgot_password.button'))
|
|
||||||
->type('@email', 'unassociated@example.com')
|
|
||||||
->assertSeeIn('@goToLogin', trans('auth.go_to_login'))
|
|
||||||
->press('@submitButton')
|
|
||||||
->waitForLocation('/auth/login')
|
|
||||||
->assertSeeIn('div[role="alert"].success > span.message', 'We have e-mailed your password reset link!')
|
|
||||||
->assertFocused('@username')
|
|
||||||
->assertValue('@username', 'unassociated@example.com');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that you can type in your email address and then click forgot password and have
|
|
||||||
* the email maintained on the new page.
|
|
||||||
*/
|
|
||||||
public function testEmailCarryover()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->visit(new LoginPage())
|
|
||||||
->type('@username', 'dane@example.com')
|
|
||||||
->click('@forgotPassword')
|
|
||||||
->waitForLocation('/auth/password')
|
|
||||||
->assertFocused('@email')
|
|
||||||
->assertValue('@email', 'dane@example.com');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Authentication;
|
|
||||||
|
|
||||||
use Facebook\WebDriver\WebDriverKeys;
|
|
||||||
use Pterodactyl\Tests\Browser\BrowserTestCase;
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\LoginPage;
|
|
||||||
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
|
||||||
|
|
||||||
class LoginProcessTest extends BrowserTestCase
|
|
||||||
{
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup tests.
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->user = $this->user();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that a user can login successfully using their email address.
|
|
||||||
*/
|
|
||||||
public function testLoginUsingEmail()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->visit(new LoginPage())
|
|
||||||
->waitFor('@username')
|
|
||||||
->type('@username', $this->user->email)
|
|
||||||
->type('@password', self::$userPassword)
|
|
||||||
->click('@loginButton')
|
|
||||||
->waitForReload()
|
|
||||||
->assertPathIs('/')
|
|
||||||
->assertAuthenticatedAs($this->user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that a user can login successfully using their username.
|
|
||||||
*/
|
|
||||||
public function testLoginUsingUsername()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->visit(new LoginPage())
|
|
||||||
->waitFor('@username')
|
|
||||||
->type('@username', $this->user->username)
|
|
||||||
->type('@password', self::$userPassword)
|
|
||||||
->click('@loginButton')
|
|
||||||
->waitForReload()
|
|
||||||
->assertPathIs('/')
|
|
||||||
->assertAuthenticatedAs($this->user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that entering the wrong password shows the expected error and then allows
|
|
||||||
* us to login without clearing the username field.
|
|
||||||
*/
|
|
||||||
public function testLoginWithErrors()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->logout()
|
|
||||||
->visit(new LoginPage())
|
|
||||||
->waitFor('@username')
|
|
||||||
->type('@username', $this->user->email)
|
|
||||||
->type('@password', 'invalid')
|
|
||||||
->click('@loginButton')
|
|
||||||
->waitFor('.alert.error')
|
|
||||||
->assertSeeIn('.alert.error', trans('auth.failed'))
|
|
||||||
->assertValue('@username', $this->user->email)
|
|
||||||
->assertValue('@password', '')
|
|
||||||
->assertFocused('@password')
|
|
||||||
->type('@password', self::$userPassword)
|
|
||||||
->keys('@password', [WebDriverKeys::ENTER])
|
|
||||||
->waitForReload()
|
|
||||||
->assertPathIs('/')
|
|
||||||
->assertAuthenticatedAs($this->user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Dashboard;
|
|
||||||
|
|
||||||
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\Dashboard\AccountPage;
|
|
||||||
|
|
||||||
class AccountEmailProcessTest extends DashboardTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that an email address can be changed successfully.
|
|
||||||
*/
|
|
||||||
public function testEmailCanBeChanged()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->assertValue('@email', $this->user->email)
|
|
||||||
->type('@email', 'new.email@example.com')
|
|
||||||
->type('@password', 'Password123')
|
|
||||||
->click('@submit')
|
|
||||||
->waitFor('@@success')
|
|
||||||
->assertSeeIn('@@success', trans('dashboard/account.email.updated'))
|
|
||||||
->assertValue('@email', 'new.email@example.com');
|
|
||||||
|
|
||||||
$this->assertDatabaseHas('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that the validation error message shows up when an invalid email is entered.
|
|
||||||
*/
|
|
||||||
public function testInvalidEmailShowsErrors()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->assertMissing('@email ~ .input-help.error')
|
|
||||||
->type('@email', 'admin')
|
|
||||||
->assertVisible('@email ~ .input-help.error')
|
|
||||||
->assertSeeIn('@email ~ .input-help.error', 'The email field must be a valid email.')
|
|
||||||
->type('@email', 'admin@example.com')
|
|
||||||
->assertMissing('@email ~ .input-help.error');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that entering the wrong password for an account returns an error.
|
|
||||||
*/
|
|
||||||
public function testInvalidPasswordShowsError()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->type('@email', 'new.email@example.com')
|
|
||||||
->click('@submit')
|
|
||||||
->assertFocused('@password')
|
|
||||||
->type('@password', 'test1234')
|
|
||||||
->click('@submit')
|
|
||||||
->waitFor('@@error')
|
|
||||||
->assertSeeIn('@@error', trans('validation.internal.invalid_password'))
|
|
||||||
->assertValue('@email', 'new.email@example.com');
|
|
||||||
|
|
||||||
$this->assertDatabaseMissing('users', ['id' => $this->user->id, 'email' => 'new.email@example.com']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Dashboard;
|
|
||||||
|
|
||||||
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\Dashboard\AccountPage;
|
|
||||||
|
|
||||||
class AccountPasswordProcessTest extends DashboardTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that a user is able to change their password.
|
|
||||||
*/
|
|
||||||
public function testPasswordCanBeChanged()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->type('@current_password', self::$userPassword)
|
|
||||||
->assertMissing('@new_password ~ .input-help.error')
|
|
||||||
->type('@new_password', 'test')
|
|
||||||
->assertSeeIn('@new_password ~ .input-help.error', 'The password field must be at least 8 characters.')
|
|
||||||
->type('@new_password', 'Test1234')
|
|
||||||
->assertMissing('@new_password ~ .input-help.error')
|
|
||||||
->assertMissing('@confirm_password ~ .input-help.error')
|
|
||||||
->type('@confirm_password', 'test')
|
|
||||||
->assertSeeIn('@confirm_password ~ .input-help.error', 'The password value is not valid.')
|
|
||||||
->type('@confirm_password', 'Test1234')
|
|
||||||
->assertMissing('@confirm_password ~ .input-help.error')
|
|
||||||
->click('@submit_password')
|
|
||||||
->waitFor('@@success')
|
|
||||||
->assertSeeIn('@@success', 'Your password has been updated.')
|
|
||||||
->assertInputValue('@current_password', '')
|
|
||||||
->assertInputValue('@new_password', '')
|
|
||||||
->assertInputValue('@confirm_password', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that invalid passwords result in the expected error message.
|
|
||||||
*/
|
|
||||||
public function testInvalidPassword()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->type('@current_password', 'badpassword')
|
|
||||||
->type('@new_password', 'testtest')
|
|
||||||
->type('@confirm_password', 'testtest')
|
|
||||||
->click('@submit_password')
|
|
||||||
->waitFor('@@error')
|
|
||||||
->assertSeeIn('@@error', trans('validation.internal.invalid_password'))
|
|
||||||
->assertInputValue('@current_password', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Dashboard;
|
|
||||||
|
|
||||||
use Pterodactyl\Tests\Browser\BrowserTestCase;
|
|
||||||
|
|
||||||
abstract class DashboardTestCase extends BrowserTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Models\User
|
|
||||||
*/
|
|
||||||
protected $user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup tests and provide a default user to calling functions.
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->user = $this->user();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser\Processes\Dashboard;
|
|
||||||
|
|
||||||
use PragmaRX\Google2FA\Google2FA;
|
|
||||||
use Facebook\WebDriver\WebDriverKeys;
|
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
|
||||||
use Pterodactyl\Tests\Browser\Pages\Dashboard\AccountPage;
|
|
||||||
|
|
||||||
class TwoFactorAuthenticationProcessTest extends DashboardTestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that the modal can be opened and closed.
|
|
||||||
*/
|
|
||||||
public function testModalOpenAndClose()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->assertMissing('.modal-mask')
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitFor('@2fa_modal')
|
|
||||||
->pause(500)// seems to fix fragile test
|
|
||||||
->clickPosition(100, 100)
|
|
||||||
->waitUntilMissing('@2fa_modal')
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitFor('@2fa_modal')
|
|
||||||
->click('svg[role="button"][aria-label="Close modal"]')
|
|
||||||
->waitUntilMissing('@2fa_modal')
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitFor('@2fa_modal')
|
|
||||||
->keys('', [WebDriverKeys::ESCAPE])
|
|
||||||
->waitUntilMissing('@2fa_modal');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that a user that does not have two-factor enabled can enable it on their account.
|
|
||||||
*/
|
|
||||||
public function testTwoFactorCanBeEnabled()
|
|
||||||
{
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitForText(trans('dashboard/account.two_factor.setup.title'))
|
|
||||||
->assertFocused('@2fa_token')
|
|
||||||
->waitFor('#grid-qr-code')
|
|
||||||
->assertSee(trans('dashboard/account.two_factor.setup.help'));
|
|
||||||
|
|
||||||
// Grab information from the database so we can ensure the correct things are showing up.
|
|
||||||
// Also because we need to generate a code to send through and activate it with.
|
|
||||||
$updated = $this->user->fresh();
|
|
||||||
|
|
||||||
$secret = Crypt::decrypt($updated->totp_secret);
|
|
||||||
$code = (new Google2FA())->getCurrentOtp($secret);
|
|
||||||
|
|
||||||
$browser->assertSeeIn('code', $secret)
|
|
||||||
->assertVisible('@2fa_enable[disabled="disabled"]')
|
|
||||||
->assertMissing('@2fa_token ~ .input-help.error')
|
|
||||||
->type('@2fa_token', '12')
|
|
||||||
->assertSeeIn('@2fa_token ~ .input-help.error', 'The token length must be 6.')
|
|
||||||
->type('@2fa_token', $code)
|
|
||||||
->assertMissing('@2fa_token ~ .input-help.error')
|
|
||||||
->click('@2fa_enable')
|
|
||||||
->waitUntilMissing('@2fa_modal')
|
|
||||||
->assertSeeIn('@@success', trans('dashboard/account.two_factor.enabled'));
|
|
||||||
|
|
||||||
$this->assertDatabaseHas('users', ['id' => $this->user->id, 'use_totp' => 1]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that a user can disable two-factor authentication on thier account.
|
|
||||||
*/
|
|
||||||
public function testTwoFactorCanBeDisabled()
|
|
||||||
{
|
|
||||||
$secret = (new Google2FA())->generateSecretKey(16);
|
|
||||||
|
|
||||||
$this->user->update([
|
|
||||||
'use_totp' => true,
|
|
||||||
'totp_secret' => Crypt::encrypt($secret),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->browse(function (PterodactylBrowser $browser) use ($secret) {
|
|
||||||
$browser->loginAs($this->user)
|
|
||||||
->visit(new AccountPage())
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitForText(trans('dashboard/account.two_factor.disable.title'))
|
|
||||||
->click('@2fa_cancel')
|
|
||||||
->waitUntilMissing('@2fa_modal')
|
|
||||||
->click('@2fa_button')
|
|
||||||
->waitForText(trans('dashboard/account.two_factor.disable.title'))
|
|
||||||
->assertVisible('@2fa_disable[disabled="disabled"]')
|
|
||||||
->assertVisible('@2fa_cancel')
|
|
||||||
->assertFocused('@2fa_token_disable')
|
|
||||||
->assertMissing('@2fa_token_disable ~ .input-help.error')
|
|
||||||
->type('@2fa_token_disable', '12')
|
|
||||||
->assertSeeIn('@2fa_token_disable ~ .input-help.error', 'The token length must be 6.');
|
|
||||||
|
|
||||||
$token = (new Google2FA())->getCurrentOtp($secret);
|
|
||||||
|
|
||||||
$browser->type('@2fa_token_disable', $token)
|
|
||||||
->assertMissing('@2fa_token_disable ~ .input-help.error')
|
|
||||||
->click('@2fa_disable')
|
|
||||||
->waitUntilMissing('@2fa_modal')
|
|
||||||
->assertSeeIn('@@success', trans('dashboard/account.two_factor.disabled'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Tests\Browser;
|
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use PHPUnit\Framework\Assert as PHPUnit;
|
|
||||||
|
|
||||||
class PterodactylBrowser extends Browser
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Move the mouse to a specific location and then perform a left click action.
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function clickPosition(int $x, int $y)
|
|
||||||
{
|
|
||||||
$this->driver->getMouse()->mouseMove(null, $x, $y)->click();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a case insensitive search for a string in the body.
|
|
||||||
*
|
|
||||||
* @param string $text
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Tests\Browser\PterodactylBrowser
|
|
||||||
*/
|
|
||||||
public function assertSee($text)
|
|
||||||
{
|
|
||||||
return $this->assertSeeIn('', $text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a case insensitive search for a string in a given selector.
|
|
||||||
*
|
|
||||||
* @param string $selector
|
|
||||||
* @param string $text
|
|
||||||
*
|
|
||||||
* @return \Pterodactyl\Tests\Browser\PterodactylBrowser
|
|
||||||
*/
|
|
||||||
public function assertSeeIn($selector, $text)
|
|
||||||
{
|
|
||||||
$fullSelector = $this->resolver->format($selector);
|
|
||||||
$element = $this->resolver->findOrFail($selector);
|
|
||||||
|
|
||||||
PHPUnit::assertTrue(
|
|
||||||
Str::contains(mb_strtolower($element->getText()), mb_strtolower($text)),
|
|
||||||
"Did not see expected text [{$text}] within element [{$fullSelector}] using case-insensitive search."
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
2
tests/Browser/console/.gitignore
vendored
2
tests/Browser/console/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
2
tests/Browser/screenshots/.gitignore
vendored
2
tests/Browser/screenshots/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
|
@ -73,7 +73,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
$this->assertFalse($user->use_totp);
|
$this->assertFalse($user->use_totp);
|
||||||
$this->assertEmpty($user->totp_secret);
|
$this->assertEmpty($user->totp_secret);
|
||||||
$this->assertEmpty($user->totp_authenticated_at);
|
$this->assertEmpty($user->totp_authenticated_at);
|
||||||
$this->assertNotEmpty($user->webauthnKeys);
|
$this->assertNotEmpty($user->securityKeys);
|
||||||
|
|
||||||
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
||||||
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
||||||
|
@ -148,7 +148,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
$this->assertFalse($user->use_totp);
|
$this->assertFalse($user->use_totp);
|
||||||
$this->assertEmpty($user->totp_secret);
|
$this->assertEmpty($user->totp_secret);
|
||||||
$this->assertEmpty($user->totp_authenticated_at);
|
$this->assertEmpty($user->totp_authenticated_at);
|
||||||
$this->assertNotEmpty($user->webauthnKeys);
|
$this->assertNotEmpty($user->securityKeys);
|
||||||
|
|
||||||
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
||||||
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
||||||
|
@ -262,7 +262,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
$this->assertEmpty($user->totp_secret);
|
$this->assertEmpty($user->totp_secret);
|
||||||
$this->assertEmpty($user->totp_authenticated_at);
|
$this->assertEmpty($user->totp_authenticated_at);
|
||||||
$this->assertFalse($user->root_admin);
|
$this->assertFalse($user->root_admin);
|
||||||
$this->assertNotEmpty($user->webauthnKeys);
|
$this->assertNotEmpty($user->securityKeys);
|
||||||
|
|
||||||
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
||||||
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
||||||
|
@ -286,7 +286,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
$this->assertEmpty($user->totp_secret);
|
$this->assertEmpty($user->totp_secret);
|
||||||
$this->assertEmpty($user->totp_authenticated_at);
|
$this->assertEmpty($user->totp_authenticated_at);
|
||||||
$this->assertTrue($user->root_admin);
|
$this->assertTrue($user->root_admin);
|
||||||
$this->assertNotEmpty($user->webauthnKeys);
|
$this->assertNotEmpty($user->securityKeys);
|
||||||
|
|
||||||
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
$this->request->shouldReceive('getRequestUri')->withNoArgs()->andReturn('/');
|
||||||
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
$this->request->shouldReceive('route->getName')->withNoArgs()->andReturn(null);
|
||||||
|
|
Loading…
Reference in a new issue