1515using Kepware . Api . Test . ApiClient ;
1616using Kepware . Api . Util ;
1717using Shouldly ;
18+ using Xunit . Sdk ;
1819
1920namespace Kepware . Api . Test . ApiClient
2021{
2122 public class ProjectLoadTests : TestApiClientBase
2223 {
2324
24- //private async Task ConfigureToServeEndpoints()
25- //{
26- // var projectData = await LoadJsonTestDataAsync();
27-
28- // var channels = projectData.Project?.Channels?.Select(c => new Channel { Name = c.Name, Description = c.Description, DynamicProperties = c.DynamicProperties }).ToList() ?? [];
29-
30- // // Serve project details
31- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + "/config/v1/project" )
32- // .ReturnsResponse(JsonSerializer.Serialize(new Project { Description = projectData?.Project?.Description, DynamicProperties = projectData?.Project?.DynamicProperties ?? [] }), "application/json");
33-
34- // // Serve channels without nested devices
35- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + "/config/v1/ project/channels")
36- // .ReturnsResponse(JsonSerializer.Serialize(channels), "application/json");
25+ [ Theory ]
26+ [ InlineData ( "KEPServerEX" , "12" , 6 , 17 , true ) ]
27+ [ InlineData ( "KEPServerEX" , "12" , 6 , 16 , false ) ]
28+ [ InlineData ( "ThingWorxKepwareEdge" , "13" , 1 , 10 , true ) ]
29+ [ InlineData ( "ThingWorxKepwareEdge" , "13" , 1 , 9 , false ) ]
30+ [ InlineData ( "UnknownProduct" , "99" , 10 , 0 , false ) ]
31+ public async Task LoadProject_ShouldLoadCorrectly_BasedOnProductSupport (
32+ string productName , string productId , int majorVersion , int minorVersion , bool supportsJsonLoad )
33+ {
34+ // This test will validate that the LoadProjectAsync method correctly loads the project structure and
35+ // content based on whether the connected server version supports JsonProjectLoad. It will compare the loaded project against expected test data to ensure accuracy.
36+ // For servers that support JsonProjectLoad, the test will configure the mock server to serve a full JSON project
37+ // and validate that the loaded project matches the test data exactly.
3738
38- // foreach (var channel in projectData?.Project?.Channels ?? [])
39- // {
40- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}")
41- // .ReturnsResponse(JsonSerializer.Serialize(new Channel { Name = channel.Name, Description = channel.Description, DynamicProperties = channel.DynamicProperties }), "application/json");
39+ // Arrange
40+ ConfigureConnectedClient ( productName , productId , majorVersion , minorVersion ) ;
4241
43- // if (channel.Devices != null)
44- // {
45- // var devices = channel.Devices.Select(d => new Device { Name = d.Name, Description = d.Description, DynamicProperties = d.DynamicProperties }).ToList();
46- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices")
47- // .ReturnsResponse(JsonSerializer.Serialize(devices), "application/json");
42+ if ( supportsJsonLoad )
43+ {
44+ await ConfigureToServeFullProject ( ) ;
45+ }
46+ else
47+ {
48+ await ConfigureToServeEndpoints ( ) ;
49+ }
4850
49- // foreach (var device in channel.Devices)
50- // {
51- // var deviceEndpoint = TEST_ENDPOINT + $"/config/v1/project/channels/{channel.Name}/devices/{device.Name}";
52- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, deviceEndpoint)
53- // .ReturnsResponse(JsonSerializer.Serialize(new Device { Name = device.Name, Description = device.Description, DynamicProperties = device.DynamicProperties }), "application/json");
51+ // Act
52+ var project = await _kepwareApiClient . Project . LoadProjectAsync ( blnLoadFullProject : true ) ;
5453
54+ // Assert
55+ project . IsLoadedByProjectLoadService . ShouldBe ( supportsJsonLoad ) ;
5556
56- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, deviceEndpoint + "/tags")
57- // .ReturnsResponse(JsonSerializer.Serialize(device.Tags), "application/json ");
57+ project . ShouldNotBeNull ( ) ;
58+ project . Channels . ShouldNotBeEmpty ( "Channels list should not be empty. ") ;
5859
59- // ConfigureToServeEndpointsTagGroupsRecursive(deviceEndpoint, device.TagGroups ?? []);
60- // }
61- // }
62- // }
63- //}
60+ var testProject = await LoadJsonTestDataAsync ( ) ;
61+ var compareResult = EntityCompare . Compare < ChannelCollection , Channel > ( testProject ? . Project ? . Channels , project ? . Channels ) ;
6462
65- //private void ConfigureToServeEndpointsTagGroupsRecursive(string endpoint, IEnumerable<DeviceTagGroup> tagGroups)
66- //{
67- // var tagGroupEndpoint = endpoint + "/tag_groups";
63+ compareResult . ShouldNotBeNull ( ) ;
64+ compareResult . UnchangedItems . ShouldNotBeEmpty ( "All channels should be unchanged." ) ;
65+ compareResult . ChangedItems . ShouldBeEmpty ( "No channels should be changed." ) ;
66+ compareResult . ItemsOnlyInLeft . ShouldBeEmpty ( "No channels should exist only in the test data." ) ;
67+ compareResult . ItemsOnlyInRight . ShouldBeEmpty ( "No channels should exist only in the loaded project." ) ;
6868
69- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, tagGroupEndpoint)
70- // .ReturnsResponse(JsonSerializer.Serialize(tagGroups), "application/json");
69+ foreach ( var ( ExpectedChannel , LoadedChannel ) in testProject ? . Project ? . Channels ? . Zip ( project ? . Channels ?? [ ] ) ?? [ ] )
70+ {
71+ var deviceCompareResult = EntityCompare . Compare < DeviceCollection , Device > ( ExpectedChannel . Devices , LoadedChannel . Devices ) ;
72+ deviceCompareResult . ShouldNotBeNull ( ) ;
73+ deviceCompareResult . UnchangedItems . ShouldNotBeEmpty ( $ "All devices in channel { ExpectedChannel . Name } should be unchanged.") ;
74+ deviceCompareResult . ChangedItems . ShouldBeEmpty ( $ "No devices in channel { ExpectedChannel . Name } should be changed.") ;
75+ deviceCompareResult . ItemsOnlyInLeft . ShouldBeEmpty ( $ "No devices should exist only in the test data for channel { ExpectedChannel . Name } .") ;
76+ deviceCompareResult . ItemsOnlyInRight . ShouldBeEmpty ( $ "No devices should exist only in the loaded project for channel { ExpectedChannel . Name } .") ;
7177
72- // foreach (var tagGroup in tagGroups)
73- // {
74- // _httpMessageHandlerMock.SetupRequest(HttpMethod.Get, string.Concat(tagGroupEndpoint, "/", tagGroup.Name, "/tags"))
75- // .ReturnsResponse(JsonSerializer.Serialize(tagGroup.Tags), "application/json");
78+ foreach ( var ( ExpectedDevice , LoadedDevice ) in ExpectedChannel . Devices ? . Zip ( LoadedChannel . Devices ?? [ ] ) ?? [ ] )
79+ {
80+ if ( ExpectedDevice . Tags ? . Count > 0 || LoadedDevice . Tags ? . Count > 0 )
81+ {
82+ var tagCompareResult = EntityCompare . Compare < DeviceTagCollection , Tag > ( ExpectedDevice . Tags , LoadedDevice . Tags ) ;
83+ tagCompareResult . ShouldNotBeNull ( ) ;
84+ tagCompareResult . UnchangedItems . ShouldNotBeEmpty ( $ "All tags in device { ExpectedDevice . Name } should be unchanged.") ;
85+ tagCompareResult . ChangedItems . ShouldBeEmpty ( $ "No tags in device { ExpectedDevice . Name } should be changed.") ;
86+ tagCompareResult . ItemsOnlyInLeft . ShouldBeEmpty ( $ "No tags should exist only in the test data for device { ExpectedDevice . Name } .") ;
87+ tagCompareResult . ItemsOnlyInRight . ShouldBeEmpty ( $ "No tags should exist only in the loaded project for device { ExpectedDevice . Name } .") ;
88+ }
7689
77- // ConfigureToServeEndpointsTagGroupsRecursive(string.Concat(tagGroupEndpoint, "/", tagGroup.Name), tagGroup.TagGroups ?? []);
78- // }
79- //}
90+ CompareTagGroupsRecursive ( ExpectedDevice . TagGroups , LoadedDevice . TagGroups , ExpectedDevice . Name ) ;
91+ }
92+ }
93+ }
8094
8195 [ Theory ]
8296 [ InlineData ( "KEPServerEX" , "12" , 6 , 17 , true ) ]
83- [ InlineData ( "KEPServerEX " , "12" , 6 , 16 , false ) ]
97+ [ InlineData ( "ThingWorxKepwareServer " , "12" , 6 , 17 , true ) ]
8498 [ InlineData ( "ThingWorxKepwareEdge" , "13" , 1 , 10 , true ) ]
85- [ InlineData ( "ThingWorxKepwareEdge" , "13" , 1 , 9 , false ) ]
86- [ InlineData ( "UnknownProduct" , "99" , 10 , 0 , false ) ]
87- public async Task LoadProject_ShouldLoadCorrectly_BasedOnProductSupport (
99+ [ InlineData ( "Kepware Edge" , "13" , 1 , 0 , true ) ]
100+ public async Task LoadProject_ShouldLoadCorrectly_Serialize_BasedOnProductSupport (
88101 string productName , string productId , int majorVersion , int minorVersion , bool supportsJsonLoad )
89102 {
103+ // This test will validate that the LoadProjectAsync method correctly loads the project structure using the optimized recursion method.
104+ // It will compare the loaded project against expected test data to ensure accuracy. The test will configure the mock server to serve
105+ // endpoints to support an optimized recursion load and validate that the loaded project matches the test data exactly.
106+
107+ // Arrange
90108 ConfigureConnectedClient ( productName , productId , majorVersion , minorVersion ) ;
91109
92110 if ( supportsJsonLoad )
93111 {
94- await ConfigureToServeFullProject ( ) ;
112+ await ConfigureToServeEndpoints ( ) ;
95113 }
96114 else
97115 {
98- await ConfigureToServeEndpoints ( ) ;
116+ // Skip this test case at runtime because it expects the server to serve a full JSON project.
117+ throw SkipException . ForSkip ( $ "Product { productName } v{ majorVersion } .{ minorVersion } (id={ productId } ) does not support JSON project load. Skipping full-project test case.") ;
99118 }
100119
101- var project = await _kepwareApiClient . Project . LoadProject ( true ) ;
120+ // Override the tag limit to ensure that we are testing the optimized recursion and selectively load objects based on the tag limit.
121+ // See _data/simdemo_en.json and json chunks in _data/projectLoadSerializeData for data that is served by the mock server for this test.
122+ var tagLimitOverride = 100 ;
102123
103- project . IsLoadedByProjectLoadService . ShouldBe ( supportsJsonLoad ) ;
124+
125+ // Act
126+ var project = await _kepwareApiClient . Project . LoadProjectAsync ( blnLoadFullProject : true , projectLoadTagLimit : tagLimitOverride ) ;
127+
128+
129+ // Assert
130+ // Optimized recursion is done for this test, which will result in false.
131+ project . IsLoadedByProjectLoadService . ShouldBeFalse ( ) ;
104132
105133 project . ShouldNotBeNull ( ) ;
106134 project . Channels . ShouldNotBeEmpty ( "Channels list should not be empty." ) ;
@@ -138,6 +166,12 @@ public async Task LoadProject_ShouldLoadCorrectly_BasedOnProductSupport(
138166 CompareTagGroupsRecursive ( ExpectedDevice . TagGroups , LoadedDevice . TagGroups , ExpectedDevice . Name ) ;
139167 }
140168 }
169+
170+ // Verify expected number of calls to the project load endpoints to ensure that the optimized recursion is selectively loading objects based on the tag limit.
171+ foreach ( var uri in _optimizedRecursionUris )
172+ {
173+ _httpMessageHandlerMock . VerifyRequest ( HttpMethod . Get , uri ) ;
174+ }
141175 }
142176
143177 private static void CompareTagGroupsRecursive ( DeviceTagGroupCollection ? expected , DeviceTagGroupCollection ? actual , string parentName )
@@ -182,7 +216,7 @@ public async Task LoadProject_NotFull_ShouldLoadCorrectly_BasedOnProductSupport(
182216
183217 await ConfigureToServeEndpoints ( ) ;
184218
185- var project = await _kepwareApiClient . Project . LoadProject ( blnLoadFullProject : false ) ;
219+ var project = await _kepwareApiClient . Project . LoadProjectAsync ( blnLoadFullProject : false ) ;
186220
187221 project . ShouldNotBeNull ( ) ;
188222 project . Channels . ShouldBeNull ( "Channels list should be null." ) ;
@@ -201,7 +235,7 @@ public async Task LoadProject_ShouldReturnEmptyProject_WhenHttpRequestFails()
201235 . ThrowsAsync ( new HttpRequestException ( ) ) ;
202236
203237 // Act
204- var project = await _kepwareApiClient . Project . LoadProject ( true ) ;
238+ var project = await _kepwareApiClient . Project . LoadProjectAsync ( true ) ;
205239
206240 // Assert
207241 project . ShouldNotBeNull ( ) ;
0 commit comments