From a382782a6faa453d261a4aa1ac16e88311429ec4 Mon Sep 17 00:00:00 2001 From: "Nikola Svitlica a.k.a. TheCelavi" Date: Mon, 22 May 2017 09:23:01 +0200 Subject: [PATCH] Added better validation on DTO --- .../Bundle/ExchangeRate/Form/Dto/Rate.php | 66 +++++++++- .../Resources/config/services/validator.xml | 7 ++ .../runopencode_exchange_rate.en.yml | 4 +- .../Validator/Constraints/BaseCurrency.php | 42 +++++++ .../Constraints/BaseCurrencyValidator.php | 56 +++++++++ .../Validator/Constraints/ExchangeRate.php | 6 +- .../Constraints/ExchangeRateValidator.php | 4 + .../Constraints/BaseCurrencyTest.php | 32 +++++ .../Constraints/BaseCurrencyValidatorTest.php | 113 ++++++++++++++++++ .../Constraints/ExchangeRateValidatorTest.php | 13 +- 10 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/BaseCurrency.php create mode 100644 src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/BaseCurrencyValidator.php create mode 100644 test/Validator/Constraints/BaseCurrencyTest.php create mode 100644 test/Validator/Constraints/BaseCurrencyValidatorTest.php diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Form/Dto/Rate.php b/src/RunOpenCode/Bundle/ExchangeRate/Form/Dto/Rate.php index ea54991..246caa2 100644 --- a/src/RunOpenCode/Bundle/ExchangeRate/Form/Dto/Rate.php +++ b/src/RunOpenCode/Bundle/ExchangeRate/Form/Dto/Rate.php @@ -12,21 +12,29 @@ use RunOpenCode\ExchangeRate\Contract\RateInterface; use RunOpenCode\ExchangeRate\Model\Rate as ExchangeRate; use Symfony\Component\Validator\Constraints as Assert; +use RunOpenCode\Bundle\ExchangeRate\Validator\Constraints as ExchangeRateAssert; /** * Class Rate * * @package RunOpenCode\Bundle\ExchangeRate\Form\Dto + * + * @ExchangeRateAssert\ExchangeRate() */ -class Rate +class Rate implements RateInterface { /** * @var string + * + * @Assert\NotBlank() */ private $rate; /** * @var \DateTime + * + * @Assert\NotBlank() + * @Assert\DateTime() */ private $date; @@ -40,6 +48,9 @@ class Rate /** * @var string + * + * @Assert\NotBlank() + * @ExchangeRateAssert\BaseCurrency() */ private $baseCurrencyCode; @@ -130,6 +141,59 @@ public function setBaseCurrencyCode($baseCurrencyCode) return $this; } + /** + * {@inheritdoc} + */ + public function getSourceName() + { + if (false !== strpos($this->getRate(), '.')) { + return explode('.', $this->getRate())[0]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getCurrencyCode() + { + if (false !== strpos($this->getRate(), '.')) { + return explode('.', $this->getRate())[2]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getRateType() + { + if (false !== strpos($this->getRate(), '.')) { + return explode('.', $this->getRate())[1]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getCreatedAt() + { + return null; // unknown + } + + /** + * {@inheritdoc} + */ + public function getModifiedAt() + { + return null; // unknown + } + + /** * Build \RunOpenCode\ExchangeRate\Model\Rate from DTO object. * diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Resources/config/services/validator.xml b/src/RunOpenCode/Bundle/ExchangeRate/Resources/config/services/validator.xml index 174c0eb..56124a4 100644 --- a/src/RunOpenCode/Bundle/ExchangeRate/Resources/config/services/validator.xml +++ b/src/RunOpenCode/Bundle/ExchangeRate/Resources/config/services/validator.xml @@ -9,6 +9,8 @@ RunOpenCode\Bundle\ExchangeRate\Validator\Constraints\ExchangeRateValidator + RunOpenCode\Bundle\ExchangeRate\Validator\Constraints\BaseCurrencyValidator + @@ -18,6 +20,11 @@ + + %runopencode.exchange_rate.base_currency% + + + diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Resources/translations/runopencode_exchange_rate.en.yml b/src/RunOpenCode/Bundle/ExchangeRate/Resources/translations/runopencode_exchange_rate.en.yml index 51ee4bf..7d3fd6b 100644 --- a/src/RunOpenCode/Bundle/ExchangeRate/Resources/translations/runopencode_exchange_rate.en.yml +++ b/src/RunOpenCode/Bundle/ExchangeRate/Resources/translations/runopencode_exchange_rate.en.yml @@ -55,7 +55,9 @@ flash: success: You have successfully modified exchange rate. error.unknown: Could not save exchange rate for unknown reason. Contact administrator. - +validator: + rate.invalid: Unknown rate provided ({{ rate_type }}, {{ currency_code }}, {{ source_name }}). + baseCurrency.invalid: Invalid base currency code "{{ value }}" provided, expected "{{ base_currency_code }}". #exchange_rate: diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/BaseCurrency.php b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/BaseCurrency.php new file mode 100644 index 0000000..b906ccb --- /dev/null +++ b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/BaseCurrency.php @@ -0,0 +1,42 @@ +baseCurrencyCode = $baseCurrencyCode; + } + + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof BaseCurrency) { + throw new InvalidArgumentException(sprintf('Expected instance of "%s", got "%s".', BaseCurrency::class, get_class($constraint))); + } + + if (null === $value) { + return; + } + + if ($this->baseCurrencyCode !== $value) { + + /** + * @var BaseCurrency $constraint + */ + $this->context + ->buildViolation($constraint->message) + ->setParameter('{{ base_currency_code }}', $this->baseCurrencyCode) + ->setParameter('{{ value }}', $value) + ->setTranslationDomain('runopencode_exchange_rate') + ->addViolation(); + } + } +} diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRate.php b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRate.php index 63035ea..5225c4f 100644 --- a/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRate.php +++ b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRate.php @@ -12,13 +12,17 @@ use Symfony\Component\Validator\Constraint; /** + * Class ExchangeRate + * + * @package RunOpenCode\Bundle\ExchangeRate\Validator\Constraints + * * {@inheritdoc} * * @Annotation */ class ExchangeRate extends Constraint { - public $message = 'runopencode.exchange_rate.rate_validator.invalid'; + public $message = 'validator.rate.invalid'; /** * {@inheritdoc} diff --git a/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRateValidator.php b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRateValidator.php index b1ea360..c2fa46a 100644 --- a/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRateValidator.php +++ b/src/RunOpenCode/Bundle/ExchangeRate/Validator/Constraints/ExchangeRateValidator.php @@ -74,6 +74,10 @@ public function validate($rate, Constraint $constraint) */ $this->context ->buildViolation($constraint->message) + ->setParameter('{{ rate_type }}', $rate->getRateType()) + ->setParameter('{{ currency_code }}', $rate->getCurrencyCode()) + ->setParameter('{{ source_name }}', $rate->getSourceName()) + ->setTranslationDomain('runopencode_exchange_rate') ->addViolation(); } } diff --git a/test/Validator/Constraints/BaseCurrencyTest.php b/test/Validator/Constraints/BaseCurrencyTest.php new file mode 100644 index 0000000..77cc5d3 --- /dev/null +++ b/test/Validator/Constraints/BaseCurrencyTest.php @@ -0,0 +1,32 @@ +assertEquals(BaseCurrency::PROPERTY_CONSTRAINT, $constraint->getTargets()); + $this->assertEquals('runopencode.exchange_rate.base_currency_validator', $constraint->validatedBy()); + } +} \ No newline at end of file diff --git a/test/Validator/Constraints/BaseCurrencyValidatorTest.php b/test/Validator/Constraints/BaseCurrencyValidatorTest.php new file mode 100644 index 0000000..626c094 --- /dev/null +++ b/test/Validator/Constraints/BaseCurrencyValidatorTest.php @@ -0,0 +1,113 @@ +validate(null, $this->getMockBuilder(Constraint::class)->setMockClassName('Constraint')->getMock()); + } + + /** + * @test + */ + public function validationPass() + { + $validator = new BaseCurrencyValidator('RSD'); + + $validationContext = $this->getMockBuilder(ExecutionContextInterface::class)->getMock(); + + $validationContext + ->expects($this->never()) + ->method('buildViolation') + ->willReturn($validationContext); + + $validator->initialize($validationContext); + + $validator->validate('RSD', new BaseCurrency()); + } + + /** + * @test + */ + public function validationPassOnNull() + { + $validator = new BaseCurrencyValidator('RSD'); + + $validationContext = $this->getMockBuilder(ExecutionContextInterface::class)->getMock(); + + $validationContext + ->expects($this->never()) + ->method('buildViolation') + ->willReturn($validationContext); + + $validator->initialize($validationContext); + + $validator->validate(null, new BaseCurrency()); + } + + /** + * @test + */ + public function validationFails() + { + $validator = new BaseCurrencyValidator('RSD'); + $constraint = new BaseCurrency(); + + $validationContext = $this->getMockBuilder(ExecutionContextInterface::class)->getMock(); + $validationBuilder = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)->getMock(); + + $validationContext + ->expects($this->exactly(1)) + ->method('buildViolation') + ->with($constraint->message) + ->willReturn($validationBuilder); + + $validationBuilder + ->expects($this->exactly(2)) + ->method('setParameter') + ->willReturn($validationBuilder); + + $validationBuilder + ->expects($this->exactly(1)) + ->method('setTranslationDomain') + ->willReturn($validationBuilder); + + $validationBuilder + ->expects($this->exactly(1)) + ->method('addViolation') + ->willReturn($validationBuilder); + + + $validator->initialize($validationContext); + + $validator->validate('CHF', $constraint); + } +} diff --git a/test/Validator/Constraints/ExchangeRateValidatorTest.php b/test/Validator/Constraints/ExchangeRateValidatorTest.php index b0c3b4c..29273e0 100644 --- a/test/Validator/Constraints/ExchangeRateValidatorTest.php +++ b/test/Validator/Constraints/ExchangeRateValidatorTest.php @@ -125,10 +125,21 @@ public function validationFails() ->with($constraint->message) ->willReturn($validationBuilder); + $validationBuilder + ->expects($this->exactly(3)) + ->method('setParameter') + ->willReturn($validationBuilder); + + $validationBuilder + ->expects($this->exactly(1)) + ->method('setTranslationDomain') + ->willReturn($validationBuilder); + $validationBuilder ->expects($this->exactly(1)) ->method('addViolation') - ->willReturn(null); + ->willReturn($validationBuilder); + $validator->initialize($validationContext);