@@ -89,6 +89,315 @@ public void GenerateChanges_WithNullNewCapacityPolicy_ShouldNotGenerateChanges()
8989 Assert . Empty ( changeSet . Scripts ) ;
9090 }
9191
92+ #region Workload Group Tests
93+
94+ [ Fact ]
95+ public void GenerateChanges_WithNewWorkloadGroup_ShouldDetectCreation ( )
96+ {
97+ // Arrange
98+ var oldCluster = new Cluster { Name = "TestCluster" , WorkloadGroups = new List < WorkloadGroup > ( ) } ;
99+ var newCluster = new Cluster
100+ {
101+ Name = "TestCluster" ,
102+ WorkloadGroups = new List < WorkloadGroup >
103+ {
104+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 )
105+ }
106+ } ;
107+
108+ // Act
109+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
110+
111+ // Assert
112+ Assert . NotNull ( changeSet ) ;
113+ Assert . NotEmpty ( changeSet . Changes ) ;
114+ Assert . NotEmpty ( changeSet . Scripts ) ;
115+
116+ var policyChange = Assert . Single ( changeSet . Changes ) as PolicyChange < WorkloadGroupPolicy > ;
117+ Assert . NotNull ( policyChange ) ;
118+ Assert . Equal ( "test-group" , policyChange . Entity ) ;
119+
120+ var scriptContainer = Assert . Single ( changeSet . Scripts ) ;
121+ Assert . Contains ( ".create-or-alter workload_group test-group" , scriptContainer . Script . Text ) ;
122+ }
123+
124+ [ Fact ]
125+ public void GenerateChanges_WithUpdatedWorkloadGroup_ShouldDetectUpdate ( )
126+ {
127+ // Arrange
128+ var oldCluster = new Cluster
129+ {
130+ Name = "TestCluster" ,
131+ WorkloadGroups = new List < WorkloadGroup >
132+ {
133+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 )
134+ }
135+ } ;
136+ var newCluster = new Cluster
137+ {
138+ Name = "TestCluster" ,
139+ WorkloadGroups = new List < WorkloadGroup >
140+ {
141+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 2048 )
142+ }
143+ } ;
144+
145+ // Act
146+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
147+
148+ // Assert
149+ Assert . NotNull ( changeSet ) ;
150+ Assert . NotEmpty ( changeSet . Changes ) ;
151+ Assert . NotEmpty ( changeSet . Scripts ) ;
152+
153+ var policyChange = Assert . Single ( changeSet . Changes ) as PolicyChange < WorkloadGroupPolicy > ;
154+ Assert . NotNull ( policyChange ) ;
155+ Assert . Equal ( "test-group" , policyChange . Entity ) ;
156+
157+ var scriptContainer = Assert . Single ( changeSet . Scripts ) ;
158+ Assert . Contains ( ".alter-merge workload_group test-group" , scriptContainer . Script . Text ) ;
159+ }
160+
161+ [ Fact ]
162+ public void GenerateChanges_WithIdenticalWorkloadGroups_ShouldDetectNoChanges ( )
163+ {
164+ // Arrange
165+ var workloadGroup = CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 ) ;
166+ var oldCluster = new Cluster
167+ {
168+ Name = "TestCluster" ,
169+ WorkloadGroups = new List < WorkloadGroup > { workloadGroup }
170+ } ;
171+ var newCluster = new Cluster
172+ {
173+ Name = "TestCluster" ,
174+ WorkloadGroups = new List < WorkloadGroup >
175+ {
176+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 )
177+ }
178+ } ;
179+
180+ // Act
181+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
182+
183+ // Assert
184+ Assert . NotNull ( changeSet ) ;
185+ Assert . Empty ( changeSet . Changes ) ;
186+ Assert . Empty ( changeSet . Scripts ) ;
187+ }
188+
189+ [ Fact ]
190+ public void GenerateChanges_WithWorkloadGroupDeletion_ShouldDetectDeletion ( )
191+ {
192+ // Arrange
193+ var oldCluster = new Cluster
194+ {
195+ Name = "TestCluster" ,
196+ WorkloadGroups = new List < WorkloadGroup >
197+ {
198+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 )
199+ }
200+ } ;
201+ var newCluster = new Cluster
202+ {
203+ Name = "TestCluster" ,
204+ WorkloadGroups = new List < WorkloadGroup > ( ) ,
205+ Deletions = new ClusterDeletions
206+ {
207+ WorkloadGroups = new List < string > { "test-group" }
208+ }
209+ } ;
210+
211+ // Act
212+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
213+
214+ // Assert
215+ Assert . NotNull ( changeSet ) ;
216+ Assert . NotEmpty ( changeSet . Changes ) ;
217+
218+ var deletionChange = Assert . Single ( changeSet . Changes ) as DeletionChange ;
219+ Assert . NotNull ( deletionChange ) ;
220+ Assert . Equal ( "test-group" , deletionChange . Entity ) ;
221+ Assert . Equal ( "workload_group" , deletionChange . EntityType ) ;
222+ Assert . Contains ( "Drop test-group" , deletionChange . Markdown ) ;
223+ }
224+
225+ [ Fact ]
226+ public void GenerateChanges_WithWorkloadGroupDeletionOfNonExistentGroup_ShouldNotCreateChange ( )
227+ {
228+ // Arrange
229+ var oldCluster = new Cluster { Name = "TestCluster" , WorkloadGroups = new List < WorkloadGroup > ( ) } ;
230+ var newCluster = new Cluster
231+ {
232+ Name = "TestCluster" ,
233+ WorkloadGroups = new List < WorkloadGroup > ( ) ,
234+ Deletions = new ClusterDeletions
235+ {
236+ WorkloadGroups = new List < string > { "non-existent-group" }
237+ }
238+ } ;
239+
240+ // Act
241+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
242+
243+ // Assert
244+ Assert . NotNull ( changeSet ) ;
245+ Assert . Empty ( changeSet . Changes ) ;
246+ Assert . Empty ( changeSet . Scripts ) ;
247+ }
248+
249+ [ Fact ]
250+ public void GenerateChanges_WithWorkloadGroupMarkedForDeletionButAlsoInNewList_ShouldOnlyProcessDeletion ( )
251+ {
252+ // Arrange
253+ var oldCluster = new Cluster
254+ {
255+ Name = "TestCluster" ,
256+ WorkloadGroups = new List < WorkloadGroup >
257+ {
258+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 1024 )
259+ }
260+ } ;
261+ var newCluster = new Cluster
262+ {
263+ Name = "TestCluster" ,
264+ WorkloadGroups = new List < WorkloadGroup >
265+ {
266+ CreateWorkloadGroup ( "test-group" , maxMemoryPerQueryPerNode : 2048 )
267+ } ,
268+ Deletions = new ClusterDeletions
269+ {
270+ WorkloadGroups = new List < string > { "test-group" }
271+ }
272+ } ;
273+
274+ // Act
275+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
276+
277+ // Assert
278+ Assert . NotNull ( changeSet ) ;
279+ Assert . NotEmpty ( changeSet . Changes ) ;
280+
281+ var deletionChange = Assert . Single ( changeSet . Changes ) as DeletionChange ;
282+ Assert . NotNull ( deletionChange ) ;
283+ Assert . Equal ( "test-group" , deletionChange . Entity ) ;
284+ Assert . Equal ( "workload_group" , deletionChange . EntityType ) ;
285+ }
286+
287+ [ Fact ]
288+ public void GenerateChanges_WithMultipleWorkloadGroupChanges_ShouldDetectAllChanges ( )
289+ {
290+ // Arrange
291+ var oldCluster = new Cluster
292+ {
293+ Name = "TestCluster" ,
294+ WorkloadGroups = new List < WorkloadGroup >
295+ {
296+ CreateWorkloadGroup ( "group1" , maxMemoryPerQueryPerNode : 1024 ) ,
297+ CreateWorkloadGroup ( "group2" , maxMemoryPerQueryPerNode : 2048 ) ,
298+ CreateWorkloadGroup ( "group3" , maxMemoryPerQueryPerNode : 512 )
299+ }
300+ } ;
301+ var newCluster = new Cluster
302+ {
303+ Name = "TestCluster" ,
304+ WorkloadGroups = new List < WorkloadGroup >
305+ {
306+ CreateWorkloadGroup ( "group1" , maxMemoryPerQueryPerNode : 1024 ) , // No change
307+ CreateWorkloadGroup ( "group2" , maxMemoryPerQueryPerNode : 4096 ) , // Update
308+ CreateWorkloadGroup ( "group4" , maxMemoryPerQueryPerNode : 256 ) // New
309+ } ,
310+ Deletions = new ClusterDeletions
311+ {
312+ WorkloadGroups = new List < string > { "group3" } // Delete
313+ }
314+ } ;
315+
316+ // Act
317+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
318+
319+ // Assert
320+ Assert . NotNull ( changeSet ) ;
321+ Assert . Equal ( 3 , changeSet . Changes . Count ) ; // 1 deletion + 1 update + 1 creation
322+
323+ // Check deletion
324+ var deletionChange = changeSet . Changes . OfType < DeletionChange > ( ) . Single ( ) ;
325+ Assert . Equal ( "group3" , deletionChange . Entity ) ;
326+
327+ // Check updates/creations
328+ var policyChanges = changeSet . Changes . OfType < PolicyChange < WorkloadGroupPolicy > > ( ) . ToList ( ) ;
329+ Assert . Equal ( 2 , policyChanges . Count ) ;
330+
331+ var group2Change = policyChanges . First ( c => c . Entity == "group2" ) ;
332+ var group4Change = policyChanges . First ( c => c . Entity == "group4" ) ;
333+
334+ Assert . NotNull ( group2Change ) ;
335+ Assert . NotNull ( group4Change ) ;
336+
337+ // Verify scripts
338+ Assert . Equal ( 3 , changeSet . Scripts . Count ) ;
339+ }
340+
341+ [ Fact ]
342+ public void GenerateChanges_WithWorkloadGroupHavingComplexPolicy_ShouldDetectChanges ( )
343+ {
344+ // Arrange
345+ var oldCluster = new Cluster
346+ {
347+ Name = "TestCluster" ,
348+ WorkloadGroups = new List < WorkloadGroup >
349+ {
350+ CreateComplexWorkloadGroup ( "complex-group" , 1024 , TimeSpan . FromMinutes ( 5 ) )
351+ }
352+ } ;
353+ var newCluster = new Cluster
354+ {
355+ Name = "TestCluster" ,
356+ WorkloadGroups = new List < WorkloadGroup >
357+ {
358+ CreateComplexWorkloadGroup ( "complex-group" , 2048 , TimeSpan . FromMinutes ( 10 ) )
359+ }
360+ } ;
361+
362+ // Act
363+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
364+
365+ // Assert
366+ Assert . NotNull ( changeSet ) ;
367+ Assert . NotEmpty ( changeSet . Changes ) ;
368+
369+ var policyChange = Assert . Single ( changeSet . Changes ) as PolicyChange < WorkloadGroupPolicy > ;
370+ Assert . NotNull ( policyChange ) ;
371+ Assert . Equal ( "complex-group" , policyChange . Entity ) ;
372+ Assert . Contains ( "MaxMemoryPerQueryPerNode" , policyChange . Markdown ) ;
373+ Assert . Contains ( "MaxExecutionTime" , policyChange . Markdown ) ;
374+ }
375+
376+ [ Fact ]
377+ public void GenerateChanges_WithWorkloadGroupHavingNullPolicy_ShouldNotCreateChange ( )
378+ {
379+ // Arrange
380+ var oldCluster = new Cluster { Name = "TestCluster" , WorkloadGroups = new List < WorkloadGroup > ( ) } ;
381+ var newCluster = new Cluster
382+ {
383+ Name = "TestCluster" ,
384+ WorkloadGroups = new List < WorkloadGroup >
385+ {
386+ new WorkloadGroup { WorkloadGroupName = "null-policy-group" , WorkloadGroupPolicy = null }
387+ }
388+ } ;
389+
390+ // Act
391+ var changeSet = ClusterChanges . GenerateChanges ( oldCluster , newCluster , _loggerMock . Object ) ;
392+
393+ // Assert
394+ Assert . NotNull ( changeSet ) ;
395+ Assert . Empty ( changeSet . Changes ) ;
396+ Assert . Empty ( changeSet . Scripts ) ;
397+ }
398+
399+ #endregion
400+
92401 #region Helper Methods
93402 private Cluster CreateClusterWithPolicy (
94403 double ? ingestionCapacityCoreUtilizationCoefficient = null ,
@@ -112,6 +421,65 @@ private Cluster CreateClusterWithPolicy(
112421 }
113422 } ;
114423 }
424+
425+ private WorkloadGroup CreateWorkloadGroup ( string name , long ? maxMemoryPerQueryPerNode = null )
426+ {
427+ return new WorkloadGroup
428+ {
429+ WorkloadGroupName = name ,
430+ WorkloadGroupPolicy = new WorkloadGroupPolicy
431+ {
432+ RequestLimitsPolicy = new RequestLimitsPolicy
433+ {
434+ MaxMemoryPerQueryPerNode = maxMemoryPerQueryPerNode . HasValue
435+ ? new PolicyValue < long > { Value = maxMemoryPerQueryPerNode . Value , IsRelaxable = false }
436+ : null
437+ }
438+ }
439+ } ;
440+ }
441+
442+ private WorkloadGroup CreateComplexWorkloadGroup ( string name , long maxMemoryPerQueryPerNode , TimeSpan maxExecutionTime )
443+ {
444+ return new WorkloadGroup
445+ {
446+ WorkloadGroupName = name ,
447+ WorkloadGroupPolicy = new WorkloadGroupPolicy
448+ {
449+ RequestLimitsPolicy = new RequestLimitsPolicy
450+ {
451+ MaxMemoryPerQueryPerNode = new PolicyValue < long > { Value = maxMemoryPerQueryPerNode , IsRelaxable = false } ,
452+ MaxExecutionTime = new PolicyValue < TimeSpan > { Value = maxExecutionTime , IsRelaxable = true } ,
453+ MaxResultRecords = new PolicyValue < long > { Value = 10000 , IsRelaxable = false }
454+ } ,
455+ RequestRateLimitPolicies = new List < RequestRateLimitPolicy >
456+ {
457+ new RequestRateLimitPolicy
458+ {
459+ IsEnabled = true ,
460+ Scope = RateLimitScope . WorkloadGroup ,
461+ LimitKind = RateLimitKind . ConcurrentRequests ,
462+ Properties = new RateLimitProperties
463+ {
464+ MaxConcurrentRequests = 100
465+ }
466+ } ,
467+ new RequestRateLimitPolicy
468+ {
469+ IsEnabled = true ,
470+ Scope = RateLimitScope . Principal ,
471+ LimitKind = RateLimitKind . ResourceUtilization ,
472+ Properties = new RateLimitProperties
473+ {
474+ ResourceKind = RateLimitResourceKind . TotalCpuSeconds ,
475+ MaxUtilization = 0.8 ,
476+ TimeWindow = TimeSpan . FromMinutes ( 5 )
477+ }
478+ }
479+ }
480+ }
481+ } ;
482+ }
115483 #endregion
116484 }
117485}
0 commit comments