symfony2 behat in test enviroment: DB tables not created - symfony

I am trying to behat my application and I have a big problem; DB tables are not created so I can't put any fixtures.
My scenario is:
Scenario: Check the stories page
Given Database is set
And I am logged as "admin" and password "123123123"
And print last response
...
Part of FeatureContext:
/**
* #Given /^Database is set$/
*/
public function databaseIsSet()
{
$this->generateSchema() ;
$admin = new User() ;
$admin->setRoles(array(User::ROLE_SUPER_ADMIN)) ;
$admin->setEnabled(true) ;
$admin->setUsername("admin") ;
$admin->setPlainPassword("123123123") ;
$admin->setEmail("admin#mysite.com") ;
$em = $this->getEntityManager() ;
$em->persist($admin) ;
$em->flush() ;
echo $admin->getId() . "==" ;
echo "db set" ;
}
/**
* #Given /^I am logged as "([^"]*)" and password "([^"]*)"$/
*/
public function iAmLoggedAsAndPassword($username, $password)
{
return array(
new Step\When('I am on "/login"'),
new Step\When('I fill in "username" with "' . $username . '"'),
new Step\When('I fill in "password" with "' . $password . '"'),
new Step\When('I press "Login"'),
);
}
protected function generateSchema()
{
// Get the metadatas of the application to create the schema.
$metadatas = $this->getMetadatas();
if ( ! empty($metadatas)) {
/**
* #var \Doctrine\ORM\Tools\SchemaTool
*/
$tool = new SchemaTool($this->getEntityManager());
// $tool->dropDatabase() ;
$tool->createSchema($metadatas);
} else {
throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
}
}
/**
* Overwrite this method to get specific metadatas.
*
* #return Array
*/
protected function getMetadatas()
{
$result = $this->getEntityManager()->getMetadataFactory()->getAllMetadata() ;return $result;
}
protected function getEntityManager()
{
return $this->kernel->getContainer()->get("doctrine")->getEntityManager() ;
}
....
The code for generateSchema is taken somewhere from internet and used in Phpunits tests I have and works perfectly.
But; when I run bin/behat, I get
SQLSTATE[HY000]: General error: 1 no such table: tbl_user
after login part of scenario.
The echo statement I have is also shown in output, just to make sure the method is actually executed. Also, $admin gets an ID of 1 which is also visible in output.
My test env is using default sqlite DB, and it is irrelevant if I put 'http://mysite.local/app_dev.php/' or 'http://mysite.local/app_test.php/' for base_url in config; the login doesn't work although I copy&pasted it from knpLabs page. To make sure $admin is still in DB, I tried to reload it from repository and it works (I removed that part of code).
Help?

Actually, I found the problem. Sqlite works in-memory and upon each request to some page like login url, the previous state had been lost. I created new enviroment app_behat.php with these setting in config_behat.yml:
imports:
- { resource: config.yml }
framework:
test: ~
session:
storage_id: session.storage.mock_file
doctrine:
dbal:
dbname: other_database
and it works now. Maybe someone will find this usefull.

I had the same problem and for me the problem was in config_test.yml file.
I changed pdo_sqlite to pdo_mysql.
doctrine:
dbal:
driver: pdo_mysql
# driver: pdo_sqlite
And it works like a charm.

Related

Out of range Ids in Symfony route

I have a common structure for Symfony controller (using FOSRestBundle)
/**
* #Route\Get("users/{id}", requirements={"userId" = "(\d+)"})
*/
public function getUserAction(User $user)
{
}
Now if I request http://localhost/users/1 everything is fine. But if I request http://localhost/users/11111111111111111 I get 500 error and Exception
ERROR: value \"11111111111111111\" is out of range for type integer"
Is there a way to check id before it is transferred to database?
As a solution I can specify length of id
/**
* #Route\Get("users/{id}", requirements={"userId" = "(\d{,10})"})
*/
but then Symfony will say that there is no such route, instead of showing that the id is incorrect.
By telling Symfony that the getUserAction() argument is a User instance, it will take for granted that the {id} url parameter must be matched to the as primary key, handing it over to the Doctrine ParamConverter to fetch the corresponding User.
There are at least two workarounds.
1. Use the ParamConverter repository_method config
In the controller function's comment, we can add the #ParamConverter annotation and tell it to use the repository_method option.
This way Symfony will hand the url parameter to a function in our entity repository, from which we'll be able to check the integrity of the url parameter.
In UserRepository, let's create a function getting an entity by primary key, checking first the integrity of the argument. That is, $id must not be larger than the largest integer that PHP can handle (the PHP_INT_MAX constant).
Please note: $id is a string, so it's safe to compare it to PHP_INT_MAX, because PHP will automatically typecast PHP_INT_MAX to a string and compare it to $id. If it were an integer, the test would always fail (by design, all integers are less than or equal to PHP_INT_MAX).
// ...
use Symfony\Component\Form\Exception\OutOfBoundsException;
class UserRepository extends ...
{
// ...
public function findSafeById($id) {
if ($id > PHP_INT_MAX) {
throw new OutOfBoundsException($id . " is too large to fit in an integer");
}
return $this->find($id);
}
}
This is only an example: we can do anything we like before throwing the exception (for example logging the failed attempt).
Then, in our controller, let's include the ParamConverter annotation:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
and modify the function comment adding the annotation:
#ParamConverter("id", class="App:User", options={"repository_method" = "findSafeById"})
Our controller function should look like:
/**
* #Get("users/{id}")
* #ParamConverter("id", class="App:User", options={"repository_method" = "findSafeById"})
*/
public function getUserAction(User $user) {
// Return a "OK" response with the content you like
}
This technique allows customizing the exception, but does not give you control over the response - you'll still get a 500 error in production.
Documentation: see here.
2. Parse the route "the old way"
This way was the only viable one up to Symfony 3, and gives you a more fine-grained control over the generated response.
Let's change the action prototype like this:
/**
* #Route\Get("users/{id}", requirements={"id" = "(\d+)"})
*/
public function getUserAction($id)
{
}
Now, in the action we'll receive the requested $id and we'll be able to check whether it's ok. If not, we throw an exception and/or return some error response (we can choose the HTTP status code, the format and anything else).
Below you find a sample implementation of this procedure.
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\Form\Exception\OutOfBoundsException;
use Symfony\Component\HttpFoundation\JsonResponse;
class MyRestController extends FOSRestController {
/**
* #Get("users/{id}", requirements={"id" = "(\d+)"})
*/
public function getUserAction($id) {
try {
if ($id > PHP_INT_MAX) {
throw new OutOfBoundsException($id . " is too large to fit in an integer");
}
// Replace App\Entity\User with your actual Entity alias
$user = $this->getDoctrine()->getRepository('App\Entity\User')->find($id);
if (!$user) {
throw new \Doctrine\ORM\NoResultException("User not found");
}
// Return a "OK" response with the content you like
return new JsonResponse(['key' => 123]);
} catch (Exception $e) {
return new JsonResponse(['message' => $e->getMessage()], 400);
}
}

Symfony parameters.yml nfs mounted filepath changes to local

On the production server, my nfs mounted path is being translated to a local directory. Any ideas what could cause this?
parameters.yml
parameters:
files_dir: /n/fs/web/doc_root/Files
services.yml
app.file_manager:
class: AppBundle\Services\FileManager
public: true
arguments: ["%files_dir%"]
FileManager.php
namespace AppBundle\Services;
class FileManager
{
/**
* #var string
*/
private $filesDir;
/**
* FileManager constructor.
* #param $filesDir
*/
public function __construct($filesDir)
{
echo $filesDir;
$this->filesDir = $filesDir;
}
}
Expected output: /n/fs/web/doc_root/Files
Actual Output: /opt/rh/httpd24/root/var/www/sites/web/doc_root/Files
Edit:
So it looks like in the container class file, appProdProjectContainer.php, the files_dir parameter is treated as a dynamic parameter.
$dir = __DIR__;
for ($i = 1; $i <= 5; ++$i) {
$this->targetDirs[$i] = $dir = dirname($dir);
}
...
case 'files_dir': $value = ($this->targetDirs[5].'/Files'); break;
This breaks in my environment because the output of __DIR__ in the context of composer install on a command line is /n/fs/web/doc_root, but when running via apache the output of __DIR__ is /opt/rh/httpd24/root/var/www/sites/web/doc_root . I'm able to delete the var/cache/prod directory and regenerate the container class file by accessing the site through a web browser or curl and it fixes my problem. Is there a better way to do this?

behat 3 mink testing redirection

In symfony 3 controller file I have function:
/**
* #Route("/user/registration", name="post_registration")
* #Method("POST")
* #return mixed
*/
public function postRegistration()
{
$post = $this->getAllPost();
$this->curl = $this->get('ApiClient');
$responseArray = $this->curl->post(
$this->container->getParameter('api_url') . '/users',
$post
);
if (isset($responseArray['api_key'])) {
return $this->redirectResponseWithCookie($responseArray['api_key']);
} else {
return $this->render(
static::REGISTRATION_TEMPLATE,
['errors' => $responseArray['errors']]
);
}
}
In one part it calls function
redirectResponseWithCookie()
which should redirect the page.
I want to test redirection header - does it have the right Location value.
I have function in UserRegistrationContext class
class UserRegistrationContext extends BaseContext implements Context, SnippetAcceptingContext
{
/**
* #When I register successfully into the system
* #return null
*/
public function iRegisterSuccessfullyIntoTheSystem()
{
$this->canIntercept();
$this->session->getDriver()->getClient()->followRedirects(false);
// Enters his data
// Posts to www.notification.guru .
$this->session->getDriver()->getClient()
->request('POST', $this->baseUrl . '/user/registration', $this->getFormArray());
echo 'test';
}
}
BaseContext class just has some helper functions, its contructor inits session:
$driver = new GoutteDriver();
$this->session = new Session($driver);
this part might be wrong:
$this->session->getDriver()->getClient()->followRedirects(false);
I just took code from behat 2.5 and tried to adjust to work it with behat 3, but not sure if its correct, but at least does not throw error.
It should stop redirect, so then I could get a response header with
getResponseHeaders()
But the problem is that it tries to redirect, and code fails, because real site is not lauched yet where it redirects. And also I would not be able to test headers I guess after real redirection.
So the redirection has to be stopped I guess.
How to do that? I cannot find info.
Test fails at line
$this->session->getDriver()->getClient()
->request('POST', $this->baseUrl . '/user/registration', $this->getFormArray());
So code in my question is not wrong, as I said in comment - I did not see well in console. Often happens that when I post to SO, I do not even finish writing the question, just from writing the details to others I see the answer.
$this->session->getDriver()->getClient()->followRedirects(false);
works well.
So to finish how to check - make function something like this and call it:
/**
* #param Behat\Mink\Session $session - mink session
* #throws \Exception
* #return null
*/
public function isLoggedIntoApi($session)
{
$headers = $session->getResponseHeaders();
if ($headers['Location'][0] != $this->appUrl) {
throw new \Exception(
'User is not redirected to ' . $this->appUrl
);
}
}

ReferenceRepository strips related entities when calling getReference()

I am writing a Symfony 2 unit test that relies heavily on data fixtures. As a shortcut, I wired up a method that will give me access to the fixture loader's ReferenceRepository so that I can access shared entities in my tests.
However, when I pull an object out of the ReferenceRepository, it has no relations, even though I persist them in the data fixture.
The weird part is, there is some code in ReferenceRepository that appears to be stripping those relations out, and I don't understand why it is doing this (let alone how to prevent it).
As an example, here is what a data fixture looks like:
public function load(ObjectManager $manager)
{
$project = new Project();
// ... populate fields ...
/* Add one detail field to the Project. */
$detail = new ProjectDetail();
// ... populate fields ...
$project->addDetail($detail);
$manager->persist($project);
$manager->flush();
$this->addReference('project-onedetail', $project);
}
In my test case, I am doing something (more or less) like this:
$project =
$this->fixtureLoader->getReferenceRepository()
->getReference('project-onedetail');
When I call the method in the test case to grab this Project object, I notice some weird behavior:
From Doctrine\Common\DataFixtures\ReferenceRepository (comments added):
public function getReference($name)
{
$reference = $this->references[$name];
// At this point, $reference contains the Project object with related ProjectDetail.
// It would be awesome if the method would just return $reference...
$meta = $this->manager->getClassMetadata(get_class($reference));
$uow = $this->manager->getUnitOfWork();
if (!$uow->isInIdentityMap($reference) && isset($this->identities[$name])) {
// ... but instead it goes into this conditional....
$reference = $this->manager->getReference(
$meta->name,
$this->identities[$name]
);
// ... and now $reference->getDetails() is empty! What just happened??
$this->references[$name] = $reference; // already in identity map
}
return $reference;
}
What's going on in ReferenceRepository->getReference()? Why are the related objects getting removed from $reference, and how do I prevent that?
What's Going On
After the fixture loader runs, it clears out the UnitOfWork's identity map.
See \Doctrine\Common\DataFixtures\Executor\AbstractExecutor:
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
...
$fixture->load($manager);
$manager->clear();
}
As a result, the condition !$uow->isInIdentityMap($reference) in ReferenceRepository->getReference() will always evaluate to false after the fixture loader has finished.
The Workaround
You can work around this by clearing out ReferenceRepository->$identities. Unfortunately, you don't have direct access to this array, so you'll need to do something slightly kludgy like:
/* #kludge The fixture loader clears out its UnitOfWork object after
* loading each fixture, so we also need to clear the
* ReferenceRepository's identity map.
*/
$repository = $this->fixtureLoader->getReferenceRepository();
$identities = array_keys($repository->getIdentities());
foreach($identities as $key)
{
$repository->setReferenceIdentity($key, null);
}
However, if you do that, you may run into some nasty ORMInvalidArgumentExceptions if you set related objects in your test fixtures:
Doctrine\ORM\ORMInvalidArgumentException: A new entity was found through the relationship '...' that was not configured to cascade persist operations for entity: url. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
The Solution
Ultimately, if you want this to work properly, you'll need to change the behavior of the fixture executor that you use in your test cases so that it does not clear the manager after loading fixtures:
/** Executes data fixtures for unit tests.
*/
class TestExecutor extends ORMExecutor
{
/** Load a fixture with the given persistence manager.
*
* #param ObjectManager|EntityManager $manager
* #param FixtureInterface $fixture
*/
public function load(ObjectManager $manager, FixtureInterface $fixture)
{
/** #kludge Unfortunately, we have to copy-paste a bit of code.
*
* The only difference between this method and AbstractExecutor->load()
* is that we don't call $manager->clear() when we're done loading.
*/
if($this->logger)
{
$prefix = '';
if($fixture instanceof OrderedFixtureInterface)
{
$prefix = sprintf('[%d] ', $fixture->getOrder());
}
$this->log('loading ' . $prefix . get_class($fixture));
}
// additionally pass the instance of reference repository to shared fixtures
if($fixture instanceof SharedFixtureInterface)
{
$fixture->setReferenceRepository($this->referenceRepository);
}
$fixture->load($manager);
/* Do NOT clear the unit of work; we will keep managed entities so that
* they are available to tests.
*/
}
}

Migrations fail when SQLite database file does not exist?

It seems that migrations (sort of) fail silently when the database file does not exist. The migration executes but no db file is created and I can run the migration again. (It never says "nothing to migrate") If I create a blank file then it works.
This is odd because I thought SQLite always created the db file if it was not found so I'm not sure if this is a bug or something I've done wrong. Maybe it's a permissions problem? But everything else is working so I don't know. I'm using Windows 7 and the project is in my
User blamh suggested to add the following snippet to app/start/artisan.php to automatically recreate the database when it doesn't exist, instead of throwing an exception.
if (Config::get('database.default') === 'sqlite') {
$path = Config::get('database.connections.sqlite.database');
if (!file_exists($path) && is_dir(dirname($path))) {
touch($path);
}
}
With this, you can safely delete the SQLite database and then re-migrate and re-seed it, if you wish.
I've issued this bug against laravel/framework.
Hopefully future versions will give an error if the database doesn't exist, or automatically create one.
This is an updated and more flexible solution from Virtlinks answer
<?php
namespace App\Providers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class SqliteServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
if (DB::getDriverName() === 'sqlite') {
$path = DB::getConfig('database');
if (!file_exists($path) && is_dir(dirname($path))) {
touch($path);
}
}
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Here's yet another way to automatically create the database file, tested on Laravel 5.4.
This is the same as Gummibeer's answer except that I moved the logic to the App\Console\Kernel class (app/Console/Kernel.php), and the check will be performed only when running the migrate command.
<?php
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* #param \Symfony\Component\Console\Input\InputInterface $input
* #param \Symfony\Component\Console\Output\OutputInterface $output
* #return int
*/
public function handle($input, $output = null)
{
$this->touchSQLiteDatabase($input);
return parent::handle($input, $output);
}
protected function touchSQLiteDatabase($input)
{
$this->bootstrap();
if (substr((string)$input, 0, 7) == 'migrate' && DB::getDriverName() === 'sqlite') {
$path = DB::getConfig('database');
if (!file_exists($path) && is_dir(dirname($path))) {
touch($path);
}
}
}
}

Resources