From 3336847e819cf8b88cf1b5a640aca455b51e3d75 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 5 Apr 2021 23:21:00 +0200 Subject: [PATCH 1/2] Add search Video by term in title use case. --- .../video/application/search/VideosSearcher.scala | 2 ++ .../codely/mooc/video/domain/VideoRepository.scala | 2 ++ .../repository/DoobieMySqlVideoRepository.scala | 6 ++++++ .../application/search/VideosSearcherShould.scala | 10 ++++++++++ .../DoobieMySqlVideoRepositoryShould.scala | 13 +++++++++++++ .../repository/VideoRepositoryMock.scala | 5 +++++ 6 files changed, 38 insertions(+) diff --git a/src/mooc/main/tv/codely/mooc/video/application/search/VideosSearcher.scala b/src/mooc/main/tv/codely/mooc/video/application/search/VideosSearcher.scala index 3b65881..9170f32 100644 --- a/src/mooc/main/tv/codely/mooc/video/application/search/VideosSearcher.scala +++ b/src/mooc/main/tv/codely/mooc/video/application/search/VideosSearcher.scala @@ -6,4 +6,6 @@ import scala.concurrent.Future final class VideosSearcher(repository: VideoRepository) { def all(): Future[Seq[Video]] = repository.all() + + def findByTermInTitle(term: String): Future[Seq[Video]] = repository.findByTermInTitle(term) } diff --git a/src/mooc/main/tv/codely/mooc/video/domain/VideoRepository.scala b/src/mooc/main/tv/codely/mooc/video/domain/VideoRepository.scala index d770462..468279f 100644 --- a/src/mooc/main/tv/codely/mooc/video/domain/VideoRepository.scala +++ b/src/mooc/main/tv/codely/mooc/video/domain/VideoRepository.scala @@ -5,5 +5,7 @@ import scala.concurrent.Future trait VideoRepository { def all(): Future[Seq[Video]] + def findByTermInTitle(term: String): Future[Seq[Video]] + def save(video: Video): Future[Unit] } diff --git a/src/mooc/main/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepository.scala b/src/mooc/main/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepository.scala index 0f2dfdd..2679d43 100644 --- a/src/mooc/main/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepository.scala +++ b/src/mooc/main/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepository.scala @@ -12,6 +12,12 @@ final class DoobieMySqlVideoRepository(db: DoobieDbConnection)(implicit executio override def all(): Future[Seq[Video]] = db.read(sql"SELECT video_id, title, duration_in_seconds, category, creator_id FROM videos".query[Video].to[Seq]) + override def findByTermInTitle(term: String) = + db.read( + sql"SELECT video_id, title, duration_in_seconds, category, creator_id FROM videos WHERE title LIKE CONCAT('%', $term, '%')" + .query[Video] + .to[Seq]) + override def save(video: Video): Future[Unit] = sql"INSERT INTO videos(video_id, title, duration_in_seconds, category, creator_id) VALUES (${video.id}, ${video.title}, ${video.duration}, ${video.category}, ${video.creatorId})".update.run .transact(db.transactor) diff --git a/src/mooc/test/tv/codely/mooc/video/application/search/VideosSearcherShould.scala b/src/mooc/test/tv/codely/mooc/video/application/search/VideosSearcherShould.scala index 32897ce..ff93881 100644 --- a/src/mooc/test/tv/codely/mooc/video/application/search/VideosSearcherShould.scala +++ b/src/mooc/test/tv/codely/mooc/video/application/search/VideosSearcherShould.scala @@ -16,4 +16,14 @@ final class VideosSearcherShould extends UnitTestCase with VideoRepositoryMock { searcher.all().futureValue shouldBe existingVideos } + + "search all videos that contain a term in title" in { + val matchingVideo = VideoMother.random + val anotherMatchingVideo = VideoMother.random + val matchingVideos = Seq(matchingVideo, anotherMatchingVideo) + val term = "Term" + repositoryShouldFindByTermInTitle(term, matchingVideos) + + searcher.findByTermInTitle(term).futureValue shouldBe matchingVideos + } } diff --git a/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepositoryShould.scala b/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepositoryShould.scala index f11e509..2a9ab17 100644 --- a/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepositoryShould.scala +++ b/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/DoobieMySqlVideoRepositoryShould.scala @@ -2,6 +2,7 @@ package tv.codely.mooc.video.infrastructure.repository import tv.codely.mooc.video.VideoIntegrationTestCase import tv.codely.mooc.video.domain.VideoMother +import tv.codely.mooc.video.domain.VideoTitleMother import doobie.implicits._ import org.scalatest.BeforeAndAfterEach @@ -29,4 +30,16 @@ final class DoobieMySqlVideoRepositoryShould extends VideoIntegrationTestCase wi repository.all().futureValue shouldBe videos } + + "search all videos that contain a term in title" in { + val term = "Term" + val videoMatch = VideoMother.random.copy(title = VideoTitleMother("This has a Term inside")) + val videoNoMatch = VideoMother.random.copy(title = VideoTitleMother("This has not a ? inside")) + + repository.save(videoMatch).futureValue + repository.save(videoNoMatch).futureValue + + repository.findByTermInTitle(term).futureValue should contain(videoMatch) + repository.findByTermInTitle(term).futureValue should not contain (videoNoMatch) + } } diff --git a/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/VideoRepositoryMock.scala b/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/VideoRepositoryMock.scala index 6433762..e0d926f 100644 --- a/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/VideoRepositoryMock.scala +++ b/src/mooc/test/tv/codely/mooc/video/infrastructure/repository/VideoRepositoryMock.scala @@ -20,4 +20,9 @@ protected[video] trait VideoRepositoryMock extends MockFactory { (repository.all _) .expects() .returning(Future.successful(videos)) + + protected def repositoryShouldFindByTermInTitle(term: String, videos: Seq[Video]): Unit = + (repository.findByTermInTitle _) + .expects(term) + .returning(Future.successful(videos)) } From 7331c7caeac1788b567a7f57b431db442eddd2df Mon Sep 17 00:00:00 2001 From: David Date: Wed, 7 Apr 2021 20:26:40 +0200 Subject: [PATCH 2/2] Log INFO message on video creation. --- app/main/tv/codely/mooc/api/MoocApiApp.scala | 2 +- app/test/tv/codely/HttpSpec.scala | 3 ++- .../video/application/create/VideoCreator.scala | 4 +++- .../VideoModuleDependencyContainer.scala | 6 ++++-- .../mooc/video/VideoIntegrationTestCase.scala | 2 +- .../application/create/VideoCreatorShould.scala | 7 +++++-- .../infrastructure/logger/LoggerMock.scala | 16 ++++++++++++++++ 7 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/shared/test/tv/codely/shared/infrastructure/logger/LoggerMock.scala diff --git a/app/main/tv/codely/mooc/api/MoocApiApp.scala b/app/main/tv/codely/mooc/api/MoocApiApp.scala index d5fdb87..ef4d53c 100644 --- a/app/main/tv/codely/mooc/api/MoocApiApp.scala +++ b/app/main/tv/codely/mooc/api/MoocApiApp.scala @@ -33,7 +33,7 @@ object MoocApiApp { val container = new EntryPointDependencyContainer( new UserModuleDependencyContainer(sharedDependencies.doobieDbConnection, sharedDependencies.messagePublisher), - new VideoModuleDependencyContainer(sharedDependencies.doobieDbConnection, sharedDependencies.messagePublisher) + new VideoModuleDependencyContainer(sharedDependencies.doobieDbConnection, sharedDependencies.messagePublisher, sharedDependencies.logger) ) val routes = new Routes(container) diff --git a/app/test/tv/codely/HttpSpec.scala b/app/test/tv/codely/HttpSpec.scala index 19e2f99..db02e2c 100644 --- a/app/test/tv/codely/HttpSpec.scala +++ b/app/test/tv/codely/HttpSpec.scala @@ -28,7 +28,8 @@ abstract class HttpSpec extends WordSpec with Matchers with ScalaFutures with Sc ) protected val videoDependencies = new VideoModuleDependencyContainer( sharedDependencies.doobieDbConnection, - sharedDependencies.messagePublisher + sharedDependencies.messagePublisher, + sharedDependencies.logger )(sharedDependencies.executionContext) private val routes = new Routes(new EntryPointDependencyContainer(userDependencies, videoDependencies)) diff --git a/src/mooc/main/tv/codely/mooc/video/application/create/VideoCreator.scala b/src/mooc/main/tv/codely/mooc/video/application/create/VideoCreator.scala index da8854f..d013648 100644 --- a/src/mooc/main/tv/codely/mooc/video/application/create/VideoCreator.scala +++ b/src/mooc/main/tv/codely/mooc/video/application/create/VideoCreator.scala @@ -4,8 +4,9 @@ import tv.codely.mooc.shared.domain.user.UserId import tv.codely.mooc.shared.infrastructure.marshaller.DomainEventsMarshaller.MessageMarshaller import tv.codely.mooc.video.domain._ import tv.codely.shared.domain.bus.MessagePublisher +import tv.codely.shared.domain.logger.Logger -final class VideoCreator(repository: VideoRepository, publisher: MessagePublisher) { +final class VideoCreator(repository: VideoRepository, publisher: MessagePublisher, logger: Logger) { def create( id: VideoId, title: VideoTitle, @@ -17,6 +18,7 @@ final class VideoCreator(repository: VideoRepository, publisher: MessagePublishe repository.save(video) + logger.info("Video created") publisher.publish(VideoCreated(video))(MessageMarshaller) } } diff --git a/src/mooc/main/tv/codely/mooc/video/infrastructure/dependency_injection/VideoModuleDependencyContainer.scala b/src/mooc/main/tv/codely/mooc/video/infrastructure/dependency_injection/VideoModuleDependencyContainer.scala index 26ba3d5..d06f4b1 100644 --- a/src/mooc/main/tv/codely/mooc/video/infrastructure/dependency_injection/VideoModuleDependencyContainer.scala +++ b/src/mooc/main/tv/codely/mooc/video/infrastructure/dependency_injection/VideoModuleDependencyContainer.scala @@ -5,16 +5,18 @@ import tv.codely.mooc.video.application.create.VideoCreator import tv.codely.mooc.video.application.search.VideosSearcher import tv.codely.mooc.video.domain.VideoRepository import tv.codely.mooc.video.infrastructure.repository.DoobieMySqlVideoRepository +import tv.codely.shared.domain.logger.Logger import scala.concurrent.ExecutionContext import tv.codely.shared.domain.bus.MessagePublisher final class VideoModuleDependencyContainer( doobieDbConnection: DoobieDbConnection, - messagePublisher: MessagePublisher + messagePublisher: MessagePublisher, + logger: Logger, )(implicit executionContext: ExecutionContext) { val repository: VideoRepository = new DoobieMySqlVideoRepository(doobieDbConnection) val videosSearcher: VideosSearcher = new VideosSearcher(repository) - val videoCreator: VideoCreator = new VideoCreator(repository, messagePublisher) + val videoCreator: VideoCreator = new VideoCreator(repository, messagePublisher, logger) } diff --git a/src/mooc/test/tv/codely/mooc/video/VideoIntegrationTestCase.scala b/src/mooc/test/tv/codely/mooc/video/VideoIntegrationTestCase.scala index 11dafce..c31d40c 100644 --- a/src/mooc/test/tv/codely/mooc/video/VideoIntegrationTestCase.scala +++ b/src/mooc/test/tv/codely/mooc/video/VideoIntegrationTestCase.scala @@ -5,7 +5,7 @@ import tv.codely.mooc.video.infrastructure.dependency_injection.VideoModuleDepen import tv.codely.shared.infrastructure.integration.IntegrationTestCase protected[video] trait VideoIntegrationTestCase extends IntegrationTestCase { - private val container = new VideoModuleDependencyContainer(doobieDbConnection, messagePublisher) + private val container = new VideoModuleDependencyContainer(doobieDbConnection, messagePublisher, logger) protected val repository: VideoRepository = container.repository } diff --git a/src/mooc/test/tv/codely/mooc/video/application/create/VideoCreatorShould.scala b/src/mooc/test/tv/codely/mooc/video/application/create/VideoCreatorShould.scala index a1cabc9..07a537c 100644 --- a/src/mooc/test/tv/codely/mooc/video/application/create/VideoCreatorShould.scala +++ b/src/mooc/test/tv/codely/mooc/video/application/create/VideoCreatorShould.scala @@ -4,10 +4,11 @@ import tv.codely.mooc.shared.infrastructure.marshaller.DomainEventsMarshaller.Me import tv.codely.mooc.video.domain.{VideoCreatedMother, VideoMother} import tv.codely.mooc.video.infrastructure.repository.VideoRepositoryMock import tv.codely.shared.infrastructure.rabbitmq.MessagePublisherMock +import tv.codely.shared.infrastructure.logger.LoggerMock import tv.codely.shared.infrastructure.unit.UnitTestCase -final class VideoCreatorShould extends UnitTestCase with VideoRepositoryMock with MessagePublisherMock { - private val creator = new VideoCreator(repository, messagePublisher) +final class VideoCreatorShould extends UnitTestCase with VideoRepositoryMock with MessagePublisherMock with LoggerMock { + private val creator = new VideoCreator(repository, messagePublisher, logger) "save a video" in { val video = VideoMother.random @@ -17,6 +18,8 @@ final class VideoCreatorShould extends UnitTestCase with VideoRepositoryMock wit publisherShouldPublish(videoCreated)(MessageMarshaller) + loggerShouldInfo() + creator.create(video.id, video.title, video.duration, video.category, video.creatorId).shouldBe(()) } } diff --git a/src/shared/test/tv/codely/shared/infrastructure/logger/LoggerMock.scala b/src/shared/test/tv/codely/shared/infrastructure/logger/LoggerMock.scala new file mode 100644 index 0000000..f1c1309 --- /dev/null +++ b/src/shared/test/tv/codely/shared/infrastructure/logger/LoggerMock.scala @@ -0,0 +1,16 @@ +package tv.codely.shared.infrastructure.logger + +import org.scalamock.scalatest.MockFactory +import tv.codely.shared.domain.logger.Logger +import tv.codely.shared.infrastructure.unit.UnitTestCase + +trait LoggerMock extends MockFactory { + this: UnitTestCase => // Make mandatory to also extend UnitTestCase in order to avoid using mocks in any other kind of test. + + protected val logger: Logger = mock[Logger] + + protected def loggerShouldInfo(): Unit = + (logger.info(_: String, _: Map[String, Any])) + .expects(*, *) + .returning(Unit) +}