diff --git a/CHANGELOG.md b/CHANGELOG.md index e8336fd8..9a9592f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Follow [upgrade instructions](UPGRADING.md#from-v7-to-v8) to migrate code & data - ([#91]) Added `rate DECIMIAL(4, 2)` column to `love_reactions` db table - ([#91]) Added ability to call `Reacter::reactTo` with already reacted reactant, same reaction type, but only `rate` differs - ([#91]) Added `Cog\Contracts\Love\Reaction\Exceptions\RateOutOfRange` exception +- ([#100]) Added `Cog\Contracts\Love\Reaction\Exceptions\RateInvalid` exception - ([#96]) Added progress bar to `love:recount` Artisan command ### Changed @@ -396,3 +397,4 @@ Follow [upgrade instructions](UPGRADING.md#from-v5-to-v6) to migrate database to [#91]: https://github.com/cybercog/laravel-love/pull/91 [#96]: https://github.com/cybercog/laravel-love/pull/96 [#99]: https://github.com/cybercog/laravel-love/pull/99 +[#100]: https://github.com/cybercog/laravel-love/pull/100 diff --git a/contracts/Reaction/Exceptions/RateInvalid.php b/contracts/Reaction/Exceptions/RateInvalid.php new file mode 100644 index 00000000..63599633 --- /dev/null +++ b/contracts/Reaction/Exceptions/RateInvalid.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Cog\Contracts\Love\Reaction\Exceptions; + +use Cog\Contracts\Love\Exceptions\LoveThrowable; +use UnexpectedValueException; + +final class RateInvalid extends UnexpectedValueException implements + LoveThrowable +{ + public static function withSameValue(float $rate): self + { + return new self(sprintf( + 'Invalid Reaction rate: `%s`. Can not change to same value.', + $rate + )); + } +} diff --git a/contracts/Reaction/Models/Reaction.php b/contracts/Reaction/Models/Reaction.php index 9994fe9b..552790d6 100644 --- a/contracts/Reaction/Models/Reaction.php +++ b/contracts/Reaction/Models/Reaction.php @@ -42,4 +42,6 @@ public function isNotToReactant(Reactant $reactant): bool; public function isByReacter(Reacter $reacter): bool; public function isNotByReacter(Reacter $reacter): bool; + + public function changeRate(float $rate): void; } diff --git a/src/Reacter/Models/Reacter.php b/src/Reacter/Models/Reacter.php index c057fa74..8c3fd871 100644 --- a/src/Reacter/Models/Reacter.php +++ b/src/Reacter/Models/Reacter.php @@ -94,15 +94,7 @@ public function reactTo( ); } - if ($reaction->getRate() === $rate) { - throw new ReactionAlreadyExists( - sprintf('Reaction of type `%s` with `%s` rate already exists.', $reactionType->getName(), $rate) - ); - } - - $reaction->update([ - 'rate' => $rate, - ]); + $reaction->changeRate($rate); } public function unreactTo( diff --git a/src/Reaction/Models/Reaction.php b/src/Reaction/Models/Reaction.php index f8d1fa52..b03d05cf 100644 --- a/src/Reaction/Models/Reaction.php +++ b/src/Reaction/Models/Reaction.php @@ -16,6 +16,7 @@ use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantContract; use Cog\Contracts\Love\Reacter\Models\Reacter as ReacterContract; use Cog\Contracts\Love\Reaction\Exceptions\RateOutOfRange; +use Cog\Contracts\Love\Reaction\Exceptions\RateInvalid; use Cog\Contracts\Love\Reaction\Models\Reaction as ReactionContract; use Cog\Contracts\Love\ReactionType\Models\ReactionType as ReactionTypeContract; use Cog\Laravel\Love\Reactant\Models\Reactant; @@ -140,4 +141,15 @@ public function isNotByReacter( ): bool { return !$this->isByReacter($reacter); } + + public function changeRate( + float $rate + ): void { + if ($this->getRate() === $rate) { + throw RateInvalid::withSameValue($rate); + } + + $this->setAttribute('rate', $rate); + $this->save(); + } } diff --git a/tests/Unit/Reacter/Facades/ReacterTest.php b/tests/Unit/Reacter/Facades/ReacterTest.php index 55d1ab2b..0b5479c8 100644 --- a/tests/Unit/Reacter/Facades/ReacterTest.php +++ b/tests/Unit/Reacter/Facades/ReacterTest.php @@ -14,6 +14,7 @@ namespace Cog\Tests\Laravel\Love\Unit\Reacter\Facades; use Cog\Contracts\Love\Reactant\Exceptions\ReactantInvalid; +use Cog\Contracts\Love\Reaction\Exceptions\RateInvalid; use Cog\Contracts\Love\Reaction\Exceptions\ReactionAlreadyExists; use Cog\Contracts\Love\Reaction\Exceptions\ReactionNotExists; use Cog\Contracts\Love\ReactionType\Exceptions\ReactionTypeInvalid; @@ -105,6 +106,20 @@ public function it_can_change_reaction_rate_with_react_to_when_reaction_already_ $this->assertSame(2.0, $assertReaction->rate); } + /** @test */ + public function it_throws_rate_invalid_on_react_to_when_reaction_already_exists_with_same_rate(): void + { + $this->expectException(RateInvalid::class); + + $reacter = factory(Reacter::class)->create(); + $reacterFacade = new ReacterFacade($reacter); + $reactable = factory(User::class)->create(); + $reactionType = factory(ReactionType::class)->create(); + + $reacterFacade->reactTo($reactable, $reactionType->getName(), 2.0); + $reacterFacade->reactTo($reactable, $reactionType->getName(), 2.0); + } + /** @test */ public function it_throws_reaction_type_invalid_on_react_to_when_reaction_type_not_exist(): void { diff --git a/tests/Unit/Reacter/Models/ReacterTest.php b/tests/Unit/Reacter/Models/ReacterTest.php index c9ae515e..4d3b3651 100644 --- a/tests/Unit/Reacter/Models/ReacterTest.php +++ b/tests/Unit/Reacter/Models/ReacterTest.php @@ -15,6 +15,7 @@ use Cog\Contracts\Love\Reactant\Exceptions\ReactantInvalid; use Cog\Contracts\Love\Reacter\Exceptions\NotAssignedToReacterable; +use Cog\Contracts\Love\Reaction\Exceptions\RateInvalid; use Cog\Contracts\Love\Reaction\Exceptions\ReactionAlreadyExists; use Cog\Contracts\Love\Reaction\Exceptions\ReactionNotExists; use Cog\Laravel\Love\Reactant\Models\NullReactant; @@ -212,6 +213,19 @@ public function it_can_change_reaction_rate_with_react_to_when_reaction_already_ $this->assertSame(2.0, $assertReaction->rate); } + /** @test */ + public function it_throws_rate_invalid_on_react_to_when_reaction_already_exists_with_same_rate(): void + { + $this->expectException(RateInvalid::class); + + $reactionType = factory(ReactionType::class)->create(); + $reacter = factory(Reacter::class)->create(); + $reactant = factory(Reactant::class)->create(); + + $reacter->reactTo($reactant, $reactionType, 2.0); + $reacter->reactTo($reactant, $reactionType, 2.0); + } + /** @test */ public function it_throws_reactant_invalid_on_react_to_when_reactant_is_null_object(): void { diff --git a/tests/Unit/Reaction/Models/ReactionTest.php b/tests/Unit/Reaction/Models/ReactionTest.php index bdc287f7..a35899bf 100644 --- a/tests/Unit/Reaction/Models/ReactionTest.php +++ b/tests/Unit/Reaction/Models/ReactionTest.php @@ -14,6 +14,7 @@ namespace Cog\Tests\Laravel\Love\Unit\Reaction\Models; use Cog\Contracts\Love\Reaction\Exceptions\RateOutOfRange; +use Cog\Contracts\Love\Reaction\Exceptions\RateInvalid; use Cog\Laravel\Love\Reactant\Models\NullReactant; use Cog\Laravel\Love\Reactant\Models\Reactant; use Cog\Laravel\Love\Reacter\Models\NullReacter; @@ -444,4 +445,52 @@ public function it_can_check_if_reaction_is_not_by_reacter_when_reacter_is_null_ $this->assertTrue($true); } + + /** @test */ + public function it_can_change_rate(): void + { + $reaction = factory(Reaction::class)->create([ + 'rate' => 1.0, + ]); + + $reaction->changeRate(2.0); + + $this->assertSame(2.0, $reaction->rate); + } + + /** @test */ + public function it_throws_rate_out_of_range_on_change_rate_with_overflow_value(): void + { + $this->expectException(RateOutOfRange::class); + + $reaction = factory(Reaction::class)->create([ + 'rate' => 1.0, + ]); + + $reaction->changeRate(Reaction::RATE_MAX + 0.01); + } + + /** @test */ + public function it_throws_rate_out_of_range_on_change_rate_with_underflow_value(): void + { + $this->expectException(RateOutOfRange::class); + + $reaction = factory(Reaction::class)->create([ + 'rate' => 1.0, + ]); + + $reaction->changeRate(Reaction::RATE_MIN - 0.01); + } + + /** @test */ + public function it_throws_rate_invalid_on_change_rate_with_same_value(): void + { + $this->expectException(RateInvalid::class); + + $reaction = factory(Reaction::class)->create([ + 'rate' => 1.0, + ]); + + $reaction->changeRate(1.0); + } }