public async Task PreLoadsLatestFundingItemDocumentsByPageSize_ExampleOne()
        {
            int pageSize     = 20;
            int preloadCount = 50;

            SearchFeedV3 <PublishedFundingIndex> pageOne = NewV3SearchFeed(_ => _.WithFeedItems(NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex()
                                                                                                ));
            SearchFeedV3 <PublishedFundingIndex> pageTwo = NewV3SearchFeed(_ => _.WithFeedItems(NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex(),
                                                                                                NewPublishedFundingIndex()
                                                                                                ));
            SearchFeedV3 <PublishedFundingIndex> pageThree = NewV3SearchFeed(_ => _.WithFeedItems(NewPublishedFundingIndex(),
                                                                                                  NewPublishedFundingIndex(),
                                                                                                  NewPublishedFundingIndex(),
                                                                                                  NewPublishedFundingIndex()
                                                                                                  ));

            GivenTheSettings(pageSize, preloadCount, true);
            AndTheSearchFeedPage(1, pageSize, pageOne);
            AndTheSearchFeedPage(2, pageSize, pageTwo);
            AndTheSearchFeedPage(3, pageSize, pageThree);

            await WhenThePreloadIsRun();

            ThenTheFeedItemDocumentsWerePreLoaded(pageOne.Entries.Select(_ => _.DocumentPath)
                                                  .Concat(pageTwo.Entries.Select(_ => _.DocumentPath)
                                                          .Concat(pageThree.Entries.Select(_ => _.DocumentPath))));
        }
Exemple #2
0
        public void Getters_WhenOnSubscriptionPage_ShouldReturnCorrectGeneratedValues()
        {
            // arrange
            SearchFeedV3 <string> SearchFeedV3 = new SearchFeedV3 <string>
            {
                Top        = 10,
                TotalCount = 45,
                PageRef    = 5
            };

            // assert & act
            SearchFeedV3
            .Current
            .Should().BeNull();

            SearchFeedV3
            .PreviousArchive
            .Should().Be(4);

            SearchFeedV3
            .NextArchive
            .Should().BeNull();

            SearchFeedV3
            .Current
            .Should().BeNull();

            SearchFeedV3
            .TotalPages
            .Should().Be(5);

            SearchFeedV3
            .IsArchivePage
            .Should().BeFalse();
        }
 private void AndTheSearchFeedPage(int page, int pageSize, SearchFeedV3 <PublishedFundingIndex> result)
 {
     _searchService.GetFeedsV3(page,
                               pageSize,
                               null, null, null)
     .Returns(result);
 }
Exemple #4
0
        public void Getters_WhenOnAMiddlePage_ShouldReturnCorrectGeneratedValues()
        {
            // arrange
            SearchFeedV3 <string> SearchFeedV3 = new SearchFeedV3 <string>
            {
                Top        = 10,
                TotalCount = 50,
                PageRef    = 3
            };

            // assert & act
            SearchFeedV3
            .Current
            .Should().Be(3);

            SearchFeedV3
            .PreviousArchive
            .Should().Be(2);

            SearchFeedV3
            .NextArchive
            .Should().Be(4);

            SearchFeedV3
            .TotalPages
            .Should().Be(5);

            SearchFeedV3
            .IsArchivePage
            .Should().BeTrue();
        }
Exemple #5
0
        public async Task RetrievesAndReversesTheLastPageIfNoPageRefSupplied()
        {
            int totalCount = 257;
            int top        = 50;

            IEnumerable <PublishedFundingIndex> sourceResults = NewResults(
                NewPublishedFundingIndex(),
                NewPublishedFundingIndex(),
                NewPublishedFundingIndex());

            GivenTheTotalCount(totalCount);
            AndTheFundingFeedResults(top, null, totalCount, sourceResults);

            SearchFeedV3 <PublishedFundingIndex> fundingFeedResults = await WhenTheFeedIsRequested(null, top);

            fundingFeedResults
            .Should()
            .NotBeNull();

            fundingFeedResults
            .Top
            .Should()
            .Be(top);

            fundingFeedResults
            .TotalCount
            .Should()
            .Be(totalCount);

            fundingFeedResults
            .Entries
            .Should()
            .BeEquivalentTo(sourceResults.Reverse(),
                            opt => opt.WithStrictOrdering());
        }
        public async Task GetNotifications_GivenSearchFeedReturnsNoResultsForTheGivenPage_ReturnsNotFoundResult()
        {
            //Arrange
            SearchFeedV3 <PublishedFundingIndex> feeds = new SearchFeedV3 <PublishedFundingIndex>()
            {
                TotalCount = 1000,
                Entries    = Enumerable.Empty <PublishedFundingIndex>()
            };

            IFundingFeedSearchService feedsSearchService = CreateSearchService();

            feedsSearchService
            .GetFeedsV3(Arg.Is(3), Arg.Is(500))
            .Returns(feeds);

            Mock <IExternalEngineOptions> externalEngineOptions = new Mock <IExternalEngineOptions>();

            externalEngineOptions
            .Setup(_ => _.BlobLookupConcurrencyCount)
            .Returns(10);

            FundingFeedService service = CreateService(feedsSearchService,
                                                       externalEngineOptions: externalEngineOptions.Object);

            HttpRequest request = Substitute.For <HttpRequest>();

            //Act
            IActionResult result = await service.GetFunding(request, pageRef : 3);

            //Assert
            result
            .Should()
            .BeOfType <NotFoundResult>();
        }
        public async Task <IActionResult> GetFunding(HttpRequest request,
                                                     int?pageRef,
                                                     IEnumerable <string> fundingStreamIds = null,
                                                     IEnumerable <string> fundingPeriodIds = null,
                                                     IEnumerable <Models.GroupingReason> groupingReasons   = null,
                                                     IEnumerable <Models.VariationReason> variationReasons = null,
                                                     int?pageSize = MaxRecords)
        {
            pageSize = pageSize ?? MaxRecords;

            if (pageRef < 1)
            {
                return(new BadRequestObjectResult("Page ref should be at least 1"));
            }

            if (pageSize < 1 || pageSize > 500)
            {
                return(new BadRequestObjectResult($"Page size should be more that zero and less than or equal to {MaxRecords}"));
            }

            SearchFeedV3 <PublishedFundingIndex> searchFeed = await _feedService.GetFeedsV3(
                pageRef, pageSize.Value, fundingStreamIds, fundingPeriodIds,
                groupingReasons?.Select(x => x.ToString()),
                variationReasons?.Select(x => x.ToString()));

            if (searchFeed == null || searchFeed.TotalCount == 0 || searchFeed.Entries.IsNullOrEmpty())
            {
                return(new NotFoundResult());
            }

            AtomFeed <AtomEntry> atomFeed = await CreateAtomFeed(searchFeed, request);

            return(new OkObjectResult(atomFeed));
        }
Exemple #8
0
        private async Task PreLoadPage(PageNumber pageNumber, SemaphoreSlim pageThrottle)
        {
            try
            {
                SearchFeedV3 <PublishedFundingIndex> resultsPage = await _searchService.GetFeedsV3(pageNumber.Value, _settings.PageSize,
                                                                                                   null,
                                                                                                   null,
                                                                                                   null);

                List <Task>   cacheDocumentTasks = new List <Task>();
                SemaphoreSlim cacheThrottle      = new SemaphoreSlim(10, 10);

                foreach (PublishedFundingIndex publishedFundingIndex in resultsPage.Entries)
                {
                    await cacheThrottle.WaitAsync();

                    cacheDocumentTasks.Add(Task.Run(() => CachePublishedFundingDocument(publishedFundingIndex, cacheThrottle)));
                }

                await TaskHelper.WhenAllAndThrow(cacheDocumentTasks.ToArray());
            }
            finally
            {
                pageThrottle.Release();
            }
        }
Exemple #9
0
        public void GenerateAtomLinksForResultGivenBaseUrl_WhenOnAMiddlePage_ShouldReturnCorrectGeneratedValues()
        {
            // arrange
            SearchFeedV3 <string> SearchFeedV3 = new SearchFeedV3 <string>
            {
                Top        = 10,
                TotalCount = 50,
                PageRef    = 3
            };

            // act
            IList <AtomLink> atomLinksForFeed = SearchFeedV3.GenerateAtomLinksForResultGivenBaseUrl("https://localhost:5009/api/v2/allocations/notifications{0}");

            // assert
            atomLinksForFeed
            .Count
            .Should().Be(4);

            var atomLink1 = atomLinksForFeed.First();

            atomLink1
            .Href
            .Should().Be("https://localhost:5009/api/v2/allocations/notifications/2");
            atomLink1
            .Rel
            .Should().Be("prev-archive");

            var atomLink2 = atomLinksForFeed.ElementAt(1);

            atomLink2
            .Href
            .Should().Be("https://localhost:5009/api/v2/allocations/notifications/4");
            atomLink2
            .Rel
            .Should().Be("next-archive");

            var atomLink3 = atomLinksForFeed.ElementAt(2);

            atomLink3
            .Href
            .Should().Be("https://localhost:5009/api/v2/allocations/notifications/3");
            atomLink3
            .Rel
            .Should().Be("current");

            var atomLink4 = atomLinksForFeed.ElementAt(3);

            atomLink4
            .Href
            .Should().Be("https://localhost:5009/api/v2/allocations/notifications");
            atomLink4
            .Rel
            .Should().Be("self");
        }
        private async Task <AtomFeed <AtomEntry> > CreateAtomFeed(SearchFeedV3 <PublishedFundingIndex> searchFeed, HttpRequest request)
        {
            const string fundingEndpointName       = "notifications";
            string       baseRequestPath           = request.Path.Value.Substring(0, request.Path.Value.IndexOf(fundingEndpointName, StringComparison.Ordinal) + fundingEndpointName.Length);
            string       fundingTrimmedRequestPath = baseRequestPath.Replace(fundingEndpointName, string.Empty).TrimEnd('/');

            string queryString = request.QueryString.Value;

            string fundingUrl = $"{request.Scheme}://{request.Host.Value}{baseRequestPath}{{0}}{(!string.IsNullOrWhiteSpace(queryString) ? queryString : "")}";

            AtomFeed <AtomEntry> atomFeed = CreateAtomFeedEntry(searchFeed, fundingUrl);

            ConcurrentDictionary <string, object> feedContentResults = new ConcurrentDictionary <string, object>();

            List <Task>   allTasks  = new List <Task>();
            SemaphoreSlim throttler = new SemaphoreSlim(initialCount: _externalEngineOptions.BlobLookupConcurrencyCount);

            foreach (PublishedFundingIndex feedIndex in searchFeed.Entries)
            {
                await throttler.WaitAsync();

                allTasks.Add(
                    Task.Run(async() =>
                {
                    try
                    {
                        //TODO; sort out the full document url as just the blob name is no good

                        string contents = await _publishedFundingRetrievalService.GetFundingFeedDocument(feedIndex.DocumentPath);

                        // Need to convert to an object, so JSON.NET can reserialise the contents, otherwise the string is escaped.
                        // Future TODO: change whole feed to output via text, instead of objects
                        object contentsObject = JsonConvert.DeserializeObject(contents);

                        feedContentResults.TryAdd(feedIndex.Id, contentsObject);
                    }
                    finally
                    {
                        throttler.Release();
                    }
                }));
            }

            await TaskHelper.WhenAllAndThrow(allTasks.ToArray());

            foreach (PublishedFundingIndex feedIndex in searchFeed.Entries)
            {
                AddAtomEntry(request, fundingTrimmedRequestPath, feedIndex, feedContentResults, atomFeed);
            }

            return(atomFeed);
        }
        private static SearchFeedV3 <PublishedFundingIndex> CreateSearchFeedResult(int pageRef,
                                                                                   int top,
                                                                                   int totalCount,
                                                                                   bool pageRefRequested,
                                                                                   IEnumerable <PublishedFundingIndex> searchResults)
        {
            PublishedFundingIndex[] fundingFeedResults = pageRefRequested ? searchResults.ToArray() : searchResults.Reverse().ToArray();

            SearchFeedV3 <PublishedFundingIndex> searchFeedResult = new SearchFeedV3 <PublishedFundingIndex>
            {
                PageRef    = pageRef,
                Top        = top,
                TotalCount = totalCount,
                Entries    = fundingFeedResults
            };

            return(searchFeedResult);
        }
        private AtomFeed <AtomEntry> CreateAtomFeedEntry(SearchFeedV3 <PublishedFundingIndex> searchFeed, string fundingUrl)
        {
            AtomFeed <AtomEntry> atomFeed = new AtomFeed <AtomEntry>
            {
                Id     = Guid.NewGuid().ToString("N"),
                Title  = "Calculate Funding Service Funding Feed",
                Author = new CalculateFunding.Models.External.AtomItems.AtomAuthor
                {
                    Name  = "Calculate Funding Service",
                    Email = "*****@*****.**"
                },
                Updated    = DateTimeOffset.Now,
                Rights     = "Copyright (C) 2019 Department for Education",
                Link       = searchFeed.GenerateAtomLinksForResultGivenBaseUrl(fundingUrl).ToList(),
                AtomEntry  = new List <AtomEntry>(),
                IsArchived = searchFeed.IsArchivePage
            };

            return(atomFeed);
        }
Exemple #13
0
        public async Task RetrievesRequestPageIfPageRefSupplied()
        {
            int totalCount = 257;
            int top        = 50;
            int pageRef    = 2;

            IEnumerable <PublishedFundingIndex> sourceResults = NewResults(
                NewPublishedFundingIndex(),
                NewPublishedFundingIndex(),
                NewPublishedFundingIndex());

            GivenTheTotalCount(totalCount);
            AndTheFundingFeedResults(top, pageRef, totalCount, sourceResults);

            SearchFeedV3 <PublishedFundingIndex> fundingFeedResults = await WhenTheFeedIsRequested(pageRef, top);

            fundingFeedResults
            .Should()
            .NotBeNull();

            fundingFeedResults
            .PageRef
            .Should()
            .Be(pageRef);    //we only set this if they ask for page

            fundingFeedResults
            .Top
            .Should()
            .Be(top);

            fundingFeedResults
            .TotalCount
            .Should()
            .Be(totalCount);

            fundingFeedResults
            .Entries
            .Should()
            .BeEquivalentTo(sourceResults,
                            opt => opt.WithStrictOrdering());
        }
        public async Task GetNotifications_GivenSearchFeedReturnsResultsWhenNoQueryParameters_EnsuresAtomLinksCorrect()
        {
            //Arrange
            SearchFeedV3 <PublishedFundingIndex> feeds = new SearchFeedV3 <PublishedFundingIndex>
            {
                PageRef    = 2,
                Top        = 2,
                TotalCount = 8,
                Entries    = CreateFeedIndexes()
            };

            Mock <IFundingFeedSearchService> feedsSearchService = new Mock <IFundingFeedSearchService>();

            feedsSearchService.Setup(_ => _
                                     .GetFeedsV3(It.IsAny <int?>(),
                                                 It.IsAny <int>(),
                                                 It.IsAny <IEnumerable <string> >(),
                                                 It.IsAny <IEnumerable <string> >(),
                                                 It.IsAny <IEnumerable <string> >(),
                                                 It.IsAny <IEnumerable <string> >()))
            .ReturnsAsync(feeds);

            Mock <IPublishedFundingRetrievalService> fundingRetrievalService = new Mock <IPublishedFundingRetrievalService>();

            fundingRetrievalService.Setup(_ => _.GetFundingFeedDocument(It.IsAny <string>(), false))
            .ReturnsAsync(string.Empty);

            Mock <IExternalEngineOptions> externalEngineOptions = new Mock <IExternalEngineOptions>();

            externalEngineOptions
            .Setup(_ => _.BlobLookupConcurrencyCount)
            .Returns(10);

            FundingFeedService service = CreateService(feedsSearchService.Object,
                                                       fundingRetrievalService.Object, externalEngineOptions.Object);

            IHeaderDictionary headerDictionary = new HeaderDictionary
            {
                { "Accept", new StringValues("application/json") }
            };

            HttpRequest request = Substitute.For <HttpRequest>();

            request.Scheme.Returns("https");
            request.Path.Returns(new PathString("/api/v3/funding/notifications/2"));
            request.Host.Returns(new HostString("wherever.naf:12345"));
            request.Headers.Returns(headerDictionary);

            //Act
            IActionResult result = await service.GetFunding(request, pageSize : 2, pageRef : 2);

            //Assert
            result
            .Should()
            .BeOfType <OkObjectResult>();

            OkObjectResult contentResult = result as OkObjectResult;

            Models.External.V3.AtomItems.AtomFeed <AtomEntry> atomFeed = contentResult.Value as Models.External.V3.AtomItems.AtomFeed <AtomEntry>;

            atomFeed
            .Should()
            .NotBeNull();

            atomFeed.Id.Should().NotBeEmpty();
            atomFeed.Title.Should().Be("Calculate Funding Service Funding Feed");
            atomFeed.Author.Name.Should().Be("Calculate Funding Service");
            atomFeed.Link.First(m => m.Rel == "prev-archive").Href.Should().Be("https://wherever.naf:12345/api/v3/funding/notifications/1");
            atomFeed.Link.First(m => m.Rel == "next-archive").Href.Should().Be("https://wherever.naf:12345/api/v3/funding/notifications/3");
            atomFeed.Link.First(m => m.Rel == "current").Href.Should().Be("https://wherever.naf:12345/api/v3/funding/notifications/2");
            atomFeed.Link.First(m => m.Rel == "self").Href.Should().Be("https://wherever.naf:12345/api/v3/funding/notifications");
        }
        public async Task GetNotifications_GivenAQueryStringForWhichThereAreResults_ReturnsAtomFeedWithCorrectLinks()
        {
            //Arrange
            string fundingFeedDocument = JsonConvert.SerializeObject(new
            {
                FundingStreamId = "PES"
            });

            int pageRef  = 2;
            int pageSize = 3;

            List <PublishedFundingIndex>         searchFeedEntries = CreateFeedIndexes().ToList();
            SearchFeedV3 <PublishedFundingIndex> feeds             = new SearchFeedV3 <PublishedFundingIndex>
            {
                PageRef    = pageRef,
                Top        = 2,
                TotalCount = 8,
                Entries    = searchFeedEntries
            };
            PublishedFundingIndex firstFeedItem = feeds.Entries.ElementAt(0);

            IFundingFeedSearchService feedsSearchService = CreateSearchService();

            feedsSearchService
            .GetFeedsV3(Arg.Is(pageRef), Arg.Is(pageSize))
            .ReturnsForAnyArgs(feeds);

            IPublishedFundingRetrievalService publishedFundingRetrievalService = Substitute.For <IPublishedFundingRetrievalService>();

            publishedFundingRetrievalService
            .GetFundingFeedDocument(Arg.Any <string>())
            .Returns(fundingFeedDocument);

            Mock <IExternalEngineOptions> externalEngineOptions = new Mock <IExternalEngineOptions>();

            externalEngineOptions
            .Setup(_ => _.BlobLookupConcurrencyCount)
            .Returns(10);

            FundingFeedService service = CreateService(
                searchService: feedsSearchService,
                publishedFundingRetrievalService: publishedFundingRetrievalService,
                externalEngineOptions.Object);

            IHeaderDictionary headerDictionary = new HeaderDictionary
            {
                { "Accept", new StringValues("application/json") }
            };

            IQueryCollection queryStringValues = new QueryCollection(new Dictionary <string, StringValues>
            {
                { "pageRef", new StringValues(pageRef.ToString()) },
                { "allocationStatuses", new StringValues("Published,Approved") },
                { "pageSize", new StringValues(pageSize.ToString()) }
            });

            string scheme      = "https";
            string path        = "/api/v3/fundings/notifications";
            string host        = "wherever.naf:12345";
            string queryString = "?pageSize=2";

            HttpRequest request = Substitute.For <HttpRequest>();

            request.Scheme.Returns(scheme);
            request.Path.Returns(new PathString(path));
            request.Host.Returns(new HostString(host));
            request.QueryString.Returns(new QueryString(queryString));
            request.Headers.Returns(headerDictionary);
            request.Query.Returns(queryStringValues);

            //Act
            IActionResult result = await service.GetFunding(request, pageRef : pageRef, pageSize : pageSize);

            //Assert
            result
            .Should()
            .BeOfType <OkObjectResult>();

            OkObjectResult contentResult = result as OkObjectResult;

            Models.External.V3.AtomItems.AtomFeed <AtomEntry> atomFeed = contentResult.Value as Models.External.V3.AtomItems.AtomFeed <AtomEntry>;

            atomFeed
            .Should()
            .NotBeNull();

            atomFeed.Id.Should().NotBeEmpty();
            atomFeed.Title.Should().Be("Calculate Funding Service Funding Feed");
            atomFeed.Author.Name.Should().Be("Calculate Funding Service");
            atomFeed.Link.First(m => m.Rel == "next-archive").Href.Should().Be($"{scheme}://{host}{path}/3{queryString}");
            atomFeed.Link.First(m => m.Rel == "prev-archive").Href.Should().Be($"{scheme}://{host}{path}/1{queryString}");
            atomFeed.Link.First(m => m.Rel == "self").Href.Should().Be($"{scheme}://{host}{path}{queryString}");
            atomFeed.Link.First(m => m.Rel == "current").Href.Should().Be($"{scheme}://{host}{path}/2{queryString}");

            atomFeed.AtomEntry.Count.Should().Be(3);

            for (int i = 0; i < 3; i++)
            {
                string text = $"id-{i + 1}";
                atomFeed.AtomEntry.ElementAt(i).Id.Should().Be($"{scheme}://{host}/api/v3/fundings/byId/{text}");
                atomFeed.AtomEntry.ElementAt(i).Title.Should().Be(text);
                atomFeed.AtomEntry.ElementAt(i).Summary.Should().Be(text);
                atomFeed.AtomEntry.ElementAt(i).Content.Should().NotBeNull();
            }

            JObject content = atomFeed.AtomEntry.ElementAt(0).Content as JObject;

            content.TryGetValue("FundingStreamId", out JToken token);
            ((JValue)token).Value <string>().Should().Be("PES");

            await feedsSearchService
            .Received(1)
            .GetFeedsV3(pageRef, pageSize, null, null, null);

            await publishedFundingRetrievalService
            .Received(searchFeedEntries.Count)
            .GetFundingFeedDocument(Arg.Any <string>());

            foreach (PublishedFundingIndex index in searchFeedEntries)
            {
                await publishedFundingRetrievalService
                .Received(1)
                .GetFundingFeedDocument(index.DocumentPath);
            }
        }