public async Task GetMatchedObservations()
        {
            var cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken       = cancellationTokenSource.Token;

            await using var context = InMemoryStatisticsDbContext();

            const string sql = "Some SQL here";

            var sqlParameters = ListOf(new SqlParameter("param1", "value"));

            var observationQueryContext = new ObservationQueryContext
            {
                SubjectId   = Guid.NewGuid(),
                Filters     = ListOf(Guid.NewGuid()),
                LocationIds = ListOf(Guid.NewGuid()),
                TimePeriod  = new TimePeriodQuery()
            };

            var queryGenerator = new Mock <IMatchingObservationsQueryGenerator>(Strict);

            var tempTable1 = new Mock <ITempTableQuery <IdTempTable> >(Strict);
            var tempTable2 = new Mock <ITempTableQuery <IdTempTable> >(Strict);

            ListOf(tempTable1, tempTable2)
            .ForEach(tempTable => tempTable
                     .Setup(t => t.DisposeAsync())
                     .Returns(new ValueTask()));

            var tempTableList = ListOf(tempTable1.Object, tempTable2.Object)
                                .Cast <IAsyncDisposable>()
                                .ToList();

            queryGenerator
            .Setup(s => s
                   .GetMatchingObservationsQuery(
                       context,
                       observationQueryContext.SubjectId,
                       ItIs.ListSequenceEqualTo(observationQueryContext.Filters),
                       ItIs.ListSequenceEqualTo(observationQueryContext.LocationIds),
                       observationQueryContext.TimePeriod,
                       cancellationToken))
            .ReturnsAsync((sql, sqlParameters, tempTableList));

            var sqlExecutor = new Mock <IRawSqlExecutor>(Strict);

            sqlExecutor
            .Setup(s => s.ExecuteSqlRaw(context, sql, sqlParameters, cancellationToken))
            .Returns(Task.CompletedTask);

            var service = BuildService(
                context,
                queryGenerator.Object,
                sqlExecutor.Object);

            await service.GetMatchedObservations(observationQueryContext, cancellationToken);

            VerifyAllMocks(queryGenerator, sqlExecutor, tempTable1, tempTable2);
        }
        private void VerifyAllMocks <T>(T message, Audit.Api.Client.Models.Audit auditMessage)
        {
            Mock.Get(_mockLogger).VerifyAll();
            Mock.Get(_mockServiceBusMessagingService)
            .Verify(sbs => sbs.SendAsBinaryXmlMessageAsync(ItIs.EquivalentTo(message), It.Is <IDictionary <string, string> >(m => m["messageType"] == typeof(T).FullName)), Times.Once);

            Mock.Get(_mockAuditService)
            .Verify(a => a.AuditAsync(ItIs.EquivalentTo(auditMessage)), Times.Once);
        }
        public async Task QueryGenerator_FullQuery()
        {
            var subjectId = Guid.NewGuid();

            var filter = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = subjectId,
                FilterGroups = CreateFilterGroups(5, 5)
            };

            var contextId = Guid.NewGuid().ToString();

            await using (var context = InMemoryStatisticsDbContext(contextId))
            {
                await context.AddAsync(filter);

                await context.SaveChangesAsync();
            }

            var tempTableCreator = new Mock <MatchingObservationsQueryGenerator.ITemporaryTableCreator>(Strict);

            var queryGenerator = new MatchingObservationsQueryGenerator
            {
                TempTableCreator = tempTableCreator.Object
            };

            await using (var context = InMemoryStatisticsDbContext(contextId))
            {
                var cancellationTokenSource = new CancellationTokenSource();
                var cancellationToken       = cancellationTokenSource.Token;

                var selectedFilterItemIds = ListOf(
                    filter.FilterGroups.ToList()[0].FilterItems.ToList()[0].Id,
                    filter.FilterGroups.ToList()[0].FilterItems.ToList()[2].Id,
                    filter.FilterGroups.ToList()[1].FilterItems.ToList()[1].Id);

                tempTableCreator
                .Setup(s =>
                       s.CreateTemporaryTable <MatchedObservation>(context, cancellationToken))
                .Returns(Task.CompletedTask);

                var selectedLocationIds = ListOf(Guid.NewGuid(), Guid.NewGuid());

                var selectedLocationIdTempTableEntries = selectedLocationIds
                                                         .Select(id => new IdTempTable(id))
                                                         .ToList();

                var locationIdsTempTableReference = new Mock <ITempTableQuery <IdTempTable> >();
                locationIdsTempTableReference
                .SetupGet(t => t.Name)
                .Returns("#LocationTempTable");

                tempTableCreator
                .Setup(s =>
                       s.CreateTemporaryTableAndPopulate(
                           context,
                           ItIs.EnumerableSequenceEqualTo(selectedLocationIdTempTableEntries),
                           cancellationToken))
                .ReturnsAsync(locationIdsTempTableReference.Object);

                var filterItemIdsTempTableReference = new Mock <ITempTableQuery <IdTempTable> >();
                filterItemIdsTempTableReference
                .SetupGet(t => t.Name)
                .Returns("#FilterTempTable");

                tempTableCreator
                .Setup(s =>
                       s.CreateTemporaryTableAndPopulate(
                           context,
                           ItIs.ListSequenceEqualTo(
                               selectedFilterItemIds
                               .OrderBy(id => id)
                               .Select(id => new IdTempTable(id))
                               .ToList()),
                           cancellationToken))
                .ReturnsAsync(filterItemIdsTempTableReference.Object);

                var tempTableMocks = ListOf(locationIdsTempTableReference);
                tempTableMocks.Add(filterItemIdsTempTableReference);

                var(sql, sqlParameters, tempTableReferences) =
                    await queryGenerator
                    .GetMatchingObservationsQuery(
                        context,
                        subjectId,
                        selectedFilterItemIds,
                        selectedLocationIds,
                        new TimePeriodQuery
                {
                    StartCode = TimeIdentifier.AcademicYear,
                    StartYear = 2015,
                    EndCode   = TimeIdentifier.AcademicYear,
                    EndYear   = 2017
                },
                        cancellationToken);

                VerifyAllMocks(tempTableCreator);
                VerifyAllMocks(tempTableMocks.Cast <Mock>().ToArray());

                const string expectedSql = @"
                      INSERT INTO #MatchedObservation 
                      SELECT o.id FROM Observation o
                      WHERE o.SubjectId = @subjectId 
                      AND (
                        (o.TimeIdentifier = 'AY' AND o.Year = 2015) OR 
                        (o.TimeIdentifier = 'AY' AND o.Year = 2016) OR 
                        (o.TimeIdentifier = 'AY' AND o.Year = 2017)
                      ) 
                      AND (o.LocationId IN (SELECT Id FROM #LocationTempTable)) 
                      AND (
                          EXISTS (SELECT 1 FROM ObservationFilterItem ofi 
                                  WHERE ofi.ObservationId = o.id 
                                  AND ofi.FilterItemId IN (SELECT Id FROM #FilterTempTable))
                      )
                      ORDER BY o.Id;";

                Assert.Equal(FormatSql(expectedSql), FormatSql(sql));

                sqlParameters.AssertDeepEqualTo(ListOf(new SqlParameter("subjectId", subjectId)));

                var expectedTempTableReferences =
                    tempTableMocks.Select(mock => mock.Object);

                // Assert that the temporary table for the LocationIds, and the 3 temporary tables for the
                // 3 Filters' sets of selected FilterItemIds are returned so that the calling code can
                // dispose of them as it needs to (the order they are returned in is not important).
                Assert.Equal(
                    expectedTempTableReferences.OrderBy(t => t.GetHashCode()),
                    tempTableReferences.OrderBy(t => t.GetHashCode()));
            }
        }
        public async Task QueryGenerator_FilterItems()
        {
            var subjectId = Guid.NewGuid();

            var filter1 = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = subjectId,
                FilterGroups = CreateFilterGroups(5, 5)
            };

            var filter2 = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = subjectId,
                FilterGroups = CreateFilterGroups(10)
            };

            var filter3 = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = subjectId,
                FilterGroups = CreateFilterGroups(2, 2, 2, 2, 2)
            };

            var unselectedFilter4 = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = subjectId,
                FilterGroups = CreateFilterGroups(2)
            };

            var unrelatedFilter = new Filter
            {
                Id           = Guid.NewGuid(),
                SubjectId    = Guid.NewGuid(),
                FilterGroups = CreateFilterGroups(5)
            };

            var contextId = Guid.NewGuid().ToString();

            await using (var context = InMemoryStatisticsDbContext(contextId))
            {
                await context.AddRangeAsync(
                    filter1,
                    filter2,
                    filter3,
                    unselectedFilter4,
                    unrelatedFilter);

                await context.SaveChangesAsync();
            }

            var tempTableCreator = new Mock <MatchingObservationsQueryGenerator.ITemporaryTableCreator>(Strict);

            var queryGenerator = new MatchingObservationsQueryGenerator
            {
                TempTableCreator = tempTableCreator.Object
            };

            await using (var context = InMemoryStatisticsDbContext(contextId))
            {
                var cancellationTokenSource = new CancellationTokenSource();
                var cancellationToken       = cancellationTokenSource.Token;

                // Set up some selected Filter Item Ids from 3 of the 4 Filters on this Subject.
                //
                // Filter1 has the least Filter Items selected and will therefore appear first
                // in the list of EXISTS clauses, in order for it to filter as many table rows down
                // as possible before the next EXISTS clause is evaluated.
                //
                // Filter3 then has the second lowest number of Filter Items selected so it will
                // appear as the second EXISTS clause, and finally Filter3 will be the final EXISTS
                // clause.
                var selectedFilter1FilterItemIds = ListOf(
                    filter1.FilterGroups.ToList()[0].FilterItems.ToList()[0].Id,
                    filter1.FilterGroups.ToList()[0].FilterItems.ToList()[2].Id,
                    filter1.FilterGroups.ToList()[1].FilterItems.ToList()[1].Id);

                var selectedFilter2FilterItemIds = ListOf(
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[0].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[2].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[4].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[3].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[7].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[6].Id,
                    filter2.FilterGroups.ToList()[0].FilterItems.ToList()[8].Id);

                var selectedFilter3FilterItemIds = ListOf(
                    filter3.FilterGroups.ToList()[0].FilterItems.ToList()[0].Id,
                    filter3.FilterGroups.ToList()[0].FilterItems.ToList()[1].Id,
                    filter3.FilterGroups.ToList()[1].FilterItems.ToList()[0].Id,
                    filter3.FilterGroups.ToList()[2].FilterItems.ToList()[1].Id,
                    filter3.FilterGroups.ToList()[3].FilterItems.ToList()[0].Id,
                    filter3.FilterGroups.ToList()[4].FilterItems.ToList()[1].Id);

                // An accidental inclusion of a Filter Item Id that doesn't belong to a
                // Filter on this Subject will be ignored.
                var invalidUnrelatedFilterFilterItemIds = ListOf(
                    unrelatedFilter.FilterGroups.ToList()[0].FilterItems.ToList()[0].Id);

                var selectFilterItemIds = selectedFilter1FilterItemIds
                                          .Concat(selectedFilter2FilterItemIds)
                                          .Concat(selectedFilter3FilterItemIds)
                                          .Concat(invalidUnrelatedFilterFilterItemIds)
                                          .ToList();

                tempTableCreator
                .Setup(s =>
                       s.CreateTemporaryTable <MatchedObservation>(context, cancellationToken))
                .Returns(Task.CompletedTask);

                var selectedFilterItemsByFilter = ListOf(
                    selectedFilter1FilterItemIds,
                    selectedFilter2FilterItemIds,
                    selectedFilter3FilterItemIds);

                var selectedFilterItemTempTableEntriesByFilter = selectedFilterItemsByFilter
                                                                 .Select(filterItemIds => filterItemIds
                                                                         .Select(id => new IdTempTable(id))
                                                                         .ToList())
                                                                 .ToList();

                var filterItemIdTempTableReferences =
                    selectedFilterItemTempTableEntriesByFilter
                    .Select(tempTableEntries =>
                {
                    var filterNumber = selectedFilterItemTempTableEntriesByFilter.IndexOf(tempTableEntries) + 1;

                    var orderedIds = tempTableEntries
                                     .OrderBy(t => t.Id)
                                     .ToList();

                    var tempTableReference = new Mock <ITempTableQuery <IdTempTable> >();
                    tempTableReference
                    .SetupGet(t => t.Name)
                    .Returns($"#Filter{filterNumber}TempTable");

                    tempTableCreator
                    .Setup(s =>
                           s.CreateTemporaryTableAndPopulate(
                               context,
                               ItIs.ListSequenceEqualTo(orderedIds),
                               cancellationToken))
                    .ReturnsAsync(tempTableReference.Object);

                    return(tempTableReference);
                })
                    .ToList();

                var(sql, sqlParameters, tempTableReferences) =
                    await queryGenerator
                    .GetMatchingObservationsQuery(
                        context,
                        subjectId,
                        selectFilterItemIds,
                        null,
                        null,
                        cancellationToken);

                VerifyAllMocks(tempTableCreator);
                VerifyAllMocks(filterItemIdTempTableReferences.Cast <Mock>().ToArray());

                // Ensure that the EXISTS clauses appear in the expected order in the generated query,
                // with the Filter with the least number of Filter Items chosen being the first.
                const string expectedSql = @"
                      INSERT INTO #MatchedObservation 
                      SELECT o.id FROM Observation o
                      WHERE o.SubjectId = @subjectId 
                      AND (
                          EXISTS (SELECT 1 FROM ObservationFilterItem ofi 
                                  WHERE ofi.ObservationId = o.id 
                                  AND ofi.FilterItemId IN (SELECT Id FROM #Filter1TempTable)) AND
                          EXISTS (SELECT 1 FROM ObservationFilterItem ofi 
                                  WHERE ofi.ObservationId = o.id 
                                  AND ofi.FilterItemId IN (SELECT Id FROM #Filter3TempTable)) AND
                          EXISTS (SELECT 1 FROM ObservationFilterItem ofi 
                                  WHERE ofi.ObservationId = o.id 
                                  AND ofi.FilterItemId IN (SELECT Id FROM #Filter2TempTable))
                      )
                      ORDER BY o.Id;";

                Assert.Equal(FormatSql(expectedSql), FormatSql(sql));

                sqlParameters.AssertDeepEqualTo(ListOf(new SqlParameter("subjectId", subjectId)));

                var expectedTempTableReferences =
                    filterItemIdTempTableReferences.Select(mock => mock.Object);

                // Assert that the temporary table for the LocationIds, and the 3 temporary tables for the
                // 3 Filters' sets of selected FilterItemIds are returned so that the calling code can
                // dispose of them as it needs to (the order they are returned in is not important).
                Assert.Equal(
                    expectedTempTableReferences.OrderBy(t => t.GetHashCode()),
                    tempTableReferences.OrderBy(t => t.GetHashCode()));
            }
        }