Buy

It doesn't make any sense to create a battle without a Programmer or a Project. But guess what - you can! Or at least, you kind of can: we don't have validation to prevent that yet!

The validation system we created in earlier courses is air-tight: as long as we add the constraint annotations, it just works. So normally, I might not write a test for failing validation. But I will now... because we're going to add a twist.

Testing for Validation

Add a new public function testPOSTBattleValidationErrors():

67 lines tests/AppBundle/Controller/Api/BattleControllerTest.php
... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 44
public function testPOSTBattleValidationErrors()
{
... lines 47 - 64
}
}

Copy the first bits from the previous function that create the data and make the request:

67 lines tests/AppBundle/Controller/Api/BattleControllerTest.php
... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 44
public function testPOSTBattleValidationErrors()
{
$programmer = $this->createProgrammer([
'nickname' => 'Fred'
], 'weaverryan');
$data = array(
'projectId' => null,
'programmerId' => $programmer->getId()
);
// 1) Create a programmer resource
$response = $this->client->post('/api/battles', [
'body' => json_encode($data),
'headers' => $this->getAuthorizedHeaders('weaverryan')
]);
... lines 62 - 64
}
}

But, don't actually create a project! Instead, send null for the projectId. Since starting a battle against nothing is nonsense, assert that 400 is the response status code. This follows the pattern we did before in ProgrammerControllerTest.

And actually, that test shows off the validation errors response format: there should be an errors key with field names for the errors below that. Each field could technically have multiple errors, so that's an array:

290 lines tests/AppBundle/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 6
class ProgrammerControllerTest extends ApiTestCase
{
... lines 9 - 211
public function testValidationErrors()
{
... lines 214 - 230
$this->asserter()->assertResponsePropertyExists($response, 'errors.nickname');
$this->asserter()->assertResponsePropertyEquals($response, 'errors.nickname[0]', 'Please enter a clever nickname');
... lines 233 - 234
}
... lines 236 - 288
}

Check for the error in our code with $this->asserter()->assertResponsePropertyExists(): the field should be errors.projectId:

67 lines tests/AppBundle/Controller/Api/BattleControllerTest.php
... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 44
public function testPOSTBattleValidationErrors()
{
... lines 47 - 61
$this->assertEquals(400, $response->getStatusCode());
$this->asserter()->assertResponsePropertyExists($response, 'errors.projectId');
... line 64
}
}

Next, check for the exact message: assertResponsePropertyEquals() with errors.projectId[0] - so the first and only error - set to This value should not be blank.:

67 lines tests/AppBundle/Controller/Api/BattleControllerTest.php
... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 44
public function testPOSTBattleValidationErrors()
{
... lines 47 - 61
$this->assertEquals(400, $response->getStatusCode());
$this->asserter()->assertResponsePropertyExists($response, 'errors.projectId');
$this->asserter()->assertResponsePropertyEquals($response, 'errors.projectId[0]', 'This value should not be blank.');
}
}

Why that message? That's the default message for Symfony's NotBlank constraint.

Before we code this up, copy the method name and run the test:

./vendor/bin/phpunit --filter testPOSTBattleValidationErrors

It explodes with a 500 error! This is what happens when you're lazy and forget to add validation: the BattleManager panics because there is no Project. We do not want 500 errors, they are not hipster.

Adding Basic Validation

We know how to fix this! Go to BattleModel. Remember, this is the class that's bound to the form: so the annotations should go here. First, add the use statement. Type use NotBlank, let it auto-complete, delete the last part and add the normal as Assert:

40 lines src/AppBundle/Form/Model/BattleModel.php
... lines 1 - 6
use Symfony\Component\Validator\Constraints as Assert;
... lines 8 - 40

That's my shortcut to get the use statement.

Now, above project, add @Assert\NotBlank(). Do the same above programmer: @Assert\NotBlank():

40 lines src/AppBundle/Form/Model/BattleModel.php
... lines 1 - 8
class BattleModel
{
/**
* @Assert\NotBlank()
*/
private $project;
/**
* @Assert\NotBlank()
*/
private $programmer;
... lines 20 - 39
}

Done! Now run the test:

./vendor/bin/phpunit --filter testPostBattleValidationErrors

We're awesome! Or are we... there's a deeper problem! What prevents an API client from starting a battle with a Programmer that they do not own? Right now - nothing, besides karma and trusting that humankind will do the right thing. Unfortunately, that doesn't usually pass a security audit. Let's be heros and fix this security hole!

Leave a comment!