4
votes

I am working on setting up a site with fosUserBundle. I need to write some unit tests, but I cannot seems to get authentication to work correctly for use with them. I have have tried a few methods with no luck.

approach1 - chain providers in security settings to make a standard login I can rely on. When I do this the only authentication that works is the in_memory provider meaning I can login in with the admin user but not any users created by fosUserBundle

security.yml

security:
    encoders:
        "FOS\UserBundle\Model\UserInterface": plaintext
        "Symfony\Component\Security\Core\User\User": plaintext

    providers:
        chain_provider:
            providers: [in_memory, fos_userbundle]
        in_memory:
            users:
                admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
        fos_userbundle:
            id: fos_user.user_manager


    firewalls:
        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                login_path:  /
                csrf_provider: form.csrf_provider
                default_target_path: /media
            logout:       true
            anonymous:    true
            http_basic:
                provider: in_memory

Method 2- I created a user in my setup of the unit test. But with this method I can never login with the user I just created, I am not requiring user confirmation.

 public function setUp() {

        $kernel = static::createKernel();
        $this->repo = $kernel->boot();
        $this->repo = $kernel->getContainer();

        $userManager = $this->repo->get('fos_user.user_manager');

        $email = "testing".(string) self::newRandomNumber()."@test.com";
        $un = "test".(string) self::newRandomNumber();
        $fn = "testin";
        $ln = "laster";
        $pwd = "passworD1";

        $user = $userManager->createUser();

        $user->setEmail($email);
        $user->setUsername($un);
        $user->setFirstName($fn);
        $user->setLastName($ln);
        $user->setPlainPassword($pwd);
        $user->setBimStaff(True);

        // Persist the user to the database         
        $userManager->updateUser($user);

        $this->user = $user;

        $this->client = static::createClient();
        $crawler = $this->client->request('GET', '/');

        $form = $crawler->selectButton('Login')->form();

        // set some values
        $un = 'Lucas'.self::newRandomNumber();
        $form['_username'] = $un;
        $form['_password'] = $pwd;

        // submit the form

        $crawler = $this->client->submit($form);
        print_r($this->client->getResponse());

        $this->client->followRedirects();

    }

I have tried both login in at the form and http basic authentication but neither seem to work.

Any one have any advice?

Thanks, Cory

2
on a side note: it's functional testing, as you're testing a feature involving the full stack, unit testing is when you test one class isolated from the others.allan.simon

2 Answers

4
votes

Hmm, It seems that if you chain providers in the security you cannot use just one of the providers, at least that solved the issue for me.

// security.yml 
providers:
    chain_provider:
        providers: [in_memory, fos_userbundle]
    in_memory:
        users:
            admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
    fos_userbundle:
        id: fos_user.user_manager


firewalls:
    main:
        pattern: ^/
        form_login:
            provider: chain_provider
            login_path:  /
            csrf_provider: form.csrf_provider
            default_target_path: /media
        logout:       true
        anonymous:    true
        http_basic:
            provider: chain_provider

FOr some reason as soon as I changed both providers to chain_provider it started cooperating.

My unit test now looks like this

public function setUp() {

    $kernel = static::createKernel();
    $this->repo = $kernel->boot();
    $this->repo = $kernel->getContainer();


    $this->client = static::createClient(array(), array(
        'PHP_AUTH_USER' => 'admin',
        'PHP_AUTH_PW'   => 'admin',
    ));
    $this->client->followRedirects();

}

If copying this I would suggest using stronger passwords for your users :)

1
votes

I had the same problem but I wanted option 2 to work. Your example was almost there and helped me. I think the reason it wasn't working for you is that the user wasn't enabled. The user constructor in FOSUserBundle will create disabled users by default, so you must call $user->setEnable(true)

Here's my working example

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

class MyControllerTest extends WebTestCase
{
    private $client = null;

    public function setUp()
    {
        $this->client = static::createClient();
    }

    private function logIn()
    {
        $session = $this->client->getContainer()->get('session');

        $userManager = $this->client->getContainer()->get('fos_user.user_manager');

        $user=$userManager->findUserByUsername('tester');
        if (!$user) {
            $user = $userManager->createUser();

            $user->setEmail('[email protected]');
            $user->setUsername('tester');
            $user->setPlainPassword('foo');
            $user->setEnabled(true);
            $user->addRole('ROLE_SUPER_ADMIN');

            $userManager->updateUser($user);
        }

        $firewall = 'main_firewall';
        $token = new UsernamePasswordToken($user, null, $firewall, array('ROLE_SUPER_ADMIN'));

        $session->set('_security_'.$firewall, serialize($token));
        $session->save();

        $cookie = new Cookie($session->getName(), $session->getId());
        $this->client->getCookieJar()->set($cookie);
    }

    public function testCompleteScenario()
    {
        $this->logIn();

        $crawler = $this->client->request('GET', '/foo/');
        $this->assertEquals(200, $this->client->getResponse()->getStatusCode(),
            "Unexpected HTTP status code for GET /foo/".$this->client->getResponse()->getContent());

    }

}