From 250e330207f304d2074e39c1b11ac329f496cf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sun, 11 Oct 2015 23:09:57 +0100 Subject: [PATCH] Add Behat tests for Mandate * Added a Provider for creating DateTime values from strings * Fixed a nasty bug with the DBAL DateTimeType: the convertion from database value to PHP value was done with the wrong timezone causing sync issues --- features/api/job.feature | 3 + features/api/mandate.feature | 127 +++++++----------- features/fixtures/ORM/mandate/collection.yml | 39 ++++++ .../fixtures/ORM/mandate/job-president.yml | 5 + .../Faker/Provider/DateTimeProvider.php | 36 +++++ .../Faker/Provider/MandateProvider.php | 4 +- .../Doctrine/DBAL/Type/UTCDateTimeType.php | 25 +++- src/ApiBundle/Entity/Mandate.php | 26 +++- src/ApiBundle/Resources/config/services.yml | 4 + .../DBAL/Type/UTCDateTimeTypeTest.php | 21 ++- 10 files changed, 197 insertions(+), 93 deletions(-) create mode 100644 features/fixtures/ORM/mandate/collection.yml create mode 100644 features/fixtures/ORM/mandate/job-president.yml create mode 100644 src/ApiBundle/DataFixtures/Faker/Provider/DateTimeProvider.php diff --git a/features/api/job.feature b/features/api/job.feature index 905e00076..7c57135fe 100644 --- a/features/api/job.feature +++ b/features/api/job.feature @@ -201,6 +201,9 @@ Feature: Jobs management And the JSON should be equal to: """ """ + + When I send a DELETE request to "/api/jobs/1" + Then the response status code should be 404 When I send a GET request to "/api/mandates/1" Then the JSON node "jobs" should have 0 element diff --git a/features/api/mandate.feature b/features/api/mandate.feature index 7ad6fe9e2..50a2f8eec 100644 --- a/features/api/mandate.feature +++ b/features/api/mandate.feature @@ -1,4 +1,4 @@ -@mandate @ignore +@mandate Feature: Mandates management There is a mandate for every year. A mandate is composed of a group of users, although may not have any user. @@ -10,69 +10,82 @@ Feature: Mandates management A user may have one or several mandate, with or without a job. Background: + Given the database is empty + Given the fixtures file "authentication.yml" is loaded Given I authenticate myself as admin - - Scenario: Get a collection + @crud + Scenario: It should be possible to get all mandates + Given the fixtures file "mandate/collection.yml" is loaded When I send a GET request to "/api/mandates" Then the response status code should be 200 + And the response should be in JSON-LD And I should get a paged collection with the context "/api/contexts/Mandate" + And the JSON node "hydra:totalItems" should be equal to 2 - Scenario: Get a resource + @crud + Scenario: It should be possible to get a specific mandate + Given the fixtures file "mandate/collection.yml" is loaded When I send a GET request to "/api/mandates/1" Then the response status code should be 200 - And the JSON node "jobs" should have 2 element + And the response should be in JSON-LD + And the JSON node "jobs" should have 3 element Then the JSON response should have the following nodes: | node | value | type  | | @context | /api/contexts/Mandate | | | @id | /api/mandates/1 | | | @type | Mandate | | - | endAt | 2007-03-18T10:25:58+00:00 |   | + | endAt | 2006-04-17T09:38:34+00:00 |   | | jobs | | array | - | jobs[0] | /api/jobs/4 | | - | jobs[1] | /api/jobs/51 | | - | name | Mandate 2005/2007 | | - | startAt | 2005-11-27T17:41:35+00:00 | | - + | jobs[0] | /api/jobs/1 | | + | jobs[1] | /api/jobs/2 | | + | jobs[2] | /api/jobs/3 | | + | name | Mandate 2005/2006 | | + | startAt | 2005-06-25T16:43:30+00:00 | | - Scenario: Create a new resource - # With valid data + @crud + Scenario: It should be possible to create a new mandate + Given the fixtures file "mandate/job-president.yml" is loaded When I send a POST request to "/api/mandates" with body: """ { - "name": "Dummy date", - "endAt": "2010-01-21T23:00:00+00:00", - "startAt": "2009-01-26T23:00:00+00:00" + "name": "My Mandate", + "startAt": "2005-08-15T15:52:01+00:00", + "endAt": "2005-12-15T15:52:01+00:00", + "jobs": [ "/api/jobs/1" ] } """ - Then the response status code should be 201 - And the JSON node "jobs" should have 0 element - Then the JSON response should have the following nodes: + And the response status code should be 201 + And the response should be in JSON-LD + And the JSON node "jobs" should have 1 element + And the JSON response should have the following nodes: | node | value | type  | | @context | /api/contexts/Mandate | | - | @id | /api/mandates/13 | | + | @id | /api/mandates/1 | | | @type | Mandate | | - | endAt | 2010-01-21T23:00:00+00:00 | string  | + | name | My Mandate | | + | startAt | 2005-08-15T15:52:01+00:00 | string | + | endAt | 2005-12-15T15:52:01+00:00 | string  | | jobs | | array | - | name | Dummy date | | - | startAt | 2009-01-26T23:00:00+00:00 | string | + | jobs[0] | /api/jobs/1 | array | - # Check if the resource has been properly persisted - When I send a GET request to "/api/mandates/13" + When I send a GET request to "/api/mandates/1" Then the response status code should be 200 - And the JSON node "jobs" should have 0 element + And the response should be in JSON-LD + And the JSON node "jobs" should have 1 element Then the JSON response should have the following nodes: - | node | value | type  | - | @context | /api/contexts/Mandate | | - | @id | /api/mandates/1 | | - | @type | Mandate | | - | endAt | 2007-03-18T10:25:58+00:00 |   | - | jobs | | array | - | name | Mandate 2005/2007 | | - | startAt | 2005-11-27T17:41:35+00:00 | | + | node | value | type  | + | @context | /api/contexts/Mandate | | + | @id | /api/mandates/1 | | + | @type | Mandate | | + | name | My Mandate | | + | startAt | 2005-08-15T15:52:01+00:00 | string | + | endAt | 2005-12-15T15:52:01+00:00 | string  | + | jobs | | array | + | jobs[0] | /api/jobs/1 | array | - # Post again with some other valid values but no name - # Expect to have a name generated + @crud + Scenario: A mandate name is automatically picked up if none is given when creating a new mandate When I send a POST request to "/api/mandates" with body: """ { @@ -80,11 +93,12 @@ Feature: Mandates management "startAt": "2050-01-26" } """ - Then the response status code should be 200 + Then the response status code should be 201 And I should get a resource page with the context "/api/contexts/Mandate" And the JSON node "name" should be equal to "Mandate 2050/2051" - # Test validation rules + @crud + Scenario: Data send for creating a mandate should be validated When I send a POST request to "/api/mandates" with body: """ { @@ -105,40 +119,3 @@ Feature: Mandates management | violations[1] | | object | | violations[1]->propertyPath | startAt | | | violations[1]->message | Cette valeur ne doit pas être nulle. | | - - - -# Scenario: If one list all the mandates, there it at least one mandate: the current one. -# #TODO -# -# Scenario: A mandate may have users. -# #TODO -# -# Scenario: It should be possible to list all the mandates. -# #TODO -# -# Scenario: It should be possible to list all the members of a given mandate. -# #TODO -# -# Scenario: It should not be possible to create a mandate unless it starts at the current year. The ending date -# should then be during the next year and may be omitted. -# #TODO -# -# Scenario: If not ending date is given for a mandate, it ends at the end of the next year. -# #TODO -# -# Scenario: If no new mandate has be created, a new one is automatically created and keep all the admin users as if -# they have a new mandate. -# #TODO -# -# Scenario: It should not be possible to delete a mandate. -# #TODO -# -# Scenario: A new member can be added to a mandate even if the mandate already ended. -# #TODO -# -# Scenario: A member may be deleted from a mandate even if the mandate already ended. -# #TODO -# -# Scenario: Once a mandate created, the dates may change but must be of the same year. -# #TODO diff --git a/features/fixtures/ORM/mandate/collection.yml b/features/fixtures/ORM/mandate/collection.yml new file mode 100644 index 000000000..2159d4042 --- /dev/null +++ b/features/fixtures/ORM/mandate/collection.yml @@ -0,0 +1,39 @@ +ApiBundle\Entity\Mandate: + mandate_2005: + startAt: + endAt: + jobs: [ @job_president, @job_pr, @job__1 ] + + mandate_2006: + startAt: + endAt: + +ApiBundle\Entity\Job: + job (template): + title: + abbreviation: + enabled: + #user: Should be kept unset since because is set in the user entity + #mandate: Should be kept unset since because is set in the mandate entity + + job_president (extends job): + title: President + abbreviation: PR + enabled: true + + job_pr (extends job): + abbreviation: PR + + job__{1..9} (extends job): {} + +ApiBundle\Entity\User: + user_president: + username: president.tendiserp + fullname: Président TENDISERP + email: president.tendiserp@incipio.fr + roles: [ ROLE_ADMIN ] + plainPassword: guest + enabled: true + jobs: [ @job_president ] + studentConvention: ~ + types: diff --git a/features/fixtures/ORM/mandate/job-president.yml b/features/fixtures/ORM/mandate/job-president.yml new file mode 100644 index 000000000..7c09a6d55 --- /dev/null +++ b/features/fixtures/ORM/mandate/job-president.yml @@ -0,0 +1,5 @@ +ApiBundle\Entity\Job: + job_president: + title: President + abbreviation: PR + enabled: true diff --git a/src/ApiBundle/DataFixtures/Faker/Provider/DateTimeProvider.php b/src/ApiBundle/DataFixtures/Faker/Provider/DateTimeProvider.php new file mode 100644 index 000000000..2239f56f9 --- /dev/null +++ b/src/ApiBundle/DataFixtures/Faker/Provider/DateTimeProvider.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiBundle\DataFixtures\Faker\Provider; + +/** + * Extends {@see \Faker\Provider\DateTime}. As all method are static does not literally extend the class to avoid + * useless overhead. + * + * @author Théo FIDRY + */ +class DateTimeProvider +{ + /** + * Parses a string into a new DateTime object according to the specified format. + * + * @param string $format Format accepted by date(). + * @param string $time String representing the time. + * + * @return \DateTime + * + * @link http://php.net/manual/en/datetime.createfromformat.php + */ + public static function dateTimeFromFormat($format, $time) + { + return \DateTime::createFromFormat($format, $time); + } +} diff --git a/src/ApiBundle/DataFixtures/Faker/Provider/MandateProvider.php b/src/ApiBundle/DataFixtures/Faker/Provider/MandateProvider.php index 52933aa51..4d817088f 100644 --- a/src/ApiBundle/DataFixtures/Faker/Provider/MandateProvider.php +++ b/src/ApiBundle/DataFixtures/Faker/Provider/MandateProvider.php @@ -11,7 +11,7 @@ namespace ApiBundle\DataFixtures\Faker\Provider; -use Faker\Provider\DateTime as DateTimeProvider; +use Faker\Provider\DateTime as FakerDateTimeProvider; /** * Faker provider for mandates. @@ -20,7 +20,7 @@ * * @author Théo FIDRY */ -class MandateProvider extends DateTimeProvider +class MandateProvider extends FakerDateTimeProvider { /** * Generate a datetime starting from the date given and on a period going from 3 month to 2 years. diff --git a/src/ApiBundle/Doctrine/DBAL/Type/UTCDateTimeType.php b/src/ApiBundle/Doctrine/DBAL/Type/UTCDateTimeType.php index ef9c69c2b..7654adaa9 100644 --- a/src/ApiBundle/Doctrine/DBAL/Type/UTCDateTimeType.php +++ b/src/ApiBundle/Doctrine/DBAL/Type/UTCDateTimeType.php @@ -12,6 +12,7 @@ namespace ApiBundle\Doctrine\DBAL\Type; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\DateTimeType; /** @@ -40,10 +41,28 @@ public function convertToDatabaseValue($phpValue, AbstractPlatform $platform) */ public function convertToPHPValue($databaseValue, AbstractPlatform $platform) { - $phpValue = parent::convertToPHPValue($databaseValue, $platform); + if (null === $databaseValue || $databaseValue instanceof \DateTime) { + return $databaseValue; + } - if ($phpValue instanceof \DateTime) { - $phpValue->setTimeZone(new \DateTimeZone('UTC')); + // The changed part is the following bloc where we put the DateTimeZone as the third argument rather than + // relying on the local timezone + $phpValue = \DateTime::createFromFormat( + $platform->getDateTimeFormatString(), + $databaseValue, + new \DateTimeZone('UTC') + ); + + if (false === $phpValue) { + $phpValue = date_create($databaseValue); + } + + if (false === $phpValue) { + throw ConversionException::conversionFailedFormat( + $databaseValue, + $this->getName(), + $platform->getDateTimeFormatString() + ); } return $phpValue; diff --git a/src/ApiBundle/Entity/Mandate.php b/src/ApiBundle/Entity/Mandate.php index 33d8c3767..12c50da3c 100644 --- a/src/ApiBundle/Entity/Mandate.php +++ b/src/ApiBundle/Entity/Mandate.php @@ -110,18 +110,42 @@ public function getEndAt() return $this->endAt; } + /** + * @param Job[] $jobs + * + * @return $this + * + * @throws \InvalidArgumentException If jobs type is incorrect + */ + public function setJobs($jobs) + { + $this->jobs = new ArrayCollection(); + foreach ($jobs as $job) { + $this->addJob($job); + } + + return $this; + } + /** * Adds Job. Will automatically update job's mandate too. * * @param Job $job * * @return $this + * + * @throws \InvalidArgumentException If job type is incorrect */ - public function addJob(Job $job) + public function addJob($job) { + if (false === $job instanceof Job) { + throw new \InvalidArgumentException('Unexpected type.'); + } + // Check for duplication if (false === $this->jobs->contains($job)) { $this->jobs->add($job); + $this->jobs = new ArrayCollection(array_values($this->jobs->toArray())); } // Ensure the relation is bidirectional diff --git a/src/ApiBundle/Resources/config/services.yml b/src/ApiBundle/Resources/config/services.yml index ed391d64e..86c6713b3 100644 --- a/src/ApiBundle/Resources/config/services.yml +++ b/src/ApiBundle/Resources/config/services.yml @@ -31,6 +31,10 @@ services: # # Faker's providers # + faker.provider.datetime: + class: ApiBundle\DataFixtures\Faker\Provider\DateTimeProvider + tags: [ { name: hautelook_alice.faker.provider } ] + faker.provider.job: class: ApiBundle\DataFixtures\Faker\Provider\JobProvider tags: [ { name: hautelook_alice.faker.provider } ] diff --git a/tests/ApiBundle/Tests/Doctrine/DBAL/Type/UTCDateTimeTypeTest.php b/tests/ApiBundle/Tests/Doctrine/DBAL/Type/UTCDateTimeTypeTest.php index 2fa22f0f0..66ec79410 100644 --- a/tests/ApiBundle/Tests/Doctrine/DBAL/Type/UTCDateTimeTypeTest.php +++ b/tests/ApiBundle/Tests/Doctrine/DBAL/Type/UTCDateTimeTypeTest.php @@ -54,19 +54,18 @@ public function testConvertToDatabaseValue($date) * @testbox Convert to PHP value * * @covers ::convertToPHPValue - * @dataProvider databaseValueProvider */ - public function testConvertToPHPValue($databaseDate) + public function testConvertToPHPValue() { $platform = $this->prophesize(AbstractPlatform::class); - $platform->getDateTimeFormatString()->willReturn('e'); - $actual = $this->type->convertToPHPValue($databaseDate, $platform->reveal()); + $platform->getDateTimeFormatString()->willReturn('Y-m-d H:i:s'); + + $phpValue = $this->type->convertToPHPValue('2012-02-10 15:10:50', $platform->reveal()); + $this->assertEquals('2012-02-10 15:10:50', $phpValue->format('Y-m-d H:i:s')); + $this->assertEquals('UTC', $phpValue->getTimezone()->getName()); - if (null === $databaseDate) { - $this->assertNull($actual); - } else { - $this->assertEquals('UTC', $actual->format('e')); - } + $phpValue = $this->type->convertToPHPValue(null, $platform->reveal()); + $this->assertNull($phpValue); } public function phpValueProvider() @@ -82,9 +81,7 @@ public function phpValueProvider() public function databaseValueProvider() { return [ - [\DateTime::createFromFormat('Y-m-d H:i:s', '2012-02-10 15:10:50', new \DateTimeZone('UTC'))], - [\DateTime::createFromFormat('Y-m-d H:i:s', '2012-02-10 15:10:50', new \DateTimeZone('Europe/Paris'))], - [\DateTime::createFromFormat('Y-m-d H:i:s', '2012-02-10 15:10:50', new \DateTimeZone('Europe/Zurich'))], + ['2012-02-10 15:10:50'], [null], ]; }