public async Task Advise_IfSongsAreAdvised_ReturnsSongsWithHigherRankFirst()
        {
            // Arrange

            var settings = new HighlyRatedSongsAdviserSettings
            {
                OneAdviseSongsNumber = 3,
                MaxTerms             = new[]
                {
                    new MaxRatingTerm
                    {
                        Rating = RatingModel.R10,
                        Days   = 30,
                    },
                },
            };

            var song1         = CreateTestSong(1, RatingModel.R10, new DateTime(2017, 02, 01));
            var song2         = CreateTestSong(2, RatingModel.R10, new DateTime(2017, 02, 01));
            var song3         = CreateTestSong(3, RatingModel.R10, new DateTime(2017, 02, 01));
            var adviseSet     = CreateTestAdviseSet("1", new[] { song1, song2, song3 });
            var adviseGroups  = CreateAdviseGroups(adviseSet);
            var playbacksInfo = new PlaybacksInfo(adviseGroups);

            var adviseRankCalculatorStub = new Mock <IAdviseRankCalculator>();

            adviseRankCalculatorStub.Setup(x => x.CalculateSongRank(song1, playbacksInfo)).Returns(0.25);
            adviseRankCalculatorStub.Setup(x => x.CalculateSongRank(song2, playbacksInfo)).Returns(0.75);
            adviseRankCalculatorStub.Setup(x => x.CalculateSongRank(song3, playbacksInfo)).Returns(0.50);

            var clockStub = new Mock <IClock>();

            clockStub.Setup(x => x.Now).Returns(new DateTime(2017, 10, 01));

            var mocker = new AutoMocker();

            mocker.Use(adviseRankCalculatorStub);
            mocker.Use(clockStub);
            mocker.Use(Options.Create(settings));

            var target = mocker.CreateInstance <HighlyRatedSongsAdviser>();

            // Act

            var advises = await target.Advise(adviseGroups, playbacksInfo, CancellationToken.None);

            // Assert

            var expectedAdvises = new[]
            {
                AdvisedPlaylist.ForHighlyRatedSongs(new[] { song2, song3, song1 }),
            };

            advises.Should().BeEquivalentTo(expectedAdvises, x => x.WithStrictOrdering());
        }
        public HighlyRatedSongsAdviser(IAdviseRankCalculator adviseRankCalculator, IClock dateTimeFacade, IOptions <HighlyRatedSongsAdviserSettings> options)
        {
            this.adviseRankCalculator = adviseRankCalculator ?? throw new ArgumentNullException(nameof(adviseRankCalculator));
            this.dateTimeFacade       = dateTimeFacade ?? throw new ArgumentNullException(nameof(dateTimeFacade));
            this.settings             = options?.Value ?? throw new ArgumentNullException(nameof(options));

            if (settings.OneAdviseSongsNumber <= 0)
            {
                throw new InvalidOperationException($"{nameof(settings.OneAdviseSongsNumber)} is not set for highly rated songs adviser");
            }

            maxTermsForRatings = settings.MaxTerms
                                 .ToDictionary(t => t.Rating, t => TimeSpan.FromDays(t.Days));
        }
        public void Constructor_OneAdviseSongsNumberIsNotSet_ThrowsInvalidOperationException()
        {
            // Arrange

            var settings = new HighlyRatedSongsAdviserSettings();

            // Act

            Func <HighlyRatedSongsAdviser> call = () => new(Mock.Of <IAdviseRankCalculator>(), Mock.Of <IClock>(), Options.Create(settings));

            // Assert

            call.Should().Throw <InvalidOperationException>();
        }
        public async Task Advise_IfTooMuchSongsAreAdvised_SplitsThemIntoSmallerPlaylists()
        {
            // Arrange

            var settings = new HighlyRatedSongsAdviserSettings
            {
                OneAdviseSongsNumber = 12,
                MaxTerms             = new[]
                {
                    new MaxRatingTerm
                    {
                        Rating = RatingModel.R10,
                        Days   = 30,
                    },
                },
            };

            var songs1        = Enumerable.Range(1, 12).Select(id => CreateTestSong(id, RatingModel.R10, lastPlaybackTime: null)).ToList();
            var songs2        = Enumerable.Range(13, 12).Select(id => CreateTestSong(id, RatingModel.R10, lastPlaybackTime: null)).ToList();
            var adviseGroups  = CreateAdviseGroups(CreateTestAdviseSet("1", songs1), CreateTestAdviseSet("2", songs2));
            var playbacksInfo = new PlaybacksInfo(adviseGroups);

            var clockStub = new Mock <IClock>();

            clockStub.Setup(x => x.Now).Returns(new DateTime(2017, 10, 01));

            var mocker = new AutoMocker();

            mocker.Use(clockStub);
            mocker.Use(Options.Create(settings));

            var target = mocker.CreateInstance <HighlyRatedSongsAdviser>();

            // Act

            var advises = await target.Advise(adviseGroups, playbacksInfo, CancellationToken.None);

            // Assert

            var expectedAdvises = new[]
            {
                AdvisedPlaylist.ForHighlyRatedSongs(songs1),
                AdvisedPlaylist.ForHighlyRatedSongs(songs2),
            };

            advises.Should().BeEquivalentTo(expectedAdvises, x => x.WithStrictOrdering());
        }
        public async Task Advise_ReturnsSongsWithHighRatingListenedEarlierThanConfiguredTerm()
        {
            // Arrange

            var settings = new HighlyRatedSongsAdviserSettings
            {
                OneAdviseSongsNumber = 12,
                MaxTerms             = new[]
                {
                    new MaxRatingTerm
                    {
                        Rating = RatingModel.R10,
                        Days   = 30,
                    },
                },
            };

            var songs         = Enumerable.Range(1, 12).Select(id => CreateTestSong(id, RatingModel.R10, new DateTime(2017, 09, 01))).ToList();
            var adviseSet     = CreateTestAdviseSet("1", songs);
            var adviseGroups  = CreateAdviseGroups(adviseSet);
            var playbacksInfo = new PlaybacksInfo(adviseGroups);

            var clockStub = new Mock <IClock>();

            clockStub.Setup(x => x.Now).Returns(new DateTime(2017, 10, 07));

            var mocker = new AutoMocker();

            mocker.Use(clockStub);
            mocker.Use(Options.Create(settings));

            var target = mocker.CreateInstance <HighlyRatedSongsAdviser>();

            // Act

            var advises = await target.Advise(adviseGroups, playbacksInfo, CancellationToken.None);

            // Assert

            var expectedAdvises = new[]
            {
                AdvisedPlaylist.ForHighlyRatedSongs(songs),
            };

            advises.Should().BeEquivalentTo(expectedAdvises, x => x.WithStrictOrdering());
        }
        public async Task Advise_IfNumberOfAdvisedSongsIsNotEnough_ReturnsNoPlaylists()
        {
            // Arrange

            var settings = new HighlyRatedSongsAdviserSettings
            {
                OneAdviseSongsNumber = 12,
                MaxTerms             = new[]
                {
                    new MaxRatingTerm
                    {
                        Rating = RatingModel.R10,
                        Days   = 30,
                    },
                },
            };

            var songs         = Enumerable.Range(1, 11).Select(id => CreateTestSong(id, RatingModel.R10, lastPlaybackTime: null)).ToList();
            var adviseSet     = CreateTestAdviseSet("1", songs);
            var adviseGroups  = CreateAdviseGroups(adviseSet);
            var playbacksInfo = new PlaybacksInfo(adviseGroups);

            var clockStub = new Mock <IClock>();

            clockStub.Setup(x => x.Now).Returns(new DateTime(2017, 10, 01));

            var mocker = new AutoMocker();

            mocker.Use(clockStub);
            mocker.Use(Options.Create(settings));

            var target = mocker.CreateInstance <HighlyRatedSongsAdviser>();

            // Act

            var advises = await target.Advise(adviseGroups, playbacksInfo, CancellationToken.None);

            // Assert

            advises.Should().BeEmpty();
        }