diff --git a/src/Controller/Security.php b/src/Controller/Security.php index cdd02d0a61..d372addbda 100644 --- a/src/Controller/Security.php +++ b/src/Controller/Security.php @@ -17,6 +17,7 @@ use app\Util\Common; use App\Util\Exception\EmailTakenException; use App\Util\Exception\NicknameTakenException; use App\Util\Exception\ServerException; +use App\Util\FormFields; use App\Util\Nickname; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Symfony\Component\Form\Extension\Core\Type\EmailType; @@ -84,7 +85,7 @@ class Security extends Controller 'label' => _m('Email'), 'constraints' => [ new NotBlank(['message' => _m('Please enter an email') ])], ]], - FormFields::password(), + FormFields::repeated_password(), ['register', SubmitType::class, ['label' => _m('Register')]], ]); @@ -123,16 +124,20 @@ class Security extends Controller fn (int $id) => DB::persist(Follow::create(['follower' => $id, 'followed' => $id])) ); DB::flush(); + // @codeCoverageIgnoreStart } catch (UniqueConstraintViolationException $e) { // _something_ was duplicated, but since we already check if nickname is in use, we can't tell what went wrong $e = 'An error occurred while trying to register'; Log::critical($e . " with nickname: '{$valid_nickname}' and email '{$data['email']}'"); throw new ServerException(_m($e)); } + // @codeCoverageIgnoreEnd // generate a signed url and email it to the user if ($_ENV['APP_ENV'] === 'dev' || Common::config('site', 'use_email')) { + // @codeCoverageIgnoreStart Common::sendVerificationEmail(); + // @codeCoverageIgnoreEnd } else { $user->setIsEmailVerified(true); } diff --git a/tests/Controller/SecurityTest.php b/tests/Controller/SecurityTest.php new file mode 100644 index 0000000000..f5e2c1b30c --- /dev/null +++ b/tests/Controller/SecurityTest.php @@ -0,0 +1,188 @@ +. + +// }}} + +namespace App\Tests\Core; + +use App\Util\GNUsocialTestCase; + +class SecurityTest extends GNUsocialTestCase +{ + // --------- Login -------------- + + private function testLogin(string $nickname, string $password) + { + // This calls static::bootKernel(), and creates a "client" that is acting as the browser + $client = static::createClient(); + $crawler = $client->request('GET', '/login'); + $this->assertResponseIsSuccessful(); + // $form = $crawler->selectButton('Sign in')->form(); + $crawler = $client->submitForm('Sign in', [ + 'nickname' => $nickname, + 'password' => $password, + ]); + $this->assertResponseStatusCodeSame(302); + $crawler = $client->followRedirect(); + return [$client, $crawler]; + } + + public function testLoginSuccess() + { + [, $crawler] = self::testLogin($nickname = 'taken_user', 'foobar'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorNotExists('.alert'); + $this->assertRouteSame('main_all'); + $this->assertSelectorTextContains('.info b', $nickname); + } + + public function testLoginAttemptAlreadyLoggedIn() + { + [$client] = self::testLogin('taken_user', 'foobar'); // Normal login + $crawler = $client->request('GET', '/login'); // attempt to login again + $client->followRedirect(); + $this->assertRouteSame('main_all'); + } + + public function testLoginFailure() + { + self::testLogin('taken_user', 'wrong password'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('.alert', 'Invalid login credentials'); + $this->assertRouteSame('login'); + } + + public function testLoginEmail() + { + self::testLogin('email@provider', 'foobar'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorNotExists('.alert'); + $this->assertRouteSame('main_all'); + $this->assertSelectorTextContains('.info b', 'taken_user'); + } + + // --------- Register -------------- + + private function testRegister(string $nickname, string $email, string $password) + { + $client = static::createClient(); + $crawler = $client->request('GET', '/register'); + $this->assertResponseIsSuccessful(); + $crawler = $client->submitForm('Register', [ + 'register[nickname]' => $nickname, + 'register[email]' => $email, + 'register[password][first]' => $password, + 'register[password][second]' => $password, + ]); + return [$client, $crawler]; + } + + public function testRegisterSuccess() + { + [$client,] = self::testRegister('new_nickname', 'new_email@email_provider', 'foobar'); + $this->assertResponseStatusCodeSame(302); + $client->followRedirect(); + $this->assertResponseIsSuccessful(); + $this->assertSelectorNotExists('.alert'); + $this->assertRouteSame('main_all'); + $this->assertSelectorTextContains('.info b', 'new_nickname'); + } + + public function testRegisterDifferentPassword() + { + $client = static::createClient(); + $crawler = $client->request('GET', '/register'); + $this->assertResponseIsSuccessful(); + $crawler = $client->submitForm('Register', [ + 'register[nickname]' => 'new_user', + 'register[email]' => 'new_email@provider', + 'register[password][first]' => 'fooobar', + 'register[password][second]' => 'barquux', + ]); + $this->assertSelectorTextContains('form[name=register] ul li', 'The password fields must match'); + $this->assertResponseStatusCodeSame(200); + $this->assertRouteSame('register'); + } + + private function testRegisterPasswordLength(string $password, string $error) + { + self::testRegister('new_nickname', 'email@provider', $password); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('#register > div:nth-child(3) > ul > li', $error); + $this->assertRouteSame('register'); + } + + public function testRegisterPassowrdEmpty() + { + self::testRegisterPasswordLength('', error: 'Please enter a password'); + } + + public function testRegisterPasswordShort() + { + self::testRegisterPasswordLength('f', error: 'Your password should be at least'); + } + + public function testRegisterPasswordLong() + { + self::testRegisterPasswordLength(str_repeat('f', 128), error: 'Your password should be at most'); + } + + private function testRegisterNoEmail() + { + self::testRegister('new_nickname', '', 'foobar'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('#register > div:nth-child(2) > ul > li', 'Please enter an email'); + $this->assertRouteSame('register'); + } + + private function testRegisterNicknameLength(string $nickname, string $error) + { + self::testRegister($nickname, 'email@provider', 'foobar'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('#register > div:nth-child(1) > ul > li', $error); + $this->assertRouteSame('register'); + } + + public function testRegisterNicknameEmpty() + { + self::testRegisterNicknameLength('', error: 'Please enter a nickname'); + } + + public function testRegisterNicknameShort() + { + self::testRegisterNicknameLength('f', error: 'Your nickname must be at least'); + } + + public function testRegisterNicknameLong() + { + self::testRegisterNicknameLength(str_repeat('f', 128), error: 'Your nickname must be at most'); + } + + public function testRegisterExistingNickname() + { + [$client, $crawler] = self::testRegister('taken_user', 'new_new_email@email_provider', 'foobar'); + $this->assertSelectorTextContains('.stacktrace', 'App\Util\Exception\NicknameTakenException'); + } + + public function testRegisterExistingEmail() + { + [$client, $crawler] = self::testRegister('other_new_nickname', 'email@provider', 'foobar'); + $this->assertSelectorTextContains('.stacktrace', 'App\Util\Exception\EmailTakenException'); + } +}