diff --git a/src/Cheeper/Chapter6/Application/Author/Command/WithDomainEvents/FollowCommandHandler.php b/src/Cheeper/Chapter6/Application/Author/Command/WithDomainEvents/FollowCommandHandler.php index 5cca266..cf7231f 100644 --- a/src/Cheeper/Chapter6/Application/Author/Command/WithDomainEvents/FollowCommandHandler.php +++ b/src/Cheeper/Chapter6/Application/Author/Command/WithDomainEvents/FollowCommandHandler.php @@ -58,10 +58,7 @@ final class FollowCommandHandler $follow = $this->followRepository->ofFromAuthorIdAndToAuthorId($fromAuthorId, $toAuthorId); if (null !== $follow) { - throw FollowDoesAlreadyExistException::withFromAuthorIdToAuthorId( - $fromAuthorId, - $toAuthorId - ); + throw FollowDoesAlreadyExistException::withFromAuthorIdToAuthorId($fromAuthorId, $toAuthorId); } } } diff --git a/src/Cheeper/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandler.php b/src/Cheeper/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandler.php index a1011fa..26ecf55 100644 --- a/src/Cheeper/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandler.php +++ b/src/Cheeper/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandler.php @@ -6,11 +6,11 @@ namespace Cheeper\Chapter6\Application\Author\Command\WithIdempotency; use Cheeper\AllChapters\DomainModel\Author\AuthorDoesNotExist; use Cheeper\AllChapters\DomainModel\Author\AuthorId; +use Cheeper\Chapter4\Application\EventBus; use Cheeper\Chapter4\DomainModel\Author\Author; use Cheeper\Chapter4\DomainModel\Author\AuthorRepository; use Cheeper\Chapter4\DomainModel\Follow\FollowRepository; use Cheeper\Chapter6\Application\Author\Command\FollowCommand; -use Cheeper\Chapter6\Application\EventBus; final class FollowCommandHandler { @@ -32,6 +32,7 @@ final class FollowCommandHandler $fromAuthor = $this->tryToFindTheAuthorOfId($fromAuthorId); $toAuthor = $this->tryToFindTheAuthorOfId($toAuthorId); + // Idempotency check $follow = $this->follows->ofFromAuthorIdAndToAuthorId($fromAuthorId, $toAuthorId); if (null !== $follow) { return; diff --git a/tests/Cheeper/Tests/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandlerTest.php b/tests/Cheeper/Tests/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandlerTest.php new file mode 100644 index 0000000..56c31bc --- /dev/null +++ b/tests/Cheeper/Tests/Chapter6/Application/Author/Command/WithIdempotency/FollowCommandHandlerTest.php @@ -0,0 +1,178 @@ +authorRepository = new InMemoryAuthorRepository(); + $this->followRepository = new InMemoryFollowRepository(); + $this->eventBus = new InMemoryEventBus(); + } + + /** @test */ + public function givenTwoNonExistingAuthorsWhenFollowingOneToAnotherOneNonExistingAuthorExceptionShouldBeThrown(): void + { + $this->expectException(AuthorDoesNotExist::class); + + $this->runHandler(self::AUTHOR_ID_FROM, self::AUTHOR_ID_TO); + } + + /** @test */ + public function givenOnlyFromAuthorIsNonExistingWhenFollowingOneToAnotherOneNonExistingAuthorExceptionShouldBeThrown(): void + { + $this->expectException(AuthorDoesNotExist::class); + + $this->authorRepository->add($this->buildAuthor( + self::AUTHOR_ID_TO, + self::USERNAME_CARLOS, + self::EMAIL_CARLOS + )); + + $this->runHandler(self::AUTHOR_ID_FROM, self::AUTHOR_ID_TO); + } + + /** @test */ + public function givenOnlyToAuthorIsNonExistingWhenFollowingOneToAnotherOneNonExistingAuthorExceptionShouldBeThrown(): void + { + $this->expectException(AuthorDoesNotExist::class); + + $this->authorRepository->add( + $this->buildAuthor( + self::AUTHOR_ID_FROM, + self::USERNAME_CARLOS, + self::EMAIL_CARLOS + ) + ); + + $this->runHandler(self::AUTHOR_ID_FROM, self::AUTHOR_ID_TO); + } + + /** @test */ + public function givenTwoAuthorsFollowingOneToAnotherAlreadyWhenTryingToFollowAgainNothingShouldBeHappening(): void + { + $fromAuthorId = self::AUTHOR_ID_FROM; + $toAuthorId = self::AUTHOR_ID_TO; + $followId = '51d8ffff-123f-78e1-48fc-f8b513391a0e'; + + $fromAuthor = Author::signUp( + AuthorId::fromString($fromAuthorId), + UserName::pick(self::USERNAME_CARLOS), + EmailAddress::from(self::EMAIL_CARLOS) + ); + + $toAuthor = Author::signUp( + AuthorId::fromString($toAuthorId), + UserName::pick(self::USERNAME_KEYVAN), + EmailAddress::from(self::EMAIL_KEYVAN) + ); + + $this->authorRepository->add($fromAuthor); + $this->authorRepository->add($toAuthor); + $this->followRepository->add( + Follow::fromAuthorToAuthor( + FollowId::fromString($followId), + $fromAuthor->authorId(), + $toAuthor->authorId(), + ) + ); + + $this->runHandler($fromAuthorId, $toAuthorId); + + $this->assertCount( + 1, + $this->followRepository->collection + ); + + $events = $this->eventBus->events(); + $this->assertCount(0, $events); + } + + /** @test */ + public function givenTwoAuthorsNotFollowingOneToAnotherWhenFollowingThenTheFollowShouldHappen(): void + { + $fromAuthorId = self::AUTHOR_ID_FROM; + $toAuthorId = self::AUTHOR_ID_TO; + + $fromAuthor = Author::signUp( + AuthorId::fromString($fromAuthorId), + UserName::pick(self::USERNAME_CARLOS), + EmailAddress::from(self::EMAIL_CARLOS) + ); + + $toAuthor = Author::signUp( + AuthorId::fromString($toAuthorId), + UserName::pick(self::USERNAME_KEYVAN), + EmailAddress::from(self::EMAIL_KEYVAN) + ); + + $this->authorRepository->add($fromAuthor); + $this->authorRepository->add($toAuthor); + + $this->runHandler($fromAuthorId, $toAuthorId); + + $this->assertCount( + 1, + $this->followRepository->collection + ); + + $events = $this->eventBus->events(); + $this->assertCount(1, $events); + $this->assertSame(AuthorFollowed::class, $events[0]::class); + } + + private function buildAuthor(string $toAuthorId, string $userName, string $email): Author + { + return Author::signUp( + AuthorId::fromString($toAuthorId), + UserName::pick($userName), + EmailAddress::from($email) + ); + } + + private function runHandler(string $fromAuthorId, string $toAuthorId): void + { + $this->eventBus->reset(); + + (new FollowCommandHandler( + $this->authorRepository, + $this->followRepository, + $this->eventBus + ))( + FollowCommand::fromAuthorIdToAuthorId( + $fromAuthorId, + $toAuthorId + ) + ); + } +}