@@ -939,6 +939,135 @@ public async Task TransactionsEndpoint_ProductPerformanceReport_SummaryDataRetur
939939 productPerformanceResponseDetail . TransactionCount . ShouldBe ( transactions . Count ( t => t . ContractProductId == product . productId ) ) ;
940940 productPerformanceResponseDetail . TransactionValue . ShouldBe ( transactions . Where ( t => t . ContractProductId == product . productId ) . Sum ( t => t . TransactionAmount ) ) ;
941941 }
942+ }
943+
944+ private static int RandomizeCount ( int baseCount , Random rnd , double variability = 0.3 )
945+ {
946+ if ( baseCount <= 0 ) return 0 ;
947+ // factor in [-variability, +variability]
948+ double factor = 1.0 + ( rnd . NextDouble ( ) * 2.0 - 1.0 ) * variability ;
949+ int result = ( int ) Math . Round ( baseCount * factor ) ;
950+ return Math . Max ( 0 , result ) ;
951+ }
952+
953+ [ Fact ]
954+ public async Task TransactionsEndpoint_TodaysSalesByHour_SummaryDataReturned ( )
955+ {
956+ Stopwatch sw = Stopwatch . StartNew ( ) ;
957+
958+ List < Transaction > todaysTransactions = new List < Transaction > ( ) ;
959+ List < Transaction > comparisonDateTransactions = new List < Transaction > ( ) ;
960+
961+ Dictionary < string , int > transactionCounts = new ( ) { { "Test Merchant 1" , 3 } , { "Test Merchant 2" , 6 } , { "Test Merchant 3" , 2 } , { "Test Merchant 4" , 0 } } ;
962+
963+ // TODO: make counts dynamic
964+ DateTime todaysDateTime = DateTime . Now ;
965+
966+ for ( int hour = 0 ; hour < 24 ; hour ++ )
967+ {
968+ List < Transaction > localList = new List < Transaction > ( ) ;
969+ DateTime date = new DateTime ( todaysDateTime . Year , todaysDateTime . Month , todaysDateTime . Day , hour , 0 , 0 ) ;
970+
971+ // Seed per-hour RNG deterministically so test results are reproducible per-day/per-hour
972+ var hourSeed = todaysDateTime . Date . GetHashCode ( ) ^ hour ;
973+ var hourRnd = new Random ( hourSeed ) ;
942974
975+ foreach ( var merchant in merchantsList )
976+ {
977+ foreach ( var contract in contractList )
978+ {
979+ var productList = contractProducts . Single ( cp => cp . Key == contract . contractId ) . Value ;
980+ foreach ( ( Guid productId , String productName , Decimal ? productValue , Int32 contractProductReportingId ) product in productList )
981+ {
982+ var baseCount = transactionCounts . Single ( m => m . Key == merchant . Name ) . Value ;
983+
984+ // keep the original hour-based multipliers, but apply random variation to the final count
985+ int hourMultiplierCount = hour switch
986+ {
987+ _ when hour >= 9 && hour < 18 => baseCount * hour , // business hours
988+ _ when hour >= 18 && hour < 21 => baseCount * ( 24 - hour ) , // evening spike
989+ _ => baseCount // off hours
990+ } ;
991+
992+ int transactionCount = RandomizeCount ( hourMultiplierCount , hourRnd , variability : 0.3 ) ;
993+
994+ for ( int i = 0 ; i < transactionCount ; i ++ )
995+ {
996+ Transaction transaction = await helper . BuildTransactionX ( date , merchant . MerchantId , contract . operatorId , contract . contractId , product . productId , "0000" , product . productValue ) ;
997+ todaysTransactions . Add ( transaction ) ;
998+ }
999+ }
1000+ }
1001+ }
1002+
1003+ todaysTransactions . AddRange ( localList ) ;
1004+ }
1005+
1006+ await this . helper . AddTransactionsX ( todaysTransactions ) ;
1007+
1008+ sw . Stop ( ) ;
1009+ this . TestOutputHelper . WriteLine ( $ "Setup Todays Txns { sw . ElapsedMilliseconds } ms") ;
1010+ sw . Restart ( ) ;
1011+
1012+ DateTime comparisonDate = todaysDateTime . AddDays ( - 1 ) ;
1013+ for ( int hour = 0 ; hour < 24 ; hour ++ )
1014+ {
1015+ List < Transaction > localList = new List < Transaction > ( ) ;
1016+ DateTime date = new DateTime ( comparisonDate . Year , comparisonDate . Month , comparisonDate . Day , hour , 0 , 0 ) ;
1017+
1018+ // Separate deterministic seed for comparison date hour
1019+ var compHourSeed = comparisonDate . Date . GetHashCode ( ) ^ hour ;
1020+ var compHourRnd = new Random ( compHourSeed ) ;
1021+
1022+ foreach ( var merchant in merchantsList )
1023+ {
1024+ foreach ( var contract in contractList )
1025+ {
1026+ var productList = contractProducts . Single ( cp => cp . Key == contract . contractId ) . Value ;
1027+ foreach ( var product in productList )
1028+ {
1029+ var baseCount = transactionCounts . Single ( m => m . Key == merchant . Name ) . Value ;
1030+
1031+ int hourMultiplierCount = hour switch
1032+ {
1033+ _ when hour >= 12 && hour < 18 => baseCount * hour , // business hours
1034+ _ when hour >= 18 && hour < 21 => baseCount * ( 24 - hour ) , // evening spike
1035+ _ => baseCount // off hours
1036+ } ;
1037+
1038+ int transactionCount = RandomizeCount ( hourMultiplierCount , compHourRnd , variability : 0.3 ) ;
1039+
1040+ for ( int i = 0 ; i < transactionCount ; i ++ )
1041+ {
1042+ Transaction transaction = await helper . BuildTransactionX ( date , merchant . MerchantId , contract . operatorId , contract . contractId , product . productId , "0000" , product . productValue ) ;
1043+ comparisonDateTransactions . Add ( transaction ) ;
1044+ }
1045+ }
1046+ }
1047+ }
1048+
1049+ comparisonDateTransactions . AddRange ( localList ) ;
1050+
1051+ }
1052+
1053+ await this . helper . AddTransactionsX ( comparisonDateTransactions ) ;
1054+
1055+ await this . helper . RunTodaysTransactionsSummaryProcessing ( comparisonDate . Date ) ;
1056+ await this . helper . RunHistoricTransactionsSummaryProcessing ( comparisonDate . Date ) ;
1057+ await this . helper . RunTodaysTransactionsSummaryProcessing ( todaysDateTime . Date ) ;
1058+
1059+ var result = await this . CreateAndSendHttpRequestMessage < List < DataTransferObjects . TodaysSalesByHour > > ( $ "{ this . BaseRoute } /todayssalesbyhour?comparisondate={ comparisonDate : yyyy-MM-dd} ", CancellationToken . None ) ;
1060+ result . IsSuccess . ShouldBeTrue ( ) ;
1061+ var todaysSalesByHour = result . Data ;
1062+ todaysSalesByHour . ShouldNotBeNull ( ) ;
1063+
1064+ foreach ( var hour in todaysSalesByHour ) {
1065+ hour . ShouldNotBeNull ( ) ;
1066+ hour . TodaysSalesCount . ShouldBe ( todaysTransactions . Count ( t => t . TransactionDateTime . Hour == hour . Hour && t . TransactionTime <= DateTime . Now . TimeOfDay ) , hour . Hour . ToString ( ) ) ;
1067+ hour . TodaysSalesValue . ShouldBe ( todaysTransactions . Where ( t => t . TransactionDateTime . Hour == hour . Hour && t . TransactionTime <= DateTime . Now . TimeOfDay ) . Sum ( t => t . TransactionAmount ) , hour . Hour . ToString ( ) ) ;
1068+ hour . ComparisonSalesCount . ShouldBe ( comparisonDateTransactions . Count ( t => t . TransactionDateTime . Hour == hour . Hour && t . TransactionTime <= DateTime . Now . TimeOfDay ) , hour . Hour . ToString ( ) ) ;
1069+ hour . ComparisonSalesValue . ShouldBe ( comparisonDateTransactions . Where ( t => t . TransactionDateTime . Hour == hour . Hour && t . TransactionTime <= DateTime . Now . TimeOfDay ) . Sum ( t => t . TransactionAmount ) , hour . Hour . ToString ( ) ) ;
1070+
1071+ }
9431072 }
9441073}
0 commit comments