Skip to content

Commit

Permalink
[make:security:form-login] add ability to generate tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jrushlow committed Apr 24, 2024
1 parent ff1c8c5 commit 990243e
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 2 deletions.
48 changes: 46 additions & 2 deletions src/Maker/Security/MakeFormLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
namespace Symfony\Bundle\MakerBundle\Maker\Security;

use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\FileManager;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait;
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder;
Expand All @@ -33,6 +36,7 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Yaml\Yaml;
Expand All @@ -48,10 +52,13 @@
*/
final class MakeFormLogin extends AbstractMaker
{
use CanGenerateTestsTrait;

private const SECURITY_CONFIG_PATH = 'config/packages/security.yaml';
private YamlSourceManipulator $ysm;
private string $controllerName;
private string $firewallToUpdate;
private string $userClass;
private string $userNameField;
private bool $willLogout;

Expand All @@ -70,6 +77,8 @@ public static function getCommandName(): string
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command->setHelp(file_get_contents(\dirname(__DIR__, 2).'/Resources/help/security/MakeFormLogin.txt'));

$this->configureCommandWithTestsOption($command);
}

public static function getCommandDescription(): string
Expand Down Expand Up @@ -116,9 +125,11 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma

$securityHelper = new InteractiveSecurityHelper();
$this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData);
$userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']);
$this->userNameField = $securityHelper->guessUserNameField($io, $userClass, $securityData['security']['providers']);
$this->userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']);
$this->userNameField = $securityHelper->guessUserNameField($io, $this->userClass, $securityData['security']['providers']);
$this->willLogout = $io->confirm('Do you want to generate a \'/logout\' URL?');

$this->interactSetGenerateTests($input, $io);
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
Expand Down Expand Up @@ -167,6 +178,39 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
$securityData = $this->securityConfigUpdater->updateForLogout($securityData, $this->firewallToUpdate);
}

if ($this->shouldGenerateTests()) {
$testClassDetails = $generator->createClassNameDetails(
'LoginControllerTest',
'Test\\',
);

$useStatements = new UseStatementGenerator([
sprintf('\%s', $this->userClass),
// $userClassNameDetails->getFullName(),
// $userRepositoryDetails->getFullName(),
EntityManager::class,
WebTestCase::class,
UserPasswordHasherInterface::class,
]);

$generator->generateFile(
targetPath: sprintf('tests/%s.php', $testClassDetails->getShortName()),
templateName: 'security/formLogin/Test.LoginController.tpl.php',
variables: [
'use_statements' => $useStatements,
'user_class' => $this->userClass,
// 'user_short_name' => $userClassNameDetails->getShortName(),
// 'user_repo_short_name' => $userRepositoryDetails->getShortName(),
// 'success_route_path' => null !== $this->controllerResetSuccessRoute ? $this->controllerResetSuccessRoute->getPath() : '/',
// 'from_email' => $this->fromEmailAddress,
],
);

if (!class_exists(WebTestCase::class)) {
$io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.');
}
}

$generator->dumpFile(self::SECURITY_CONFIG_PATH, $securityData);

$generator->writeChanges();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?= "<?php\n" ?>
namespace App\Tests;

<?= $use_statements ?>

class LoginControllerTest extends WebTestCase
{
public function testLogin(): void
{
$client = static::createClient();
$container = static::getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$userRepository = $em->getRepository(\<?= $user_class ?>::class);

foreach ($userRepository->findAll() as $user) {
$em->remove($user);
}

$em->flush();

self::assertCount(0, $userRepository->findAll());

// Ensure login with invalid credentials shows error message
$client->request('GET', '/login');
self::assertResponseIsSuccessful();

$client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'password',
]);

self::assertResponseRedirects('/login');

$client->followRedirect();

self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.');

// Ensure login with valid credentials
/** @var UserPasswordHasherInterface $passwordHasher */
$passwordHasher = (static::getContainer())->get('security.user_password_hasher');

$user = (new User())->setEmail('email@example.com');
$user->setPassword($passwordHasher->hashPassword($user, 'password'));

$em->persist($user);
$em->flush();

$client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'password',
]);

self::assertResponseRedirects('/');
$client->followRedirect();
self::assertSelectorNotExists('.alert-danger');
self::assertResponseIsSuccessful();
}
}
31 changes: 31 additions & 0 deletions tests/Maker/Security/MakeFormLoginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,37 @@ public function getTestDetails(): \Generator
$this->assertSame('app_logout', $securityConfig['security']['firewalls']['main']['logout']['path']);
}),
];

yield 'generates_form_login_using_defaults_with_test' => [$this->createMakerTest()
->run(function (MakerTestRunner $runner) {
// Make the UserPasswordHasherInterface available in the test
$runner->renderTemplateFile('security/make-form-login/FixtureController.php', 'src/Controller/FixtureController.php', []);

$this->makeUser($runner);

$output = $runner->runMaker([
'SecurityController', // Controller Name
'y', // Generate Logout,
'y', // Generate tests
]);

$this->assertStringContainsString('Success', $output);
$fixturePath = \dirname(__DIR__, 2).'/fixtures/security/make-form-login/expected';

$this->assertFileEquals($fixturePath.'/SecurityController.php', $runner->getPath('src/Controller/SecurityController.php'));
$this->assertFileEquals($fixturePath.'/login.html.twig', $runner->getPath('templates/security/login.html.twig'));

$securityConfig = $runner->readYaml('config/packages/security.yaml');

$this->assertSame('app_login', $securityConfig['security']['firewalls']['main']['form_login']['login_path']);
$this->assertSame('app_login', $securityConfig['security']['firewalls']['main']['form_login']['check_path']);
$this->assertTrue($securityConfig['security']['firewalls']['main']['form_login']['enable_csrf']);
$this->assertSame('app_logout', $securityConfig['security']['firewalls']['main']['logout']['path']);

$runner->configureDatabase();
$runner->runTests();
}),
];
}

private function runLoginTest(MakerTestRunner $runner): void
Expand Down

0 comments on commit 990243e

Please sign in to comment.